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