C和Go相互呼叫

技術小能手發表於2018-11-02

C可以呼叫Go,並且Go可以呼叫C, 如果更進一步呢, C-->Go-->C 或者 Go-->C-->Go的呼叫如何實現?

本文通過兩個簡單的例子幫助你瞭解這兩種複雜的呼叫關係。本文不涉及兩者之間的複雜的資料轉換,官方文章C? Go? Cgo!、wiki/cgo和cmd/cgo有一些介紹。

Go–>C–>Go

Go程式呼叫C實現的函式,然後C實現的函式又呼叫Go實現的函式。

1、首先,我們新建一個hello.go的檔案:


1package main



2import "C"



3import "fmt"



4//export HelloFromGo



5func HelloFromGo() {



6 fmt.Printf("Hello from Go!
"
)



7}


它定義了一個HelloFromGo函式,注意這個函式是一個純的Go函式,我們定義它的輸出符號為HelloFromGo

2、接著我們新建一個hello.c的檔案:


1#include <stdio.h>



2#include "_cgo_export.h"



3int helloFromC() {



4 printf("Hi from C
"
);



5 //call Go function



6 HelloFromGo();



7 return 0;



8}


這個c檔案定義了一個C函式helloFromC,內部它會呼叫我們剛才定義的HelloFromGo函式。

這樣,我們實現了C呼叫Go: C-->Go,下面我們再實現Go呼叫C。

3、最後新建一個main.go檔案:


1package main



2/*



3extern int helloFromC();



4*/



5import "C"



6func main() {



7 //call c function



8 C.helloFromC()



9}


它呼叫第二步實現的C函式helloFromC

執行測試一下:


1$ go run .



2Hi from C



3Hello from Go!


可以看到,期望的函式呼叫正常的執行。第一行是C函式的輸出,第二行是Go函式的輸出。

C–>Go–>C

第二個例子演示了C程式呼叫Go實現的函式,然後Go實現的函式又呼叫C實現的函式。

1、首先新建一個hello.c檔案:


1#include <stdio.h>



2int helloFromC() {



3 printf("Hi from C
"
);



4 return 0;



5}


它定義了一個純C實現的函式。

2、接著新建一個hello.go檔案:


1// go build -o hello.so -buildmode=c-shared .



2package main



3/*



4extern int helloFromC();



5*/



6import "C"



7import "fmt"



8//export HelloFromGo



9func HelloFromGo() {



10 fmt.Printf("Hello from Go!
"
)



11 C.helloFromC()



12}



13func main() {



14}


它實現了一個Go函式HelloFromGo,內部實現呼叫了C實現的函式helloFromC,這樣我們就實現了Go-->C

注意包名設定為package main,並且增加一個空的main函式。

執行go build -o hello.so -buildmode=c-shared .生成一個C可以呼叫的庫,這調命令執行完後會生成hello.so檔案和hello.h檔案。

3、最後新建一個資料夾,隨便起個名字,比如main

將剛才生成的hello.so檔案和hello.h檔案複製到main資料夾,並在main資料夾中新建一個檔案main.c:


1#include <stdio.h>



2#include "hello.h"



3int main() {



4 printf("use hello lib from C:
"
);



5



6 HelloFromGo();



7



8 return 0;



9}


執行gcc -o main main.c hello.so生成可執行檔案main, 執行main:


1$ ./main



2use hello lib from C:



3Hello from Go!



4Hi from C


第一行輸出來自main.c,第二行來自Go函式,第三行來自hello.c中的C函式,這樣我們就實現了C-->Go--C的複雜呼叫。

C-->Go-->C的狀態變數

我們來分析第二步中的一個特殊的場b景, 為了下面我們好區分,我們給程式標記一下, 記為C1-->Go-->C2, C2的程式修改一下,加入一個狀態變數a,並且函式helloFromC中會列印a的地址和值,也會將a加一。


1#include <stdio.h>



2int a = 1;



3int helloFromC() {



4 printf("Hi from C: %p, %d
"
, &a, a++);



5 return 0;



6}


然後修改main.c程式,讓它既通過Go嗲用C1.helloFromC,又直接呼叫C1.helloFromC,看看多次呼叫的時候a的指標是否一致,並且a的值是否有變化。


1#include <stdio.h>



2#include "hello.h"



3int main() {



4 printf("use hello lib from C:
"
);



5



6 // 1. 直接呼叫C函式



7 helloFromC();



8 // 2. 呼叫Go函式



9 HelloFromGo();



10



11 // 3. 直接呼叫C函式



12 helloFromC();



13 return 0;



14}


激動人心的時候到了。我們不同的編譯方式會產生不同的結果。

1、gcc -o main main.c hello.so

和第二步相同的編譯方式,編譯出main並執行, 因為hello.so中包含C1.helloFromC實現,所以可以正常執行。


1./main



2use hello lib from C:



3Hi from C: 0x10092a370, 1



4Hello from Go!



5Hi from C: 0x10092a370, 2



6Hi from C: 0x10092a370, 3


可以看到a的指標是同一個值,無論通過Go函式改變還是通過C函式改變都是更改的同一個變數。

nm可以檢視生成的main的符號:


1nm main



2 U _HelloFromGo



30000000100000000 T __mh_execute_header



4 U _helloFromC



50000000100000f10 T _main



6 U _printf



7 U dyld_stub_binder


U代表這個符號是未定義的符號,通過動態庫連結進來。

2、 gcc -o main main.c hello.so ../hello.c

我們編譯的時候直接連結hello.c的實現,然後執行main:


1./main



2use hello lib from C:



3Hi from C: 0x104888020, 1



4Hello from Go!



5Hi from C: 0x1049f7370, 1



6Hi from C: 0x104888020, 2


可以看到a是不同的兩個變數。

nm可以檢視生成的main的符號:


1nm main



2 U _HelloFromGo



30000000100000000 T __mh_execute_header



40000000100001020 D _a



50000000100000f10 T _helloFromC



60000000100000ec0 T _main



7 U _printf



8 U dyld_stub_binder


可以看到_a是初始化的環境變數,_helloFromC的型別是T而不是U,代表它是一個全域性的Text符號,這和上一步是不一樣的。

參考文件

 ●  https://medium.com/using-go-in-mobile-apps/using-go-in-mobile-apps-part-1-calling-go-functions-from-c-be1ecf7dfbc6
 ●  https://github.com/vladimirvivien/go-cshared-examples
 ●  http://golang.org/cmd/cgo
 ●  https://gist.github.com/zchee/b9c99695463d8902cd33
 ●  https://medium.com/@liamkelly17/working-with-packed-c-structs-in-cgo-224a0a3b708b
 ●  https://groups.google.com/forum/#!topic/golang-nuts/EhndTzcPJxQ
 ●  https://docs.google.com/document/d/1nr-TQHw_er6GOQRsF6T43GGhFDelrAP0NqSS_00RgZQ/edit#

 ●  https://www.mkssoftware.com/docs/man1/nm.1.asp


原文釋出時間為:2018-11-1

本文作者:smallnest

本文來自雲棲社群合作伙伴“Golang語言社群”,瞭解相關資訊可以關注“Golang語言社群”。


相關文章