【GO】Golang/C++混合編程 - 基礎(chǔ)

文章系列
【GO】Golang/C++混合編程 - SWIG
【GO】Golang/C++混合編程 - 初識
【GO】Golang/C++混合編程 - 入門
【GO】Golang/C++混合編程 - 基礎(chǔ)
【GO】Golang/C++混合編程 - 進階一
【GO】Golang/C++混合編程 - 進階二
【GO】Golang/C++混合編程 - 實戰(zhàn)

Golang/C++混合編程

類型轉(zhuǎn)換

CGO 是一個聯(lián)通 GO 語言和 C 語言的雙向橋梁盗舰,它允許 GO 語言調(diào)用 C 語言庫垛孔,反之亦然。CGO 的一個核心功能就是類型轉(zhuǎn)換泽示,它允許 GO 語言和 C 語言之間的數(shù)據(jù)交換。

在 GO 語言中訪問 C 語言的符號時谅猾,一般是通過虛擬的“C”包訪問抗果,比如C.int對應 C 語言的 int 類型。有些 C 語言的類型是由多個關(guān)鍵字組成纳像,但通過虛擬的“C”包訪問 C 語言類型時名稱部分不能有空格字符,比如unsigned int不能直接通過C.unsigned int訪問拯勉。因此CGO為C語言的基礎(chǔ)數(shù)值類型都提供了相應轉(zhuǎn)換規(guī)則竟趾,比如C.uint對應C語言的unsigned int憔购。

基礎(chǔ)類型對照表

以下為CGO類型和C語言類型的對照表:

C語言類型 CGO類型 Go語言類型
char C.char byte
signed char C.schar int8
unsigned char C.uchar uint8
short C.short int16
unsigned short C.ushort uint16
int C.int int32
unsigned int C.uint uint32
long C.long int32
unsigned long C.ulong uint32
long long int C.longlong int64
unsigned long long int C.ulonglong uint64
float C.float float32
double C.double float64
size_t C.size_t uint
int8_t C.int8_t int8
uint8_t C.uint8_t uint8
int16_t C.int16_t int16
uint16_t C.uint16_t uint16
int32_t C.int32_t int32
uint32_t C.uint32_t uint32
int64_t C.int64_t int64
uint64_t C.uint64_t uint64

需要注意的是,雖然在 C 語言中int岔帽、short等類型沒有明確定義內(nèi)存大小玫鸟,但是在 CGO 中它們的內(nèi)存大小是確定的。在 CGO 中犀勒,C 語言的 int 和 long 類型都是對應4個字節(jié)的內(nèi)存大小屎飘,size_t 類型可以當作 Go 語言 uint 無符號整數(shù)類型對待。
CGO 中贾费,雖然 C 語言的 int 固定為4字節(jié)的大小钦购,但是 Go 語言自己的 int 和 uint 卻在32位和64位系統(tǒng)下分別對應4個字節(jié)和8個字節(jié)大小。如果需要在 C 語言中訪問 Go 語言的 int 類型褂萧,可以通過 GoInt 類型訪問押桃。

CGO 頭文件 "_cgo_export.h"

typedef signed char GoInt8;
typedef unsigned char GoUint8;
typedef short GoInt16;
typedef unsigned short GoUint16;
typedef int GoInt32;
typedef unsigned int GoUint32;
typedef long long GoInt64;
typedef unsigned long long GoUint64;
typedef GoInt64 GoInt;
typedef GoUint64 GoUint;
typedef float GoFloat32;
typedef double GoFloat64;
typedef struct { const char *p; GoInt n; } GoString;
typedef void *GoMap;
typedef void *GoChan;
typedef struct { void *t; void *v; } GoInterface;
typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;

不過需要注意的是,其中只有字符串和切片在 CGO 中有一定的使用價值导犹,因為 CGO 為他們的某些 GO 語言版本的操作函數(shù)生成了 C 語言版本唱凯,因此二者可以在 Go 調(diào)用 C 語言函數(shù)時馬上使用;而 CGO 并未針對其他的類型提供相關(guān)的輔助函數(shù),且 Go 語言特有的內(nèi)存模型導致我們無法保持這些由 Go 語言管理的內(nèi)存指針谎痢,所以它們 C 語言環(huán)境并無使用的價值磕昼。

結(jié)構(gòu)體、聯(lián)合节猿、枚舉類型

結(jié)構(gòu)體

C 語言的結(jié)構(gòu)體票从、聯(lián)合、枚舉類型不能作為匿名成員被嵌入到 Go 語言的結(jié)構(gòu)體中滨嘱。在 Go 語言中峰鄙,我們可以通過C.struct_xxx來訪問 C 語言中定義的struct xxx結(jié)構(gòu)體類型。結(jié)構(gòu)體的內(nèi)存布局按照 C 語言的通用對齊規(guī)則九孩,在32位 Go 語言環(huán)境 C 語言結(jié)構(gòu)體也按照32位對齊規(guī)則先馆,在64位 Go 語言環(huán)境按照64位的對齊規(guī)則发框。對于指定了特殊對齊規(guī)則的結(jié)構(gòu)體躺彬,無法在 CGO 中訪問。

// 示例:
package main

/*
struct A {
    int i;
    float f;
    int type; // type 是 Go 語言的關(guān)鍵字
    float _type; // 將屏蔽CGO對 type 成員的訪問
};
*/
import "C"
import "fmt"
func main() {
    var a C.struct_A
    fmt.Println(a.i)
    fmt.Println(a.f)
    fmt.Println(a._type)
    // 未聲明 float _type 時梅惯, _type 對應 int type
    // 聲明 float _type 時宪拥, _type 對應 float _type
}

注:C 語言結(jié)構(gòu)體中位字段對應的成員無法在 Go 語言中訪問,如果需要操作位字段成員铣减,需要通過在 C 語言中定義輔助函數(shù)來完成她君。對應零長數(shù)組的成員,無法在 Go 語言中直接訪問數(shù)組的元素葫哗。在C語言中缔刹,我們無法直接訪問Go語言定義的結(jié)構(gòu)體類型球涛。

聯(lián)合

對于聯(lián)合類型,我們可以通過C.union_xxx來訪問 C 語言中定義的union xxx類型校镐。但是Go語言中并不支持C語言聯(lián)合類型亿扁,它們會被轉(zhuǎn)為對應大小的字節(jié)數(shù)組。

// 示例:
package main

/*
#include <stdint.h>
union B1 {
    int i;
    float f;
};
union B2 {
    int8_t i8;
    int64_t i64;
};
*/
import "C"
import "fmt"
func main() {
    var b1 C.union_B1;
    fmt.Printf("%T\n", b1) // [4]uint8
    var b2 C.union_B2;
    fmt.Printf("%T\n", b2) // [8]uint8

    // 如果需要操作C語言的聯(lián)合類型變量鸟廓,一般有三種方法:
    // 第一種是在C語言中定義輔助函數(shù)从祝;
    // 第二種是通過Go語言的”encoding/binary”手工解碼成員(需要注意大端小端問題);
    // 第三種是使用unsafe包強制轉(zhuǎn)型為對應類型(這是性能最好的方式)引谜。下面展示通過unsafe包訪問聯(lián)合類型成員的方式:
    fmt.Println("b1.i:", *(*C.int)(unsafe.Pointer(&b1)))
    fmt.Println("b1.f:", *(*C.float)(unsafe.Pointer(&b1)))
}

枚舉

對于枚舉類型牍陌,我們可以通過C.enum_xxx來訪問 C 語言中定義的enum xxx結(jié)構(gòu)體類型。

// 示例:
package main

/*
enum C {
    ONE,
    TWO,
};
*/
import "C"
import "fmt"
func main() {
    var c C.enum_C = C.TWO
    fmt.Println(c)
    fmt.Println(C.ONE)
    fmt.Println(C.TWO)
}

數(shù)組员咽、字符串和切片

C/GO 數(shù)組字符串定義

在 C 語言中毒涧,數(shù)組名其實對應于一個指針,指向特定類型特定長度的一段內(nèi)存骏融,但是這個指針不能被修改链嘀;當把數(shù)組名傳遞給一個函數(shù)時,實際上傳遞的是數(shù)組第一個元素的地址档玻。為了討論方便怀泊,我們將一段特定長度的內(nèi)存統(tǒng)稱為數(shù)組。C 語言的字符串是一個 char 類型的數(shù)組误趴,字符串的長度需要根據(jù)表示結(jié)尾的 NULL 字符的位置確定霹琼。C 語言中沒有切片類型。

在 GO 語言中凉当,數(shù)組是一種值類型枣申,而且數(shù)組的長度是數(shù)組類型的一個部分。GO 語言字符串對應一段長度確定的只讀 byte 類型的內(nèi)存看杭。GO 語言的切片則是一個簡化版的動態(tài)數(shù)組忠藤。

相互轉(zhuǎn)換

CGO 的C 虛擬包提供了以下一組函數(shù),用于Go語言和C語言之間數(shù)組和字符串的雙向轉(zhuǎn)換:

// 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
// Go []byte slice to C array
// The C array 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.CBytes([]byte) unsafe.Pointer
// C string to Go string
func C.GoString(*C.char) string
// C data with explicit length to Go string
func C.GoStringN(*C.char, C.int) string
// C data with explicit length to Go []byte
func C.GoBytes(unsafe.Pointer, C.int) []byte

其中C.CString針對輸入的 GO 字符串楼雹,克隆一個 C 語言格式的字符串模孩;返回的字符串由 C 語言的 malloc 函數(shù)分配,不使用時需要通過 C 語言的 free 函數(shù)釋放贮缅。C.CBytes函數(shù)的功能和C.CString類似榨咐,用于從輸入的 GO 語言字節(jié)切片克隆一個 C 語言版本的字節(jié)數(shù)組,同樣返回的數(shù)組需要在合適的時候釋放谴供。C.GoString用于將從 NULL 結(jié)尾的 C 語言字符串克隆一個 GO 語言字符串块茁。C.GoStringN是另一個字符數(shù)組克隆函數(shù)。C.GoBytes用于從 C 語言數(shù)組,克隆一個 GO 語言字節(jié)切片数焊。

該組輔助函數(shù)都是以克隆的方式運行永淌,轉(zhuǎn)換前和轉(zhuǎn)換后的內(nèi)存依然在各自的語言環(huán)境中,它們并沒有跨越Go語言和C語言佩耳⊙鲑鳎克隆方式實現(xiàn)轉(zhuǎn)換的優(yōu)點是接口和內(nèi)存管理都很簡單,缺點是克隆需要分配新的內(nèi)存和復制操作都會導致額外的開銷蚕愤。

指針

在 C 語言中答恶,不同類型的指針是可以顯式或隱式轉(zhuǎn)換的,如果是隱式只是會在編譯時給出一些警告信息萍诱。但是 GO 語言對于不同類型的轉(zhuǎn)換非常嚴格悬嗓,任何 C 語言中可能出現(xiàn)的警告信息在 GO 語言中都可能是錯誤!指針是 C 語言的靈魂裕坊,指針間的自由轉(zhuǎn)換也是 CGO 代碼中經(jīng)常要解決的第一個重要的問題包竹。

CGO 存在的一個目的就是打破 GO 語言的禁止,恢復 C 語言應有的指針的自由轉(zhuǎn)換和指針運算籍凝。以下代碼演示了如何將X類型的指針轉(zhuǎn)化為Y類型的指針:

var p *X
var q *Y

q = (*Y)(unsafe.Pointer(p)) // *X => *Y
p = (*X)(unsafe.Pointer(q)) // *Y => *X

任何類型的指針都可以通過強制轉(zhuǎn)換為unsafe.Pointer指針類型去掉原有的類型信息周瞎,然后再重新賦予新的指針類型而達到指針間的轉(zhuǎn)換的目的。類似 C 語言的 void* 指針饵蒂。

數(shù)值與指針

在 C 語言中声诸,數(shù)值和指針之間可以相互轉(zhuǎn)換,但是轉(zhuǎn)換的結(jié)果可能出乎意料退盯。在 CGO 中彼乌,數(shù)值和指針之間的轉(zhuǎn)換也是被禁止的,但是通過unsafe包渊迁,可以繞過這個限制慰照。GO 語言針對unsafe.Pointr指針類型特別定義了一個uintptr類型。我們可以uintptr為中介琉朽,實現(xiàn)數(shù)值類型到unsafe.Pointr指針類型到轉(zhuǎn)換毒租。以下代碼演示了如何將數(shù)值轉(zhuǎn)換為指針:

var p *X
var i uintptr

i = uintptr(unsafe.Pointer(p)) // *X => uintptr
p = (*X)(unsafe.Pointer(i)) // uintptr => *X

任何數(shù)值類型都可以通過uintptr類型轉(zhuǎn)換為指針類型,但是轉(zhuǎn)換的結(jié)果可能并不是我們期望的箱叁。數(shù)值到指針的轉(zhuǎn)換墅垮,實際上是將數(shù)值解釋為內(nèi)存地址,然后通過這個地址去訪問內(nèi)存蝌蹂。如果數(shù)值對應的內(nèi)存地址是非法的噩斟,那么轉(zhuǎn)換后的指針將無法正常工作曹锨。因此孤个,數(shù)值到指針的轉(zhuǎn)換需要格外小心,必須保證數(shù)值對應的內(nèi)存地址是合法的沛简。

函數(shù)

函數(shù)是 C 語言編程的核心齐鲤,通過 CGO 技術(shù)我們不僅僅可以在 GO 語言中調(diào)用 C 語言函數(shù)斥废,也可以將Go語言函數(shù)導出為C語言函數(shù)。

GO調(diào)用C函數(shù)

package main

/*
static int div(int a, int b) {
    return a/b;
}
*/
import "C"
import "fmt"

func main() {
    v := C.div(6, 3)
    fmt.Println(v)
}

在 C 語言中给郊,函數(shù)并不支持返回多個返回值牡肉。若我們期望 C 語言函數(shù)像 GO 語言函數(shù)一樣同時返回結(jié)果和錯誤信息,則可以借助errno.h標準庫所提供的errno宏來實現(xiàn)淆九。errno變量是一個全局變量统锤,當 C 語言函數(shù)執(zhí)行失敗時,會設(shè)置errno變量的值為一個錯誤碼炭庙,然后返回一個錯誤結(jié)果饲窿。GO 語言函數(shù)可以通過檢查errno變量的值來判斷 C 語言函數(shù)是否執(zhí)行成功。

package main

/*
#include <errno.h>
static int div(int a, int b) {
    if(b == 0) {
        errno = EINVAL;
        return 0;
    }
    return a/b;
}
*/
import "C"
import "fmt"

func main() {
    v0, err0 := C.div(2, 1)
    fmt.Println(v0, err0)
    v1, err1 := C.div(1, 0)
    fmt.Println(v1, err1)
}

C 語言 void 函數(shù)

C 語言函數(shù)還有一種沒有返回值類型的函數(shù)焕蹄,用void表示返回值類型逾雄。一般情況下,我們無法獲取void類型函數(shù)的返回值腻脏,因為沒有返回值可以獲取鸦泳。前面的例子中提到,CGO 對errno做了特殊處理永品,可以通過第二個返回值來獲取 C 語言的錯誤狀態(tài)做鹰。對于void類型函數(shù),這個特性依然有效鼎姐。

C調(diào)用GO函數(shù)

CGO 還有一個強大的特性:將 GO 函數(shù)導出為 C 語言函數(shù)誊垢。

package main
import "C"

//export add
func add(a, b C.int) C.int {
    return a+b
}

注:當導出 C 語言接口時,需要保證函數(shù)的參數(shù)和返回值類型都是 C 語言友好的類型症见,同時返回值不得直接或間接包含Go語言內(nèi)存空間的指針喂走。如果在兩個不同的 GO 語言包內(nèi),都存在一個同名的要導出為 C 語言函數(shù)的add函數(shù)谋作,那么在最終的鏈接階段將會出現(xiàn)符號重名的問題芋肠。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市遵蚜,隨后出現(xiàn)的幾起案子帖池,更是在濱河造成了極大的恐慌,老刑警劉巖吭净,帶你破解...
    沈念sama閱讀 212,718評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件睡汹,死亡現(xiàn)場離奇詭異,居然都是意外死亡寂殉,警方通過查閱死者的電腦和手機囚巴,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人彤叉,你說我怎么就攤上這事庶柿。” “怎么了秽浇?”我有些...
    開封第一講書人閱讀 158,207評論 0 348
  • 文/不壞的土叔 我叫張陵浮庐,是天一觀的道長。 經(jīng)常有香客問我柬焕,道長审残,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,755評論 1 284
  • 正文 為了忘掉前任斑举,我火速辦了婚禮维苔,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘懂昂。我一直安慰自己介时,他們只是感情好,可當我...
    茶點故事閱讀 65,862評論 6 386
  • 文/花漫 我一把揭開白布凌彬。 她就那樣靜靜地躺著沸柔,像睡著了一般。 火紅的嫁衣襯著肌膚如雪铲敛。 梳的紋絲不亂的頭發(fā)上褐澎,一...
    開封第一講書人閱讀 50,050評論 1 291
  • 那天,我揣著相機與錄音伐蒋,去河邊找鬼工三。 笑死,一個胖子當著我的面吹牛先鱼,可吹牛的內(nèi)容都是我干的俭正。 我是一名探鬼主播,決...
    沈念sama閱讀 39,136評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼焙畔,長吁一口氣:“原來是場噩夢啊……” “哼掸读!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起宏多,我...
    開封第一講書人閱讀 37,882評論 0 268
  • 序言:老撾萬榮一對情侶失蹤儿惫,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后伸但,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體肾请,經(jīng)...
    沈念sama閱讀 44,330評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,651評論 2 327
  • 正文 我和宋清朗相戀三年更胖,在試婚紗的時候發(fā)現(xiàn)自己被綠了铛铁。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片隔显。...
    茶點故事閱讀 38,789評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖避归,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情管呵,我是刑警寧澤梳毙,帶...
    沈念sama閱讀 34,477評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站捐下,受9級特大地震影響账锹,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜坷襟,卻給世界環(huán)境...
    茶點故事閱讀 40,135評論 3 317
  • 文/蒙蒙 一奸柬、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧婴程,春花似錦廓奕、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至衙四,卻和暖如春铃肯,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背传蹈。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評論 1 267
  • 我被黑心中介騙來泰國打工押逼, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人惦界。 一個月前我還...
    沈念sama閱讀 46,598評論 2 362
  • 正文 我出身青樓挑格,卻偏偏與公主長得像,于是被迫代替她去往敵國和親沾歪。 傳聞我的和親對象是個殘疾皇子恕齐,可洞房花燭夜當晚...
    茶點故事閱讀 43,697評論 2 351

推薦閱讀更多精彩內(nèi)容