实践总结:在 Java 中调用 Go 代码

原文地址: https://liujiacai.net/blog/2020/08/08/go-meet-java/

在 Java 中调用 Go 的大体过程以下html

go --> cgo --> jna --> java

整个过程要解决的问题主要两个:java

  1. 数据类型在两种语言中如何转化
  2. 什么时候清理无用的数据

下面就围绕上述调用过程来阐述,本文涉及代码完整版能够下面连接找到:linux

Go -> Cgo

这是跨语言调用的第一步,主要是借助 cgo,把 Go 代码编译 C 共享库。
cgo 是 Go 语言提供与 C 语言互调的一工具。提供一个名为 C 的伪 package,供 Go 访问 C 中的变量与函数,如 C.size_t C.stdout 等;同时提供 5 个特殊函数,用于两种语言间类型的转化:git

// Go string to C string
// The C string is allocated in the C heap using malloc.
// It is the caller's responsibility to arrange for it to be
// freed, such as by calling C.free (be sure to include stdlib.h
// if C.free is needed).
func C.CString(string) *C.char

// Go []byte slice to C array
// The C array is allocated in the C heap using malloc.
// It is the caller's responsibility to arrange for it to be
// freed, such as by calling C.free (be sure to include stdlib.h
// if C.free is needed).
func C.CBytes([]byte) unsafe.Pointer

// C string to Go string
func C.GoString(*C.char) string

// C data with explicit length to Go string
func C.GoStringN(*C.char, C.int) string

// C data with explicit length to Go []byte
func C.GoBytes(unsafe.Pointer, C.int) []byte

须要注意一点,cgo 中函数不能直接返回 slice/map 等具备 go pointer (区别与 C pointer,由 go runtime 管理生命周期)的数据类型,不然会报下面的 panic 信息:github

panic: runtime error: cgo result has Go pointer

缘由也很简单,go 是有 gc 的,假如容许返回具备 go pointer 的数据,那么 C 代码中获得的数据没法保证合法性,颇有可能已经被 gc 了,即悬挂指针问题。解决的方式也很简单,就是采用 go 提供的特殊转化函数,将数据转为 unsafe.Pointer,在 C 中用 void * 的方式去使用。golang

能够想象,这些特殊转化函数必定对数据进行了深拷贝,来保证数据的合法性,可参考 C.CBytes 的定义api

const cBytesDef = `
func _Cfunc_CBytes(b []byte) unsafe.Pointer {
    p := _cgo_cmalloc(uint64(len(b)))
    pp := (*[1<<30]byte)(p)
    copy(pp[:], b)
    return p
}
`

但这也意味着,Go/C 代码中须要负责 free 掉无用的数据(至于哪边 free,要看实际状况)。示例:bash

func main() {
    cs := C.CString("Hello from stdio")
    C.myprint(cs)
    C.free(unsafe.Pointer(cs))
}

将 Go 函数导出供 C 调用,须要用 //export 标示相关函数,而且 Go 文件须要在 package main下。而后用相似下面的 build 命令,便可获得与 C 互调的动态库,同时会生产一个头文件,里面有 export 函数的相关签名。网络

# linux 下可输出到 libawesome.so,这里以 Mac 下的动态库为例
go build -v -o libawesome.dylib -buildmode=c-shared ./main.go
//export Hello
func Hello(msg string) *C.char {
    return C.CString("hello " + strings.ToUpper(msg))

}

// 头文件中 Hello 的定义
// ptrdiff_t is the signed integer type of the result of subtracting two pointers.
// n 这里表示字符串的长度
typedef struct { const char *p; ptrdiff_t n; } _GoString_;
extern char* Hello(GoString p0);

完整代码可参考 main.go 、对应的头文件 libawesome.horacle

Cgo -> JNA

这一步主要是 Java 中如何调用 C 代码,目前主要有两种方式,

  • JNA,优点是调用方便,只须要编写 Java 代码,JNA 框架负责在 C/Java 中进行数据类型转化
  • JNI,优点是性能好,缺点是调用繁琐

详细区别这里不展开叙述,感兴趣的读者可参考下面文章:

JNA -> Java

这一步主要是在 Java 代码中如何调用 JNA 框架提供的库进行跨语言调用,也是本文的重点。
JNA 将 Java 基本类型直接映射为 C 中同等大小的类型,这里摘抄以下

Native Type Size Java Type Common Windows Types
char 8-bit integer byte BYTE, TCHAR
short 16-bit integer short WORD
wchar_t 16/32-bit character char TCHAR
int 32-bit integer int DWORD
int boolean value boolean BOOL
long 32/64-bit integer NativeLong LONG
long long 64-bit integer long __int64
float 32-bit FP float
double 64-bit FP double
char* C string String LPCSTR
void* pointer Pointer LPVOID, HANDLE, LPXXX

对于 C 中的 struct/pointer,JNA 中也提供了 Structure/Pointer 类来对应。JNA 的具体使用过程可参考:

上述 GettingStarted 中第三种加载动态库的方式(即 resources 下的 {OS}-{ARCH}/{LIBRARY} 目录内)能够把动态库一块儿打包到 jar 中,这对于提供基础类库时比较方便,用户不须要再额外配置。

resources/
├── darwin
│   └── libawesome.dylib
├── linux-x86-64
│   └── libawesome.so

vladimirvivien/go-cshared-examples 这个仓库演示了四个函数 Add/Cosine/Sort/Log 的 JNA 调用,但这四个函数的返回类型都是基本类型(int/float64),没有 string/slice 等复杂类型,所以这里经过五个示例讲述复杂类型的返回问题:

  1. BadStringDemo.java 本示例演示了网络上一种常见,但有内存泄露问题的返回 string 的方式
  2. GoodStringDemo.java 这个示例演示了如何正确的返回 string
  3. AutoClosableStringDemo.java 本示例在 GoodStringDemo 的基础上,利用 AutoCloseabletry-with-resource 特性来释放内存
  4. ReturnByteSliceDemo.java 本示例演示如何返回 slice,以及如何在 Java 中处理 Go 中的多个返回值
  5. ReturnInterfaceDemo.java 本示例演示返回具备 Go Pointer 的结构时的报错行为

上述示例均使用 direct mapping 的方式作 JNA,这种方式性能更好,可是支持的参数类型有限,读者可参考 vladimirvivien/go-cshared-examples 学习 interface mapping 的使用方式。

总结

C 语言做为链接不一样高级语言的胶水语言,不具有垃圾回收功能,因此开发者在作 JNA 时要注意回收无用的内存结构。

参考

相关文章
相关标签/搜索