I want to wrap a C++ routine which returns a std::map of integers and pointers to C++ class instances. I am having trouble getting this to work with SWIG and would appreciate any help that can be offered. I've tried to boil this issue down to its essence through a simple example.
The header test.h is defined as follows:
/* File test.h */
#include <stdlib.h>
#include <stdio.h>
#include <map>
class Test {
private:
static int n;
int id;
public:
Test();
void printId();
};
std::map<int, Test*> get_tests(int num_tests);
The implementation is defined in test.cpp below:
/* File test.cpp */
#include "test.h"
std::map<int, Test*> get_tests(int num_tests) {
std::map<int, Test*> tests;
for (int i=0; i < num_tests; i++)
tests[i] = new Test();
return tests;
}
int Test::n = 0;
Test::Test() {
id = n;
n++;
}
void Test::printId() {
printf("Test ID = %d", id);
}
I have written a SWIG interface file test.i to try to accommodate this routine so that I can return a std::map<int, Test*> as a dictionary in Python:
%module test
%{
#define SWIG_FILE_WITH_INIT
#include "test.h"
%}
%include <std_map.i>
%typemap(out) std::map<int, Test*> {
$result = PyDict_New();
int size = $1.size();
std::map<int, Test*>::iterator iter;
Test* test;
int count;
for (iter = $1.begin(); iter != $1.end(); ++iter) {
count = iter->first;
test = iter->second;
PyDict_SetItem($result, PyInt_FromLong(count),
SWIG_NewPointerObj(SWIG_as_voidptr(test), SWIGTYPE_p_Test, SWIG_POINTER_NEW | 0));
}
}
%include "test.h"
I wrap the routines and compile the SWIG-generated wrapper code, and link it as a shared library as follows:
> swig -python -c++ -o test_wrap.cpp test.i
> gcc -c test.cpp -o test.o -fpic -std=c++0x
> gcc -I/usr/include/python2.7 -c test_wrap.cpp -o test_wrap.o -fpic -std=c++0x
> g++ test_wrap.o test.o -o _test.so -shared -Wl,-soname,_test.so
I then want to be able to do the following from within Python:
import test
tests = test.get_tests(3)
print tests
for test in tests.values():
test.printId()
If I run this as a script example.py, however, I get the following output:
> python example.py
{0: <Swig Object of type 'Test *' at 0x7f056a7327e0>, 1: <Swig Object of type 'Test *' at
0x7f056a732750>, 2: <Swig Object of type 'Test *' at 0x7f056a7329f0>}
Traceback (most recent call last):
File "example.py", line 8, in <module>
test.printId()
AttributeError: 'SwigPyObject' object has no attribute 'printId'
Any ideas why I get SwigPyObject instances as output, rather than the SWIG proxies for Test? Any help would be greatly appreciated!
As it stands the problem you're seeing is caused by the default behaviours in the SWIG provided std_map.i. It supplies typemaps that try to wrap all std::map usage sensibly.
One of those is interfering with your own out typemap, so if we change your interface file to be:
%module test
%{
#define SWIG_FILE_WITH_INIT
#include "test.h"
%}
%include <std_map.i>
%clear std::map<int, Test*>;
%typemap(out) std::map<int, Test*> {
$result = PyDict_New();
int size = $1.size();
std::map<int, Test*>::iterator iter;
Test* test;
int count;
for (iter = $1.begin(); iter != $1.end(); ++iter) {
count = iter->first;
test = iter->second;
PyObject *value = SWIG_NewPointerObj(SWIG_as_voidptr(test), SWIGTYPE_p_Test, 0);
PyDict_SetItem($result, PyInt_FromLong(count), value);
}
}
%include "test.h"
then your example works. The %clear suppresses the default typemaps from std_map.i, but leaves the definition itself. I'm not too clear on exactly what causes the problem beyond that without some more digging, but you could just use %template and the default behaviours instead probably unless there's a good reason not to.
As an aside, your call:
SWIG_NewPointerObj(SWIG_as_voidptr(test), SWIGTYPE_p_Test, SWIG_POINTER_NEW | 0));
Probably doesn't do what you wanted - it transfers ownership of the pointer to Python, meaning once the Python proxy is finished with it will call delete for you and leave a dangling pointer in your map.
You can also use $descriptor to avoid having to figure out SWIG's internal name mangling scheme, so it becomes:
// No ownership, lookup descriptor:
SWIG_NewPointerObj(SWIG_as_voidptr(test), $descriptor(Test*), 0);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With