如何使用SWIG生成的接口在C#中正确转发?

我有一个非常庞大和成熟的C ++代码库,我正在尝试使用SWIG生成一个C#接口。 我无法改变实际的C ++代码本身,但我们可以使用SWIG提供的任何方式来扩展/更新它。 我面临的问题是,如下所示编写的C ++函数会导致C#出现问题。

A* SomeClass::next(A*) 

调用者可能会执行以下操作:

 A* acurr = 0; while( (acurr = sc->next(acurr)) != 0 ){ if( acurr isoftype B ){ B* b = (B*)a; ...do some stuff with b.. } elseif( acurr isoftype C ) ... } 

本质上,迭代一个元素容器,根据它们的真实类型,做一些不同的事情。 SWIG为“next”函数生成了C#层,遗憾的是执行了以下操作:

 return new A(); 

因此,C#中的调用代码无法确定返回的对象是否实际上是派生类,它实际上似乎总是基类(这确实有意义)。 我遇到过几种解决方案:

  1. 使用%extend SWIG关键字在对象上添加方法,最终调用dynamic_cast。 正如我所看到的,这种方法的缺点是,这需要您了解inheritance层次结构。 在我的情况下它是相当巨大的,我认为这是一个维护问题。
  2. 使用%factory关键字提供方法和派生类型,并让SWIG自动生成dynamic_cast代码。 这似乎是第一个更好的解决方案,但是从更深层次的角度来看,它仍然需要您搜索所有方法以及它可能返回的所有可能的派生类型。 同样,一个巨大的维护问题。 我希望我有一个文档链接,但我找不到一个。 通过查看SWIG附带的示例代码,我发现了这个function。
  3. 创建C#方法以创建派生对象的实例,并将cPtr传输到新实例。 虽然我认为这很笨拙,但确实有效。 请参阅下面的示例。
     public static object castTo(object fromObj,Type toType)
     {
         object retval = null;

         BaseClass fromObj2 = fromObj as BaseClass;
         HandleRef hr = BaseClass.getCPtr(fromObj2);
         IntPtr cPtr = hr.Handle;
         object toObj = Activator.CreateInstance(toType,cPtr,false);

         //确保它实际上是我们认为的
         if(fromObj.GetType()。IsInstanceOfType(toObj))
         {
            回到Obj;
         }

        返回;
     }

这些真的是选择吗? 如果我不愿意深入研究所有现有的函数和类推导,那么我会留下#3? 任何帮助,将不胜感激。

默认情况下, SWIG生成不支持向下转换多态返回类型的C#和Java代码 。 如果您的C ++代码能够识别返回的C ++实例的具体类,我找到了一种直接解决此问题的方法 。 也就是说,只有当您使用SWIG包装的C ++ API具有类似于C#object.GetType()或Java Object.getClass()的东西时,我的技术才有效。

解决方案是添加一个C#中间类方法来实例化C ++所说的具体类。 然后,使用%typemap(out)告诉SWIG在返回抽象类时使用此中间类方法。

这是一个非常简洁的解释,所以请参阅我的博客文章, 该文章展示了如何生成可以向下转换的多态C#和Java 。 它有所有细节。

原始post中的第三个解决方案在Swig 2.0中不起作用,其中构造函数是私有的(因此Activator无法找到构造函数)。 因此必须使用不同的BindingFlags。 以下是原帖中提到的第三种解决方案的通用变体:

 public class SwigHelper { public static T CastTo(object from, bool cMemoryOwn) { System.Reflection.MethodInfo CPtrGetter = from.GetType().GetMethod("getCPtr", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static); return CPtrGetter == null ? default(T) : (T) System.Activator.CreateInstance ( typeof(T), System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance, null, new object[] { ((HandleRef) CPtrGetter.Invoke(null, new object[] { from })).Handle, cMemoryOwn }, null ); } } 

给定两个SWIG包装器FooBar where Bar : Foo ,您现在可以尝试将Foo向下转换为Bar ,如下例所示:

 Foo foo = new Bar(); Bar bar = SwigHelper.CastTo(foo, false); 

我们在界面中添加了函数以获取所需的特定类型:

 // .cpp class foo : public bar { } ///////////// part of swig // .i (swig) %extend foo { static foo* GetFoo( bar* iObj ) { return (foo*)iObj; } } 

这有点单调乏味,因为必须为每个class级完成,但是再一次,它可以变成一个SWIG宏。