SWIG高级应用之智能指针

SWIG高级应用之智能指针

SWIG 作为C++到其他语言包装的万能胶水语言,无疑是非常强大的,对于C++研发人员来说, SWIG 的使用做是作为必备技能来的,关于SWIG的基础知识我在前面的文章《SWIG与JAVA的交互指南》也详细的讲过了,这里不在叙述(如果还不清楚的自己去官网查资料)。在今天的这篇文章中,我们将学习一些SWIG的高级应用,这些应用可以让基于C++底层的其他高级语言二次开发变的与原生语言交互一致的用户体验。

ps:本文例子是基于C++包装C#,其他语言基本类似,更多的还是掌握其语言的特性。

在本专题中我们将会学到以下内容

  • 如何改写swig模板实现自定义智能指针的封装问题(确保在C#只有智能指针和普通指针是一个类对象)
  • 将自定义的字符串类转为平台默认的字符串类如(QString 转C#的String对象)
  • 对于自己写的迭代类(如 vector、List 等)如何在C#中可以用如 foreach(var item:collections) 的使用方式来遍历对象
  • 解决类的向下转换问题(默认不支持类的向下转换,如C#的 as功能)
  • 解决C++类在C#中继承的对象的生命周期问题
    本章要解决自定义智能指针的封装问题。

改写SWIG智能指针

开始使用swig的时候很多人可能会有个疑惑,C++ new出来的对象是在堆上的,需要自己的手动调用释放才能将内存释放。但C#的生命周期管理是由C#的垃圾回收机制决定的。对于写C#的人而言,肯定不希望自己手动的去管理内存,太过麻烦,而且与C#的代码风格也是不一致的。

//以下是开始写代码经常遇到的问题,NEW的对象要手动去释放内存,对于C++来可说,可能还好一些,但是一旦封装到C#就成了一个大问题。
//一个加法类;
class AddCls
{
public:
    int add(int x, int y)
    {
        return x + y;
    }
};

//计算类;
class Calculate
{
public:
    AddCls* addCls()
    {
        return _add_class;
    }
    void setAddCls(AddCls* cls)
    {
        __add_class.reset(cls);
    }
private:
    AddCls* _add_class;
};

//C#类
Calculate cal=new Calculate();
cal.addCls().add(3,4);
//?? 什么时候释放,不释放就内存泄露,我是不是该加一个函数去释放呢?

方案一:使用template模板化智能指针

使用c++11的 std::shared_ptr 智能指针类来解决,在引用计数为0的时候就可以自动释放内存了。具体分为以下四步

  1. 在代码的所需要的类的头文件中加入以下内容如:
#ifdef SWIG
%template(AddClsPtr)std::shared_ptr;
#endif
  1. 改写需要使用这个类的地方的代码
class Calculate
{
public:
    std::shared_ptr addCls()
    {
        return _add_class;
    }
    void setAddCls(AddCls* cls)
    {
        _add_class.reset(cls);
    }
private:
    std::shared_ptr _add_class;//使用智能指针
};
  1. 使用SWIG生成C#代码

发现有两个类似的C#文件(AddCls.cs 和AddClsPtr.cs) 一个是普通类生成的,一个是模板化指针类生成的。在C#使用是这样

Calculate cal=new Calculate();
AddClsPtr cls=new AddClsPtr();
//setAddCls 使用的是AddCls类,要使用智能指针的 get函数获取另一个类.
cal.setAddCls(cls.get());

看这个简单的例子好像还行,不是什么大的问题,但是对于一个大的系统而言,就算是有了代码规范,但是每个人的使用风格还是有一点差异的。如果使用方案1的方案,那这个友好度太差了,瞬间不想用了。

方案二 使用智能指针的SWIG模板

  1. 在SWIG的i文件中,添加shared_ptr的swig头文件如 %include std::shared_ptr.i
  2. 在代码的所需要的类的头文件中加入以下内容如:
#ifdef SWIG
%std::shared_ptr(AddCls); //注意与上面的不同,可以识别这个%std::shared_ptr关键字是因为加了头文件%include std::shared_ptr.i
#endif
  1. Calculate这个类还是方案一的,通用SWIG生成C#文件

这个使用生成的文件只有一个文件AddCls.cs ,这个其实是智能指针类的封装(不是普通类的封装),原理是swig在对接口进行封装时都使用智能指针封装了一层出去。有兴趣的可以看看生成的cxx代码,可以发现所有关于这个AddCls作为参数输出都是std::shared_ptr(AddCls)。(相当于new了一个std::shared_ptr(AddCls)类,但是将ADDCls的指针传了进去。

  1. C#中的使用
Calculate cal=new Calculate();
AddCls cls=new AddCls();
cal.setAddCls(cls);

方案二算是比较完美了,看起来C#的已经没有什么特别的地方了,不需要调用释放,也不需要在两个类中切换。

方案三 自己写智能指针模板

方案二使用C++11自带 std::shared_ptr ,但是有些场景还是希望使用自定义的智能指针。

  • std::shared_ptr 是非侵入式指针,对于很多应用场景,还是使用侵入式的智能指针比较方便(可以普通指针和智能指针互用,主要是在某些地方可以确定智能指针的生命周期肯定存在时)
  • 很多现成的框架时有自己的智能指针类的,改用shared_ptr就不太现实了.
  • C++很喜欢自己造轮子,金窝银窝都不如自己的好。
编写SWIG的智能指针模板(假设定义的智能指针类为 Auto_Object)

主要还是参考 std::shared_ptr.i 这个头文件(可以在swig的安装包的LIB目录中找到,代码太长不贴出来了,有需要的可以联系我),这里做一个改造说明,以下是AutoObject.i文件的内容的部分说明

std::shared_ptr.i文件的代码使用说明,以下的example代码是基于std::shared_ptr.i头文件改造的

## 这个是我们在swig的i文件声明为某个类为智能指针的宏如 %Auto_Object(AddCls)
examples:
%define %Auto_Object(TYPE...)
%feature("autoptr", noblock=1) TYPE { Auto_Object< TYPE > }
SWIG_AUTOOBJECT_PTR_TYPEMAPS(SWIGEMPTYHACK, TYPE)
SWIG_AUTOOBJECT_PTR_TYPEMAPS(const, TYPE)
%enddef

## 关于typemaps的使用说明
//使用 %typemap 指令来指示转换代码行为,in表示从目标语言到C/C++,out表示从C/C++到目标语言
//主要作用是用来定义C/C++层包装代码的生成行为,可以定义各类类型在C++与C#的转换行为。
%typemap(in)                输入参数转换
%typemap(typecheck)  输入参数类型检查重载方法中使用的类型
%typemap(argout)        输出参数处理
%typemap(check)         输入参数值检查
%typemap(arginit)        输入参数初始化
%typemap(out)              函数返回值转换
%typemap(ret)               返回值资源管理(“ret”类型映射)
%typemap(newfree)      新分配对象的资源管理(“newfree”typemap)


examples:
//代表的是封装C++的代码到C的代码的使用,$input代表的是当前输入参数,$1当前参数的转换结果 TYPE是指类的类型如AutoCls
%typemap(in, canthrow=1) CONST TYPE & %{
  $1 = ($1_ltype)(((Auto_Object< CONST TYPE > *)$input) ? ((Auto_Object< CONST TYPE > *)$input)->get() : 0);
  if (!$1) {
    SWIG_CSharpSetPendingExceptionArgument(SWIG_CSharpArgumentNullException, "$1_type reference is null", 0);
    return $null;
  } %}
//csin是C#这边封装的代码,下面代码是指参数如果为Auto_Object,Auto_Object*,Auto_Object*,Auto_Object*&
//翻译出来的C#代码是   AutoCls.getCPtr(类的参数名);
%typemap(csin) Auto_Object< CONST TYPE >, 
               Auto_Object< CONST TYPE > &,
               Auto_Object< CONST TYPE > *,
               Auto_Object< CONST TYPE > *& "$typemap(cstype, TYPE).getCPtr($csinput)"

## TypesMap的参数说明,可参考官方文档
* csdirctorin csdirctorout directorout dierectorin 回调类参数处理 cs为C#这边代码,没有这个为C++这边处理代码
* csvarout csvarin varout,varin  全局参数
* in out csin csout 函数参数

## 智能指针类在C#的代理类
// 智能指针在C#中的代理类,这个是基类
%typemap(csbody) TYPE %{
  private global::System.Runtime.InteropServices.HandleRef swigCPtr;
  protected bool swigCMemOwnBase;
  public $csclassname(global::System.IntPtr cPtr, bool cMemoryOwn) {
    swigCMemOwnBase = cMemoryOwn;
    swigCPtr = new global::System.Runtime.InteropServices.HandleRef(this, cPtr);
  }
       
  public static global::System.Runtime.InteropServices.HandleRef getCPtr($csclassname obj) {
    return (obj == null) ? new global::System.Runtime.InteropServices.HandleRef(null, global::System.IntPtr.Zero) : obj.swigCPtr;
  }
%}

// 智能指针在C#中的代理类,这个是继承的类
%typemap(csbody_derived) TYPE %{
  //这个里面可以写一些C#的函数或者代码;
  private global::System.Runtime.InteropServices.HandleRef swigCPtr;

  public $csclassname(global::System.IntPtr cPtr, bool cMemoryOwn) : base($imclassname.$csclazznameSWIGSmartPtrUpcast(cPtr), true) {
    swigCMemOwnDerived = cMemoryOwn;
    swigCPtr = new global::System.Runtime.InteropServices.HandleRef(this, cPtr);
  }

  public static global::System.Runtime.InteropServices.HandleRef getCPtr($csclassname obj) {
    return (obj == null) ? new global::System.Runtime.InteropServices.HandleRef(null, global::System.IntPtr.Zero) : obj.swigCPtr;
  }
%}
侵入式智能指针模板

SWIG模板的使用对智能指针的实现没有特殊的要求,大家使用自己的智能指针类即可,类的名字必须与自己编写的i文件里的智能指针类一致即可,也可参考网上的实现 https://blog.csdn.net/jiange_zh/article/details/52512337
notes:SWIG3.10的版本在处理回调类的智能指针的时候是有问题,需要使用SWIG4.0以上的版本。

使用自定义智能指针模板

  • swig的文件中添加包含文件如 %Auto_Object(ADDCls)
  • 在需要使用的头文件写上宏
#ifdef SWIG
%Auto_Object;
#endif
  • C#使用与方案二一样的方式即可

你可能感兴趣的:(SWIG高级应用之智能指针)