C可以調用Go,并且Go可以調用C, 如果更進一步呢, C-->Go-->C Go-->C-->Go
或者
的調用如何實作?
本文通過兩個簡單的例子幫助你了解這兩種複雜的調用關系。本文不涉及兩者之間的複雜的資料轉換,官方文章C? Go? Cgo!、wiki/cgo和cmd/cgo有一些介紹。
Go-->C-->GoGo程式調用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語言社群”,了解相關資訊可以關注“
”。