go通过swig封装、调用c++共享库的技术总结 (2)

Go是有gc的语言,C是没有gc的语言,且两者都是有指针的语言,所以在处理指针时应该格外注意。Go的内存管理模块无法知道C内部发生了什么,C也不知道Go的内存管理。

在使用指针时,应该对某个指针时C指针Go指针有明确的认知。C指针是指向通过C的库分配的内存,Go指针是指向Go代码分配的内存。这个目的是区分这块内存是由谁(C还是Go)来分配的,而不是由指针的类型决定的。

Go code may pass a Go pointer to C provided the Go memory to which it points does not contain any Go pointers. The C code must preserve this property: it must not store any Go pointers in Go memory, even temporarily.

Go代码给C传递一个指针,应该保证这块内存或结构中,不包含其他Go指针。C代码应该保证,不保存任何Go指针,即便临时保存也不行。

回到我们最初的示例代码。

func main() { cs := C.CString("Hello from stdio") C.myprint(cs) C.free(unsafe.Pointer(cs)) }

由于cs是一个C字符串,这块内存是C内存,因此需要我们在用完之后手动释放。即便这是在Go代码中操作的。

封装

对C函数及类型的访问和操作,如上述几段代码中操作C.开头的变量或函数,应该限制在一个package里,这个package的作用就是将C的函数封装为Go的函数。因为使用C类型需要操心的东西比较多,封装起来更容易管理也不容易出问题,例如上例中释放字符串这种操作。一般人应该不会希望在写Go代码时,心理还一直惦记着这些事情。

SWIG

有了上一节提到的Cgo支持,所有C库及接口都可以被Go调用,标准C接口也是大多数库的开发包都提供的接口。然而,由于一些历史问题,有些不那么规范的库只提供了C++风格的接口,例如接口用到vector、map、string等,Cgo是不支持C++的这些特性的。遇到这种情况,标准的做法是给这些C++接口再封装一个标准C接口。这个封装工作需要我们再写一份C或C++代码,做一些类型或者接口的转换,通过extern C导出C风格的接口。然后再按照上一节的做法,用Go去调用这个标准C接口。

如果接口比较简单,数量也不多,上述封装可以手工完成。如果接口数量较多,且涉及大量C++特性,上述封装工作可能就不难么容易了。

SWIG就是自动帮你做了这件事。准确地说,SWIG生成了两个文件,一个文件是*_wrapper.cpp文件,一个是*.go文件。*_wrapper.cpp文件将C++接口封装为C接口。*.go文件通过上一节说的import "C"来引用C接口,并把对这些C接口的调用,封装为不涉及任何C特性的Go函数或方法。因此,它实际做了两件事,一是我们上面说的将C++接口封装为C接口,另一件是上一节说的封装问题,在Go代码里把对C接口的使用细节封装起来。

接下来我们就看一下SWIG的使用方法及它对Go的支持。

SWIG简介

SWIG是一个软件开发工具,用于将C或C++程序与其他高级程序语言连接起来。它支持多种目标语言,包括脚本语言如Javascript、Perl、PHP、Python、Tcl、Ruby,也包括非脚本语言如C#,Common Lisp (CLISP, Allegro CL, CFFI, UFFI)、D、Go 、Java等。SWIG解析C或C++接口,生成“连接代码”,使其他高级语言可以调用C或C++的代码。

使用SWIG需要先定义一个接口文件,这个接口文件说明了需要导出的接口及数据类型,这个接口文件以.i作为后缀。我以一个简单的实例,来说明我用到的一些特性,其他技术细节可以参考SWIG Doc和SWIGPlus Doc。

%module compare_length %{ #include "compare_length.h" %} %include "typemaps.i" %include "std_vector.i" %template(VecInt) std::vector<int>; int compare(const std::vector<int>& vl, const std::vector<int>& vr);

从语法层面看,SWIG文件是一个增强版的C++文件。它支持所有C++语法,它还包括SWIG指令。所有以%开头的行,都是SWIG指令,位于“%{”和“%}”之间的部分不会被处理,会被原封不动地复制到*_wrapper.cpp文件中,这可以使wrapper引用一些头文件。你甚至可以让SWIG来直接处理一个.h文件或.cpp文件,但是并不推荐这么做。

一般使用来说,SWIG接口文件应该包括

模块声明,位于第一行,以%module指令开头;

定义需要在_wrapper.cpp文件里包含的头文件,即%{和%}之间的部分。在上面的例子,就是包含int compare函数的那个头文件;

声明要导出的接口及类型。这就是完全的ANSI C/C++声明语法。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zyzdff.html