關註↑↑↑我們獲得更多精彩內容!
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
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
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
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
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
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_essay-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_essay-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