I have a C++ class I'm exporting via swig, and a function that takes an array of Foo
s:
typedef class Foo {
int i;
} Foo;
void func(Foo *all_foos);
Now I'd like to be able to pass a python list containing those into all_foos:
afoo = mymod.Foo()
bfoo = mymod.Foo()
mymod.func([afoo, bfoo])
I have a typemap which doesn't work. See the FIXME line for where I need help.
%typemap(in) Foo ** {
/* Check if it's a list */
if (PyList_Check($input)) {
int size = PyList_Size($input);
int i = 0;
$1 = (Foo **) malloc((size+1)*sizeof(Foo *));
for (i = 0; i < size; i++) {
PyObject *o = PyList_GetItem($input,i);
// here o->ob_type->tp_name is "Foo"; could check that
// FIXME: HOW DO I GO FROM o -> SwigPyObject -> Foo *? THIS IS WRONG
$1[i] = (Foo *)(reinterpret_cast<SwigPyObject *>(o))->ptr;
}
} else {
PyErr_SetString(PyExc_TypeError,"not a list");
return NULL;
}
}
Basically, I have a PyObject
o; I need to get the SwigPyObject
from it (do I just cast it? Or is it a member?) and then get my Foo
pointer from the SwigPyObject
somehow.
Your example is a little confused because your C++ function takes
Foo*
, but your typemap takesFoo**
(i.e. array of Foos vs array of pointer to Foos). I'm assuming you meant the latter, because that's the only sane way to tell how long the array is from the function declaration you've given.In terms of the immediate question "how can I convert Python objects to C++ pointers of a given type?" I usually solve that question by letting SWIG generate some code for me and then inspecting it. So for example if you have a function
void bar(Foo*);
then SWIG will generate some code in the wrapper:The interesting bit of that is the call to
SWIG_ConvertPtr
which is doing what you want. With that knowledge we just need to put it inside the loop that you've already written for your typemap, so your 'in' typemap becomes:Notice that in the interests of making the typemap generic and reusable I've replaced parts of it with special typemap variables - the same code gets generated as for the single example we saw, but you can reuse it slightly more.
That's sufficient to compile and run the example code you gave (with the one change as noted), however there's a memory leak still. You call
malloc()
, but neverfree()
, so we need to add a corresponding 'freearg' typemap:This gets called both on success and error, but that's fine because
$1
is initalised to NULL, so the behavior is correct regardless of if we successfullymalloc
or not.As a general point since this is C++ I think your interface is designed wrong - there's no good reason not to use
std::vector
,std::list
or similar which also makes the wrapping simpler. It's also weird style to use typedefs like you have in your header file.If it were me writing this though, I'd be looking to use RAII in the wrapper, even if I couldn't change the interface to use a container. So that means I'd write my 'in' typemap as:
which then removes the need for a 'freearg' typemap and never leaks.
If for some reason you don't want to write a custom typemap, or change the interface to use a type that already has good typemaps in the SWIG library you can still give your Python users an intuitive pythonic interface by using
%rename
to 'hide' the default implementation and%pythoncode
to inject some additional Python with the same name that "massages" the Python input into the carrays interface transparently to the Python user.I would try with carrays.i, something like
Then in python:
Typemaps should only be used if there is no other SWIG tool to achieve the API you want. I think it might even be possible to do better than the above, with %inline (separate answer because very different from this).
You should be able to do everything with standard "front end" SWIG tools:
There shouldn't be need for typemap here.