天天看點

C和Go互相調用

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

4 printf("Hi from C\n");

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

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

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

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\n", &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:\n");

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

是不同的兩個變量。

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語言社群

”,了解相關資訊可以關注“

”。

繼續閱讀