简体   繁体   中英

How do I use SWIG to return specific exceptions based on C function return type?

Here are examples of functions in my C API:

int do_something(struct object *with_this)
struct object *get_something(struct object *from_this)

do_something will return 0 for success or -1 and set errno for failure. get_something will return a valid pointer on success or NULL and set errno for failure.

I'm using to SWIG 2.0 to generate python bindings. For the do_something binding I'd like it to return an exception based on errno if it fails and return the Python None object if it succeeds. For the get_something binding I'd like it to return an exception based on errno if it fails and the opaque object if it succeeds.

The question is how do I get SWIG to do all of this magic for me?

Currently I'm using SWIG 2.0.

I can use %exception and define different exceptions per symbol, but I'd like it to be based on return type. I'll have a lot of the API functions and I don't want to list them out. So I can do this for each symbol:

%exception do_something {
    $action
    if (result < 0) {
        return PyErr_SetFromErrno(PyExc_RuntimeError);
    }
}

%exception get_something {
    $action
    if (result == NULL) {
        return PyErr_SetFromErrno(PyExc_RuntimeError);
    }
}

It would be a lot better if I could do something like this (like you can do with %typemap ):

%exception int {
    $action
    if (result < 0) {
        return PyErr_SetFromErrno(PyExc_RuntimeError);
    }
}

%exception struct object * {
    $action
    if (result == NULL) {
        return PyErr_SetFromErrno(PyExc_RuntimeError);
    }
}

Can I do something like this? If I had something like this, then that would take care of the exceptions. And I believe my get_something binding would be perfect.

My do_something binding would still need something like this to get it to return the Python None object if I use %typemap :

%typemap(out) int {
    $result = Py_BuildValue("");
}

Does the magic I'm looking for exist or am I going about this all wrong? Is there a better way to do this?

To help answer this question I took your functions and created the following test.h file that included enough real code to actually illustrate the problem:

struct object { int bar; };

static int do_something(struct object *with_this) { 
  errno = EACCES;
  return -1; 
}

static struct object *get_something(struct object *from_this) {
  errno = EAGAIN;
  return NULL;
}

I'm fairly sure that %typemap(out) is the place to write this code in, not %exception , because the behaviour you want is determined by the return type, not the name of the function called. The fact that you also care about the value of the return further backs this up. You can to some degree avoid repetition using $typemap to reference other typemaps within yours.

So I wrote a little SWIG interface to illustrate this:

%module test

%{
#include <errno.h>
#include "test.h"
%}

%typemap(out) int do_something %{
  if ($1 < 0) {
    PyErr_SetFromErrno(PyExc_RuntimeError);
    SWIG_fail;    
  }
  $result = Py_None; // Return only controls exception
  Py_INCREF($result);
%}

%typemap(out) struct object *get_something %{
  if (!$1) {
    PyErr_SetFromErrno(PyExc_RuntimeError);
    SWIG_fail; 
  }

  // Return passed through unless NULL
  $typemap(out,$1_ltype);
%}

%include "test.h"

Which worked when tested:

Python 2.7.6 (default, Jun 22 2015, 17:58:13) 
[GCC 4.8.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import test
>>> test.do_something(None)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
RuntimeError: (13, 'Permission denied')
>>> test.get_something(None)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
RuntimeError: (11, 'Resource temporarily unavailable')
>>> 

This only worked though because I explicitly named do_something when matching the typemap, so the fallback without the named function is fine. If you just try to remove that restriction you'll get an error about recursive typemaps. You can work around that using %apply , eg

%typemap(out) struct object * MY_NULL_OR_ERRNO %{
  if (!$1) {
    PyErr_SetFromErrno(PyExc_RuntimeError);
    SWIG_fail;
  }

  $typemap(out,$1_ltype);
%}

%apply struct object * MY_NULL_OR_ERRNO { struct object *get_something, struct object *some_other_function };

If that's too painful you can of course just write the typemap by hand:

%typemap(out) struct object * %{
  if (!$1) {
    PyErr_SetFromErrno(PyExc_RuntimeError);
    SWIG_fail;
  }

  $result = SWIG_NewPointerObj(SWIG_as_voidptr($1), $1_descriptor, $owner);
%}

Which seems the simplest option if you really want that everywhere a struct object* gets returned.

There are other possible solutions using %pythoncode or %pythonappend , but neither of them are really improvements over the above in my view.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM