實踐總結:在 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 時要注意回收無用的內存結構。

參考

相關文章
相關標籤/搜索