java 调用 go_实践总结:在 Java 中调用 Go 代码
在 Java 中調(diào)用 Go 的大致過程如下go --> cgo --> jna --> java
整個(gè)過程要解決的問題主要兩個(gè):數(shù)據(jù)類型在兩種語言中如何轉(zhuǎn)化
何時(shí)清理無用的數(shù)據(jù)
下面就圍繞上述調(diào)用過程來闡述,本文涉及代碼完整版可以下面鏈接找到:
Go -> Cgo
這是跨語言調(diào)用的第一步,主要是借助 cgo,把 Go 代碼編譯 C 共享庫(kù)。
cgo 是 Go 語言提供與 C 語言互調(diào)的一工具。提供一個(gè)名為 C 的偽 package,供 Go 訪問 C 中的變量與函數(shù),如 C.size_t C.stdout 等;同時(shí)提供 5 個(gè)特殊函數(shù),用于兩種語言間類型的轉(zhuǎn)化:// 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
需要注意一點(diǎn),cgo 中函數(shù)不能直接返回 slice/map 等具有 go pointer (區(qū)別與 C pointer,由 go runtime 管理生命周期)的數(shù)據(jù)類型,否則會(huì)報(bào)下面的 panic 信息:panic: runtime error: cgo result has Go pointer
原因也很簡(jiǎn)單,go 是有 gc 的,假如允許返回具有 go pointer 的數(shù)據(jù),那么 C 代碼中得到的數(shù)據(jù)無法保證合法性,很有可能已經(jīng)被 gc 了,即懸掛指針問題。解決的方式也很簡(jiǎn)單,就是采用 go 提供的特殊轉(zhuǎn)化函數(shù),將數(shù)據(jù)轉(zhuǎn)為 unsafe.Pointer,在 C 中用 void * 的方式去使用。
可以想象,這些特殊轉(zhuǎn)化函數(shù)一定對(duì)數(shù)據(jù)進(jìn)行了深拷貝,來保證數(shù)據(jù)的合法性,可參考 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 代碼中需要負(fù)責(zé) free 掉無用的數(shù)據(jù)(至于哪邊 free,要看實(shí)際情況)。示例:func main() {
cs := C.CString("Hello from stdio")
C.myprint(cs)
C.free(unsafe.Pointer(cs))
}
將 Go 函數(shù)導(dǎo)出供 C 調(diào)用,需要用 //export 標(biāo)示相關(guān)函數(shù),并且 Go 文件需要在 package main下。然后用類似下面的 build 命令,即可得到與 C 互調(diào)的動(dòng)態(tài)庫(kù),同時(shí)會(huì)生產(chǎn)一個(gè)頭文件,里面有 export 函數(shù)的相關(guān)簽名。# linux 下可輸出到 libawesome.so,這里以 Mac 下的動(dòng)態(tài)庫(kù)為例
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 這里表示字符串的長(zhǎng)度
typedef struct { const char *p; ptrdiff_t n; } _GoString_;
extern char* Hello(GoString p0);
Cgo -> JNA
這一步主要是 Java 中如何調(diào)用 C 代碼,目前主要有兩種方式,JNA,優(yōu)勢(shì)是調(diào)用方便,只需要編寫 Java 代碼,JNA 框架負(fù)責(zé)在 C/Java 中進(jìn)行數(shù)據(jù)類型轉(zhuǎn)化
JNI,優(yōu)勢(shì)是性能好,缺點(diǎn)是調(diào)用繁瑣
詳細(xì)區(qū)別這里不展開敘述,感興趣的讀者可參考下面文章:
JNA -> Java
這一步主要是在 Java 代碼中如何調(diào)用 JNA 框架提供的庫(kù)進(jìn)行跨語言調(diào)用,也是本文的重點(diǎn)。
JNA 將 Java 基本類型直接映射為 C 中同等大小的類型,這里摘抄如下Native TypeSizeJava TypeCommon Windows Typeschar8-bit integerbyteBYTE, TCHAR
short16-bit integershortWORD
wchar_t16/32-bit charactercharTCHAR
int32-bit integerintDWORD
intboolean valuebooleanBOOL
long32/64-bit integerNativeLongLONG
long long64-bit integerlong__int64
float32-bit FPfloat
double64-bit FPdouble
char*C stringStringLPCSTR
void*pointerPointerLPVOID, HANDLE, LPXXX
對(duì)于 C 中的 struct/pointer,JNA 中也提供了 Structure/Pointer 類來對(duì)應(yīng)。JNA 的具體使用過程可參考:
上述 GettingStarted 中第三種加載動(dòng)態(tài)庫(kù)的方式(即 resources 下的 {OS}-{ARCH}/{LIBRARY} 目錄內(nèi))可以把動(dòng)態(tài)庫(kù)一起打包到 jar 中,這對(duì)于提供基礎(chǔ)類庫(kù)時(shí)比較方便,用戶不需要再額外配置。resources/
├── darwin
│?? └── libawesome.dylib
├── linux-x86-64
│?? └── libawesome.so
vladimirvivien/go-cshared-examples 這個(gè)倉(cāng)庫(kù)演示了四個(gè)函數(shù) Add/Cosine/Sort/Log 的 JNA 調(diào)用,但這四個(gè)函數(shù)的返回類型都是基本類型(int/float64),沒有 string/slice 等復(fù)雜類型,因此這里通過五個(gè)示例講述復(fù)雜類型的返回問題:BadStringDemo.java 本示例演示了網(wǎng)絡(luò)上一種常見,但有內(nèi)存泄露問題的返回 string 的方式
GoodStringDemo.java 這個(gè)示例演示了如何正確的返回 string
ReturnByteSliceDemo.java 本示例演示如何返回 slice,以及如何在 Java 中處理 Go 中的多個(gè)返回值
ReturnInterfaceDemo.java 本示例演示返回具有 Go Pointer 的結(jié)構(gòu)時(shí)的報(bào)錯(cuò)行為
上述示例均使用 direct mapping 的方式做 JNA,這種方式性能更好,但是支持的參數(shù)類型有限,讀者可參考 vladimirvivien/go-cshared-examples 學(xué)習(xí) interface mapping 的使用方式。
總結(jié)
C 語言作為連接不同高級(jí)語言的膠水語言,不具備垃圾回收功能,所以開發(fā)者在做 JNA 時(shí)要注意回收無用的內(nèi)存結(jié)構(gòu)。
參考
總結(jié)
以上是生活随笔為你收集整理的java 调用 go_实践总结:在 Java 中调用 Go 代码的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 领克01 EM-F官宣:混动上车、油耗大
- 下一篇: 因涉嫌垄断被立案调查!知网公开征集整改意