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是不同的两个变量。

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符号,这和上一步是不一样的。

参考文档

 ●  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语言社区”,了解相关信息可以关注“Golang语言社区”。

相关文章
|
Go C语言 C++
go与c互相调用
此例子来自于go源码中,借此来和大家分享一下两者如何调用,网上很多文章语言不详,也没有一个完整的测试例子 目录结构 首先src 目录下有 testcgowin目录下: 这里的_obj目录是cgo生成的 这里需要展示的是go中如何调用c语言导出函数,以及在c语言中如何调用go的导出函数.
946 0
|
4月前
|
存储 安全 Java
【Golang】(4)Go里面的指针如何?函数与方法怎么不一样?带你了解Go不同于其他高级语言的语法
结构体可以存储一组不同类型的数据,是一种符合类型。Go抛弃了类与继承,同时也抛弃了构造方法,刻意弱化了面向对象的功能,Go并非是一个传统OOP的语言,但是Go依旧有着OOP的影子,通过结构体和方法也可以模拟出一个类。
277 1
|
12月前
|
编译器 Go
揭秘 Go 语言中空结构体的强大用法
Go 语言中的空结构体 `struct{}` 不包含任何字段,不占用内存空间。它在实际编程中有多种典型用法:1) 结合 map 实现集合(set)类型;2) 与 channel 搭配用于信号通知;3) 申请超大容量的 Slice 和 Array 以节省内存;4) 作为接口实现时明确表示不关注值。此外,需要注意的是,空结构体作为字段时可能会因内存对齐原因占用额外空间。建议将空结构体放在外层结构体的第一个字段以优化内存使用。
|
12月前
|
运维 监控 算法
监控局域网其他电脑:Go 语言迪杰斯特拉算法的高效应用
在信息化时代,监控局域网成为网络管理与安全防护的关键需求。本文探讨了迪杰斯特拉(Dijkstra)算法在监控局域网中的应用,通过计算最短路径优化数据传输和故障检测。文中提供了使用Go语言实现的代码例程,展示了如何高效地进行网络监控,确保局域网的稳定运行和数据安全。迪杰斯特拉算法能减少传输延迟和带宽消耗,及时发现并处理网络故障,适用于复杂网络环境下的管理和维护。
|
6月前
|
Cloud Native 安全 Java
Go:为云原生而生的高效语言
Go:为云原生而生的高效语言
390 1
|
6月前
|
Cloud Native Go API
Go:为云原生而生的高效语言
Go:为云原生而生的高效语言
466 0
|
6月前
|
Cloud Native Java Go
Go:为云原生而生的高效语言
Go:为云原生而生的高效语言
313 0
|
6月前
|
Cloud Native Java 中间件
Go:为云原生而生的高效语言
Go:为云原生而生的高效语言
340 0
|
6月前
|
Cloud Native Java Go
Go:为云原生而生的高效语言
Go:为云原生而生的高效语言
376 0

热门文章

最新文章