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

时间:2010-03-16 20:49:17

标签: c# c++ swig downcast

我有一个非常庞大且成熟的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为“下一个”函数生成了C#层,遗憾的是执行了以下操作:

return new A();

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

  1. 使用%extend SWIG关键字在对象上添加方法,最终调用dynamic_cast。正如我所看到的,这种方法的缺点是,这需要您了解继承层次结构。在我的情况下,它是相当巨大的,我认为这是一个维护问题。
  2. 使用%factory关键字提供方法和派生类型,并让SWIG自动生成dynamic_cast代码。这似乎是第一个更好的解决方案,但是从更深层次的角度来看,它仍然需要您搜索所有方法以及它可能返回的所有可能的派生类型。同样,一个巨大的维护问题。我希望我有一个文档链接,但我找不到一个。通过查看SWIG附带的示例代码,我发现了这个功能。
  3. 创建C#方法以创建派生对象的实例,并将cPtr传输到新实例。虽然我认为这很笨拙,但确实有效。请参阅下面的示例。
  4.     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);
    
            // make sure it actually is what we think it is
            if (fromObj.GetType().IsInstanceOfType(toObj))
            {
                return toObj;
            }
    
            return retval;
        }
    

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

3 个答案:

答案 0 :(得分:3)

默认情况下SWIG generates C# and Java code that does not support downcast用于多态返回类型。我找到了一种直接的方法来解决这个问题,如果你的C ++代码有办法识别返回的C ++实例的具体类。也就是说,只有当您使用SWIG包装的C ++ API具有类似于C#object.GetType()或Java Object.getClass()的内容时,我的技术才有效。

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

这是一个非常简洁的解释,请参阅我的博客文章how to generate polymorphic C# and Java that you can downcast。它有所有细节。

答案 1 :(得分:2)

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

public class SwigHelper
{
    public static T CastTo<T>(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<Bar>(foo, false);

答案 2 :(得分:1)

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

// .cpp
class foo : public bar {
}

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

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