cgo实践三:swig介绍
swig 对于写 C/C++ 的同学来说并不陌生,它是一个开发工具,用以把当前的C/C++项目和其他高层次语言连接起来,直白的说就是:可以生成供其他语言调用的api
接口代码。那么对于Go的项目来说,就是他可以直接为C/C++公共项目封装生成对应的cgo代码,作为Go应用开发的同学不用再手动封装cgo代码(别人给你写好了),从而降低了接入方的成本。
而且swig不单是针对Go语言来设计的,而是适配了众多的高级语言,如Python,Java、PHP、Javascript 等等,都可以生成对应语言所调用的接口,方便应用层快速接入。
本文主要是介绍swig和Go语言集成,如果你对其他语言也感兴趣,不妨去查阅一下官方文档。
在实际应用中,如果是大型C++项目,使用swig会如虎添翼。但如果是一个小型的C项目,使用swig反而会把事情复杂化,所以最好视具体情况评估。
安装 swig
linux 安装直接下载源码编译即可:www.swig.org/download.ht… ,yum 也提供swig
包,但是默认版本比较老旧,macOS 可以直接使用 brew install swig 进行安装。如果你还需要选择其它平台,可以查看官方文档:swig install
wget http://prdownloads.sourceforge.net/swig/swig-{versoin}.tar.gz && tar -zxvf
cd swig-*
./configure
make
sudo make install
使用 swig 流程
使用swig也不是很复杂,主要分为两个步:
- 定义
swig接口文件(swig interface file); - 使用
swig命令行生成对应语言的代码; - 集成使用。
swig接口文件
在使用swig时,我们需要提供一个接口定义文件(SWIG interface file),文件后缀往往是 .i 或者 .swig,这个文件用来告诉swig具体的操作逻辑,一个常规的接口文件大致内容如下:
/* File : example.i */
%module example
%{
/* Put headers and other declarations here */
extern double My_variable;
extern int fact(int);
extern int my_mod(int n, int m);
%}
extern double My_variable;
extern int fact(int);
extern int my_mod(int n, int m);
/*...*/ 代表的是注释部分。
%module 在go中表示对应的 package 包名。最终对应生成的cgo文件会转变为:
// example.go
package example
%{...%} 代表额外要插入的头文件声明变量或函数声明(如果有),对应cgo文件的如下部分区域,这和直接编写cgo文件没有区别:
/*
extern void _wrap_My_variable_set_example_dffde00251d303fd(double arg1);
extern double _wrap_My_variable_get_example_dffde00251d303fd(void);
*/
import "C"
import "unsafe"
import _ "runtime/cgo"
我们拿example.i声明的My_variable变量来说,你会发觉,最终生成的代码并没有直接是原生的My_variable名称,而是提供了包裹的俩Getter 和 Setter 函数。swig
默认会对我们需要暴露的数据类型做一层包装,而且这些代码都是通过命令行工具结合 example.i 接口文件动态生成的,所以你不能直接修改这些代码。
之所以要包裹一层,是因为swig需要做额外的数据管理,比如内存分配,内存释放等。使用方不用关心这些变量的内存管理问题,swig来包装生成对应的内存管理函数方法。因为对于go这种自动内存管理,和C
这种手动管理内存,通信数据的内存管理是不对等的,就像是前两节手撸cgo代码提到的char * 等指针类型数据内存释放注意问题。
而example.i剩下部分,则表示的是暴露给go直接使用的函数方法,swig会负责把如下三行生成对应的go函数:
...
extern double My_variable;
extern int fact(int);
extern int my_mod(int n, int m);
最终的example.go会生成类似如下函数方法:
// 变量
func SetMy_variable(arg1 float64) {
_swig_i_0 := arg1
C._wrap_My_variable_set_example_dffde00251d303fd(C.double(_swig_i_0))
}
func GetMy_variable() (_swig_ret float64) {
var swig_r float64
swig_r = (float64)(C._wrap_My_variable_get_example_dffde00251d303fd())
return swig_r
}
// 函数
func Fact(arg1 int) (_swig_ret int) {
var swig_r int
_swig_i_0 := arg1
swig_r = (int)(C._wrap_fact_example_dffde00251d303fd(C.swig_intgo(_swig_i_0)))
return swig_r
}
// ...
swig默认会为每个变量声明一组Getter和Setter函数来操作这组数据,如果你希望数据是readonly的,那么你需要添加额外的指令 %immutable, 此指令只会生成Getter
函数。它对于定义在%{ %}中和之外的变量都适用。
/* Some read-only variables */
%immutable;
%inline %{
extern double My_variable;
%}
extern double My_variable;
swig 操作命令
swig 工具为每个集成的高级语言都有对于的命令行参数,拿Go来说必要的参数时:
-go
-cgo
-intgosize
如果我们希望把上述example.i生成对应的cgo文件,那么执行一下命令即可:
swig -go -cgo -intgosize 64 example.i
最终会生成如下的文件:
example/
├── example.go
├── example.i
└── example_wrap.c
example.go 即是最终需要在Go语言集成的代码文件,而配套的example_wrap.c 则是需要在你的公共库一起编译的文件,二者缺一不可。
上述参数中 intgosize 需要告诉生成工具当前go编译整型字节长度是64位架构的还是32位的,这点比较特殊,究其原因,我们也大致能猜到,Go语言中尤其是64位操作系统中int所占字节和C语言所占字节数是不一样的,需要额外适配,使用原生cgo编码也会遇到样的问题。
其他参数说明
| 参数 | 说明 |
|---|---|
| -cgo | 生成文件将作为go tool cgo输入使用,go1.5版本之后可用,未来会考虑内置为默认参数 |
| -intgosize | 设置Go int 类型长度,s取值位 32或64 |
| -package | 设置导出文件包名,类似%module |
| -use-shlib | 生成共享库so,再需要把go项目导出为共享库是才有用 |
| -soname | 共享库名称 |
| -gccgo | 针对选择gccgo编译时,默认只生成基于gc编译器文件 |
| -go-pkgpath | 针对选择gccgo编译器时,设置的pkgpath |
| -go-prefix | 针对选择gccgo编译器时,设置路径前缀。如果-go-pkgpath存在,则此变量忽略 |
不过,在实践中,我们发觉不使用-cgo也是可行的,因为一般我们直接会使用go build直接构建,它内部会自动识别cgo代码而自动选择cgo工具来处理。另外对于编译Go代码我们一般都是使用内置go compiler默认编译器,所以相关的gccgo编译器其实使用场景不多。
集成使用
有了对swig基础了解后,针对上述的演示项目,假设我们有一个C接口函数需要暴露给Go使用:
char* getName()
那么我们只需要在C库实现这个函数功能,例如:
char* getName(){
return "hello the swig";
}
把这个文件命名为example.c, 并且和example.i 都放在 example/路径下,然后声明 example.i 接口:
%module example
%{
extern char* getName();
%}
/*暴露给Go使用的函数*/
extern char* getName();
请注意上面的代码,暴露给Go函数一定要和%{...%}中声明的函数配套,缺一不可。因为最终生成的文件实质就是在调用%{...%}代码块中声明的函数。

从上图我们可以看出(红色箭头)表示最终生成的代码,而真正的调用过程也变成了绿色箭头所示的步骤。
然后运行:
swig -go -intgosize 64 example/example.i
最终的目录文件结构如下:
example
├── example.c
├── example.go
├── example.i
└── example_wrap.c
之后便是直接在 Go代码直接使用这个包目录即可。
package main
import (
"cgo/example"
"fmt"
)
func main() {
fmt.Println(example.GetName())
}
最终输出如下结果:
# go run main.go
hello the swig
相关资料
swig调用cgo代码示例:github.com/swig/swig/t…swig调用cgo文档:Running SWIG with Go.
转载自:https://juejin.cn/post/7084154796822757406