Как правильно выполнить понижение в С# с интерфейсом, сгенерированным SWIG?

У меня есть очень большая и зрелая база кода 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 слой С# для "следующей" функции, к сожалению, делает следующее:

return new A();

Таким образом, вызывающий код в C# не может определить, является ли возвращаемый объект фактически производным классом или нет, на самом деле он всегда кажется базовым классом (что имеет смысл). Я наткнулся на несколько решений:

  1. Используйте ключевое слово %extend SWIG, чтобы добавить метод к объекту и в конечном итоге вызвать dynamic_cast. Недостатком этого подхода, на мой взгляд, является то, что он требует от вас знания иерархии наследования. В моем случае это довольно огромно, и я вижу, что это проблема обслуживания.
  2. Используйте ключевое слово %factory для указания метода и производных типов, и SWIG автоматически сгенерирует код dynamic_cast. Это кажется лучшим решением, чем первое, однако при более глубоком рассмотрении оно по-прежнему требует от вас поиска всех методов и всех возможных производных типов, которые он может вернуть. Опять же, огромные проблемы с обслуживанием. Я хотел бы иметь ссылку на документ для этого, но я не могу ее найти. Я узнал об этой функциональности, просматривая пример кода, поставляемый с SWIG.
  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);

        // make sure it actually is what we think it is
        if (fromObj.GetType().IsInstanceOfType(toObj))
        {
            return toObj;
        }

        return retval;
    }

Это действительно варианты? И если я не хочу копаться во всех существующих функциях и производных классах, то мне остается № 3? Любая помощь будет оценена по достоинству.


person JamesG    schedule 16.03.2010    source источник
comment
Какое решение вы выбрали, если я могу спросить? Я столкнулся с очень похожей проблемой на другом целевом языке…   -  person dnadlinger    schedule 03.01.2011


Ответы (3)


По умолчанию SWIG генерирует код C# и Java, который не поддерживает преобразование вниз для полиморфных типов возврата. . Я нашел простой способ решить эту проблему, при условии, что ваш код C++ позволяет идентифицировать конкретный класс возвращаемых экземпляров C++. То есть мой метод будет работать только в том случае, если C++ API, который вы оборачиваете с помощью SWIG, имеет что-то похожее на C# object.GetType() или Java Object.getClass().

Решение состоит в том, чтобы добавить метод промежуточного класса C# для создания экземпляра конкретного класса, который C++ называет таковым. Затем используйте %typemap(out), чтобы указать SWIG использовать этот метод промежуточного класса при возврате абстрактных классов.

Это очень краткое объяснение, поэтому обратитесь к статье в моем блоге, в которой показано как генерировать полиморфные C# и Java что вы можете понизить. В нем есть все детали.

person John McGehee    schedule 11.04.2011

Третье решение в исходном посте не работает в Swig 2.0, где конструкторы закрыты (таким образом, активатор не может найти конструктор). Поэтому необходимо использовать разные 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);
person japanitrat    schedule 23.08.2011

Мы добавили в интерфейс функции для получения нужного типа:

// .cpp
class foo : public bar {
}

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

Это немного утомительно, потому что это нужно делать для каждого класса, но опять же, это можно превратить в макрос SWIG.

person JamesG    schedule 07.01.2011