這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
Cgo 使得Go程式能夠調用C代碼. cgo讀入一個用特別的格式寫的Go語言源檔案, 輸出Go和C程式, 使得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 包包含4個到C包的引用: 調用 C.random和C.srandom, 類型轉換 C.uint(i)還有引用語句.
Random函數調用libc中的random函數, 然後回返結果. 在C中, random返回一個C類型的長整形值, 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的文檔中有完整的類型列表.
這個例子中還有一個細節我們沒有說到, 那就是匯入語句上面的注釋.
/* #include <stdlib.h> */ import "C"
Cgo可以識別這個注釋, 並在編譯C語言程式的時候將它當作一個標頭檔來處理. 在這個例子中, 它只是一個include語句, 然而其實它可以是使用有效C語言代碼. 這個注釋必需緊靠在import "C"這個語句的上面, 不能有空行, 就像是文檔注釋一樣.
Strings and things
與Go語言不同, C語言中沒有顯式的字串類型. 字串在C語言中是一個以0結尾的字元數組.
Go和C語言中的字串轉換是通過C.CString, C.GoString,和C.GoStringN這些函數進行的. 這些轉換將得到字串類型的一個副本.
下一個例子是實現一個Print函數, 它使用C標準庫中的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(Go中與C的void 等價的東西), 使用C.free來釋放分配的記憶體. 一個慣用法是在分配記憶體後緊跟一個defer(特別是當這段代碼比較複雜的時候), 這樣我們就有了下面這個Print函數:
func Print(s string) { cs := C.CString(s) defer C.free(unsafe.Pointer(cs)) C.fputs(cs, (*C.FILE)(C.stdout)) }
構建 cgo 包
如果你使用goinstall, 構建cgo包就比較容易了, 只要調用像平常一樣使用goinstall命令, 它就能自動識別這個特殊的import "C", 然後自動使用cgo來編譯這些檔案.
如果你想使用Go的Makefiles來構建, 那在CGOFILES變數中列出那些要用cgo處理的檔案, 就像GOFILES變數包含一般的Go源檔案一樣.
rand包的Makefile可以寫成下面這樣:
include $(GOROOT)/src/Make.inc TARG=goblog/rand CGOFILES=\ rand.go\ include $(GOROOT)/src/Make.pkg
然後輸入gomake開始構建.
更多 cgo 的資源
cgo的文檔中包含了關於C偽包的更多詳細的說明, 以及構建過程. Go代碼樹中的cgo的例子給出了更多更進階的用法.
一個簡單而又符合Go慣用法的基於cgo的包是Russ Cox寫的gosqlite. 而Go語言的網站上也列出了更多的的cgo包.
最後, 如果你對於cgo的內部是怎麼運作這個事情感到好奇的話, 去看看運行時包的cgocall.c檔案的注釋吧.
原文連結:http://blog.golang.org/2011/03/c-go-cgo.html