SWIG包装器使用指南——(一)基本概念

SWIG包装器使用指南——(一)基本概念SWIG 是一个用于 C C 代码封装的工具 能生成 C Python 等语言的接口

大家好,欢迎来到IT知识分享网。

探索SWIG的Demo请参考:SWIG-Playground

一、前言

二、简介

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 会解析这个文件来生成对应的包装代码。此文件的文件名与文件后缀无特殊要求,可随意更改。

文件大致分为四部分:

  1. 模块名:%module开头,必须有但意义不大,就是个名字而已。
  2. 需要include的头文件:用%{ %}括起来且内部用#include开头,这些include的头文件会原样不动的复制粘贴到包装代码中。此项必须有。
  3. swig配置项及指令:如%typemap , %apply等,不同的指令有不同的功能。此项可选。
  4. 要解析的头文件:%include后面跟待解析的头文件。SWIG会解析这些头文件中都有哪些类,哪些成员,并对解析到的这些东西进行包装。

.i文件编写完成之后,我们就需要使用命令行来让swig生成包装代码:
swig.exe -c++ -outdir ../../ConsoleTest/lib -namespace ModuleNameSharp -csharp swig.i

  • -c++:表示启用c++语法解析。如果你的是C++项目,就要有这项。
  • -outdir:生成的C#类文件的输出目录。
  • -namespace:指定生成的C#类的命名空间。
  • -csharp:表示生成的目前语言是C#。
  • swig.i.i文件的路径
  • -debug-tmsearch:显示typemap查找信息

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 整型数据

3.3.2 浮点类型

float、double 完美支持

3.3.3 字符类型

char会映射为只含单个字符的字符串(对于部分脚本语言),但对于c#来说会映射为c#的char
char* 会映射为字符串,但也可以通过后续介绍的typemap指令将其映射为byte

3.3.4 注意项

  1. long long类型需要谨慎使用:因为long long并不是所有的目标语言都支持,常常是超出了脚本语言的整型精度,而且在Tcl和Perl里会将其转为字符串。
  2. 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指令(设置忽略、重命名)

适用场景:

  1. 命名冲突
  2. 关键字冲突,如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指令或者指定段的指令来插入:

  1. 指定段指令:
%begin %{ //插入到wrapper文件最开始的位置,常定义一些宏 %} %runtime %{ //在SWIG生成的包装代码之前,可以定义一些帮助函数 %} %header %{ //在SWIG生成的包装代码之前,添加头文件。等同于%{ … %} %} %wrapper %{ //在SWIG生成的包装代码里(末尾) %} %init %{ //在wrapper文件的末尾,可以添加初始化的代码 %} 
  1. 使用insert:
// begin段 %insert(“begin”) “somecode”; //header段 %insert(“header”) %{ //somecode %} 

3.10 %inline指令

该指令使用方式如下,功能也是进行代码插入:

%inline{ void inlineFunc() { // 插入的位置在header之前,runtime之后 } %} 

但是与%insert差别有如下两点:

  1. %insert 只是将文本复制粘贴到wrapper.cpp中,目标语言无法调用
  2. %inline 复制粘贴之后还会解析和包装,供目标语言调用

3.11 .i 文件的编写策略

  1. 首先识别要包装的C++类都有哪些,不要包装所有的C++类。
  2. 一种目标语言一个.i文件
  3. 使用%include包含合适的头文件声明(注意依赖的头文件)
  4. 注意SWIG指令的顺序,在使用之前先定义
  5. 重命名原有的main()函数
  6. Run swig.exe and Compile


免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/125823.html

(0)
上一篇 2025-09-23 20:33
下一篇 2025-09-23 21:00

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信