SWIG包装器使用指南——(四)C#使用SWIG简介与实践
SWIG系列:http://t.csdn.cn/cIAcr
文章目录
- 一、简介
- 二、全局函数、变量、常量
- 三、继承
- 四、传递指针、引用、数组与值
- 五、基本类型的指针与引用
- 六、基本类型的数组
- 七、基本类型的默认map规则
- 八、常用的typemap方法
- 九、代码插入
- 十、实践
- 10.1 如何映射Foo*&到ref Foo
- 10.2 映射Foo* array数组
- 10.3 如何将char**映射为string[]
- 10.4 如何映射C++的容器(vector\pair)
- 10.5 如何映射函数指针
- 十一、要点总结
一、简介
SWIG如何实现让C#方便的调用C++函数的?
其实原理并不负责,仍然使用C#的互操作技术P/Invoke实现,只不过SWIG对C++代码进行的包装,使开发者更易于调用。
生成C#代码时注意可选添加几个额外的命令行选项:
-dllimport:指定P/Invoke时要调用的dll名称-namespace:设置C#的命名空间-outfile:将所有生成的C#代码放到一个cs文件中
二、全局函数、变量、常量
int a = 0;
void funA();
#define PI 3.14class Foo
{
public:
int * bar(int* items);
};
因C#里没有全局成员的概念,所以SWIG都把它们生产到了与%module同名的类里,且都是静态成员,可直接访问。

三、继承
class Foo
{
};class Bar : public Foo
{};
以上的c++代码SWIG可以完整的生成为C#代码,并且保留继承关系:
public class Foo
{
};pulic class Bar : Foo
{};
注意:因C#里没有多继承,所以如果C++里的代码是多继承的,则生成的C#代码只会继承第一个父类。
四、传递指针、引用、数组与值
class Foo
{
public:void spam1(Foo* x); // 传指针void spam2(Foo& x); // 传引用void spam3(Foo x); // 传值void spam4(Foo x[]); // 传数组
};
由于SWIG认为一切皆是指针,所以上面的Foo* Foo& Foo Foo[]四种类型并没有什么区别,生成C#代理类后调用方式都一样:
var f = new Foo();
f.spam1(f);
f.spam2(f);
f.spam3(f);
f.spam4(f); //接收是数组,但是我们只传了一个值进去。不符合期望,后续要做处理。
同理,对于函数的返回值类型来说,以下也没有什么区别:
class Foo
{
public:Foo* spam1();Foo& spam2();Foo spam3();
};
五、基本类型的指针与引用
如果未做特殊说明,这里的基本类型指的就是int、long、bool、char、float等这些类型,非自己定义的类。
class Foo
{
public:void add(int* a, int* b, int* result) {*result = *a + *b;}
};
默认情况下指针和引用会被生成为以SWIGTYPE_开头的C#类型:
public void add(SWIGTYPE_p_int a, SWIGTYPE_p_int b, SWIGTYPE_p_int result)
{
}
对于这些不太友好的类型,需要编写.i文件进行映射,将其映射为C#的int类型:
%include <typemaps.i>%apply int *OUTPUT {int * result};
%apply int *INPUT {int *a,int *b};
生成如下易于调用到C#代码:
public void add(int a, int b, out int result)
{
}
同理,引用类型的处理也是类似,可以将其映射为c#的ref:
%include <typemaps.i> //添加SWIG自带的库%apply bool& INOUT {bool&};
注:在上述过程中,我们并没有自己定义typemap。因为以上的操作SWIG已经帮我们实现了只是默认没有这么做而已,其实现的代码就是写在了
<typemaps.i>库,我们只需要将其%apply一下。
六、基本类型的数组
C++里的int*即可以表示单个对象,也可以表示一个数组。这里演示如何将其映射为C#的数组。
class Foo
{
public:void copy(int* source, int* target, int count){}
};
可以通过如下的定义将上述数组转为C#的数组:
%include <arrays_csharp.i> // 添加SWIG自带的库
// apply一下
%apply int INPUT[] {int * source}
%apply int OUTPUT[] {int * target}
然后就生成了如下的C#代码:
public void copy(int[] source, int[] target, int count) {
demoModulePINVOKE.Foo_copy(swigCPtr, source, target, count);
}
调用起来非常之方便,我们观察下demoModulePINVOKE.Foo_copy方法的实现:

原来是通过Marshal将C#数组元素的首指针封送了过去。
七、基本类型的默认map规则
部分C++类型映射到C#类型的规则如下:

更多默认规则,下载完SWIG之后,使用Everything软件搜索csharp.swg文件即可。
八、常用的typemap方法

是否还记得前几篇文章介绍typemap时的哪些in、out、freeargs方法? 上图里的方法也属于同等范畴,只不过是特定于C#语言映射时使用,后续你将会频繁使用。
九、代码插入
指令:%pragma(csharp) method={ … my code …}。
常用method有:
- imclassbase
- imclasscode
- imclassclassmodifiers
- imclassimports
- imclassinterfaces
- modulebase
- modulecode
- moduleimports
- … 等等
分别可以在不同的代理类的不同位置插入自己定义的代码。
如:
%pragma(csharp) imclasscode={ … my code …}`
就是在moduleInvoke.cs文件里插入代码。
十、实践
10.1 如何映射Foo*&到ref Foo
class Spam
{public:void changeFoo(Foo*& f);
};
对于上述的C++代码,我们期望的调用方式为:
var s = new Spam();
var f = new Foo();
s.changeFoo(ref f);
需要进行如下typemap定义,一步一步的映射过去:
// 告诉SWIG,C++的Foo*&就是C#的ref Foo
%typemap(cstype) Foo*& "ref Foo";// 因为一切皆是指针,所以我们需要获取ref Foo的指针,供后续P/Invoke时传递。pre和post就是用来处理Foo与IntPtr的生成。
%typemap(csin,
pre="System.IntPtr temp_$csinput=Foo.getCPtr($csinput).Handle; System.IntPtr back_$csinput=temp_$csinput;",
post="if(temp_$csinput!=back_$csinput) $csinput=new Foo(temp_$csinput,false);") Foo*& "ref temp_$csinput";// 因为一切皆是指针,所以我们就把指针传过去,并加上ref关键字,因为值会被修改。
%typemap(imtype) Foo*& "ref System.IntPtr";// &是指针,*是指针,*&就是指针的指针,所以为void**
%typemap(ctype) Foo*& "void**";
其中cstype和csin具体指代如下图:

imtype指代如下:

ctype指代如下:

此时就可以正常调用了。
10.2 映射Foo* array数组
简单类型的数组上面已经介绍过了,那么如何映射复杂类型的数组呢?Foo*可以直接映射为C#的Foo[]吗?
考虑有如下的C++代码:
class Spam
{
public:void setFoo(Foo* array,int count){}
};
在这里我们可以编写.i文件将Foo*映射为一个数组类,但无法直接映射为C#的数组。
%include "carrays.i" // 添加库%array_class(Foo,FooArray); // 定义一个数组类,其元素类型为Foo
然后我们就可以通过FooArray这个数组类,进行调用:
var s = new Spam();FooArray fooArray = new FooArray(2);
for(int i = 0; i < 2; i++)
{fooArray.setitem(i, new Foo());
}
//.cast()返回的类型正好就是Foo,数组的第一个元素
s.setFoo(fooArray.cast(), 2);
但是你仔细查看setitem的代码会发现:
public void setitem(int index, Foo value) {demoModulePINVOKE.FooArray_setitem(swigCPtr, index, Foo.getCPtr(value)); }
Foo.getCPtr(value)是获取到了value对象的指针传到C++层,setitem并未持有value对象自身的引用。这就会导致一个问题,当你的应用程序面临很大的内存使用压力时,value对象完全有可能被GC掉,导致C++调用出现异常。
如何解决过早被GC的问题?
思路也很简单:让setitem所在的对象继续持有value对象的引用即可,当该对象被GC时,才会顺便GCvalue,而此时对C++调用也一定已经结束了。
所以我们对FooArray类做如下的扩展,添加了一个自定义方法SetItemAndHold:
%typemap(cscode) FooArray %{ private List<object> _ref = new List<object>(); public void SetItemAndHold(int index,Foo value){_ref.Add(value); //将其添加一个列表里,使其不被GCsetitem(index,value);}
%}
然后将原有对setitem的调用重新调用到SetItemAndHold上即可。
FooArray除了cast()之外另一个有用的方法就是frompointer(),它可以根据首元素读取一个数组:
//f是C++层返回的一个值,是数组的首个元素
FooArray array=FooArray.formpointer(f);
// 后续对array操作,遍历
10.3 如何将char**映射为string[]
使用如下的规则即可:
CSHARP_ARRAYS(char *, string)%typemap(imtype, inattributes="[System.Runtime.InteropServices.In, System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.LPArray, SizeParamIndex=0, ArraySubType=System.Runtime.InteropServices.UnmanagedType.LPStr)]") char *INPUT[] "string[]";%apply char *INPUT[] { char ** };
10.4 如何映射C++的容器(vector\pair)
class Spam
{
public:void setFoo(std::vector<Foo> foos){}
};
与数组的映射基本一样:
%include <std_vector.i>
%template(FooVector) std::vector<Foo>;
使用时:
var s = new Spam();
FooVector vec = new FooVector();
vec.Add(new Foo());
s.setFoo(vec);
同数组类一样,setFoo也有内存过早被GC的问题,解决方式也是一样,不在赘述。
10.5 如何映射函数指针
记住一点:函数指针与普通对象指针没有什么不同。
typedef void(*act)();
class Spam
{
public:void setAction(act a){}
};
对于act的包装,我们可以编写以下规则:
%typemap(csin) void(*)() "System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate($csinput)";%typemap(imtype,out="IntPtr") void(*)() "System.IntPtr";// 将void(*)()映射为C#名为OperationFunction的委托
%typemap(cstype) void(*)() "OperationFunction";//定义一个名为OperationFunction的委托
%pragma(csharp) moduleimports=%{
public delegate void OperationFunction();
%}
然后就可以使用原生的C#方式调用:
var s = new Spam();
OperationFunction action = new OperationFunction(() => { });
s.setAction(action);
十一、要点总结
- 理解一切皆是指针
- 避免在
interface.i里写业务逻辑 - 注意SWIG指令顺序(大多
%xxx在前,%include在后) - 关注内存回收的节点,避免被过早GC
相关文章:
SWIG包装器使用指南——(四)C#使用SWIG简介与实践
SWIG系列:http://t.csdn.cn/cIAcr 文章目录一、简介二、全局函数、变量、常量三、继承四、传递指针、引用、数组与值五、基本类型的指针与引用六、基本类型的数组七、基本类型的默认map规则八、常用的typemap方法九、代码插入十、实践10.1 如何映射Foo*&到ref F…...
HashTable, HashMap 和 ConcurrentHashMap
HashTable, HashMap 和 ConcurrentHashMap 都是 Java 集合框架中的类,用于存储和操作键值对。它们之间存在一些关键区别,如下所示: 1.同步性: HashTable:线程安全,所有的方法都是同步的(synchr…...
ToBeWritten之IoT 技战法
也许每个人出生的时候都以为这世界都是为他一个人而存在的,当他发现自己错的时候,他便开始长大 少走了弯路,也就错过了风景,无论如何,感谢经历 转移发布平台通知:将不再在CSDN博客发布新文章,敬…...
基于ASP.NET开发的医院手术麻醉信息管理系统源码 项目源码
系统主要功能介绍: 门诊科室管理系统:手术快速申请、手术申请、手术审核 麻醉科管理系统:手术安排、术后处方、术后小结、PCS实施及管理记录、手术流程 手术护理系统:手术安排、安排临时手术、添加急诊手术、局麻手术护理、整体护…...
伪加密超具体破解办法,直击原理底层,细致演示!!!
前言: 由于我自己目前在misc和取证工作中,也遇到很多压缩包的问题,我个人非常喜欢做压缩包的题目,但也会遇到伪加密问题难以破解,全网ctf教程我都看完了,但是都觉得不够具体,所以我写一篇博客&…...
ChatGPT大规模封锁亚洲地区账号
我是卢松松,点点上面的头像,欢迎关注我哦! 在毫无征兆的情况下,从3月31日开始OpenAI大规模封号,而且主要集中在亚洲地区,特别是ip地址在台湾、日本、香港三地的,命中率目测40%。新注册的账号、…...
脂肪酸脂质Myristic acid PEG NHS,Myristic-acid PEG NHS ester,肉豆蔻酸PEG活性酯,具有优异疏水性
一、基础产品数据: 中文名:肉豆蔻酸PEG N-羟基琥珀酰亚胺,肉豆蔻酸PEG活性酯 英文名:Myristic acid PEG NHS,Myristic-acid PEG NHS ester,Myristic acid PEG SE 结构式(Structural)…...
MFC - CFormView类学习1
CFormView简介 MFC提供了一个名为CFormView的特殊视图类,我们称其为表单视图。表单视图是指用控件来输入和输出数据的视图,用户可以方便地在表单视图中使用控件。表单视图具有对话框和滚动视图的特性,它使程序看起来象是一个具有滚动条的对话…...
图像预处理方法
图像预处理 膨胀腐蚀概述 ⚫ 膨胀、腐蚀属于形态学的操作, 简单来说就是基于形状的一系列图像处理操作 ⚫ 膨胀腐蚀是基于高亮部分(白色)操作的, 膨胀是対高亮部分进行膨胀, 类似“领域扩张”, 腐蚀是高亮部分被腐蚀, 类似“领域被蚕食” ⚫ 膨胀腐蚀的应用和功能: 消除噪声…...
【蓝桥杯C/C++】专题六:动态规划
专题六:动态规划 目录专题六:动态规划导读什么是动态规划解决的问题解题步骤动态规划应该如何debug记忆化搜索斐波那契数题目代码题解爬楼梯题目代码题解使用最小花费爬楼梯题目代码题解不同路径题目题解dfsdp凑硬币题目题解dfsdp滑雪题目代码题解汉罗塔…...
图的定义和基本术语
图的定义和基本术语1.图的定义2.图的基本术语3.图的分类1.图的定义 图是由顶点和有穷非空集合和顶点边的集合吗,表示为G(V,E)。 G表示一个图,V是图G的顶点(数据元素)的集合,E是图G中顶点之间边的集合。在图中…...
041:cesium加载Blue Marble地图
第041个 点击查看专栏目录 本示例的目的是介绍如何在vue+cesium中加载Blue Marble地图。Blue Marble是一个术语,用来描述星球漂浮在浩瀚太空中的形象。早在 1972 年,阿波罗 17 号任务的工作人员就首次捕捉到了地球的标志性卫星图像,并将其称为“Blue Marble”。从那时起,NA…...
【概念梳理】激活函数
一、引言 常用的激活函数如下: 1、Sigmoid函数 2、Tanh函数 3、ReLU函数 4、ELU函数 5、PReLU函数 6、Leaky ReLU函数 7、Maxout函数 8、Mish函数 二、激活函数的定义 多层神经网络中,上层节点的输出和下层节点的输入之间具有一个函数关系,…...
【python】@property 和 @staticmethod
property 和 staticmethod 是 Python 中的两个装饰器,它们分别用于在类中创建属性或静态方法。它们的作用如下: property property:用于将类的一个方法作为属性访问。在 Python 中,使用“getter” 和“setter”方法来实现属性&a…...
Spring题集 - Spring AOP相关面试题总结
文章目录01. Spring AOP 的理解?02. Spring AOP 思想的代码实现03. Spring AOP 的相关术语有哪些?04. Spring AOP 基于注解的切面实现?05. Spring AOP 的通知有哪些类型?06. AOP 有哪些实现方式?07. Spring AOP 和 AspectJ AOP 有…...
分考场
[蓝桥杯 2017 国 C] 分考场(假题:最小色数) 题目描述 nnn 个人参加某项特殊考试。 为了公平,要求任何两个认识的人不能分在同一个考场。 求最少需要分几个考场才能满足条件。 输入格式 第一行,一个整数 n(1<n<100)n(1<n<100…...
BI技巧丨DAX Studio
DAX Studio DAX Studio,作为PowerBI外部插件使用率排名第一的插件,相信各位小伙伴或多或少都听说过,那么DAX Studio具体有哪些功能呢? PS:DAX Studio的下载链接,小伙伴们可以自行搜索,这里就不…...
Java 8常用时间 API
Date: 你不爱我了吗? 🚡本地时间时区相关格式化在Java 8中,Instant类用于表示时间戳,相当于旧的Date类;LocalDateTime类用于表示日期和时间,相当于旧的Calendar类;DateTimeFormatter类用于格式化日期和时间…...
C++运算符
C运算符 运算符是一种告诉编译器执行特定的数学或逻辑操作的符号。C 内置了丰富的运算符,并提供了以下类型的运算符: 算术运算符关系运算符逻辑运算符位运算符赋值运算符杂项运算符 1. 算术运算符 运算符描述实例把两个操作数相加A B 将得到 30-从第…...
低/无代码赋能企业,IT与业务的角色正在悄然改变
现在这个社会,年轻人的压力是真的大,需要会的技能多到数不清。想学习多点技能也不知道去哪学,主要是网络资源太丰富,很难找到一个适合自己的。那接下来推荐4个大神级别的资源网站你可一定得码住,都是年轻人特别 …...
【JavaEE】-- HTTP
1. HTTP是什么? HTTP(全称为"超文本传输协议")是一种应用非常广泛的应用层协议,HTTP是基于TCP协议的一种应用层协议。 应用层协议:是计算机网络协议栈中最高层的协议,它定义了运行在不同主机上…...
基于服务器使用 apt 安装、配置 Nginx
🧾 一、查看可安装的 Nginx 版本 首先,你可以运行以下命令查看可用版本: apt-cache madison nginx-core输出示例: nginx-core | 1.18.0-6ubuntu14.6 | http://archive.ubuntu.com/ubuntu focal-updates/main amd64 Packages ng…...
uniapp微信小程序视频实时流+pc端预览方案
方案类型技术实现是否免费优点缺点适用场景延迟范围开发复杂度WebSocket图片帧定时拍照Base64传输✅ 完全免费无需服务器 纯前端实现高延迟高流量 帧率极低个人demo测试 超低频监控500ms-2s⭐⭐RTMP推流TRTC/即构SDK推流❌ 付费方案 (部分有免费额度&#x…...
Rust 异步编程
Rust 异步编程 引言 Rust 是一种系统编程语言,以其高性能、安全性以及零成本抽象而著称。在多核处理器成为主流的今天,异步编程成为了一种提高应用性能、优化资源利用的有效手段。本文将深入探讨 Rust 异步编程的核心概念、常用库以及最佳实践。 异步编程基础 什么是异步…...
06 Deep learning神经网络编程基础 激活函数 --吴恩达
深度学习激活函数详解 一、核心作用 引入非线性:使神经网络可学习复杂模式控制输出范围:如Sigmoid将输出限制在(0,1)梯度传递:影响反向传播的稳定性二、常见类型及数学表达 Sigmoid σ ( x ) = 1 1 +...
LeetCode - 199. 二叉树的右视图
题目 199. 二叉树的右视图 - 力扣(LeetCode) 思路 右视图是指从树的右侧看,对于每一层,只能看到该层最右边的节点。实现思路是: 使用深度优先搜索(DFS)按照"根-右-左"的顺序遍历树记录每个节点的深度对于…...
初探Service服务发现机制
1.Service简介 Service是将运行在一组Pod上的应用程序发布为网络服务的抽象方法。 主要功能:服务发现和负载均衡。 Service类型的包括ClusterIP类型、NodePort类型、LoadBalancer类型、ExternalName类型 2.Endpoints简介 Endpoints是一种Kubernetes资源…...
【JVM面试篇】高频八股汇总——类加载和类加载器
目录 1. 讲一下类加载过程? 2. Java创建对象的过程? 3. 对象的生命周期? 4. 类加载器有哪些? 5. 双亲委派模型的作用(好处)? 6. 讲一下类的加载和双亲委派原则? 7. 双亲委派模…...
人工智能--安全大模型训练计划:基于Fine-tuning + LLM Agent
安全大模型训练计划:基于Fine-tuning LLM Agent 1. 构建高质量安全数据集 目标:为安全大模型创建高质量、去偏、符合伦理的训练数据集,涵盖安全相关任务(如有害内容检测、隐私保护、道德推理等)。 1.1 数据收集 描…...
嵌入式常见 CPU 架构
架构类型架构厂商芯片厂商典型芯片特点与应用场景PICRISC (8/16 位)MicrochipMicrochipPIC16F877A、PIC18F4550简化指令集,单周期执行;低功耗、CIP 独立外设;用于家电、小电机控制、安防面板等嵌入式场景8051CISC (8 位)Intel(原始…...
