SWIG高级应用之向下转换

本文探讨了在C++和C#联合开发中,如何解决C++返回基类对象无法在C#中转换为子类的问题。通过C++类信息提取,利用SWIG实现C#代码的反射,并修改SWIG模板来实现向下转换。详细介绍了SWIG的修改过程和测试方法。
摘要由CSDN通过智能技术生成

场景问题

我们在C++,C#联合开发中会经常遇到一个问题,C++返回的基类由C#转换为其子类的时候就会报错(下面的代码解释了这种情况),这个问题非常严重,不解决那么这种交互方式,那二次开发就没办法调用子类的实现,导致很多子类的接口使用不了。

//C++代码;
class IObject
{
public:
    virtual void run()=0;
};

class Sample:public IObject //继承IObject
{
public:
       virtual void run()
       {
           std::cout<<"my name is lili";
       }   
       int add(int x,int y) { return x+y;}
};

Class Caler
{
 IObject* _object;
public:
    Caler(){_object=new Sample();}
   IObject* getObject(){ return _object;}//返回基类的指针;
};

//c#代码
static void Main(string[] args)
{
    Caler caler=new Caler();
    ## 注意这个代码是会出现编译错误
    Sample sample=caler.getObject() as Sample; 
    sample.add(4,5);
}

引起这个的原因是因为C++到C#的封装根本不可能是同一个类对象,C#封装的类其实可以理解为C++的代理类,C#代理了C++的指针,C#的方法调用了C的方法,在内部C的方法调用对应的C++类的方法。所以C++方法返回的基类对象在C#中就是返回代理类的基类对象,因此没有办法使用C#的 as 关键字转换为子类对象。
解决这个问题的核心应该是由C++返回的对象可以告诉C#使用的是基类还是子类,如果是子类,那么就应该封装成子类对象再转换为基类。这样才能在C# 使用 as 关键字转换为子类对象,因此我们需要在C++基类中增加一些信息可以通知到C#做相应的操作

C++类信息提取

由于C#可以通过类名反射出对象,因此我们需要在C++的类中记录类的名字,同时为了效率我们可以类名的hash值做个判断,提高执行效率

  //定义基类的类信息宏

#define BASE_CLASSINFO(classname)\ 
public:\
   virtual  dlonglong  classId() {return 0;};\ //  基类的ID;
 //静态方法,判断某个类的ID是否属于当前类对象,如果是,那么直接返回该类对象,如果不是,在C#中通过类名反射类对象;
    static bool isClassIdOf(dlonglong cid)\ 
   {\
    return dan::qt_hash(dan::SGString::fromLocal8Bit(#classname))==cid;\
   }\
  virtual const char * className()\  //返回C#的类名;
{\
    return #classname;\
};

//定义子类的宏
#define DEVICED_CLASSIDINFO(classname)\
public:\
   virtual dlonglong  classId()\
    {\
       dlonglong iid = dan::qt_hash(this->className()); return iid; \
    }\
    static bool isClassIdOf(dlonglong cid)\
   {\
    return dan::qt_hash(#classname)==cid;\
   }\
  virtual const char * className()\
{\
    return #classname;\
};

//C++代码;
class IObject
{
    BASE_CLASSINFO(IObject) //添加基类信息宏;
public:
    virtual void run()=0;
};
//一个通用类,通过void*指针返回类id,和类的classname,在后面会用到;
class Common
{
 public:
  static long classID(void*p) 
  {
      IObject* object=(IObject*)p;
      return object->classId();
  }
  static long className(void*p) 
  {
      IObject* object=(IObject*)p;
      return object->className();
  }  
};
class Sample:public IObject //继承IObject
{
    DEVICED_CLASSIDINFO(Sample) //使用宏获取类信息;
public:
       virtual void run()
       {
           std::cout<<"my name is lili";
       }   
       int add(int x,int y) { return x+y;}
};
//调用的地方用智能指针;
Class Caler
{
 Auto_Object<IObject> _object;
public:
    Caler(){_object=new Sample();}
   IObject* getObject(){ return _object.get();}//返回基类的指针;
};

SWIG实现C#代码解决类的反射问题

通过C++我们可以解决类信息的获取,但是获取对应的C#代理类需要通过类的反射得到

%pragma(csharp) imclassimports=%{
using System.Reflection;
using System.IO;
using System.Collections;
using System;
using System.Collections.Generic;
     class AssemblyDll
    {
        private AssemblyDll()
        {
        }
        static private   Dictionary<string,Assembly> native_wappers_dlls = new Dictionary<string, Assembly>();
        static private Dictionary<string, Type> class_types = new Dictionary<string, Type>();
        public  Assembly getAssemblyFromDll(string dllPath)
        {
            Assembly pObjAssembly = null;
            if (native_wappers_dlls.ContainsKey(dllPath))
            {
                pObjAssembly = native_wappers_dlls[dllPath] as Assembly;
            }
            else
            {
                if (System.IO.File.Exists(dllPath))
                {
                    pObjAssembly = Assembly.LoadFrom(dllPath);
                    native_wappers_dlls.Add(dllPath, pObjAssembly);
                }
            }

            return pObjAssembly;
        }
        public  object GetClassObject(string dllpath, string className, System.IntPtr cPtr, bool cMemoryOwn)
        {
            var assembly = getAssemblyFromDll(dllpath);
            var type = assembly.GetType(className);
            var objT = Activator.CreateInstance(type, new object[] { cPtr, cMemoryOwn });
            return objT;
        }
         //查找特定的dll的类名,并反射得到对象;
        public object GetClassObject(string className, System.IntPtr cPtr, bool cMemoryOwn)
        {
		    if (className.Contains(":"))
            {

               var array=className.Split(':');
                var dllpath = array[0];
                var name = array[1];
				return GetClassObject(dllpath,name, cPtr,cMemoryOwn);
            }
            className="com.sample."+className;//完整类名;
            Type targetType = null;
            bool containsOne = (class_types.ContainsKey(className));
            if(containsOne){
                targetType=class_types[className];
            }
             else
            {
                bool findType = false;
                foreach (var item in native_wappers_dlls)
                {
                    if (findType)
                        break;
                    var assembly = item.Value;
                    Type[] allTypes = assembly.GetTypes();
                    foreach (Type type in allTypes)
                    {
                        if (type.FullName.EndsWith(className))
                        {
                            targetType = type;
                            findType = true;
                            break;
                        }
                    }
                }
            }
            if (targetType == null)
                return null;
            if (!containsOne)
                class_types.Add(className, targetType);//第一次查找到之后,以后就直接判断调用就可,提高性能;

            var objT = Activator.CreateInstance(targetType, new object[] {cPtr,cMemoryOwn });
            return objT;

        }
    }
%}

SWIG修改

这个才是重点,根据某个特征,修改内部模板代码,按照自己的想法思路去达到我们的目的。如果大家对SWIG的内部语法不熟悉,需要多看看swig的官方文档,多动手,多实验才行。由于在实际系统开发中,对于C++的类对象在C#中的使用,都是使用的智能指针(关于智能指针的使用,请参考我的相关文章《SWIG高级应用之智能指针》

//根据前面文章修改智能指针的文件,如Auto_Object.i文件,以下是部分核心的代码;
//模板文件中由很多类似的如
//%typemap(csout, excode=SWIGEXCODE)、%typemap(csvarout, excode=SWIGEXCODE2) 这样开头的代码,改写成下面的方式;
%typemap(csout, excode=SWIGEXCODE) CONST TYPE & {
    global::System.IntPtr cPtr = $imcall;
       if(cPtr == global::System.IntPtr.Zero)
       {
         return null;
       }
    //Common.classID(void* p)是C++函数,目的提供了一个通用的方法获取某个IObject*指针的classid;,下面的方法className也是类似;
      	if($typemap(cstype, TYPE).isClassIdOf(Common.classID(cPtr))) //判断类ID是否与自身类的静态类ID是否一致;
        {
           $typemap(cstype, TYPE) ret = (cPtr == global::System.IntPtr.Zero) ? null : new $typemap(cstype, TYPE)(cPtr, true);$excode
           return ret;
        }
        else
        {
            //调用C#的反射功能;
	         object tempobj= SwigAssembly.Instance.GetClassObject(Common.className(cPtr),cPtr,true);
          $typemap(cstype, TYPE) ret= tempobj as $typemap(cstype, TYPE);
			  if(ret==null)
			  {
			     ret = (cPtr == global::System.IntPtr.Zero) ? null : new $typemap(cstype, TYPE)(cPtr, true);$excode
			  }
			   return ret;
        }	
}

//这个是回调类中的使用方法,核心原理都是一样的;
%typemap(csdirectorin,pre="$typemap(cstype,TYPE) tempobj$iminput=null;\n"
"while(true)\n {"
"if($1==global::System.IntPtr.Zero) {"
"   tempobj$iminput=null;\n break;}\n"
"   if($typemap(cstype, TYPE).isClassIdOf(Core.classID($1)))"	
  "{\n    tempobj$iminput=new $typemap(cstype,TYPE)($1,true);\n break; \n} \n"
	"    object newobj$iminput= SwigAssembly.Instance.GetClassObject(Core.className($1),$1,true);\n"
	"   if(newobj$iminput==null)	{tempobj$iminput=new $typemap(cstype,TYPE)($1,true);break;}  \n"
	 "    tempobj$iminput=newobj$iminput as $typemap(cstype, TYPE); if(tempobj$iminput==null){ tempobj$iminput=new $typemap(cstype,TYPE)($1,true);}  break;}\n"
) SWIG_SMARTGIS_PTR_NAMESPACE::Smart_Object< CONST TYPE >
%{ tempobj$iminput %};

SWIG测试

  1. 将主要核心逻辑写好之后,我们编写swig的i文件简单测试下
//sample.i文件
%module(directors="1") exampleDll
%{
#include "example.h"
%}

/* turn on director wrapping Callback */
%apply void *VOID_INT_PTR { void * }
%include "auto_object.i"
%include "example.h"

  1. 通过swig生成的代码的Caler类代码
public class Caler : global::System.IDisposable {
  private global::System.Runtime.InteropServices.HandleRef swigCPtr;
  protected bool swigCMemOwn;

  public Caler(global::System.IntPtr cPtr, bool cMemoryOwn) {
    swigCMemOwn = cMemoryOwn;
    swigCPtr = new global::System.Runtime.InteropServices.HandleRef(this, cPtr);
  }
    public static global::System.Runtime.InteropServices.HandleRef getCPtr(SGCommandManager obj) {
    return (obj == null) ? new global::System.Runtime.InteropServices.HandleRef(null, global::System.IntPtr.Zero) : obj.swigCPtr;
  }

  ~Caler() {
    Dispose(false);
  }

  public void Dispose() {
    Dispose(true);
    global::System.GC.SuppressFinalize(this);
  }
  
  protected virtual void Dispose(bool disposing) {
    lock(this) {
      if (swigCPtr.Handle != global::System.IntPtr.Zero) {
        if (swigCMemOwn) {
          swigCMemOwn = false;
          SampleDll_wrapperPINVOKE.delete_Sample(swigCPtr);
        }
        swigCPtr = new global::System.Runtime.InteropServices.HandleRef(null, global::System.IntPtr.Zero);
      }
    }
  }
    //返回基类对象,但是封装的是Caler类;
   public IObject getObject() {
    global::System.IntPtr cPtr = SamleDll_wrapperPINVOKE.SGMapToolMap_map(swigCPtr);
       if(cPtr == global::System.IntPtr.Zero)
       {
         return null;
       }
      	if(Common.isClassIdOf(Common.classID(cPtr)))
        {
           IObject ret = (cPtr == global::System.IntPtr.Zero) ? null : new Sample(cPtr, true);
           return ret;
        }
        else
        {
	         object tempobj= SwigAssembly.Instance.GetClassObject(Common.className(cPtr),cPtr,true);//内部返回的是Sample封装类;
            IObject ret= tempobj as IObject;
			  if(ret==null)
			  {
			     ret = (cPtr == global::System.IntPtr.Zero) ? null : new IObject(cPtr, true);
			  }
			   return ret;
        }
  }
    
}
SWIG (Simplified Wrapper and Interface Generator)是一个广泛使用的工具,它允许将C/C++库轻松地绑定到Python、Perl等脚本语言。当处理C/C++中的void*指针时,SWIG会遇到挑战,因为这些类型的指针通常是用于通用数据存储,而具体的类型信息在运行时并不明确。 在Python中,由于动态类型系统,直接将void*转换成Python对象可能会出现问题,因为Python不知道如何正确解析这个无类型的内存区域。不过,SWIG提供了一些机制来处理这种情况: 1. 使用`%apply`指令:你可以定义一个模板或结构体,描述void*可能指向的数据类型,并将其映射到Python。例如,创建一个Pyrex模块,其中包含对void*的包装,然后由SWIG自动调用。 ```c++ %apply void * { PyObject *(*func)(void *) }; ``` 这告诉SWIG当你看到这样的函数声明时,应该创建一个可以接受Python对象并返回void*的代理。 2. `SWIG_POINTER_TO_PY()`宏:虽然不是直接支持void*,但在某些特定情况下,你可以手动使用这个宏将void*转换为Python整数或字符串,然后在Python代码中自行解码。 3. 如果void*实际上是指向自定义对象的地址,你需要创建一个适配器类,该类有一个成员变量指向void*,并在必要的时候提供转换函数。 尽管如此,最佳做法通常是尽量避免在API设计阶段使用void*,而是选择更类型安全的方式传递数据。如果必须使用,确保文档清晰,开发者了解如何正确处理这些数据。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

揽月凡尘

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

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

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

打赏作者

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

抵扣说明:

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

余额充值