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

问题描述 投票:0回答:4

我有一个非常庞大且成熟的 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 作为 BaseClass;
        HandleRef hr = BaseClass.getCPtr(fromObj2);
        IntPtr cPtr = hr.Handle;
        对象 toObj = Activator.CreateInstance(toType, cPtr, false);

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

        返回retval;
    }

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

c# c++ swig downcast
4个回答
6
投票

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

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

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


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 包装器

Foo

Bar where Bar : Foo
,您现在可以尝试将 
Foo
 向下转换为 
Bar
,如以下示例所示:

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

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

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

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


0
投票
距离提出最初的问题已经过去很长时间了。我只是想提高人们的认识,SWIG 中的问题尚未解决。

但是我在 SWIG 中创建了一个上游问题(位于

https://github.com/swig/swig/issues/2788),该问题再次详细讨论了该问题,并且还概述了目标的完整解决方案C# 语言和 std::shared_ptr

.
的用例

同样的概念也应该适用于其他智能指针,只需付出相当小的额外努力。并且应该也可以使其适应原始指针。

© www.soinside.com 2019 - 2024. All rights reserved.