用于SWIG,Python的C函数的错误代码的%typemap和%exception

时间:2012-02-26 20:12:14

标签: python c exception swig

我有一些我希望向Python公开的C代码。它有一个这样的调用约定:

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

其中返回值为(a + b)或其他什么,但如果出现问题,那么我会在*错误中得到错误代码。我想从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 kludge会崩溃。

在不更改C代码的调用约定的情况下,解决此问题的更好方法是什么?

3 个答案:

答案 0 :(得分:3)

诀窍不是使用%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 :(得分:1)

来自Karl Wette,通过swig-user邮件列表:

  

您可以通过移动声明来修改“in”类型映射   字体图中的“myerr”:

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

只要每个函数中只有一个“int * err”参数,这个   应该没事。然后,您可以直接使用“myerr”而无需参数   号。

这似乎是正确的解决方案,不需要kludges。谢谢卡尔!

答案 2 :(得分:0)

如果您愿意接受它不会重入,您可以使用全局而不是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
}

您可能会使用第三种可能的方法():

%{
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;
    }
}

诀窍是来自typemap的特定myerrN掩盖了static const全局变量 - if语句总是只引用一个本地常量,它是唯一可以非零的本地常量