%typemap and %exception for error codes from C functions for SWIG, Python

我怕爱的太早我们不能终老 提交于 2019-12-08 07:44:53

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!