原文链接:https://colobu.com/2018/08/28/c-and-go-calling-interaction/

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的文件:

hello.go

  1. package main
  2. import "C"
  3. import "fmt"
  4. //export HelloFromGo
  5. func HelloFromGo() {
  6. fmt.Printf("Hello from Go!\n")
  7. }

它定义了一个HelloFromGo函数,注意这个函数是一个纯的Go函数,我们定义它的输出符号为HelloFromGo。

2、接着我们新建一个hello.c的文件:

hello.c

  1. #include <stdio.h>
  2. #include "_cgo_export.h"
  3. int 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文件:

main.go

  1. package main
  2. /*
  3. extern int helloFromC();
  4. */
  5. import "C"
  6. func main() {
  7. //call c function
  8. C.helloFromC()
  9. }

它调用第二步实现的C函数helloFromC。

运行测试一下:

  1. $ go run .
  2. Hi from C
  3. Hello from Go!

可以看到,期望的函数调用正常的运行。第一行是C函数的输出,第二行是Go函数的输出。

C—>Go—>C

第二个例子演示了C程序调用Go实现的函数,然后Go实现的函数又调用C实现的函数。

1、首先新建一个hello.c文件:

hello.c

  1. #include <stdio.h>
  2. int helloFromC() {
  3. printf("Hi from C\n");
  4. return 0;
  5. }

它定义了一个纯C实现的函数。

2、接着新建一个hello.go文件:

  1. // go build -o hello.so -buildmode=c-shared .
  2. package main
  3. /*
  4. extern int helloFromC();
  5. */
  6. import "C"
  7. import "fmt"
  8. //export HelloFromGo
  9. func HelloFromGo() {
  10. fmt.Printf("Hello from Go!\n")
  11. C.helloFromC()
  12. }
  13. func 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:

main.c

  1. #include <stdio.h>
  2. #include "hello.h"
  3. int main() {
  4. printf("use hello lib from C:\n");
  5. HelloFromGo();
  6. return 0;
  7. }

运行gcc -o main main.c hello.so生成可执行文件main, 运行main:

  1. $ ./main
  2. use hello lib from C:
  3. Hello from Go!
  4. Hi from C

第一行输出来自main.c,第二行来自Go函数,第三行来自hello.c中的C函数,这样我们就实现了C—>Go—C的复杂调用。

C—>Go—>C的状态变量

我们来分析第二步中的一个特殊的场景, 为了下面我们好区分,我们给程序标记一下, 记为C1—>Go—>C2, C2的程序修改一下,加入一个状态变量a,并且函数helloFromC中会打印a的地址和值,也会将a加一。

hello.c

  1. #include <stdio.h>
  2. int a = 1;
  3. int helloFromC() {
  4. printf("Hi from C: %p, %d\n", &a, a++);
  5. return 0;
  6. }

然后修改main.c程序,让它既通过Go嗲用C1.helloFromC,又直接调用C1.helloFromC,看看多次调用的时候a的指针是否一致,并且a的值是否有变化。

main.c

  1. #include <stdio.h>
  2. #include "hello.h"
  3. int main() {
  4. printf("use hello lib from C:\n");
  5. // 1. 直接调用C函数
  6. helloFromC();
  7. // 2. 调用Go函数
  8. HelloFromGo();
  9. // 3. 直接调用C函数
  10. helloFromC();
  11. return 0;
  12. }

激动人心的时候到了。我们不同的编译方式会产生不同的结果。

1、gcc -o main main.c hello.so

和第二步相同的编译方式,编译出main并执行, 因为hello.so中包含C1.helloFromC实现,所以可以正常执行。

  1. ./main
  2. use hello lib from C:
  3. Hi from C: 0x10092a370, 1
  4. Hello from Go!
  5. Hi from C: 0x10092a370, 2
  6. Hi from C: 0x10092a370, 3

可以看到a的指针是同一个值,无论通过Go函数改变还是通过C函数改变都是更改的同一个变量。

nm可以查看生成的main的符号:

  1. nm main
  2. U _HelloFromGo
  3. 0000000100000000 T __mh_execute_header
  4. U _helloFromC
  5. 0000000100000f10 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
  2. use hello lib from C:
  3. Hi from C: 0x104888020, 1
  4. Hello from Go!
  5. Hi from C: 0x1049f7370, 1
  6. Hi from C: 0x104888020, 2

可以看到a是不同的两个变量。

nm可以查看生成的main的符号:

  1. nm main
  2. U _HelloFromGo
  3. 0000000100000000 T __mh_execute_header
  4. 0000000100001020 D _a
  5. 0000000100000f10 T _helloFromC
  6. 0000000100000ec0 T _main
  7. U _printf
  8. U dyld_stub_binder

可以看到_a是初始化的环境变量,_helloFromC的类型是T而不是U,代表它是一个全局的Text符号,这和上一步是不一样的。

参考文档

  1. https://medium.com/using-go-in-mobile-apps/using-go-in-mobile-apps-part-1-calling-go-functions-from-c-be1ecf7dfbc6
  2. https://github.com/vladimirvivien/go-cshared-examples
  3. http://golang.org/cmd/cgo
  4. https://gist.github.com/zchee/b9c99695463d8902cd33
  5. https://medium.com/@liamkelly17/working-with-packed-c-structs-in-cgo-224a0a3b708b
  6. https://groups.google.com/forum/#!topic/golang-nuts/EhndTzcPJxQ
  7. https://docs.google.com/document/d/1nr-TQHw_er6GOQRsF6T43GGhFDelrAP0NqSS_00RgZQ/edit#
  8. https://www.mkssoftware.com/docs/man1/nm.1.asp
ft_authoradmin  ft_create_time2018-11-04 19:37
 ft_update_time2018-11-04 19:42