SWIG学习记录(三)SWIG封装C#API实例
目录
- 1 一个构建接口的策略
- 1.1 为SWIG编写C语言程序
- 1.2 SWIG接口文件
- 1.3 为什么使用单独的接口文件?
- 1.4 获取正确的头文件
- 1.5 如何处理main()函数
- 2 SWIG封装C#API实例
- 2.1 创建工程与属性配置
- 2.2 C#接口实现(一)
- 2.2 C#接口实现(二)
Swig主要是为了将c++/c中的代码所实现的功能移植到别的语言上。一般我们要将c++/c移植到别的语言上,基本操作是将c++中的实现代码转变成dll,然后再供别的语言调用,这样安全性高,且易于调用。但是因为各种语言不相同,如果不借助swig,自己去封装。在调用dll时就会有很多类型(包括基本类型,结构体和类)转换需要注意,比如c#调c++ dll,可能得自己造一个相对应与c++的结构体和类才能成功调用。但是如果我们用了swig,这些我们都不用考虑,我们只需要考虑swig给我们的接口类型是什么,然后我们只要按照swig给的接口类型传入参数,就OK了。说白了就是swig代替了我们利用c#去重新定义dll中所需要传入的参数类型(如结构体或类)这个工作。
1 一个构建接口的策略
1.1 为SWIG编写C语言程序
SWIG 不需要修改你的 C 代码,但如果你提供原始 C 头文件或源代码的集合,结果可能不是你所期望的——实际上,它们可能很糟糕。以下是为 C 程序创建接口时可以遵循的一系列步骤:
- 确定要包装的函数。可能没有必要访问 C 程序的每个函数——因此,一点预先的考虑可以大大简化最终的脚本语言接口。查找要包装的东西的话,C 头文件是特别好的源头。
- 创建一个新的接口文件来描述程序的脚本语言接口。
- 将适当的声明复制到接口文件中,或使用 SWIG 的 %include 指令处理整个 C 源/头文件。
- 确保接口文件中的所有内容都使用ISO C/ c++语法。
- 确保所有必要的’ typedef’声明和类型信息在接口文件中可用。特别是,确保按照 C/C++ 编译器的要求以正确的顺序指定类型信息。最重要的是,在使用类型之前定义它! 如果需要完整的类型信息,C编译器会告诉您,而SWIG通常不会发出警告或出错,因为它被设计为在没有完整类型信息的情况下工作。但是,如果未正确指定类型信息,则包装器可能是次优的,甚至会导致无法编译的 C/C++ 代码。
- 如果你的程序具有 main() 函数,则可能需要重命名。
- 运行 SWIG 并编译。
1.2 SWIG接口文件
使用 SWIG 的首选方法是生成单独的接口文件。假设你有以下 C 头文件:
/* File : header.h */
#include <stdio.h>
#include <math.h>
extern int foo(double);
extern double bar(int, int);
extern void dump(FILE *f);
此头文件的典型 SWIG 接口文件如下所示:
/* File : interface.i */
%module mymodule
%{#include "header.h"
%}
extern int foo(double);
extern double bar(int, int);
extern void dump(FILE *f);
当然,在这种情况下,我们的头文件非常简单,所以我们可以使用更简单的方法并使用这样的接口文件:
/* File : interface.i */
%module mymodule
%{#include "header.h"
%}
%include "header.h"
这种方法的主要优点是当将来头文件发生变化时,接口文件的维护最少。在更复杂的项目中,包含大量%include和#include语句的接口文件是最常见的接口文件设计方法之一,因为维护开销更低。
1.3 为什么使用单独的接口文件?
虽然 SWIG 可以解析许多头文件,但更常见的是编写一个特殊的 .i 文件来定义包的接口。你希望这样做的可能原因有以下几种:
- 很少需要访问大型包中的每个功能。许多 C 函数可能在脚本环境中很少或没有用处。因此,为什么要包装它们?
- 单独的接口文件提供了一个机会,可以提供有关如何构造接口的更精确的规则。
- 接口文件可以提供更多的结构和组织。
- SWIG 无法解析头文件中出现的某些定义。拥有单独的文件可以消除或解决这些问题。
- 接口文件提供了更精确的接口定义。想要扩展系统的用户可以转到接口文件,并立即查看可用的内容,而无需从头文件中挖掘。
1.4 获取正确的头文件
有时,为了使SWIG生成的代码能够正确编译,需要使用某些头文件。使用%{ %}块确保包含某些头文件
1.5 如何处理main()函数
大多数脚本语言都定义自己的main()过程,然后调用它。在处理动态加载时,Main()也没有意义。有几种方法可以解决main()冲突:
- 完全摆脱main()。
- 将main()重命名为其他名称。可以通过使用-Dmain=oldmain这样的选项来编译C程序。
- 当不使用脚本语言时,使用条件编译来只包含main()。
取消main()可能会导致程序潜在的初始化问题。为了处理这个问题,您可以考虑编写一个名为program_init()的特殊函数,该函数在程序启动时初始化程序。然后可以从脚本语言中作为第一个操作调用该函数,或者在加载SWIG生成的模块时调用该函数。
2 SWIG封装C#API实例
本实例将C++代码封装成C#的API。
2.1 创建工程与属性配置
- 首先,我们创建一个C++空项目,命名为SWIG_TEST。将项目编译环境设置为Release、x64,在工程项目属性中,设置目标文件扩展名和配置类型为dll,要注意的是C++项目所配置的版本与平台类型要与C#项目中使用的一致,因为不兼容可能会导致编译问题或不可预测的行为。这里是Release版本x64,那么就要保持版本一致。
- 添加接口文件,swig的接口文件(.i)实际上就是VS中的Midl文件(.idl)。
添加->新建项->Visual C++/代码/Midl文件。文件命名为SWIG_TEST.idl。注意,在工程项目新添加的idl文件的文件属性中,设置项目类型为自定义生成工具(或者设置为不参与生成)。实现C++接口后,可以编辑该接口文件,来指引SWIG生成API包装器代码。
- 创建C# 控制台程序工程,命名为SWIG_CSharp,Release版本x64,用于测试C++封装的C# API接口。新建的控制台工程有一个默认的Program.cs类文件,测试代码就写在main()函数中。
然后在工程中创建类文件,添加接口定义与实现。我们先提供一个代码量很小的接口实现,然后再不断复杂化接口,以便我们探索SWIG对各种C++类型代码的C# API封装。
以下提供了几个C#接口封装实现,都是在2.1的配置基础上进行的。
2.2 C#接口实现(一)
- C++工程中接口实现
// gmeDefine.h
#pragma once
#include <string>
#include <vector>
using namespace std;
namespace Smart3dMap
{struct point3d{point3d() { point3d(0, 0, 0);};point3d(double xx, double yy, double zz){x = xx; y = yy; z = zz;};double x; double y; double z;};struct triangle{int a; int b; int c;};typedef vector<point3d> vec3dList;typedef vector<triangle> vecTriList;
}
// gmeParseObj.h#pragma once
#include "gmeDefine.h"
namespace Smart3dMap
{class gmeParseObj{public:gmeParseObj();~gmeParseObj();void fillPoints(vec3dList pnts);void fillTriangles(vecTriList tris);void setObjName(string name);int getPointsNum();int getTrianglesNum();string getName();vector<int> getBatchId();private:vec3dList m_points;vecTriList m_triangles;string m_name;vector<int> m_bachId;};
}
// gmeParseObj.cpp#include "gmeParseObj.h"
Smart3dMap::gmeParseObj::gmeParseObj()
{point3d pnt1 = point3d(1, 2, 3);point3d pnt2 = point3d(1, 2, 3);m_points.push_back(pnt1);m_points.push_back(pnt2);
}
Smart3dMap::gmeParseObj::~gmeParseObj()
{}
void Smart3dMap::gmeParseObj::fillPoints(vec3dList pnts)
{m_points.assign(pnts.begin(), pnts.end());
}
void Smart3dMap::gmeParseObj::fillTriangles(vecTriList tris)
{m_triangles.assign(tris.begin(), tris.end());
}
void Smart3dMap::gmeParseObj::setObjName(string name)
{m_name = name;
}
int Smart3dMap::gmeParseObj::getPointsNum()
{return m_points.size();
}
int Smart3dMap::gmeParseObj::getTrianglesNum()
{return m_triangles.size();
}string Smart3dMap::gmeParseObj::getName()
{return m_name;
}
vector<int> Smart3dMap::gmeParseObj::getBatchId()
{return m_bachId;
}
// SWIG_TEST.idl%module SWIG_TEST
%{#include "gmeDefine.h"#include "gmeParseObj.h"
%}
%include "std_string.i"
using namespace std;
%include "gmeDefine.h"
%include "gmeParseObj.h"
添加以上接口实现文件后,在VS工程中编译通过。打开到该C++工程的代码目录下,进入命令行,输入命令
E:\C++_Code\code_2022_7_24\SWIG_TEST>E:\swigwin-4.0.2\swig.exe -csharp -c++ SWIG_TEST.idl
然后在该目录下会编译生成一系列的C# .cs文件,以及C++代码包装器文件SWIG_TEST_wrap.cxx 。
将SWIG_TEST_wrap.cxx 文件添加到SWIG_TEST工程,然后重新生成。将.cs文件以及编译生成的dll和lib文件加入到SWIG_CSharp工程目录下。
2. C#主函数调用
将SWIG生成的.cs文件添加到工程。
// Program.csusing System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace SWIG_CSharp
{class Program{static void Main(string[] args){gmeParseObj obj = new gmeParseObj();string fname = "obj_1";obj.setObjName(fname);int pntsNum = obj.getPointsNum();Console.WriteLine(fname + "内有" + pntsNum.ToString() + "个点数据");Console.ReadKey();}}
}
运行结果,接口运行正常。
3. 分析
在SWIG_TEST工程代码中,一共定义了2个结构体和1个类,引用了C++标准库 string和 vector库,是一个非常简单的实例。可以看到在接口文件中,使用了%include "gmeDefine.h"和%include “gmeParseObj.h”,直接让SWIG处理整个头文件,这样SWIG为头文件中的每一个类和函数都生成了包装器代码。
我们可以看到每一个C++接口函数都生成了C#的托管,在最后的调用中,调用了gmeParseObj类的构造函数,实现了初始化;
调用了setObjName(string )接口,这里可以直接传入string类型的函数参数,是因为在接口文件中使用了指令 %include “std_string.i”;using namespace std;将C++中的string类型与C#中的string类型进行了链接。如果是默认情况下,SWIG会自动为string类型生成包装器。
public void setObjName(string name){SWIG_TESTPINVOKE.gmeParseObj_setObjName(swigCPtr, name);if (SWIG_TESTPINVOKE.SWIGPendingException.Pending) throw SWIG_TESTPINVOKE.SWIGPendingException.Retrieve();}
C++头文件中的接口 void fillPoints(vec3dList pnts); 在C#托管代码中定义如下:
public void fillPoints(SWIGTYPE_p_vectorT_Smart3dMap__point3d_t pnts) {SWIG_TESTPINVOKE.gmeParseObj_fillPoints(swigCPtr, SWIGTYPE_p_vectorT_Smart3dMap__point3d_t.getCPtr(pnts));if (SWIG_TESTPINVOKE.SWIGPendingException.Pending) throw SWIG_TESTPINVOKE.SWIGPendingException.Retrieve();}
可以看到接口形参vec3dList类型( typedef vector vec3dList;),被 SWIGTYPE_p_vectorT_Smart3dMap__point3d_t 重新包装了。这样也许不是我们想要的,这样会给C#调用接口产生困难,试着做一点改变,将接口文件修改一下:
%module SWIG_TEST
%{#include "gmeDefine.h"#include "gmeParseObj.h"using namespace Smart3dMap;
%}
%include <std_string.i>
%include <std_vector.i>
using namespace std;
%include "gmeDefine.h"
%template(vec3ds) vector<Smart3dMap::point3d>;
%template(vecTris) vector<Smart3dMap::triangle>;%include "gmeParseObj.h"
代码块%{……%}中包含的部分,SWIG不会解析,而是逐字复制到包装器代码中,如上所示,如果不添加using namespace Smart3dMap; 你会发现在工程中添加包装器代码文件SWIG_TEST_wrap.cxx后,无法编译通过,总会出现未找到相关定义的问题,这是因为没有加入命名空间,无法引用命名空间下定义的结构体和类。在%template(vec3ds) vectorSmart3dMap::point3d指令中,特别要注意加入命名空间,否则SWIG找不到point3d结构体的定义,就会自己默认生成相应的包装类了,总之SWIG并不是那么智能,像等价替换之类都是不能识别的,要注意给SWIG提供明确直接的定义。
如此编译通过后,将生成的相应接口文件拷贝到C#工程目录下,添加调用接口代码:
// Program.csusing System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace SWIG_CSharp
{class Program{static void Main(string[] args){point3d pd_1 = new point3d(2, 3, 4);point3d pd_2 = new point3d(3, 4, 5);point3d pd_3 = new point3d(6, 7, 2);gmeParseObj obj = new gmeParseObj();string fname = "obj_1";obj.setObjName(fname);vec3ds points = new vec3ds();points.Add(pd_1);points.Add(pd_2);points.Add(pd_3);obj.fillPoints(points);int pntsNum = obj.getPointsNum();Console.WriteLine(fname + "内有" + pntsNum.ToString() + "个点数据");Console.ReadKey();}}
}
所有接口就都可以正常调用了。
最后在说明一下文件拷贝的细节:
4. C++工程SWIG_TEST:
将c++工程目录下的SWIG_TEST\x64\Release中生成的动态库和静态库文件拷贝到C#工程相应的SWIG_CSharp\bin\x64\Release目录下。注意动态库的名字要与接口文件中的%module SWIG_TEST指令中的名字相同,否则在编译C#工程时,会出现找不到动态库和无法初始化的错误。
将生成的C#文件拷贝到C#工程目录下,并添加到工程
2.2 C#接口实现(二)
本实例实现一个坐标转换的简单接口,涉及到虚函数,以及需要调用第三方库的接口封装。
将第三方库proj的头文件(proj.h,proj_api.h)、动态库dll(proj_7_1.dll)、静态库lib(proj.lib),加入到工程项目的环境目录中,并在工程属性中配置加载静态库的路径。
文件,定义接口。
//gmeDefine.h#pragma once
#include <string>
#include <vector>
using namespace std;
namespace Smart3dMap
{struct point3d{point3d(){point3d(0, 0, 0);};point3d(double xx, double yy, double zz){x = xx;y = yy;z = zz;};point3d operator+ (const point3d& vec){double nx = x + vec.x;double ny = y + vec.y;double nz = z + vec.z;return point3d(nx, ny , nz);};double x;double y;double z;};
}
// projTrans.h #pragma once
#define ACCEPT_USE_OF_DEPRECATED_PROJ_API_H //使用Proj4库需要预定义的宏
#include "gmeDefine.h"
using namespace std;
namespace Smart3dMap
{class projBase{public:projBase();virtual ~projBase();virtual point3d projTrans(const point3d& vec) { return point3d(0, 0, 0); };};class projMethod1 :public projBase //WGS84 转 CGCS2000 高斯克吕格3度带 中央经线114{public:point3d projTrans(const point3d& vec);};class projTrans{public:point3d coordinateConv(const point3d& vec, projBase* projMethod);};}
//projTrans.cpp#include "projTrans.h"
#include "proj.h"
#include "proj_api.h"namespace Smart3dMap
{point3d projTrans::coordinateConv(const point3d & vec, projBase * projMethod){return projMethod->projTrans(vec);}point3d projMethod1::projTrans(const point3d & vec)//wgs84 to cgcs2000{///113.52002678285267, 28.175856297122078, 232.68600000000001//度转化为弧度制double x = vec.x * DEG_TO_RAD;double y = vec.y * DEG_TO_RAD;double z = vec.z;//proj4库投影参数const char* cgcs2000 = "+proj=tmerc +lat_0=0 +lon_0=114 +k=1 +x_0=500000 +y_0=0 +ellps=GRS80 +units=m +no_defs ";//CGCS2000不加带号,中央经线114,3度分带const char *wgs84 = "+proj=longlat +datum=WGS84 +no_defs ";//wgs84projPJ pj_src = pj_init_plus(wgs84); //目标投影projPJ pj_dst = pj_init_plus(cgcs2000);//源投影//坐标投影变换pj_transform(pj_src, pj_dst, 1, 1, &x, &y, &z);pj_free(pj_src); //销毁投影指针pj_free(pj_dst);return point3d(x, y, z);}projBase::projBase(){}projBase::~projBase(){}
}
//SWIG_TEST.idl%module(directors = "1") SWIG_TEST
%{#include "gmeDefine.h"#include "projTrans.h"using namespace Smart3dMap;
%}
%include <std_string.i>
%include <std_vector.i>
using namespace std;%rename(add_point) Smart3dMap::point3d::operator + (const point3d &);
%include "gmeDefine.h"%feature("director") Smart3dMap::projBase;
%include "projTrans.h"
//Program.csusing System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace SWIG_CSharp
{class Program{static void Main(string[] args){point3d pd_1 = new point3d(113.520, 28.176, 232.686);projTrans projTool = new projTrans();projMethod1 projMethod_wgs84ToCGCS2000 = new projMethod1();point3d pntOut = projTool.coordinateConv(pd_1, projMethod_wgs84ToCGCS2000);Console.WriteLine("WGS84坐标:" + pd_1.x.ToString() + " " + pd_1.y.ToString() + " " + pd_1.z.ToString() +" 转换为CGCS2000坐标:" + pntOut.x.ToString() + " " + pntOut.y.ToString() + " " + pntOut.z.ToString());point3d pd_2 = new point3d(10, 15, 25);point3d pd_3 = new point3d(12, 16, 27);point3d pd_Add = pd_2.add_point(pd_3);Console.WriteLine("坐标1:" + pd_2.x.ToString() + " " + pd_2.y.ToString() + " " + pd_2.z.ToString() +" + 坐标2:" + pd_3.x.ToString() + " " + pd_3.y.ToString() + " " + pd_3.z.ToString() + " = "+ pd_Add.x.ToString() + " " + pd_Add.y.ToString() + " " + pd_Add.z.ToString());Console.ReadKey();}}
}
运行结果
分析:%module(directors = “1”)指令与%feature(“director”) Smart3dMap::projBase启动了director,允许基类projBase中的虚函数在子类中重写,从而实现多态。
gmeDefine.h中还在结构体中实现了一个重载操作符的函数,重载操作符函数名在大多数脚本语言中是非法标识符,所以必须对其进行重命名才能为其构建包装器,在接口函数中调用%rename(add_point) Smart3dMap::point3d::operator + (const point3d &)指令,创建add_point函数实现相同功能。
SWIG学习记录(三)SWIG封装C#API实例相关推荐
- SWIG学习记录(一)SWIG基础
SWIG学习记录 1 什么是SWIG? 1.2 特性 1.2.1 预处理 2 SWIG安装 3 SWIG基础介绍 3.1 运行SWIG 3.1.1 输入格式 3.1.2 输出 3.1.3 注释 3.1 ...
- MySQL学习记录 (三) ----- SQL数据定义语句(DDL)
相关文章: <MySQL学习记录 (一) ----- 有关数据库的基本概念和MySQL常用命令> <MySQL学习记录 (二) ----- SQL数据查询语句(DQL)> &l ...
- css学习记录三:文本属性
css学习记录三:CSS文本属性 一.文本属性的作用 二.文本颜色 三.文本对齐 四.装饰文本 五.文本缩进 六.行间距 一.文本属性的作用 CSSS Text(文本)属性可定义文本的外观,比如文本的 ...
- Kafka学习记录(三)——Broker
Kafka学习记录(三)--Broker 目录 Kafka学习记录(三)--Broker 对应课程 Zookeeper存储的Kafka信息 Broker总体工作流程 Broker的服役和退役 Kafk ...
- webrtc学习记录三【创建基于RTCPeerConnection本机内的1v1音视频互通】
系列文章目录 webrtc学习记录一[媒体录制MediaRecorder] webrtc学习记录二[基于socket.io创建信令服务器聊天室] 目录 系列文章目录 前言 一.媒体能力的协商过程 1. ...
- TPM零知识学习八 —— tpm组件联调及API实例解析
一.组件联调准备 1. 运行TPM模拟器 ~/TPM/ibmtpm/ibmtpm$ ./src/tpm_server LIBRARY_COMPATIBILITY_CHECK is ON Startin ...
- SWIG学习记录(二)SWIG实用性基础
目录 1 SWIG实用基础 1.1 值传递结构体 1.2 返回值 1.3 链接到结构体变量 1.4 链接到char* 1.5 数组 1.6 创建只读变量 1.7 重命名和忽略声明 1.7.1 特定标识 ...
- 【学习记录】使用高德地图API开发一个简单基础的WebGIS系统(GIS考研院校专题地图网站)
文章目录 0. 前言 1. 功能设计 2. 数据处理 3. 项目结构 4. 功能实现 4.1. 初始化 4.2. 地图基础功能模块 4.2.1. 放大缩小功能, 图层管理功能 4.2.2. 地图测量功 ...
- 《你好,放大器》----学习记录(三)
3 多种多样的运算放大器 ADI把放大器分为精密和高速两大类 3.1 精密运放和高速运放 3.1.1 精密运放概述 一般来讲,带宽小于 50MHz 的,能够具有某些特殊指标优异性的运放,都属于精密运放 ...
最新文章
- py 的 第 31 天
- 英伟达人工智能和处理器驱动的制药、生命科学合作
- 5.1.3.jvm java虚拟机系统参数查看
- awk bc命令 linux_linux之awk命令(转载)
- 自己动手写Docker系列 -- 5.5实现容器停止
- 浅谈压缩感知(十六):感知矩阵之RIP
- java private list_Java基础知识回顾之四 ----- 集合List、Map和Set
- python解码和编码的区别_python基础小知识,is和==的区别,编码和解码
- XXL-API v1.1.1 发布,API管理平台
- Linux的操作系统下载及安装
- MFC之打开(开发)映美精相机
- [人工智能-深度学习-32]:卷积神经网络CNN - 常见分类网络- AlexNet网络结构分析与详解
- 计算机考研落榜了怎么办,一位考研落榜者的自述:考研失败的原因
- 数据库、MySQL基本知识
- 【电子设计大赛】2021 年全国大学生电子设计竞赛仪器设备和主要元器件及器材清单
- 这100佳创新互联网公司值得你去
- armadillo使用,armadillo提高编译效率和速度
- OpenGLES---设置获取Shader程序属性
- 去除.gif图片中的文字
- java生成文字与图片_java生成word(文字和图片)