CGO簡明教程
Hello World
package main // #include <stdlib.h> import "C" import ( "fmt" ) func main() { fmt.Println(int(C.random())) }
當然這並不是Hello World。我們不首先輸出Hello World是有原因的,接下來就會講到。不過首先我們分析一下現在這個程式。
首先從結構上來看可以知道這就是一個普通的Go程式,第一行package main
宣告這個程式碼是在main包裡。然後下面有func main
是程式的入口。
// #inlcude <stdlib.h> import "C"
這三行是Go調C才這樣的,import "C"
是為了可以在Go程式裡直接使用C裡的一些函式,例如main中C.random()
,而import "C"
上邊的註釋叫做preamble,注意必須和import "C"
緊緊挨著中間不能有空格。註釋的風格可以是// #include...
也可以是/*#include ...*/
這樣的。此外可以在 preamble 中加入// #cgo
開頭的註釋,用於指示編譯和連結中發生的一些事情,例如連結哪個動態連結庫等。
接下來我們看看Hello World。
首先我們需要三個檔案,helloworld.h
:
#ifndef __helloworld #define __helloworld void Printf(char *s); #endif
helloworld.c
:
#include <stdio.h> void Printf(char *s) { printf("%s", s); }
main.go
:
package main // #include "helloworld.h" import "C" func main() { C.Printf("hello world") }
為啥不直接C.printf
輸出呢,因為在wiki中提到cgo目前暫時還不支援變長引數的C函式,所以要我們自己包裝一下。編譯:
$ go build ./main.go:7: cannot use "hello world" (type string) as type *_Ctype_char in argument to _Cfunc_Printf
原因是C和Go的字串不是通用的,我們要把Go的字串轉成C的字串,但是因為不是在編譯的這個過程申請記憶體,而是在堆裡 申請記憶體儲存字串,而Go的垃圾回收是管不到C申請的記憶體,所以我們需要自行銷燬對應的記憶體。
package main // #include <stdlib.h> // #include "helloworld.h" import "C" import ( "unsafe" ) func main() { cs := C.CString("hello world\n") defer C.free(unsafe.Pointer(cs)) C.Printf(cs) }
也可以我們先把C編譯成動態連結庫,然後在Go裡指示連結:
package main // #cgo LDFLAGS: -L${SRCDIR}/ -Wl,-rpath,${SRCDIR}/ -lhelloworld // #include <stdlib.h> // #include "helloworld.h" import "C" import ( "unsafe" ) func main() { cs := C.CString("hello world\n") defer C.free(unsafe.Pointer(cs)) C.Printf(cs) }
我們先把helloworld.c
編譯成動態連結庫libhelloworld.so
。
型別
C和Go中有很多型別是對應的,但是需要我們自行轉換型別。例如:
- C.int - C.long - C.ulong
如果是訪問struct得這麼用C.struct_<struct的名字>
,enum,union和sizeof也類似。
具體參考:ofollow,noindex" target="_blank">https://golang.org/cmd/cgo/#hdr-Go_references_to_C
此外,對於指標型別,則是該咋用咋用,比如*C.int
。但是對於void *
,則需要用unsafe.Pointer
來表示。
其他知識
如果說需要什麼其他知識,那就是編譯連結相關的知識了,推薦《程式設計師的自我修養-連結、裝載與庫》。這本書非常的好,國產神書, 不過說實話,因為不經常接觸,看過然後又忘記了大部分內容♂️
CGO是如何執行的
在Go中呼叫C函式,cgo生成的程式碼呼叫runtime.cgocall(_cgo_Cfunc_f, frame)
,_cgo_Cfunc_f
就是GCC編譯
出來的程式碼。runtime.cgocall
會呼叫runtime.asmcgocall(_cgo_Cfunc_f, frame)
。
runtime.asmcgocall
會切換到m->go
的棧然後執行程式碼,因為g0
的棧是作業系統分配的棧(大小為8k),足夠
執行C程式碼。_cgo_Cfunc_f
在frame中執行C函式,然後返回到runtime.asmcgocall
。之後再切回撥用它的
G的棧。