1. 程式人生 > >C和Go相互呼叫

C和Go相互呼叫

640?wx_fmt=jpeg

關注↑↑↑我們獲得更多精彩內容!

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!\n")

7}

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

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

1#include <stdio.h>

2#include "_cgo_export.h"

3int helloFromC() {

printf("Hi from C\n");

//call Go function

6 HelloFromGo();

return 0;

8}

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

函式。

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

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

1package main

2/*

3extern int helloFromC();

4*/

5import"C"

6func main() {

//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() {

printf("Hi from C\n");

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!\n")

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() {

printf("use hello lib from C:\n");

5

6 HelloFromGo();

7

return 0;

9}

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

1$ ./main

2use hello lib from C:

3Hello fromGo!

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() {

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

return 0;

6}

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

1#include <stdio.h>

2#include "hello.h"

3int main() {

printf("use hello lib from C:\n");

5

// 1. 直接呼叫C函式

7 helloFromC();

// 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 fromGo!

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 fromGo!

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