原文地址:https://liujiacai.net/blog/2020/08/08/go-meet-java/
在 Java 中调用 Go 的大抵过程如下
go --> cgo --> jna --> java
整个过程要解决的问题次要两个:
- 数据类型在两种语言中如何转化
- 何时清理无用的数据
上面就围绕上述调用过程来论述,本文波及代码完整版能够上面链接找到:
- https://github.com/jiacai2050…
Go -> Cgo
这是跨语言调用的第一步,次要是借助 cgo,把 Go 代码编译 C 共享库。
cgo 是 Go 语言提供与 C 语言互调的一工具。提供一个名为 C
的伪 package,供 Go 拜访 C 中的变量与函数,如 C.size_t
C.stdout
等;同时提供 5 个非凡函数,用于两种语言间类型的转化:
// 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 信息:
panic: runtime error: cgo result has Go pointer
起因也很简略,go 是有 gc 的,如果容许返回具备 go pointer 的数据,那么 C 代码中失去的数据无奈保障合法性,很有可能曾经被 gc 了,即悬挂指针问题。解决的形式也很简略,就是采纳 go 提供的非凡转化函数,将数据转为 unsafe.Pointer
,在 C 中用 void *
的形式去应用。
能够设想,这些非凡转化函数肯定对数据进行了深拷贝,来保证数据的合法性,可参考 C.CBytes 的定义
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,要看理论状况)。示例:
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.h。
Cgo -> JNA
这一步次要是 Java 中如何调用 C 代码,目前次要有两种形式,
- JNA,劣势是调用不便,只须要编写 Java 代码,JNA 框架负责在 C/Java 中进行数据类型转化
- JNI,劣势是性能好,毛病是调用繁琐
具体区别这里不开展叙述,感兴趣的读者可参考上面文章:
- https://blog.caplin.com/2014/…
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 的具体应用过程可参考:
- https://github.com/java-nativ…
- http://java-native-access.git…
上述 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 等简单类型,因而这里通过五个示例讲述简单类型的返回问题:
- BadStringDemo.java 本示例演示了网络上一种常见,但有内存泄露问题的返回 string 的形式
- GoodStringDemo.java 这个示例演示了如何正确的返回 string
- AutoClosableStringDemo.java 本示例在 GoodStringDemo 的根底上,利用 AutoCloseable 与 try-with-resource 个性来开释内存
- ReturnByteSliceDemo.java 本示例演示如何返回 slice,以及如何在 Java 中解决 Go 中的多个返回值
- ReturnInterfaceDemo.java 本示例演示返回具备 Go Pointer 的构造时的报错行为
上述示例均应用 direct mapping 的形式做 JNA,这种形式性能更好,然而反对的参数类型无限,读者可参考 vladimirvivien/go-cshared-examples 学习 interface mapping 的应用形式。
总结
C 语言作为连贯不同高级语言的胶水语言,不具备垃圾回收性能,所以开发者在做 JNA 时要留神回收无用的内存构造。
参考
- https://stackoverflow.com/que…
- https://stackoverflow.com/que…
- https://github.com/java-nativ…
- https://documentation.help/Go…
- https://docs.yottadb.com/Pres…
- https://stackoverflow.com/que…