最近在kubernetes上搞集成其它team算法的事情拌消,他們的算法是用c寫的蚤霞,自然地我需要用cgo去調(diào)用酗失。本文整理了使用cgo過程中的一些函數(shù),特此整理下來昧绣,記錄规肴。
1,給c函數(shù)傳遞二維整型數(shù)組
c函數(shù)需要接收一個(gè)二維整型數(shù)組作為參數(shù)夜畴,
extern bool xxx_func(int** _matrix);
最初實(shí)現(xiàn)如下:
goArray := [][]int{{1},{3,3},{3,3,2}}
cArray := make([][]C.int, len(goArray))
for i, _ := range goArray {
cArray[i] = make([]C.int, len(goArray[i]))
for j, _ := range goArray[i] {
cArray[i][j] = C.int(goArray[i][j])
}
}
C.xxx_func((**C.int)(unsafe.Pointer(&cArray[0][0]))) // 把該數(shù)組的首地址傳遞給c函數(shù)
這樣實(shí)現(xiàn)對嗎拖刃?當(dāng)然不對! 仔細(xì)看該函數(shù)贪绘,這個(gè)函數(shù)其實(shí)是創(chuàng)建了一個(gè)切片兑牡,然后里邊保存了N個(gè)獨(dú)立的切片,它的內(nèi)存地址并不是連續(xù)的税灌!當(dāng)然這對Go本身來說是正確的均函,但是對c來說卻大錯(cuò)特錯(cuò),c中的二維數(shù)組內(nèi)存地址必須是連續(xù)的(說白了c中的二維數(shù)組其實(shí)就是個(gè)一維數(shù)組菱涤,尋址方式不同)苞也。所以應(yīng)該按照如下方式去實(shí)現(xiàn):
- 分配一個(gè)長度為N*M的slice (N和M代表二維數(shù)組的行和列)
- 用go的二維數(shù)組中的值填充這個(gè)slice
- 傳遞這個(gè)slice的指針傳遞給c函數(shù)
第一種代碼實(shí)現(xiàn)如下:
n, m := len(goArray), 0
for _, row := range goArray {
if len(row) > m {
m = len(row)
}
}
cArray := make([]C.int, n*m)
for i, row := range goArray {
for j, v := range row {
a[i * n + j] = v
}
}
C.xxx_func((**C.int)(unsafe.Pointer(&cArray[0])))
然后運(yùn)行,發(fā)現(xiàn)cannot use &cArray[0] (type *C.int) as type **C.int in argument to func literal
, 上述的操作其實(shí)是把go的二維數(shù)組轉(zhuǎn)化為一維數(shù)組再傳遞給c函數(shù)狸窘,但是c函數(shù)要接收的是int**
即二維數(shù)組墩朦,所以報(bào)錯(cuò)坯认,最好的解決方式是把c函數(shù)的參數(shù)改為接收int*
一維數(shù)組翻擒。
But, 如果不能改c函數(shù)的參數(shù),該怎么辦呢牛哺?
第二種代碼實(shí)現(xiàn):
cArray := make([]*C.int, len(goArray))
for i, row := range goArray {
p := (*C.int)(C.malloc(C.size_t(C.sizeof_int * len(row))))
cArray[i] = p
pa := (*[1 << 30]C.int)(unsafe.Pointer(p))
for j, v := range row {
(*pa)[j] = C.int(v)
}
}
這里陋气,我們?yōu)間oArray的每一行分配一個(gè)源大小的C.int數(shù)組,并給將指針p付給cArray,
然后把p類型*C.int
轉(zhuǎn)化為*[1<<30]C.int
類型(指向一個(gè)很大的C.int型數(shù)組)引润,但是這里需要注意兩個(gè)問題:
- C.malloc()的內(nèi)存釋放
- 我們給每一行
row
分配了一個(gè)數(shù)組巩趁,但是數(shù)組長度信息丟失了!在go這邊沒問題,但是對于c來說议慰,一個(gè)數(shù)組僅僅是一個(gè)指針蠢古,它通過這個(gè)指針沒法知道指向這個(gè)內(nèi)存區(qū)域的長度,你得告訴它别凹,或者寫個(gè)包含該指針和長度的結(jié)構(gòu)體草讶,這個(gè)依賴于c那邊的具體實(shí)現(xiàn)
推薦使用第一種方法,因?yàn)閏go和c的內(nèi)存釋放問題就能折騰你好幾天炉菲,除非你清楚地知道堕战,哪里該釋放,哪里不應(yīng)該釋放拍霜。
2嘱丢,給c函數(shù)傳遞字符串?dāng)?shù)組
這個(gè)簡單多了,但是需要注意內(nèi)存釋放祠饺。代碼如下:
goString := []string{"w0", "w1", "w2", "w3"}
cString := make([]*C.char, len(goString))
for i, _ := range goString {
cString[i] = C.CString(goString[i])
// 注意這個(gè)內(nèi)存釋放越驻,該不該在調(diào)用完c函數(shù)之后釋放,依賴于c那邊的實(shí)現(xiàn)道偷,這里要小心使用伐谈!
defer C.free(unsafe.Pointer(cString[i]))
}
C.xxx_func2((**C.char)(unsafe.Pointer(&cString[0])))
關(guān)于C.CString的官方文檔:
// Go string to C string
// The C string is allocated in the C heap using malloc.
// It is the caller's responsibility to arrange for it to be
// freed, such as by calling C.free (be sure to include stdlib.h
// if C.free is needed).
func C.CString(string) *C.char
3,把c字符串?dāng)?shù)組轉(zhuǎn)化為go字符串切片
//你必須清楚地知道返回的c字符串?dāng)?shù)組的長度
func GoStrings(length int, argv **C.char) []string {
if argv == nil {
return nil
}
tmpslice := (*[1 << 30]*C.char)(unsafe.Pointer(argv))[:length:length]
gostrings := make([]string, length)
for i, s := range tmpslice {
gostrings[i] = C.GoString(s)
}
return gostrings
}
C.GoString的官方文檔试疙,真簡潔:(
// C string to Go string
func C.GoString(*C.char) string
參考資料:https://golang.org/cmd/cgo/
cgo wiki : https://github.com/golang/go/wiki/cgo
google大神多: https://groups.google.com/forum/#!topic/golang-nuts/Nb-nfVdAyF0