天天看点

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语言社区

”,了解相关信息可以关注“

”。

继续阅读