C和Go相互调用

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

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的状态变量

我们来分析第二步中的一个特殊的场景, 为了下面我们好区分,我们给程序标记一下, 记为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是不同的两个变量。

nm可以查看生成的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符号,这和上一步是不一样的。

参考文档

  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

版权申明:内容来源网络,版权归原创者所有。除非无法确认,我们都会标明作者及出处,如有侵权烦请告知,我们会立即删除并表示歉意。谢谢。

原文发布于微信公众号 - Golang语言社区(Golangweb)

原文发表时间:2018-11-01

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Python小屋

Python编程常见出错信息及原因分析(1)

1.被0除错误 演示代码: >>> 2 / 0 Traceback (most recent call last): File "<pyshell#0>",...

31560
来自专栏运维小白

linux基础(day26)

9.1正则介绍_grep(上) 正则介绍 正则就是一串有规律的字符串 掌握好正则对编写shell脚本帮助交大 各种编程语言中都有正则,原理是一样的 grep/e...

256100
来自专栏IMWeb前端团队

Zepto源码分析之ie模块

本文作者:IMWeb 谦龙 原文出处:IMWeb社区 未经同意,禁止转载 前言 ? Zepto中的ie模块主要是改写getComputedStyle...

20180
来自专栏武军超python专栏

2018-7-16python中四种组合数据类型和pycharm的安装和使用

集合(set) discard删除数据时如果集合里面没有那个数据什么也不做,集合相减可以直接用-,+*/都不能用

18850
来自专栏有趣的Python和你

Flask学习笔记之模板(一)

之前的视图函数返回的都是字符串,这样是很不利于网站建设,大家都知道,我们都网页构造三大元素(html,css,js),那这些数据如何通过视图函数返回了?答案就是...

13720
来自专栏JackeyGao的博客

Django小技巧08: Blank or Null

Django Model API 中提供了blank和null两个参数, 非常容易混淆。当我第一次使用 Django 的时候, 总是不能恰当的使用这两个参数。

8130
来自专栏http://www.cnblogs.com

第一周作业-三级菜单

image.png 1. 运行程序输出第一级菜单 2. 选择一级菜单某项,输出二级菜单,同理输出三级菜单 3. 菜单数据保存在文件中 4. 让用户选择是否要退出...

381100
来自专栏黑白安全

C++如何调用class类中方法实现多线程编程

众所周知在使用C++创建多线程执行时只能传递一个方法到thread模块中去创建线程执行。但是有时候我们往往需要使用多线程去执行某个对象中的方法,而对象中的方法却...

8220
来自专栏http://www.cnblogs.com

shelve模块

shelve是一个简单的数据存储方案,类似key-value数据库,可以很方便的保存python对象,其内部是通过pickle协议来实现数据序列化。shelve...

376170
来自专栏C/C++基础

shell编程知识点集锦

shell脚本是按行分隔每一条shell语句。如果每一条shell语句写在单独一行,此时可以加分号,也可以不加,没有什么区别。如果多条shell写在同一行,那么...

10410

扫码关注云+社区

领取腾讯云代金券