【问题标题】:How to properly downcast in C# with a SWIG generated interface?如何使用 SWIG 生成的界面在 C# 中正确向下转换?
【发布时间】:2011-08-23 21:13:36
【问题描述】:

我有一个非常庞大且成熟的 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。在我看来,这种方法的缺点是需要您了解继承层次结构。就我而言,它相当大,我认为这是一个维护问题。
  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了吗?任何帮助将不胜感激。

【问题讨论】:

  • 如果我可以问的话,您采用了哪种解决方案?我在另一种目标语言中面临一个非常相似的问题……

标签: c# c++ swig downcast


【解决方案1】:

默认情况下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。它包含所有细节。

【讨论】:

    【解决方案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);
    

    【讨论】:

      【解决方案3】:

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

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

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

      【讨论】:

        猜你喜欢
        • 2019-07-15
        • 1970-01-01
        • 2018-02-22
        • 1970-01-01
        • 2011-04-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多