目录

  • 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 创建工程与属性配置

  1. 首先,我们创建一个C++空项目,命名为SWIG_TEST。将项目编译环境设置为Release、x64,在工程项目属性中,设置目标文件扩展名和配置类型为dll,要注意的是C++项目所配置的版本与平台类型要与C#项目中使用的一致,因为不兼容可能会导致编译问题或不可预测的行为。这里是Release版本x64,那么就要保持版本一致。
  2. 添加接口文件,swig的接口文件(.i)实际上就是VS中的Midl文件(.idl)。
    添加->新建项->Visual C++/代码/Midl文件。文件命名为SWIG_TEST.idl。注意,在工程项目新添加的idl文件的文件属性中,设置项目类型为自定义生成工具(或者设置为不参与生成)。实现C++接口后,可以编辑该接口文件,来指引SWIG生成API包装器代码。
  3. 创建C# 控制台程序工程,命名为SWIG_CSharp,Release版本x64,用于测试C++封装的C# API接口。新建的控制台工程有一个默认的Program.cs类文件,测试代码就写在main()函数中。

    然后在工程中创建类文件,添加接口定义与实现。我们先提供一个代码量很小的接口实现,然后再不断复杂化接口,以便我们探索SWIG对各种C++类型代码的C# API封装。
    以下提供了几个C#接口封装实现,都是在2.1的配置基础上进行的。

2.2 C#接口实现(一)

  1. 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实例相关推荐

  1. 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 ...

  2. MySQL学习记录 (三) ----- SQL数据定义语句(DDL)

    相关文章: <MySQL学习记录 (一) ----- 有关数据库的基本概念和MySQL常用命令> <MySQL学习记录 (二) ----- SQL数据查询语句(DQL)> &l ...

  3. css学习记录三:文本属性

    css学习记录三:CSS文本属性 一.文本属性的作用 二.文本颜色 三.文本对齐 四.装饰文本 五.文本缩进 六.行间距 一.文本属性的作用 CSSS Text(文本)属性可定义文本的外观,比如文本的 ...

  4. Kafka学习记录(三)——Broker

    Kafka学习记录(三)--Broker 目录 Kafka学习记录(三)--Broker 对应课程 Zookeeper存储的Kafka信息 Broker总体工作流程 Broker的服役和退役 Kafk ...

  5. webrtc学习记录三【创建基于RTCPeerConnection本机内的1v1音视频互通】

    系列文章目录 webrtc学习记录一[媒体录制MediaRecorder] webrtc学习记录二[基于socket.io创建信令服务器聊天室] 目录 系列文章目录 前言 一.媒体能力的协商过程 1. ...

  6. TPM零知识学习八 —— tpm组件联调及API实例解析

    一.组件联调准备 1. 运行TPM模拟器 ~/TPM/ibmtpm/ibmtpm$ ./src/tpm_server LIBRARY_COMPATIBILITY_CHECK is ON Startin ...

  7. SWIG学习记录(二)SWIG实用性基础

    目录 1 SWIG实用基础 1.1 值传递结构体 1.2 返回值 1.3 链接到结构体变量 1.4 链接到char* 1.5 数组 1.6 创建只读变量 1.7 重命名和忽略声明 1.7.1 特定标识 ...

  8. 【学习记录】使用高德地图API开发一个简单基础的WebGIS系统(GIS考研院校专题地图网站)

    文章目录 0. 前言 1. 功能设计 2. 数据处理 3. 项目结构 4. 功能实现 4.1. 初始化 4.2. 地图基础功能模块 4.2.1. 放大缩小功能, 图层管理功能 4.2.2. 地图测量功 ...

  9. 《你好,放大器》----学习记录(三)

    3 多种多样的运算放大器 ADI把放大器分为精密和高速两大类 3.1 精密运放和高速运放 3.1.1 精密运放概述 一般来讲,带宽小于 50MHz 的,能够具有某些特殊指标优异性的运放,都属于精密运放 ...

最新文章

  1. py 的 第 31 天
  2. 英伟达人工智能和处理器驱动的制药、生命科学合作
  3. 5.1.3.jvm java虚拟机系统参数查看
  4. awk bc命令 linux_linux之awk命令(转载)
  5. 自己动手写Docker系列 -- 5.5实现容器停止
  6. 浅谈压缩感知(十六):感知矩阵之RIP
  7. java private list_Java基础知识回顾之四 ----- 集合List、Map和Set
  8. python解码和编码的区别_python基础小知识,is和==的区别,编码和解码
  9. XXL-API v1.1.1 发布,API管理平台
  10. Linux的操作系统下载及安装
  11. MFC之打开(开发)映美精相机
  12. [人工智能-深度学习-32]:卷积神经网络CNN - 常见分类网络- AlexNet网络结构分析与详解
  13. 计算机考研落榜了怎么办,一位考研落榜者的自述:考研失败的原因
  14. 数据库、MySQL基本知识
  15. 【电子设计大赛】2021 年全国大学生电子设计竞赛仪器设备和主要元器件及器材清单
  16. 这100佳创新互联网公司值得你去
  17. armadillo使用,armadillo提高编译效率和速度
  18. OpenGLES---设置获取Shader程序属性
  19. 去除.gif图片中的文字
  20. java生成文字与图片_java生成word(文字和图片)

热门文章

  1. Misc(buuoj)
  2. html4播放mp3,如何使用ipod shuffle4代?导入和播放mp3文件格式的音乐?
  3. FANUC机器人INTP-250或251用户坐标系或工具坐标系与示教资料不符报警的处理办法
  4. OCaml 安装以及简单的加减乘除Demo(以Ubuntu16.04为例)
  5. 路在何方?大龄程序员不得不思考的2个问题
  6. Django视图优化(页面显示以及页面跳转)
  7. 安卓手机在高端手机市场全面败退,苹果独领风骚
  8. Json解析数据数组
  9. 七星炒股主线科技反复走强
  10. 观察者Observe(刷新数据)