cgo practice three: swig introduction

swig is not unfamiliar to C/C++the it. It is a development tool used to connect the current C/C++project with other high-level languages. To put it bluntly, it can generate apiinterface code for other languages ​​to call . Then for Gothe project, it is that he can directly generate the corresponding code for the C/C++public project package , as the students of application development do not need to manually package the code (others wrote it for you), thus reducing the cost of the access party.cgoGocgo

And it swigis not only designed for the Golanguage, but adapted to many high-level languages, such as Python, Java, PHP, Javascriptetc., can generate the interface called by the corresponding language, which is convenient for the application layer to quickly access.

This article is mainly about introduction swigand Golanguage integration. If you are also interested in other languages, you may wish to check the official documentation .

In practical applications, if it is a large C++ project, using swig will be even more powerful. But if it's a small C project, using swig can complicate things, so it's best to evaluate on a case-by-case basis.

install swig

linuxThe installation can be downloaded directly from the source code and compiled: www.swig.org/download.ht… , yumalso provides swigthe package, but the default version is relatively old, macOSyou can use it directly brew install swigto install. If you need to choose other platforms, you can check the official documentation: swig install

wget http://prdownloads.sourceforge.net/swig/swig-{versoin}.tar.gz && tar -zxvf
cd swig-*
./configure
make
sudo make install
复制代码

Use swig process

The use swigis not very complicated, and it is mainly divided into two steps:

  1. 定义 swig 接口文件(swig interface file);
  2. 使用 swig 命令行生成对应语言的代码;
  3. 集成使用。

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);
复制代码

/*...*/ 代表的是注释部分。

%modulego中表示对应的 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名称,而是提供了包裹的俩GetterSetter 函数。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默认会为每个变量声明一组GetterSetter函数来操作这组数据,如果你希望数据是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取值位 3264
-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函数一定要和%{...%}中声明的函数配套,缺一不可。因为最终生成的文件实质就是在调用%{...%}代码块中声明的函数。

cgo-swig-01.png

从上图我们可以看出(红色箭头)表示最终生成的代码,而真正的调用过程也变成了绿色箭头所示的步骤。

然后运行:

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
复制代码

相关资料

  1. swigCalling cgocode example: github.com/swig/swig/t…
  2. swigCalling cgodocumentation: Running SWIG with Go .

Guess you like

Origin juejin.im/post/7084154796822757406