Как применить SWIG OUTPUT typemaps для типов классов в Python?


У меня возникли некоторые проблемы с созданием оболочки Python вокруг библиотеки C++ с помощью SWIG (версия 3.0.6).

Моя проблема связана с применением выходной типовой карты, особенно в случае указателей / ссылок на типы классов.

Чтобы проиллюстрировать, это то, что я хочу для стандартных типов, и это работает:

// .h
int add(const long arg1,const long arg2,long& resultLong);

// interface.i
%apply long& OUTPUT { long& resultLong };
int add(const long arg1,const long arg2,long& resultLong);

// projectWrapper.py
def add(arg1, arg2):
    return _projectWrapper.add(arg1, arg2)
addTerm = _projectWrapper.add

// usage
>>> result = projectWrapper.add(2, 4)
>>> print result
[0, 6L]

Вы не должны передавать "resultLong", но он добавляется к результату автоматически. Отлично!

Однако, это, кажется, не работает, как я ожидаю, когда тип вывода - это некоторый указатель на тип класса:

// .h
int GetClassType(const char* name, exportedClassType*& resultPointer);

class exportedClassType
{...}

// interface.i
%apply exportedClassType*& OUTPUT { exportedClassType*& resultPointer };    
int GetClassType(const char* name, exportedClassType*& resultPointer);

// projectWrapper.py
def GetClassType(name, resultPointer):
    return _projectWrapper.GetClassType(name, resultPointer)
GetClassType = _projectWrapper.GetClassType
Проблема, по-видимому, заключается в том, что SWIG не обработал его таким же образом, как простой тип. Он по-прежнему отображается как "входной" параметр в сигнатуре обернутой функции.
// attempted usage
>>> classType = projectWrapper.GetClassType("name")
TypeError: GetClassType() takes exactly 2 arguments (1 given)

>>> result = 0
>>> projectWrapper.GetClassType("name", result)
Traceback (most recent call last):
File "<input>", line 1, in <module>
TypeError: in method 'GetClassType', argument 2 of type 'exportedClassType *&'
Может ли кто-нибудь, пожалуйста, сказать мне, что я делаю неправильно или указать мне правильное направление? Любую помощь с благодарностью принимаю! Спасибо
3 10

3 ответа:

Этот вопрос долгое время оставался неразрешенным, поэтому я подумал, что лучше дать решение этому вопросу. Выходная типовая карта применяется только к простым типам, поэтому решение дается путем объединения in и argout типовых карт.

Рассмотрим ситуацию, когда мы имеем класс C++ SampleImpl, реализующий интерфейс C++ SampleBase, который технически не является интерфейсом, поскольку он включает в себя реализацию виртуального деструктора. Предположим, что у нас есть статическая функция, которая возвращает код ошибки и реализацию интерфейса. Последнее в качестве ссылки на указатель, что и является ситуацией выше.

Заголовок интерфейса:

// Sample.hpp
#pragma once
namespace Module {
  class SampleBase {
  public:
#ifndef SWIG
    // Hint to the programmer to implement this function
    static int SampleCreate(SampleBase *&obj);
#endif
    virtual ~SampleBase() = default;
  };
}

Заголовок реализации:

// Sample_impl.hpp
#pragma once
#include "Sample.hpp"

namespace Module {
  class SampleImpl : public SampleBase {
  public:
    static int SampleCreate(Module::SampleBase *&obj);

    SampleImpl();
    virtual ~SampleImpl();
  private:
    float a;
  };
}

Реализация:

// Sample_impl.cpp
#include "Sample_impl.hpp"
#include <cstdio>

namespace Module {
  int SampleImpl::SampleCreate(Module::SampleBase*& obj) {
    obj = (SampleBase*) new SampleImpl();
    return 0;
  }
  SampleImpl::SampleImpl() {
    printf("SampleImpl::SampleImpl()\n");
  }

  SampleImpl::~SampleImpl() {
    printf("SampleImpl::~SampleImpl()\n");
  }
}

SWIG интерфейс (с использованием argout typemap)

// example.i
%module example
%{
  #define SWIG_FILE_WITH_INIT
  #include "Sample.hpp"
  #include "Sample_impl.hpp"
%}

%include "typemaps.i"

%typemap(in, numinputs=0) Module::SampleBase *&obj (Module::SampleBase *temp) {
  $1 = &temp;
}

%typemap(argout) Module::SampleBase *& {
  PyObject* temp = NULL;
  if (!PyList_Check($result)) {
    temp = $result;
    $result = PyList_New(1);
    PyList_SetItem($result, 0, temp);

    // Create shadow object (do not use SWIG_POINTER_NEW)
    temp = SWIG_NewPointerObj(SWIG_as_voidptr(*$1),
             $descriptor(Module::SampleBase*),
             SWIG_POINTER_OWN | 0);

    PyList_Append($result, temp);
    Py_DECREF(temp);
  }
}

Использование в Python

import example

// Creating specialization
obj = example.SampleImpl()
del obj

// Creation of object using output typemap
errorCode, obj = example.SampleImpl_SampleCreate()
del obj

Это не ответ, просто недостаточно репутации для комментария : (

Потому что вам нужно использовать указатель в C++, а у Python нет указателей (так что вы все равно не смогли бы ничего сделать с вашим текущим "результатом" в Python).

Не могли бы вы добавить обертки, чтобы скрыть указатели .h как было предложено @Jens Munk:

class exportedClassType_ptr {
public:
    exportedClassType* ptr;
    exportedClassType_ptr( exportedClassType& input ) {
        this->ptr = &input;
    }
};

int GetClassType( const char* name, exportedClassType_ptr& resultPointer ) {
    return GetClassType( name, resultPointer.ptr );
}

Изменить .I файл для вызова нового метода:

%apply exportedClassType_ptr& OUTPUT { exportedClassType_ptr& resultPointer };    
int GetClassType( const char* name, exportedClassType_ptr& resultPointer );

В Python напишите что-то вроде этого:

>>> realResult = projectWrapper.exportedClassType()
>>> result = projectWrapper.exportedClassType_ptr(realResult)
>>> projectWrapper.GetClassType("name", result)

И использовать "realResult" для будущей работы.

Я думаю, что вам нужно использовать указатели. Я также не уверен, что происходит, когда смешиваются typemaps и return-операторы. Минимальный пример файла tst.i:

%module tst

%{

  // declaration:
  void add(long *resultLong, const long arg1,const long arg2);
  long mul(const long a, const long b);

  // the code:
  void add(long *resultLong, const long arg1,const long arg2) {
    *resultLong = arg1 + arg2;
  }
  long mul(const long a, const long b) {
    return a*b;
  }

%}

// The wrapper:
%apply (long* OUTPUT) { long* resultLong }; 
void add(long* resultLong, const long arg1,const long arg2);
long mul(const long a, const long b);

После перевода (я всегда использую CMake ), Использование в python будет:

import tst
x = tst.add(3, 4)  # results in 7L    
y = tst.mul(3, 4)  # results in 12L

Я думаю, что лучше использовать операторы return вместо typemaps для скалярных типов данных. При взаимодействии массивов я рекомендую использовать предопределенные типовые карты numpy.i .