国产chinesehdxxxx野外,国产av无码专区亚洲av琪琪,播放男人添女人下边视频,成人国产精品一区二区免费看,chinese丰满人妻videos

Go語(yǔ)言 cgo關(guān)鍵技術(shù)

2018-07-25 16:21 更新

上一節(jié)我們看了一些預(yù)備知識(shí),解答了前面的一點(diǎn)疑惑。這一節(jié)我們將接著從宏觀上分析cgo實(shí)現(xiàn)中使用到的一些關(guān)鍵技術(shù)。而對(duì)于其中一些細(xì)節(jié)部分將留到下一節(jié)具體分析。

整個(gè)cgo的實(shí)現(xiàn)依賴于幾個(gè)部分,依賴于cgo命令生成樁文件,依賴于6c和6g對(duì)Go這一端的代碼進(jìn)行編譯,依賴gcc對(duì)C那一端編譯成動(dòng)態(tài)鏈接庫(kù),同時(shí),還依賴于運(yùn)行時(shí)庫(kù)實(shí)現(xiàn)Go和C互操作的一些支持。

cgo命令會(huì)生成一些樁文件,這些樁文件是給6c和6g命令使用的,它們是Go和C調(diào)用之間的橋梁。原始的C文件會(huì)使用gcc編譯成動(dòng)態(tài)鏈接庫(kù)的形式使用。

cgo命令

gc編譯器在編譯源文件時(shí),如果識(shí)別出go源文件中的

import "C"

字段,就會(huì)先調(diào)用cgo命令。cgo提取出相應(yīng)的C函數(shù)接口部分,生成樁文件。比如我們寫(xiě)一個(gè)go文件test.go,內(nèi)容如下:

package main

/*
#include "stdio.h"

void test(int n) {
  char dummy[10240];

  printf("in c test func iterator %d\n", n);
  if(n <= 0) {
    return;
  }
  dummy[n] = '\a';
  test(n-1);
}
#cgo CFLAGS: -g
*/
import "C"

func main() {
    C.test(C.int(2))
}

對(duì)它執(zhí)行cgo命令:

go tool cgo test.go

在當(dāng)前目錄下會(huì)生成一個(gè)_obj的文件夾,文件夾里會(huì)包含下列文件:

.
├── _cgo_.o
├── _cgo_defun.c
├── _cgo_export.c
├── _cgo_export.h
├── _cgo_flags
├── _cgo_gotypes.go
├── _cgo_main.c
├── test.cgo1.go
└── test.cgo2.c

樁文件

cgo生成了很多文件,其中大多數(shù)作用都是包裝現(xiàn)有的函數(shù),或者進(jìn)行聲明。比如在test.cgo2.c中,它生成了一個(gè)函數(shù)來(lái)包裝test函數(shù):

void
_cgo_1b9ecf7f7656_Cfunc_test(void *v)
{
    struct {
        int p0;
        char __pad4[4];
    } __attribute__((__packed__)) *a = v;
    test(a->p0);
}

在_cgo_defun.c中是封裝另一個(gè)函數(shù)來(lái)調(diào)用它:

void
·_Cfunc_test(struct{uint8 x[8];}p)
{
    runtime·cgocall(_cgo_1b9ecf7f7656_Cfunc_test, &p);
}

test.cgo1.go文件中包含一個(gè)main函數(shù),它調(diào)用封裝后的函數(shù):

func main() {
    _Cfunc_test(_Ctype_int(2))
}

cgo做這些封裝原因來(lái)自兩方面,一方面是Go運(yùn)行時(shí)調(diào)用cgo代碼時(shí)要做特殊處理,比如runtime.cgocall。另一方面是由于Go和C使用的命名空間不一樣,需要加一層轉(zhuǎn)換,像·_Cfunc_test中的·字符是Go使用的命令空間區(qū)分,而在C這邊使用的是_cgo_1b9ecf7f7656_Cfunc_test。

cgo會(huì)識(shí)別任意的C.xxx關(guān)鍵字,使用gcc來(lái)找到xxx的定義。C中的算術(shù)類型會(huì)被轉(zhuǎn)換為精確大小的Go的算術(shù)類型。C的結(jié)構(gòu)體會(huì)被轉(zhuǎn)換為Go結(jié)構(gòu)體,對(duì)其中每個(gè)域進(jìn)行轉(zhuǎn)換。無(wú)法表示的域?qū)?huì)用byte數(shù)組代替。C的union會(huì)被轉(zhuǎn)換成一個(gè)結(jié)構(gòu)體,這個(gè)結(jié)構(gòu)體中包含第一個(gè)union成員,然后可能還會(huì)有一些填充。C的數(shù)組被轉(zhuǎn)換成Go的數(shù)組,C指針轉(zhuǎn)換為Go指針。C的函數(shù)指針會(huì)被轉(zhuǎn)換為Go中的uinptr。C中的void指針轉(zhuǎn)換為Go的unsafe.Pointer。所有出現(xiàn)的C.xxx類型會(huì)被轉(zhuǎn)換為_(kāi)C_xxx。

如果xxx是數(shù)據(jù),那么cgo會(huì)讓C.xxx引用那個(gè)C變量(先做上面的轉(zhuǎn)換)。為此,cgo必須引入一個(gè)Go變量指向C變量,鏈接器會(huì)生成初始化指針的代碼。例如,gmp庫(kù)中:

mpz_t zero;

cgo會(huì)引入一個(gè)變量引用C.zero:

var _C_zero *C.mpz_t

然后將所有引用C.zero的實(shí)例替換為(*_C_zero)。

cgo轉(zhuǎn)換中最重要的部分是函數(shù)。如果xxx是一個(gè)C函數(shù),那么cgo會(huì)重寫(xiě)C.xxx為一個(gè)新的函數(shù)_C_xxx,這個(gè)函數(shù)會(huì)在一個(gè)標(biāo)準(zhǔn)pthread中調(diào)用C的xxx。這個(gè)新的函數(shù)還負(fù)責(zé)進(jìn)行參數(shù)轉(zhuǎn)換,轉(zhuǎn)換輸入?yún)?shù),調(diào)用xxx,然后轉(zhuǎn)換返回值。

參數(shù)轉(zhuǎn)換和返回值轉(zhuǎn)換與前面的規(guī)則是一致的,除了數(shù)組。數(shù)組在C中是隱式地轉(zhuǎn)換為指針的,而在Go中要顯式地將數(shù)組轉(zhuǎn)換為指針。

處理垃圾回收是個(gè)大問(wèn)題。如果是Go中引用了C的指針,不再使用時(shí)進(jìn)行釋放,這個(gè)很容易。麻煩的是C中使用了Go的指針,但是Go的垃圾回收并不知道,這樣就會(huì)很麻煩。

運(yùn)行時(shí)庫(kù)部分

運(yùn)行時(shí)庫(kù)會(huì)對(duì)cgo調(diào)用做一些處理,就像前面說(shuō)過(guò)的,執(zhí)行C函數(shù)之前會(huì)運(yùn)行runtime.entersyscall,而C函數(shù)執(zhí)行完返回后會(huì)調(diào)用runtime.exitsyscall。讓cgo的運(yùn)行仿佛是在另一個(gè)pthread中執(zhí)行的,然后函數(shù)執(zhí)行完畢后將返回值轉(zhuǎn)換成Go的值。

比較難處理的情況是,在cgo調(diào)用的C函數(shù)中,發(fā)生了C回調(diào)Go函數(shù)的情況,這時(shí)處理起來(lái)會(huì)比較復(fù)雜。因?yàn)榇藭r(shí)是沒(méi)有Go運(yùn)行環(huán)境的,所以必須再進(jìn)行一次特殊處理,回到Go的goroutine中調(diào)用相應(yīng)的Go函數(shù)代碼,完成之后繼續(xù)回到C的運(yùn)行環(huán)境??瓷先ビ悬c(diǎn)復(fù)雜,但是cgo對(duì)于在C中調(diào)用Go函數(shù)也是支持的。

從宏觀上來(lái)講cgo的關(guān)鍵技術(shù)就是這些,由cgo命令生成一些樁代碼,負(fù)責(zé)C類型和Go類型之間的轉(zhuǎn)換,命名空間處理以及特殊的調(diào)用方式處理。而運(yùn)行時(shí)庫(kù)部分則負(fù)責(zé)處理好C的運(yùn)行環(huán)境,類似于給C代碼一個(gè)非分段的??臻g并讓它脫離與調(diào)度系統(tǒng)的交互。


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)