3

我有一个非常庞大且成熟的 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 传输到新实例。虽然我认为这很笨拙,但它确实有效。请参阅下面的示例。
    公共静态对象 castTo(object fromObj, Type toType)
    {
        对象 retval = null;

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

        // 确保它实际上是我们认为的那样
        if (fromObj.GetType().IsInstanceOfType(toObj))
        {
            返回对象;
        }

        返回 retval;
    }

这些真的是选择吗?如果我不愿意挖掘所有现有的函数和类派生,那么我只剩下#3了吗?任何帮助,将不胜感激。

4

3 回答 3

4

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

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

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

于 2011-04-11T01:02:54.763 回答
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);
于 2011-08-23T11:07:10.390 回答
1

我们在接口中添加了函数来获取所需的特定类型:

// .cpp
class foo : public bar {
}

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

这有点乏味,因为它必须为每个类都完成,但话又说回来,它可以变成一个 SWIG 宏。

于 2011-01-07T04:26:36.863 回答