问题
I've got some C code that I want to expose to Python. It has a calling convention like this:
int add(int a, int b, int *err)
where the return value would be (a+b) or whatever, but if something went wrong, then I would get an error code in *err. I want to wrap this function so that it behaves like this, from the Python perspective:
def add(a,b):
if something_bad:
raise RuntimeError("something bad")
return a+b
This should be easy, right? But I'm not finding it so.
Here is something that I have that works, but look out for the 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);
This behaves as it should, eg with
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"
but I can't really use this solution, because if I have another function like
int add(int a, int b, int c, int *err)
then my myerr3
kludge will break down.
What's the better way to solve this problem, without changing the calling convention of the C code?
回答1:
The trick is not to use %exception
but to define %typemap(argout)
. Also don't refer to your temporary variable directly. The %typemap(in)
suppresses the argument in the target language and provides a local temporary variable, but you should still refer to the argument itself in %typemap(argout)
. Here's a modified version of your original .i file. I've also added more generic exception throwing, so it should work for other languages also:
%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);
And here is the result:
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
回答2:
From Karl Wette, via the swig-user mailing list:
You could modify your "in" typemap by moving the declaration of "myerr" inside the typemap:
%typemap(in,numinputs=0, noblock=1) int *err { int myerr = 0; $1 = &myerr; };
So long as there's only one "int *err" argument in each function, this should be fine. You can then use "myerr" directly without the argument number.
This seems to be exactly the right solution, no kludges required. Thanks Karl!
回答3:
If you're willing to accept that it won't be re-entrant you can use a global instead of myerr3
, e.g.:
%{
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;
}
};
The other alternative is to slightly abuse the freearg
typemap, instead of the %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;
}
}
Or if you can't use 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
}
There's a third possible (bodge) approach you might use:
%{
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;
}
}
The trick is that the specific myerrN
from the typemap masks the static const
global ones - the if statement is always referring only to one, local constant which is the only one that can be non-zero
来源:https://stackoverflow.com/questions/9456723/typemap-and-exception-for-error-codes-from-c-functions-for-swig-python