本文主要是介绍SWIG包装器使用指南——(一)基本概念,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
SWIG系列:http://t.csdn.cn/YCYzg
文章目录
- 一、前言
- 二、简介
- 2.1 SWIG是什么?
- 2.2 为什么要使用SWIG?
- 三、基本概念
- 3.1 .i 文件简介
- 3.2 接入SWIG之后项目上的变化
- 3.3 简单数据类型的处理
- 3.3.1 整型数据
- 3.3.2 浮点类型
- 3.3.3 字符类型
- 3.3.4 注意项
- 3.4 指针与复杂类型
- 3.5 理解一切皆是指针
- 3.5.1 一切皆是指针--值传递
- 3.5.2 一切皆是指针--数组
- 3.6 %ignore与%rename指令(设置忽略、重命名)
- 3.7 函数指针的处理
- 3.8 %extend:扩展数据结构、替换函数
- 3.9 如何在wrapper.cpp中插入自定义代码
- 3.10 %inline指令
- 3.11 .i 文件的编写策略
一、前言
SWIG 版本:4.1.1
官方文档:https://www.swig.org/doc.html
二、简介
2.1 SWIG是什么?
- SWIG = Simplified Wrapper and Interface Generator
- 是一个exe小工具
- 主要用来包装已有的 C/C++ 代码、生成目标语言(C#、Lua、Python等)代码(本系列文章将以C#如何调用C++函数为例)。
2.2 为什么要使用SWIG?
- 可以极大简化目标语言(C#、Python、Lua)到C/C++语言的调用
- 解耦,原生的开发方式
- 更易于测试
三、基本概念
3.1 .i 文件简介
.i
文件是SWIG规则描述文件,这个文件需要我们自己手动编写。SWIG 会解析这个文件来生成对应的包装代码。此文件的文件名与文件后缀无特殊要求,可随意更改。
文件大致分为四部分:
- 模块名:
%module
开头,必须有但意义不大,就是个名字而已。 - 需要include的头文件:用
%{ %}
括起来且内部用#include开头,这些include的头文件会原样不动的复制粘贴到包装代码中。此项必须有。 - swig配置项及指令:如
%typemap %apply
等,不同的指令有不同的功能。此项可选。 - 要解析的头文件:
%include
后面跟待解析的头文件。SWIG会解析这些头文件中都有哪些类,哪些成员,并对解析到的这些东西进行包装。
当.i文件编写完成之后,我们就需要使用命令行来让swig生成包装代码:
swig.exe -c++ -outdir ../../ConsoleTest/lib -namespace ModuleNameSharp -csharp swig.i
- -c++:表示启用c++语法解析,一般都要有这项。
- -outdir:表示自动生成的c#类文件应该放到哪里。
- -namespace:生成的C#类还可以指定命名空间。
- -csharp:生成的目前语言是c#。
- swig.i:.i文件的名称
3.2 接入SWIG之后项目上的变化
接入前:
我们的C#项目一般都是通过P/Invoke的方式或者通过添加一个C++/CLI中间层项目的方式来调用C++代码。这些方式都需要我们自己进行中间层代码维护,不同语言间数据结构的转换。
接入后:
中间层代码就由SWIG代劳了,上图的绿色部分完全由SWIG自动生成。可以将SWIG生成的C#类全部放到一个单独的项目中,称之为C# Module。此模块中的C#代码称之为代理类代码。
SWIG生成的C++函数的Wrapper.cpp,需要放到原有的C++项目中。wrapper.cpp称之为包装代码。
调用链路:c#调用到c# module上,然后调用到wrapper.cpp上,然后从wrapper.cpp再调用到具体的某个c++目标函数上。
3.3 简单数据类型的处理
这里的简单数据类型是指:int、 long、 short、 unsigned int、float
这种,不是类。
3.3.1 整型数据
int
short
long
unsigned short
unsigned long
unsigned char
signed char
bool
以上都属于C++里的的整型,大多数目标语言都只有32位整型。但是C++的整型位数不确定,对于C#来说上述的整型都会被映射为C#的int
类型(32位)。而反向从C#到C++映射时可能就会有数据丢失的问题,所以需要特别注意。
3.3.2 浮点类型
float、double
完美支持
3.3.3 字符类型
char
会映射为只含单个字符的字符串(对于部分脚本语言),但对于c#来说会映射为c#的char
。
char*
会映射为字符串,但也可以通过后续介绍的typemap指令将其映射为byte
。
3.3.4 注意项
long long
类型需要谨慎使用:因为long long
并不是所有的目标语言都支持,常常是超出了脚本语言的整型精度,而且在Tcl和Perl里会将其转为字符串。long double
类型不被swig支持。
3.4 指针与复杂类型
int *
double ***
char **
指针、指针的指针等的包装swig完美全支持。
类、结构体、数组等复杂数据类型,也是指针。
☝ 在这里你将会遇到SWIG的第一个核心知识点:除了基础类型之外一切皆是指针。
3.5 理解一切皆是指针
假设我们有如下的C++代码需要包装:
//test.h
FILE* open(char* path);
void display(FILE* file);
编写swig.i文件:
%module XXX
%{
#include "test.h"
%}%include "test.h"
然后观察生成的包装文件:
typedef struct _iobuf{void* _Placeholder;} FILE;void * CSharp_DemoNamespace_open(char * jarg1) {void * jresult ;char *arg1 = (char *) 0 ;FILE *result = 0 ;arg1 = (char *)jarg1; result = (FILE *)open(arg1);jresult = (void *)result; return jresult;
}
void CSharp_DemoNamespace_display(void * jarg1) {FILE *arg1 = (FILE *) 0 ;arg1 = (FILE *)jarg1; display(arg1);
}
我们在swig.i里并没有告诉swig FILE
是个什么类型,所以它就自行定义了一个typedef
,而这个FILE
又对应的生成了一个C#的SWIGTYPE_p_FILE
类型。如下:
namespace DemoNamespace {public class demoModule {public static SWIGTYPE_p_FILE open(string path) {global::System.IntPtr cPtr = demoModulePINVOKE.open(path);SWIGTYPE_p_FILE ret = (cPtr == global::System.IntPtr.Zero) ? null : new SWIGTYPE_p_FILE(cPtr, false);return ret;}public static void display(SWIGTYPE_p_FILE file) {demoModulePINVOKE.display(SWIGTYPE_p_FILE.getCPtr(file));}
}
}
我们在使用C#调用时只需要调用到open和display上,而不需要考虑SWIGTYPE_p_FILE
的真实类型是什么,我们不使用它,它只是一个载体:
SWIGTYPE_p_FILE f=demoModule.open("xxxx");
demoModule.display(f);
而这个载体载的是什么呢?答案为:C++的指针,一个指向FILE
类型对象的指针。
观察一下生成的SWIGTYPE_p_FILE
类:
public class SWIGTYPE_p_FILE {private global::System.Runtime.InteropServices.HandleRef swigCPtr;// 注意此成员internal SWIGTYPE_p_FILE(global::System.IntPtr cPtr, bool futureUse) {swigCPtr = new global::System.Runtime.InteropServices.HandleRef(this, cPtr);}protected SWIGTYPE_p_FILE() {swigCPtr = new global::System.Runtime.InteropServices.HandleRef(null, global::System.IntPtr.Zero);}internal static global::System.Runtime.InteropServices.HandleRef getCPtr(SWIGTYPE_p_FILE obj) {return (obj == null) ? new global::System.Runtime.InteropServices.HandleRef(null, global::System.IntPtr.Zero) : obj.swigCPtr;}internal static global::System.Runtime.InteropServices.HandleRef swigRelease(SWIGTYPE_p_FILE obj) {return (obj == null) ? new global::System.Runtime.InteropServices.HandleRef(null, global::System.IntPtr.Zero) : obj.swigCPtr;}
}
注意成员swigCPtr
,它就是C++对象的指针,这里的SWIGTYPE_p_FILE
就是C++里的FILE
的代理类,可以将其理解为一个包含了C++指针的容器。
3.5.1 一切皆是指针–值传递
假如我们有如下的C++函数:
Vector cross_product(Vector v1,Vector v2);
对应的包装代码为:
void * CSharp_DemoNamespace_cross_product(void * jarg1, void * jarg2)
{Vector * argp1 = (Vector *)jarg1; Vector arg1 = *argp1; Vector * argp2 = (Vector *)jarg2; Vector arg2 = *argp2; Vector result = cross_product(arg1,arg2);void * jresult = new Vector(result); return jresult;
}
可见即使从C#调用过来时传的是值,包装函数里接收的也是指针,然后根据指针再取值,传到cross_product
上。
3.5.2 一切皆是指针–数组
上述两种C++里表示数组的方式,对于SWIG来说没有什么区别,不管是一维还是多维数组,SWIG都将其包装为指针。如下:
int CSharp_DemoNamespace_foobar(void * jarg1)
{int * arg1 = (int *)jarg1; int result = (int)foobar(arg1);int jresult = result; return jresult;
}void CSharp_DemoNamespace_grok(void * jarg1)
{// 二维数组强转为指针的指针 char ** arg1 = (char **)jarg1; grok(arg1);
}void CSharp_DemoNamespace_transpose(void * jarg1)
{double (*arg1)[20] ;// 强转arg1 = (double (*)[20])jarg1; transpose(arg1);
}
3.6 %ignore与%rename指令(设置忽略、重命名)
适用场景:
- 命名冲突
- 关键字冲突,如c++里的某个变量名是C#里的关键字,或者是SWIG自身的一个关键字。
考虑有如下的C++代码:
void internal(const char *);
internal
是c#的关键字,如果不加修改则生成C#代理类时肯定会报错。我们可以配置如下的规则:
// 将internal 重命名为my_print
%rename(my_print) internal;// 或者忽略这个internal函数,不让SWIG去包装它
%ignore internal;
%rename
很强大,可以将一个很长很长的名字缩为较短的一个名字,或者根据字符串匹配规则替换名字里的某些字符:
%rename("myprefix_%s") ""; // print 重命名为 myprefix_print
%rename("%(lowercamelcase)s") ""; // foo_bar 重命名为 fooBar; FooBar -> fooBar
%rename("%(strip:[wx])s") ""; // wxHello -> Hello; FooBar -> FooBar
%rename("%(regex:/wx(?!EVT)(.*)/\\1/)s") ""; // 支持正则 wxSomeWidget -> SomeWidget wxEVT_PAINT -> wxEVT_PAINT
3.7 函数指针的处理
int binary_op(int a, int b, int (*op)(int, int));
上述函数的第三个指针是一个函数指针,SWIG处理时与其他类型的指针包装方式没有什么不同,就是个指针而已。来看下生成的包装代码(无非也是指针强转):
int CSharp_DemoNamespace_binary_op(int jarg1, int jarg2, void * jarg3)
{int arg1 = (int)jarg1; int arg2 = (int)jarg2; int (*arg3)(int,int) = (int (*)(int,int))jarg3; int result = (int)binary_op(arg1,arg2,arg3);int jresult = result; return jresult;
}
此时你可能会想生成的C#代理是什么样的?C#能否直接调用?来看一下:
public class demoModule
{public static int binary_op(int a, int b, SWIGTYPE_p_f_int_int__int op) {int ret = demoModulePINVOKE.binary_op(a, b, SWIGTYPE_p_f_int_int__int.getCPtr(op));return ret;}
}
注意:这里SWIG生成函数指针op
的代理类为SWIGTYPE_p_f_int_int__int
,名字的_p
就是pointer的意思,_f
就是function的意思,后面的_int_int__int
则分别为入参类型与返回值类型。此种代理类我们之前已有介绍,它就是一个包含指针的容器,它的成员只有一个swigCPtr
而已。我们无法只用c#直接new
,也无法直接调用。
此时我们可以暂时先忽略它,不妨碍我们对SWIG机制的理解。在后续章节中,我会介绍如何将其映射为C#里直接使用的委托。
3.8 %extend:扩展数据结构、替换函数
考虑有如下的c++代码:
class Vector
{public:double x,y,z;
};
如何我们想给Vector
这个数据结构添加一个额外的成员,用来打印出x,y,z的值。其中一种方式你可以直接修改现有代码,另外也可以通过%extend
指令,告诉SWIG在生成包装代码时,额外添加成员。如下:
%{
#include "vector.h"
%}
%include "vector.h"%extend Vector {
void print() {
printf("Vector [%g, %g, %g]\n", $self->x, $self->y, $self->z);
}
}
这样就可以使用如下的C#代码直接调用print
:
Vector v = new Vector();
v.x = 1;
v.y = 2;
v.print();
%extend
另外的一个功能是可以用来替换原数据结构中的某个成员:
class Foo
{public:void bar(int a);
};
如果我们觉得现有的bar
函数不符合我们的期望,可以结合使用%ignore
进行替换:
%{
#include "Foo.h"
%}%extend Foo {
void bar(int a){// 在这里编写自己的逻辑
}
}
// 忽略原有的bar
%ignore Foo::bar;
%include "Foo.h"
注意:%extend和%ignore的顺序不能反。
3.9 如何在wrapper.cpp中插入自定义代码
可以通过%insert
指令或者指定段的指令来插入:
- 指定段指令:
%begin %{//插入到wrapper文件最开始的位置,常定义一些宏
%}%runtime %{
//在SWIG生成的包装代码之前,可以定义一些帮助函数
%}%header %{
//在SWIG生成的包装代码之前,添加头文件。等同于%{ … %}
%}%wrapper %{
//在SWIG生成的包装代码里(末尾)
%}%init %{
//在wrapper文件的末尾,可以添加初始化的代码
%}
- 使用insert:
// begin段
%insert(“begin”) “somecode”;//header段
%insert(“header”) %{ //somecode %}
3.10 %inline指令
该指令使用方式如下,功能也是进行代码插入:
%inline{void inlineFunc(){// 插入的位置在header之前,runtime之后}
%}
但是与%insert
差别有如下两点:
%insert
只是将文本复制粘贴到wrapper.cpp中,目标语言无法调用%inline
复制粘贴之后还会解析和包装,供目标语言调用
3.11 .i 文件的编写策略
- 首先识别要包装的C++类都有哪些,不要包装所有的C++类。
- 一种目标语言一个
.i
文件 - 使用
%include
包含合适的头文件声明(注意依赖的头文件) - 注意SWIG指令的顺序,在使用之前先定义
- 重命名原有的
main()
函数 - Run swig.exe and Compile
这篇关于SWIG包装器使用指南——(一)基本概念的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!