场景问题
我们在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测试
- 将主要核心逻辑写好之后,我们编写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"
- 通过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;
}
}
}