最近由於工作原因,需要實作go語言與c語言的相互呼叫。由於go語言與c語言有著千絲萬縷的曖昧關係,兩者之間的召喚可以透過語言層面實現。下文是對此的總結。
go語言呼叫c語言
以下為一個簡短的例子:
package main // #include <stdio.h> // #include <stdlib.h> /* void print(char *str) { printf("%s\n", str); } */ import "C" import "unsafe" func main() { s := "Hello Cgo" cs := C.CString(s) C.print(cs) C.free(unsafe.pointer(cs)) }
與「正常」的go程式碼相比,上述程式碼有幾處「特殊」的地方:
在開頭的註解中出現了c語言頭檔的include字樣
在註解中定義了c語言函數print
import了一個名為C的「套件」
#在main函數中呼叫了上述定義的c語言函數print
#首先,go原始碼檔案中的c語言程式碼是需要用註解包裹的,就像上面的include頭檔以及print函數定義;其次,import "C"這個語句是必須的,而且其與上面的c程式碼之間不能用空行分隔,必須緊密相連。這裡的」C「不是包名,而是一種類似名字空間的概念,或可以理解為偽包,c語言所有語法元素均在該偽包下面;最後,訪問c語法元素時都要在其前面加上偽包前綴,例如C.uint和上面程式碼中的C.print、C.free等。
更詳細的在go中呼叫c語言的用法可以參考Go與C語言的互通,本文不再一一細述。
在上面的例子中,c語言是內嵌在go程式碼中的,如果程式碼量更大更複雜的話,這顯然是很不」專業「的。那麼,是否可以將c語言程式碼從go程式碼中分離出去,單獨定義呢?答案是肯定的,可以透過共享庫的方式來實現。
cgo提供了#cgo
指示符可以指定go原始碼在編譯後與哪些共享庫進行連結。範例如下:
// hello.go package main // #cgo LDFLAGS: -L ./ -lhello // #include <stdio.h> // #include <stdlib.h> // #include "hello.h" import "C" func main() { C.hello() } // hello.c #include "hello.h" void hello() { printf("hello, go\n"); } // hello.h extern void hello();
其中在hello.go中,#cgo
指示符後面加上LDFLAGS: -L ./ -lhello
,作用是在go程式碼編譯時,指定在目前目錄中尋找so庫並進行連結。
因此,只要要把hello.c編譯成動態函式庫,再編譯go程式碼,即可在執行go程式碼的時候呼叫共享函式庫中的c語言函式。指令如下:
gcc -fPIC -o libhello.so hello.c
##go build -o hello
./hello
// hello.go package main import "C" import "fmt" // export Go2C func Go2C() { fmt.Println("hello, C") }
go build的編譯選項,將go程式碼編譯成共享函式庫以供c程式碼呼叫。注意,編譯so函式庫時必須存在main及main函式(即使main函式為空)。編譯指令如下:
go build -v -x -buildmode=c-shared -o libhello.so hello.go。
// hello.c #include <stdio.h> #include "libhello.h" int main() { Go2C(); return 0; }
gcc -o hello -L. -lhello,即可編譯成執行程式。注意,運行前必須確定共享庫運行時查找路徑中存在需要連結的共享庫,可透過將so庫路徑放到/usr/lib或修改環境變數LD_LIBRARY_PATH。
go build將go程式碼編譯成共享函式庫提供給c程式碼使用。注意,本文中的共享庫均為動態共享庫,至於靜態共享庫則未曾實驗,有興趣的可以實現一下。