[SWIG] SWIG原理(以C#为例)

本文将以C#为例,讲解SWIG的原理
原文链接:https://www.yuque.com/cppdev/swig/nuaxps

SWIG本质上是一个代码生成器,它帮我们生成了一个中间层,使得目标语言能够调用到C/C++的代码。
请添加图片描述

接下来,我们将自己动手编写这个中间层,看看SWIG到底为我们做了什么。

代码框架

本文代码:https://github.com/geodoer/swig-examples/tree/main/A-HowSWIGWork

本示例代码的框架如下:

  1. simple模块是我们写的C/C++代码
  2. usesimple模块是C#客户端程序,需要调用我们的C/C++代码
  3. simple_csharp是C#中间层,它的职责有两个
    1. 为C#客户端提供和C++代码一样的接口(如simple.cs
    2. 通过动态链接库,调用C/C++的代码,完成整个流程(如simplePINVOKE.cs文件)
  4. simple_wrap是C++中间层,它将C/C++包装成C#想要的样子,供C#调用

请添加图片描述

simple模块(C/C++代码)

先来看一下C/C++代码。
C/C++代码很简单,只有一个example.h文件,里面只有一个变量和一个函数
而我们的目标就是在C#程序中,能够使用到这个全局变量和函数。

//File: example.h

/* A global variable */
SIMPLE_API extern double Foo;

/* Compute the greatest common divisor of positive integers */
SIMPLE_API extern int gcd(int x, int y);

usesimple(C#如何使用C/C++代码)

了解C/C++代码之后,我们再看看在C#中,会如何使用我们写的C++代码。

//File: Program.cs
using System;

namespace usesimple
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");

            //Program.cs
            //调用C/C++的gcd()函数
            int x = 42;
            int y = 105;
            int g = simple.gcd(x, y);
            Console.WriteLine("The gcd of " + x + " and " + y + " is " + g);

            //操纵C/C++的Foo全局变量
            Console.WriteLine("Foo = " + simple.Foo);	//输出当前值
            simple.Foo = 3.1415926;						//改变值
            Console.WriteLine("Foo = " + simple.Foo);	//看看改变是否生效

            Console.ReadLine();
        }
    }
}

中间层

simple_csharp/simple.cs(提供给C#的接口文件)

在上一节中,我们明白了C#要如何使用C/C++的代码。
因此,我们很清楚的知道,我们需要提供给C#怎么样的接口。

//File: simple.cs
/* 提供给C#客户端调用的接口 */
public class simple
{
    public static double Foo
    {
        set
        {
            //TODO:将value设置给example.h中的Foo变量
        }
        get
        {
            //TODO:获取example.h中Foo的值
        }
    }

    public static int gcd(int x, int y)
    {
        //TODO:调用example.h中的gcd函数
    }
}

simple_wrap模块(将C/C++代码进行包装)

根据simple.cs文件,我们知道了要如何包装我们的C++代码了。
simple_wrap即是一个中间层,它将simple模块的C/C++代码,组织成C#需要的样子

//File:example_wrap.cpp
__declspec(dllexport) void __stdcall CSharp_Foo_set(double jarg1) {
	Foo = jarg1;
}

__declspec(dllexport) double __stdcall CSharp_Foo_get() {
	return Foo;
}

__declspec(dllexport) int __stdcall CSharp_gcd(int jarg1, int jarg2) {
	return gcd(jarg1, jarg2);
}

simple_csharp/simplePINVOKE.cs(C#调用C++)

simple_csharp模块中的simplePINIVOKE.cs将通过动态链接库,调用在simple_wrap模块中已经包装好C++函数,从而将整个流程打通,实现C#调用C++的通路。

//simplePINVOKE.cs

/* C#中间层
1. 调用C++中间层的代码,完成C#与C++之间的通讯
 */
class simplePINVOKE
{
    static simplePINVOKE()
    {
    }

    /// 从DLL中导入一个外部函数
    /// 
    /// DllImport的参数说明
    /// 1. 第一个参数   simple_wrap      DLL的名称
    /// 2. 第二个参数   CSharp_Foo_set   入口点,即函数名称
    [global::System.Runtime.InteropServices.DllImport("simple_wrap", EntryPoint = "CSharp_Foo_set")]
    public static extern void Foo_set(double jarg1);

    [global::System.Runtime.InteropServices.DllImport("simple_wrap", EntryPoint = "CSharp_Foo_get")]
    public static extern double Foo_get();

    /// 对于simple_csharp模块中的函数      int CSharp_gcd(int jarg1, int jarg2)
    /// 映射到C#中,函数为签名为           int gcd(int jarg1, int jarg2)
    /// 
    /// 不难发现,这里其实做了一个映射
    /// 1. C++的int类型              => C#的int类型
    /// 2. C++的函数名称为CSharp_gcd => C#的函数名称为gcd
    /// 
    /// 
    [global::System.Runtime.InteropServices.DllImport("simple_wrap", EntryPoint = "CSharp_gcd")]
    public static extern int gcd(int jarg1, int jarg2);
}

这里使用了C#里的一个功能,global::System.Runtime.InteropServices.DllImport
它的作用是从动态链接库中导入函数

  1. 第一个参数,是动态链接库的名称
  2. 第二个参数,是入口点名称,在这里就是函数的名称

不难发现,在DllImport中,做了一个函数映射

  1. C++的int类型 => C#的int类型
  2. C++的函数名称为CSharp_gcd => C#的函数名称为gcd

总结

simple_wrap、simple_csharp是一个中间层。
在实际应用中,example_wrap.cpp、simplePINVOKE.cs、simple.cs由SWIG帮我们自动生成。
而我们只需要拿到这些文件,组织成simple_wrapsimple_csharp模块即可。

请注意,模块划分不一定要像本文一样,这只是笔者建议的工程化模式。

后语

  1. simple_csharp/simplePINVOKE.cs中,只涉及了基础类型的映射。但在实际工程中,类型映射很复杂,有C++模板类、智能指针、C++STL、自定义类等等,这些该如何映射呢?
  2. 还有对异常的处理,C++的异常要能抛到C#中。

其实还有很多内容,都挺复杂的。
但SWIG都能帮我们处理,而且它也提供了灵活度很高的特性,使得我们能够自定义一些规则,完成工程化的需求。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

geodoer

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值