在 LuaJIT 中調用 Go 函數

今天同事在公司羣裏轉發了一篇文章:
Calling Go Functions from Other Languagesgit

其原理是github

  1. 經過編譯時指定 -buildmode=c-shared 選項,把 Go 程序編譯成 C 的動態連接庫。golang

  2. 由其餘語言經過 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.sogo 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

  1. 被編譯的程序必須是 package main 下面的。

  2. 導出的函數須要加 cgo //export 修飾(見 awesome.go 開頭的 import "C" 和函數擡頭的 //export)。另外函數簽名只能包含基礎類型

前者能夠寫一個 package main 的入口文件,而後由該文件導入其餘模塊內的內容,這麼作來繞過。
可是後者確實是個坎,畢竟有 cgo 的實現上限制。這意味着,想要爲所欲爲地導入任意 Go 包是不可能的,至少須要包上一層。
此外,相對於 C 語言,用 Go 編譯出的動態連接庫作 FFI 的資料比較少,不能保證其中沒有坑。

說完壞的一面,是時候補上一個光明的尾巴。至少編寫 Lua 所缺乏的功能時,除了用 C/C++,咱們能夠有多一種選擇。畢竟 Go 的庫很多,並且比起C/C++,實現業務的難度會小一些。也許在未來,咱們能夠看到更多這方面的實踐。

相關文章
相關標籤/搜索