最近因爲工做緣由,須要實現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代碼相比,上述代碼有幾處「特殊」的地方:ui
首先,go源碼文件中的c語言代碼是須要用註釋包裹的,就像上面的include頭文件以及print函數定義;其次,import "C"這個語句是必須的,並且其與上面的c代碼之間不能用空行分隔,必須緊密相連。這裏的」C「不是包名,而是一種相似名字空間的概念,或能夠理解爲僞包,c語言全部語法元素均在該僞包下面;最後,訪問c語法元素時都要在其前面加上僞包前綴,好比C.uint和上面代碼中的C.print、C.free等。code
更詳細的在go中調用c語言的用法能夠參考Go與C語言的互操做,本文再也不一一細述。get
在上面的例子中,c語言是內嵌在go代碼中的,若是代碼量更大更復雜的話,這顯然是很不」專業「的。那麼,是否能夠將c語言代碼從go代碼中分離出去,單獨定義呢?答案是確定的,能夠經過共享庫的方式實現。源碼
cgo提供了#cgo
指示符能夠指定go源碼在編譯後與哪些共享庫進行連接。例子以下:it
// 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庫並進行連接。io
所以,只須要把hello.c編譯成動態庫,再編譯go代碼,便可在運行go代碼的時候調用共享庫中的c語言函數。指令以下:編譯
gcc -fPIC -o libhello.so hello.c
go build -o hello
./hello
與在go中調用c源碼相比,在c中使用go函數的場合較少。由於通常來講,採用高級語言做爲粘合劑調用低級語言能充分發揮各自的特色,而用低級語言調用高級語言反而有可能下降低級語言的性能優點,在go中,可使用」export + 函數名「來導出go函數爲c代碼所用,看一個簡單的例子:import
// 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
。
編譯成功後,只需在c代碼中引入新生成的頭文件及編譯時連接動態庫便可實現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語言能夠經過內嵌c代碼的形式調用c語言,也能夠經過調用共享庫函數的方式實現;至於c語言調用go函數,則能夠經過go build
將go代碼編譯成共享庫提供給c代碼使用。注意,本文中的共享庫均爲動態共享庫,至於靜態共享庫則不曾實驗,有興趣的能夠實現一下。