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

2023-11-22 09:20

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

文件大致分为四部分:

  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++语法解析,一般都要有这项。
  • -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 注意项

  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

这篇关于SWIG包装器使用指南——(一)基本概念的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/409134

相关文章

2025最新版Python3.13.1安装使用指南(超详细)

《2025最新版Python3.13.1安装使用指南(超详细)》Python编程语言自诞生以来,已经成为全球最受欢迎的编程语言之一,它简单易学易用,以标准库和功能强大且广泛外挂的扩展库,为用户提供包罗... 目录2025最新版python 3.13.1安装使用指南1. 2025年Python语言最新排名2.

JAVA SE包装类和泛型详细介绍及说明方法

《JAVASE包装类和泛型详细介绍及说明方法》:本文主要介绍JAVASE包装类和泛型的相关资料,包括基本数据类型与包装类的对应关系,以及装箱和拆箱的概念,并重点讲解了自动装箱和自动拆箱的机制,文... 目录1. 包装类1.1 基本数据类型和对应的包装类1.2 装箱和拆箱1.3 自动装箱和自动拆箱2. 泛型2

Java中八大包装类举例详解(通俗易懂)

《Java中八大包装类举例详解(通俗易懂)》:本文主要介绍Java中的包装类,包括它们的作用、特点、用途以及如何进行装箱和拆箱,包装类还提供了许多实用方法,如转换、获取基本类型值、比较和类型检测,... 目录一、包装类(Wrapper Class)1、简要介绍2、包装类特点3、包装类用途二、装箱和拆箱1、装

Deepseek使用指南与提问优化策略方式

《Deepseek使用指南与提问优化策略方式》本文介绍了DeepSeek语义搜索引擎的核心功能、集成方法及优化提问策略,通过自然语言处理和机器学习提供精准搜索结果,适用于智能客服、知识库检索等领域... 目录序言1. DeepSeek 概述2. DeepSeek 的集成与使用2.1 DeepSeek API

Rsnapshot怎么用? 基于Rsync的强大Linux备份工具使用指南

《Rsnapshot怎么用?基于Rsync的强大Linux备份工具使用指南》Rsnapshot不仅可以备份本地文件,还能通过SSH备份远程文件,接下来详细介绍如何安装、配置和使用Rsnaps... Rsnapshot 是一款开源的文件系统快照工具。它结合了 Rsync 和 SSH 的能力,可以帮助你在 li

什么是cron? Linux系统下Cron定时任务使用指南

《什么是cron?Linux系统下Cron定时任务使用指南》在日常的Linux系统管理和维护中,定时执行任务是非常常见的需求,你可能需要每天执行备份任务、清理系统日志或运行特定的脚本,而不想每天... 在管理 linux 服务器的过程中,总有一些任务需要我们定期或重复执行。就比如备份任务,通常会选在服务器资

C++11的函数包装器std::function使用示例

《C++11的函数包装器std::function使用示例》C++11引入的std::function是最常用的函数包装器,它可以存储任何可调用对象并提供统一的调用接口,以下是关于函数包装器的详细讲解... 目录一、std::function 的基本用法1. 基本语法二、如何使用 std::function

C++包装器

包装器 在 C++ 中,“包装器”通常指的是一种设计模式或编程技巧,用于封装其他代码或对象,使其更易于使用、管理或扩展。包装器的概念在编程中非常普遍,可以用于函数、类、库等多个方面。下面是几个常见的 “包装器” 类型: 1. 函数包装器 函数包装器用于封装一个或多个函数,使其接口更统一或更便于调用。例如,std::function 是一个通用的函数包装器,它可以存储任意可调用对象(函数、函数

【机器学习】高斯过程的基本概念和应用领域以及在python中的实例

引言 高斯过程(Gaussian Process,简称GP)是一种概率模型,用于描述一组随机变量的联合概率分布,其中任何一个有限维度的子集都具有高斯分布 文章目录 引言一、高斯过程1.1 基本定义1.1.1 随机过程1.1.2 高斯分布 1.2 高斯过程的特性1.2.1 联合高斯性1.2.2 均值函数1.2.3 协方差函数(或核函数) 1.3 核函数1.4 高斯过程回归(Gauss

2409wtl,网浏包装

原文 介绍 本教程帮助你用IE引擎构建一个基于WTL,并使用我编写的处理IWebBrowser2接口包装类的迷你浏览器. 因为知道代码可能很难读,因此本教程帮助你逐步开发一个迷你浏览器. 背景 大部分项都与互联网浏览有关.我常用超文本视图使用SDI. 有时,我要用真正的浏览器函数,因此我为IWebBrowser2编写了一个包装器. 此包装类可处理在IE中嵌入的窗口.它还可非常简单的处