2012-02-26 29 views
1

我有一些C代码,我想暴露给Python。它有一个调用约定是这样的:%typemap和%exception for C语言错误代码为SWIG,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杂牌:

%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代码的调用约定?

+0

为什么错误添加负数? – 2012-02-26 20:34:27

+0

我怀疑这是一个简单的例子,它实际上是一个更复杂的函数,显示了它的方式 – Flexo 2012-02-26 20:40:42

+0

是正确的。完整的应用程序是http://ascend4.org/FPROPS,这是一个流体属性库。 – jdpipe 2012-02-26 21:02:54

回答

1

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

你可以通过移动 “myerr中”声明的类型映射内修改“在”类型表:只要有只有一个“诠释* ERR”在每个函数参数

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

,这 应该没问题。然后你可以直接使用“myerr”,而不需要参数 。

这似乎是完全正确的解决方案,不需要kludges。谢谢Karl!

+0

+1我喜欢,我以前没见过noblock。 – Flexo 2012-02-27 17:46:16

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 
} 

还有第三个可能的(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; 
    } 
} 

诀窍是,从类型映射口罩static const全球那些具体myerrN - if语句总是指只有一个局部常量,这是唯一可以非零的那个

+0

我一直希望能够重入其中......我也无法完全掌握C方法上的方法,http://www.swig.org/Doc1.1/HTML/SWIG .html#n5 – jdpipe 2012-02-26 21:02:09

+0

嗯,我想不出一个比'__thread'更清洁的解决方案。你能举出一个不适用于所有物体的例子吗?只是抛出并处理它而不是使用指针会更简单吗? – Flexo 2012-02-26 22:37:22

+0

@jdpipe - 我想我已经制定了一个更好的方法来实现这一点 - 它是可重入的,并使用'freearg'代替 – Flexo 2012-02-26 23:34:40

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 
+0

argout。我希望我能想到像那样使用它。比我的任何一个都干净得多。 – Flexo 2012-03-05 11:20:43