Go 部落格
C?Go?Cgo!
簡介
Cgo 讓 Go 封包得以呼叫 C 程式碼。cgo 會在給定具有某些特殊功能的 Go 原始碼檔案後,輸出可結合在單一 Go 封包中的 Go 和 C 檔案。
首先用一個範例說明,這裡有一個 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 中的字串由以 0 結尾的 char 陣列表示。
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
會傳回一個指針來指向 char 陣列的開頭,所以在函式結束之前,我們會將它轉換成一個 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 的簡介說明。