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))
}
讓我們從匯入語句開始,看看這裡發生了什麼。
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 資源
有關 C 偽包和構建過程的更多詳細資訊,請參閱 cgo 命令文件。Go 樹中的 cgo 示例演示了更高階的概念。
最後,如果你好奇所有這些是如何在內部工作的,可以看看執行時包的 cgocall.go 的介紹性註釋。
下一篇文章:Gobs of data
上一篇文章:Go 變得更穩定
部落格索引