Как правильно выполнить downcast в C # с помощью интерфейса, созданного SWIG? - PullRequest
2 голосов
/ 16 марта 2010

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

return new A();

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

  1. Используйте ключевое слово SWRE%%, чтобы добавить метод к объекту и в конечном итоге вызвать 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? Любая помощь будет оценена.

Ответы [ 3 ]

3 голосов
/ 11 апреля 2011

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

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

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

2 голосов
/ 23 августа 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);
1 голос
/ 07 января 2011

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

// .cpp
class foo : public bar {
}

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

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

...