Go 部落格
C? Go? Cgo!
引言
Cgo 允許 Go 包呼叫 C 程式碼。給定一個帶有特殊功能的 Go 原始檔,cgo 會輸出 Go 和 C 檔案,這些檔案可以組合成一個 Go 包。
為了以一個例子開頭,這裡有一個 Go 包,它提供了兩個函式——Random
和 Seed
——它們封裝了 C 的 random
和 srandom
函式。
package rand
/*
#include <stdlib.h>
*/
import "C"
func Random() int {
return int(C.random())
}
func Seed(i int) {
C.srandom(C.uint(i))
}
讓我們看看這裡發生了什麼,從 import 語句開始。
rand
包匯入了 "C"
,但你會發現標準 Go 庫中沒有這樣一個包。這是因為 C
是一個“偽包”,一個由 cgo 解釋為對 C 名稱空間的引用的特殊名稱。
rand
包包含對 C
包的四處引用:呼叫 C.random
和 C.srandom
,轉換 C.uint(i)
,以及 import
語句。
Random
函式呼叫標準 C 庫的 random
函式並返回結果。在 C 中,random
返回 C 型別 long
的值,cgo 將其表示為 C.long
型別。在使用此包之外的 Go 程式碼之前,它必須使用普通的 Go 型別轉換轉換為 Go 型別
func Random() int {
return int(C.random())
}
這裡有一個使用臨時變數更明確地說明型別轉換的等效函式
func Random() int {
var r C.long = C.random()
return int(r)
}
Seed
函式在某種程度上做了相反的事情。它接受一個普通的 Go int
,將其轉換為 C unsigned int
型別,並將其傳遞給 C 函式 srandom
。
func Seed(i int) {
C.srandom(C.uint(i))
}
注意 cgo 將 unsigned int
型別識別為 C.uint
;有關這些數值型別名稱的完整列表,請參閱cgo 文件。
這個例子中我們還沒有考察的一個細節是 import
語句上方的註釋。
/*
#include <stdlib.h>
*/
import "C"
Cgo 能夠識別此註釋。任何以 #cgo
後跟一個空格字元開頭的行都會被刪除;這些行會成為 cgo 的指令。其餘行在編譯包的 C 部分時用作標頭檔案。在本例中,這些行只是一個簡單的 #include
語句,但它們可以是幾乎任何 C 程式碼。#cgo
指令用於在構建包的 C 部分時為編譯器和連結器提供標誌。
有一個限制:如果你的程式使用了任何 //export
指令,那麼註釋中的 C 程式碼只能包含宣告(extern int f();
),不能包含定義(int f() { return 1; }
)。你可以使用 //export
指令使 Go 函式可以被 C 程式碼訪問。
#cgo
和 //export
指令在cgo 文件中有詳細說明。
字串等
與 Go 不同,C 沒有顯式的字串型別。C 中的字串由以零結尾的字元陣列表示。
Go 字串和 C 字串之間的轉換使用 C.CString
、C.GoString
和 C.GoStringN
函式完成。這些轉換會複製字串資料。
下一個例子實現了一個 Print
函式,它使用 C stdio
庫中的 fputs
函式將字串寫入標準輸出
package print
// #include <stdio.h>
// #include <stdlib.h>
import "C"
import "unsafe"
func Print(s string) {
cs := C.CString(s)
C.fputs(cs, (*C.FILE)(C.stdout))
C.free(unsafe.Pointer(cs))
}
C 程式碼進行的記憶體分配對於 Go 的記憶體管理器來說是未知的。當你使用 C.CString
(或任何 C 記憶體分配)建立一個 C 字串時,你必須記住在使用完畢後透過呼叫 C.free
來釋放記憶體。
呼叫 C.CString
會返回一個指向字元陣列開頭的指標,因此在函式退出之前,我們將其轉換為 unsafe.Pointer
並使用 C.free
釋放記憶體分配。cgo 程式中一個常見的習慣用法是在分配後立即 defer
釋放(尤其是在後續程式碼比單個函式呼叫更復雜時),就像 Print
的這個重寫版本一樣
func Print(s string) {
cs := C.CString(s)
defer C.free(unsafe.Pointer(cs))
C.fputs(cs, (*C.FILE)(C.stdout))
}
構建 cgo 包
要構建 cgo 包,只需像往常一樣使用 go build
或 go install
。go 工具會識別特殊的 "C"
匯入並自動對這些檔案使用 cgo。
更多 cgo 資源
cgo 命令文件提供了關於 C 偽包和構建過程的更多細節。Go 原始碼樹中的cgo 示例展示了更高階的概念。
最後,如果你對這一切的內部工作原理感到好奇,可以看看執行時包的 cgocall.go 檔案開頭的註釋。
下一篇文章:Gobs of data
上一篇文章:Go 變得更穩定
部落格索引