Using SWIG and the Python/C API to wrap a function which returns a std::map

爱⌒轻易说出口 提交于 2019-12-07 08:42:53

问题


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!


回答1:


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);


来源:https://stackoverflow.com/questions/26308019/using-swig-and-the-python-c-api-to-wrap-a-function-which-returns-a-stdmap

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