>[info]原文鏈接:https://golang.org/cmd/cgo/
[TOC]
Cgo可以創建出能夠調用C代碼的Go包。
#### 在go命令行中使用cgo
使用cgo書寫普通的Go代碼,導入一個偽包“C”。Go代碼就可以關聯到如C.size_t的類型,如C.stdout的變量,或者如C.putchar的方法。
如果“C”包的導入緊接在注釋之后,那個這個注釋,被稱為前導碼【preamble】,就會作為編譯Go包中的C語言部分的頭文件。例如:
~~~
// #include <stdio.h>
// #include <errno.h>
import "C"
~~~
前導碼【preamble】可以包含任何C語言代碼,包括方法和變量聲明及定義。這些代碼后續可能會由Go代碼引用,就像它們定義在了名為“C”的Go包中。所有在前導碼中聲明的名字都可能被使用,即使它們以小寫字母開頭。有一個例外:前導碼中的static變量不能被Go代碼引用;而static方法則可以被Go引用。
可以查看`$GOROOT/misc/cgo/stdio`和`$GOROOT/misc/cgo/gmp`中的例子。也可以在https://golang.org/doc/articles/c_go_cgo.html 中了解使用cgo的介紹。
`CFLAGS`, `CPPFLAGS`, `CXXFLAGS`, `FFLAGS`,和`LDFLAGS`可以在注釋區域中,使用#cgo偽指令來定義,以改變C,C++,或Fortran編譯器的行為。被多個指令定義的值會被聯系在一起。指令還可以包含一個構建約束的列表,將其對系統的影響限制為滿足其中一個約束條件。可以查看https://golang.org/pkg/go/build/#hdr-Build_Constraints 了解約束語法的詳情。例如:
~~~
// #cgo CFLAGS: -DPNG_DEBUG=1
// #cgo amd64 386 CFLAGS: -DX86=1
// #cgo LDFLAGS: -lpng
// #include <png.h>
import "C"
~~~
或者,`CPPFLAGS`和`LDFLAGS`也可通過`pkg-config`工具獲取,使用`#cgo pkg-config:`指令,后面跟上包名即可。比如:
~~~
// #cgo pkg-config: png cairo
// #include <png.h>
import "C"
~~~
默認的`pkg-config`工具可以通過設置`PKG_CONFIG`環境變量來改變。
當編譯時,`CGO_CFLAGS`,`CGO_CPPFLAGS`,`CGO_CXXFLAGS`,`CGO_FFLAGS`和`CGO_LDFLAGS`這些環境變量都會從指令中提取出來,并加入到flags中。包特定的flags需要使用指令來設置,而不是通過環境變量,所以這些構建可以在未更改的環境中也能正常運行。
一個包中的所有cgo CPPFLAGS和CFLAGS指令會被連接起來,并用來編譯包中的C文件。一個包中的所有CPPFLAGS和CXXFLAGS指令會被連接起來,并用來編譯包中的C++文件。一個包中的所有CPPFLAGS和FFLAGS指令會被連接起來,并用來編譯包中的Fortran文件。在這個程序中任何包內的所有LDFLAGS指令會被連接起來,并在鏈接時使用。所有的pkg-config指令會被連接起來,并同時發送給pkg-config,以添加到每個適當的命令行標志集中。
當cgo指令被轉化【parse】時,任何出現${SRCDIR}字符串的地方,都會被替換為包含源文件的目錄的絕對路徑。這就允許預編譯的靜態庫包含在包目錄中,并能夠正確的鏈接。例如,如果foo包在/go/src/foo目錄下:
~~~
// #cgo LDFLAGS: -L${SRCDIR}/libs -lfoo
~~~
就會被展開為:
~~~
// #cgo LDFLAGS: -L/go/src/foo/libs -lfoo
~~~
>[info]心得: `// #cgo LDFLAGS:` 可用來鏈接靜態庫。`-L`指定靜態庫所在目錄,`-l`指定靜態庫文件名,注意靜態庫文件名必須有`lib`前綴,但是這里不需要寫,比如上面的-lfoo實際找的是libfoo.a文件
當Go tool發現一個或多個Go文件使用了特殊導入“C”包,它就會在目錄中尋找其它非Go文件,并將其編譯為Go包的一部分。任何.c, .s, 或者.S文件會被C編譯器編譯。任何.cc, .cpp, 或者.cxx文件會被C++編譯器編譯。任何.f, .F, .for或者.f90文件會被fortran編譯器編譯。任何.h, .hh, .hpp或者.hxx文件不會被分開編譯,但是,如果這些頭文件修改了,那么C和C++文件就會被重新編譯。默認的C和C++編譯器有可能會分別被CC和CXX環境變量改變,那些環境變量可能包含命令行選項。
cgo tool對于本地構建默認是啟用的。而在交叉編譯時默認是禁用的。你可以在運行go tool時,通過設置CGO_ENABLED環境變量來控制它:設為1表示啟用cgo, 設為0關閉它。如果cgo啟用,go tool將會設置構建約束“cgo”。
在交叉編譯時,你必須為cgo指定一個C語言交叉編譯器。你可以在使用make.bash構建工具鏈時,設置CC_FOR_TARGET環境變量來指定,或者是在你運行go tool時設置CC環境變量來指定。與此相似的還有作用于C++代碼的CXX_FOR_TARGET和CXX環境變量。
#### Go引用C代碼
在Go文件中,C的結構體屬性名是Go的關鍵字,可以加上下劃線前綴來訪問它們:如果x指向一個擁有屬性名"type"的C結構體,那么就可以用`x._type`來訪問這個屬性。對于那些不能在Go中表達的C結構體字段(例如位字段或者未對齊的數據),會在Go結構體中被省略,而替換為適當的填充,以到達下一個屬性,或者結構體的結尾。
標準C數字類型,可以通過如下名字訪問:
~~~
C.char,
C.schar(signed char),
C.uchar(unsigned char),
C.short,
C.ushort(unsigned short),
C.int,
C.uint(unsigned int),
C.long,
C.ulong(unsigned long),
C.longlong(long long),
C.ulonglong(unsigned long long),
C.float,
C.double,
C.complexfloat(complex float),
C.complexdouble(complex double)
~~~
C的`void*`類型由Go的`unsafe.Pointer`表示。C的`__int128_t`和`__uint128_t`由[16]byte表示。
如果想直接訪問一個結構體,聯合體,或者枚舉類型,請加上如下前綴:`struct_`, `union_`, 或者`enum_`。比如`C.struct_stat`。
>[warning]心得:但是如果C結構體用了typedef struct設置了別名,則就不需要加上前綴,可以直接C.alias訪問該類型。
任何一個C類型T的尺寸大小,都可以通過`C.sizeof_T`獲取,比如`C.sizeof_struct_stat`。
因為在通常情況下Go并不支持C的聯合體類型【union type】,所以C的聯合體類型,由一個等長的Go byte數組來表示。
Go結構體不能嵌入具有C類型的字段。
Go代碼不能引用發生在非空C結構體末尾的零尺寸字段。如果要獲取這個字段的地址(這也是對于零大小字段唯一能做的操作),你必須傳入結構體的地址,并加上結構體的大小,才能算出這個零大小字段的地址。
cgo會將C類型轉換為對應的,非導出的的Go類型。因為轉換是非導出的,一個Go包就不應該在它的導出API中暴露C的類型:在一個Go包中使用的C類型,不同于在其它包中使用的同樣C類型。
可以在多個賦值語境中,調用任何C函數(甚至是void函數),來獲取返回值(如果有的話),以及C errno變量作為Go error(如果方法返回void,則使用 _ 來跳過返回值)。例如:
~~~
n, err = C.sqrt(-1)
_, err := C.voidFunc()
var n, err = C.sqrt(1)
~~~
調用C的方法指針目前還不支持,然而你可以聲明Go變量來引用C的方法指針,然后在Go和C之間來回傳遞它。C代碼可以調用來自Go的方法指針。例如:
~~~
package main
// typedef int (*intFunc) ();
//
// int
// bridge_int_func(intFunc f)
// {
// return f();
// }
//
// int fortytwo()
// {
// return 42;
// }
import "C"
import "fmt"
func main() {
f := C.intFunc(C.fortytwo)
fmt.Println(int(C.bridge_int_func(f)))
// Output: 42
}
~~~
在C中,一個方法參數被寫為一個固定大小的數組,實際上需要的是一個指向數組第一個元素的指針。C編譯器很清楚這個調用習慣,并相應的調整這個調用,但是Go不能這樣做。在Go中,你必須顯式的傳入指向第一個元素的指針:C.f(&C.x[0])。
在Go和C類型之間,通過拷貝數據,還有一些特殊的方法轉換。用Go偽代碼定義如下:
~~~
// Go string to C string
// The C string is allocated in the C heap using malloc.
// It is the caller's responsibility to arrange for it to be
// freed, such as by calling C.free (be sure to include stdlib.h
// if C.free is needed).
func C.CString(string) *C.char
// Go []byte slice to C array
// The C array is allocated in the C heap using malloc.
// It is the caller's responsibility to arrange for it to be
// freed, such as by calling C.free (be sure to include stdlib.h
// if C.free is needed).
func C.CBytes([]byte) unsafe.Pointer
// C string to Go string
func C.GoString(*C.char) string
// C data with explicit length to Go string
func C.GoStringN(*C.char, C.int) string
// C data with explicit length to Go []byte
func C.GoBytes(unsafe.Pointer, C.int) []byte
~~~
作為一個特殊例子,C.malloc并不是直接調用C的庫函數malloc,而是調用一個Go的幫助函數【helper function】,該函數包裝了C的庫函數malloc,并且保證不會返回nil。如果C的malloc指示內存溢出,這個幫助函數會崩潰掉程序,就像Go自己運行時內存溢出一樣。因為C.malloc從不失敗,所以它不會返回包含errno的2值格式。