Wrapping return by reference functions with SWIG
At MegaCorp I'm developing in C++, but the APIs are in C. From time to time, we use Python for scripting something using those APIs. We write the bindings in SWIG.
All the functions in the APIs return a status code; it can be OK or many different values for different type of errors. This means that if a function needs to return a value, it actually takes a pointer (not a reference)1 to such a value and at the end of the function, depending on its success, the value is returned at that address.
Then came the question of wrapping such functions with SWIG. According to
the scarce documentation about it
one should declare the type (+name if needed) as argout
and not much more...
except for the first part of the example. See, what numinputs=0
is doing is
to make SWIG ignore that type as argument2,
meaning that the wrapper function won't be expecting it. Even so, the body
does something, and a very important something. Let's see in my example:
// ignore any input parameter of type PageSetHandle_t * %typemap(in, numinputs=0) PageSetHandle_t *pPageSetHandle (PageSetHandle_t temp) { $1= &temp; } // on the other hand, this is how we convert a PageSetHandle_t* to a PyCObject // and add it to the list of returned values %typemap(argout) PageSetHandle_t * { $result= SWIG_Python_AppendOutput ($result, PyCObject_FromVoidPtr (*$1, NULL)); }
In my case the type is PageSetHandle_t
. The fact that behind is an actual
void *
is not important3, but that messed with my head while trying to
understand the problem. What the first part is saying is «look, ignore any
parameter of type PageSetHandle_t *
, but when you find one, create a
temporary variable of the dereferenced type (PageSetHandle_t
, in this case),
and assign a reference to it to the parameter you're going to pass to the C
function». This is extremely important; otherwise, you wouldn't have an address
where to store the reference returned by the function. Notice that that
temporary variable will be declared in the wrapper's stack and will die when
the wrapper finishes, but that's OK because of what we do in the actual
conversion code: we create a PyCObject
from the dereferencing of the parameter. Here's part of the generated code:
SWIGINTERN PyObject *_wrap_MFaST_PMCreatePageSet(PyObject *SWIGUNUSEDPARM(self), PyObject *args) { PageSetHandle_t *arg3 = (PageSetHandle_t *) 0 ; PageSetHandle_t temp3 ; { arg3= &temp3; } result = MFaST_PMCreatePageSet(arg1,arg2,arg3); resultobj = SWIG_NewPointerObj((new FqStatusCode_t(static_cast< const FqStatusCode_t& >(result))), SWIGTYPE_p__FqStatusCode_, SWIG_POINTER_OWN | 0 ); { resultobj= SWIG_Python_AppendOutput (resultobj, PyCObject_FromVoidPtr (*arg3, NULL)); } return resultobj; }
This means that now I can call pm.MFaST_PMCreatePageSet()
with only two
parameters instead of three, and that both the status and the returned value
will be returned in a tuple. Notice again the variables generated by SWIG and
how they interact in the body of the typemap(in)
and specially in the typemap(argout)
, where
you can't reference the temporary variable, which means you can only dereference
the argument (*$1
) instead of trying to use the tempvar (temp
).
-
That is, the parameter is declared as
int *foo
and notint &foo
. I think this is a mistake. ↩ -
I understand what it means now that I saw it in effect. At the beginning it was not clear, mostly because the
(int temp)
part and the fact that it has code associated. ↩ -
Except for the part that I will use
PyObject_FromVoidPtr()
andPyObject_AsVoidPtr()
to create Pyhton objects with such values. ↩