% typemap и% исключение для кодов ошибок из функций C для SWIG, Python - PullRequest
1 голос
/ 27 февраля 2012

У меня есть код на C, который я хочу представить в Python. У этого есть соглашение о вызовах как это:

int add(int a, int b, int *err)

где возвращаемое значение будет (a + b) или что-то еще, но если что-то пойдет не так, я получу код ошибки в * err. Я хочу обернуть эту функцию так, чтобы она работала так с точки зрения Python:

def add(a,b):
    if something_bad:
        raise RuntimeError("something bad")
    return a+b

Это должно быть легко, верно? Но я не нахожу это так.

Вот кое-что, что у меня работает, но обратите внимание на myerr3 kludge:

%module myswig
%feature("autodoc","1");

%{
int add(int a, int b, int *err){
    if(a < 0)*err = 1;
    if(b < 0)*err = 2;
    return a+b;
}

char *err_string(int err){
    switch(err){
    case 1:return "first argument was less than 0";
    case 2:return "second argument was less than 0";
    default:return "unknown error";
    }
}
%}

%typemap(in,numinputs=0) int *err (int myerr = 0){
    $1 = &myerr;
};

%exception{
    $action
    if(myerr3 != 0){
        PyErr_SetString(PyExc_RuntimeError,err_string(myerr3));
        return NULL;
    }
};

int add(int a, int b, int *err);

Это ведет себя как следует, например, с

import myswig
print "add(1,1) = "
print myswig.add(1,1)
# prints '2'

print "add(1,-1) = "
print myswig.add(1,-1)
# raises an exception

# we never get here...
print "here we are"

но я не могу использовать это решение, потому что если у меня есть другая функция, такая как

int add(int a, int b, int c, int *err)

тогда мой myerr3 кладж сломается.

Как лучше решить эту проблему, не меняя соглашение о вызовах кода C?

Ответы [ 3 ]

3 голосов
/ 05 марта 2012

Хитрость заключается не в том, чтобы использовать %exception, а в определении %typemap(argout). Также не обращайтесь напрямую к вашей временной переменной. %typemap(in) подавляет аргумент на целевом языке и предоставляет локальную временную переменную, но вы все равно должны ссылаться на сам аргумент в %typemap(argout). Вот измененная версия вашего исходного файла .i. Я также добавил более общее генерирование исключений, поэтому оно должно работать и для других языков:

%module x
%feature("autodoc","1");

// Disable some Windows warnings on the generated code
%begin %{
#pragma warning(disable:4100 4127 4211 4706)
%}

%{
int add(int a, int b, int *err){
    if(a < 0)*err = 1;
    if(b < 0)*err = 2;
    return a+b;
}

char *err_string(int err){
    switch(err){
    case 1:return "first argument was less than 0";
    case 2:return "second argument was less than 0";
    default:return "unknown error";
    }
}
%}

%include <exception.i>

%typemap(in,numinputs=0) int *err (int myerr = 0) {
    $1 = &myerr;
}

%typemap(argout) int* err {
    if(*$1 != 0) {
        SWIG_exception(SWIG_ValueError,err_string(*$1));
    }
}

int add(int a, int b, int *err);

А вот и результат:

Python 2.7.2 (default, Jun 12 2011, 15:08:59) [MSC v.1500 32 bit (Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import x
>>> x.add(1,1)
2
>>> x.add(3,4)
7
>>> x.add(-1,4)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "x.py", line 73, in add
    return _x.add(*args)
RuntimeError: first argument was less than 0
>>> x.add(3,-1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "x.py", line 73, in add
    return _x.add(*args)
RuntimeError: second argument was less than 0
1 голос
/ 27 февраля 2012

От Карла Ветта, через список рассылки swig-user:

Вы можете изменить свою карту типа in, переместив объявление "myerr" внутри карты типов:

%typemap(in,numinputs=0, noblock=1) int *err {
   int myerr = 0;
   $1 = &myerr;
};

Пока в каждой функции есть только один аргумент "int * err", это все должно быть в порядке. Затем вы можете использовать «myerr» напрямую без аргумента число.

Похоже, это правильное решение, без клуджей. Спасибо, Карл!

0 голосов
/ 27 февраля 2012

Если вы согласны с тем, что это не будет повторным входом, вы можете использовать глобал вместо myerr3, например:

%{
static int myerr = 0;
%}

%typemap(in,numinputs=0) int *err {
    $1 = &myerr;
};

%exception{
    $action
    if(myerr != 0){
        PyErr_SetString(PyExc_RuntimeError,err_string(myerr));
        return NULL;
    }
};

Другой альтернативой является немногозлоупотреблять freearg типографской картой вместо %exception:

// "" makes sure we don't go inside {}, which means using alloca is sane
%typemap(in,numinputs=0) int *err "*($1=alloca(sizeof(int)))=0;"

%typemap(freearg) int *err {
    if (*$1 != 0) {
       PyErr_SetString(PyExc_RuntimeError,err_string($1));
       SWIG_fail;
    }
}

или если вы не можете использовать alloca:

%typemap(in,numinputs=0) int *err {
    $1=malloc(sizeof(int));
    *$1=0;
}

%typemap(freearg) int *err {
    if ($1 && *$1 != 0) {
       PyErr_SetString(PyExc_RuntimeError,err_string($1));
       // Don't leak even if we error
       free($1);
       $1=NULL; // Slightly ugly - we need to avoid a possible double free
       SWIG_fail;
    }
    free($1);
    $1=NULL; // even here another arg may fail
}

Возможен третий вариант(bodge) подход, который вы можете использовать:

%{
static const int myerr1 = 0;
static const int myerr2 = 0;
static const int myerr3 = 0;
static const int myerr4 = 0;
static const int myerr5 = 0;
//...
%}

%typemap(in,numinputs=0) int *err (int myerr = 0){
    $1 = &myerr;
}

%exception{
    $action
    // Trick: The local myerrN from the typemap "masks" the const global one!
    if(myerr1 != 0 || myerr2 != 0 || myerr3 != 0 || myerr4 != 0 || myerr5 != 0) {
        PyErr_SetString(PyExc_RuntimeError,err_string(myerr1|myerr2|myerr3|myerr4|myerr5));
        return NULL;
    }
}

Хитрость в том, что конкретный myerrN из таблицы типов маскирует static const глобальные - оператор if всегда ссылается только на одну локальную константуединственный, который может быть ненулевым

...