Go 部落格

C?Go?Cgo!

Andrew Gerrand
2011 年 3 月 17 日

簡介

Cgo 讓 Go 封包得以呼叫 C 程式碼。cgo 會在給定具有某些特殊功能的 Go 原始碼檔案後,輸出可結合在單一 Go 封包中的 Go 和 C 檔案。

首先用一個範例說明,這裡有一個 Go 封包提供了兩個函式,分別是 RandomSeed,包裝了 C 的 randomsrandom 函式。

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.randomC.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.CStringC.GoStringC.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 buildgo install 即可。go 工具會辨識特殊的 "C" 匯入,並自動為那些檔案使用 cgo。

更多 cgo 資源

cgo 指令 文件提供了關於 C 偽套件和建置程序的更多詳細資訊。Go 樹狀結構中的 cgo 範例 展示了更進階的概念。

最後,如果你很想知道這一切是如何在內部運作的,可以看看執行時期套件中的 cgocall.go 的簡介說明。

下一篇: 大量資料
上一篇: Go 變更穩定
部落格索引