今天同事在公司羣裏轉發了一篇文章:
Calling Go Functions from Other Languagesgit
其原理是github
經過編譯時指定 -buildmode=c-shared
選項,把 Go 程序編譯成 C 的動態連接庫。golang
由其餘語言經過 FFI 的形式,去調用動態連接庫的函數。shell
因而,只要能支持 FFI 的語言,就能調用事先編譯到動態連接庫裏 Go 的函數。api
想到 LuaJIT 也支持 FFI,我試着在 LuaJIT 代碼裏實現對 Go 函數的調用。函數
原文中的 Go 代碼以下:ui
// awesome.go package main import "C" import ( "fmt" "math" "sort" "sync" ) var count int var mtx sync.Mutex //export Add func Add(a, b int) int { return a + b } //export Cosine func Cosine(x float64) float64 { return math.Cos(x) } //export Sort func Sort(vals []int) { sort.Ints(vals) } //export Log func Log(msg string) int { mtx.Lock() defer mtx.Unlock() fmt.Println(msg) count++ return count } func main() {}
編譯出 awesome.so
:go build -o awesome.so -buildmode=c-shared awesome.go
隨同生成的還有一個 awesome.h
頭文件。lua
接下來就是用 ffi 去調用暴露出來的幾個 Go 函數:code
local ffi = require "ffi" local awesome = ffi.load("./awesome.so")
FFI 調用須要知道連接庫中的符號的類型,這時候 awesome.h
就派上用場了。咱們僅需從中複製用得上的那部分聲明:get
ffi.cdef[[ typedef long long GoInt64; typedef GoInt64 GoInt; typedef double GoFloat64; typedef struct { const char *p; GoInt n; } GoString; typedef struct { void *data; GoInt len; GoInt cap; } GoSlice; extern GoInt Add(GoInt p0, GoInt p1); extern GoFloat64 Cosine(GoFloat64 p0); extern void Sort(GoSlice p0); extern GoInt Log(GoString p0); ]]
剩下就是用 LuaJIT 的 FFI api,作好類型轉換:
print("awesome.Add(12, 99) = ", awesome.Add(12, 99)) print("awesome.Cosine(1) = ", awesome.Cosine(1)) local slice = ffi.new("GoSlice") local data = {12,54,0,423,9} slice.data = ffi.cast("void*", ffi.new("GoInt[?]", #data, data)) slice.len = 5; slice.cap = 5; awesome.Sort(slice) local sorted_data = ffi.cast("GoInt*", slice.data) print("\nAfter sort:") for i = 0, 4 do print(sorted_data[i]) end print() local go_str = ffi.new("GoString") local s = "Hello LuaJIT!" go_str.p = s; go_str.n = #s; awesome.Log(go_str);
運行輸出以下:
awesome.Add(12, 99) = 111LL awesome.Cosine(1) = 0.54030230586814 After sort: 0LL 9LL 12LL 54LL 423LL Hello LuaJIT!
數字後面帶 LL 後綴是由於 GoInt
是 long long 類型的~
在歡呼 Go 程序能夠爲我所用以前,先潑一盆冷水。首先,-buildmode=c-shared
有兩點要求:(見go help buildmode
)
被編譯的程序必須是 package main 下面的。
導出的函數須要加 cgo //export 修飾(見 awesome.go
開頭的 import "C"
和函數擡頭的 //export
)。另外函數簽名只能包含基礎類型。
前者能夠寫一個 package main 的入口文件,而後由該文件導入其餘模塊內的內容,這麼作來繞過。
可是後者確實是個坎,畢竟有 cgo 的實現上限制。這意味着,想要爲所欲爲地導入任意 Go 包是不可能的,至少須要包上一層。
此外,相對於 C 語言,用 Go 編譯出的動態連接庫作 FFI 的資料比較少,不能保證其中沒有坑。
說完壞的一面,是時候補上一個光明的尾巴。至少編寫 Lua 所缺乏的功能時,除了用 C/C++,咱們能夠有多一種選擇。畢竟 Go 的庫很多,並且比起C/C++,實現業務的難度會小一些。也許在未來,咱們能夠看到更多這方面的實踐。