今天同事在公司群里转发了一篇文章:
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++,实现业务的难度会小一些。也许在未来,咱们能够看到更多这方面的实践。