使用 SWIG 和 Python/C API 来包装返回 std::map 的函数 [英] Using SWIG and the Python/C API to wrap a function which returns a std::map
问题描述
我想包装一个 C++ 例程,它返回一个 std::map
整数和指向 C++ 类实例的指针.我无法将其与 SWIG 一起使用,希望能提供任何帮助.我试图通过一个简单的例子将这个问题归结为它的本质.
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.
头文件test.h
定义如下:
/* 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);
在下面的test.cpp
中定义了实现:
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);
}
我编写了一个 SWIG 接口文件 test.i
来尝试适应这个例程,以便我可以返回一个 std::map
作为 Python 中的字典:
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"
我包装例程并编译 SWIG 生成的包装器代码,并将其链接为共享库,如下所示:
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
然后我希望能够在 Python 中执行以下操作:
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()
如果我将其作为脚本 example.py
运行,则会得到以下输出:
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'
任何想法为什么我将 SwigPyObject
实例作为输出,而不是 Test
的 SWIG 代理?任何帮助将不胜感激!
Any ideas why I get SwigPyObject
instances as output, rather than the SWIG proxies for Test
? Any help would be greatly appreciated!
推荐答案
就目前而言,您所看到的问题是由 SWIG 提供的 std_map.i 中的默认行为引起的.它提供的类型映射试图合理地包装所有 std::map
用法.
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"
那么你的例子有效.%clear
抑制来自 std_map.i 的默认类型映射,但保留定义本身.我不太清楚究竟是什么导致了除此之外的问题,而无需进行更多挖掘,但您可以只使用 %template
和默认行为,除非有充分的理由不这样做.
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));
可能没有做你想做的 - 它将指针的所有权转移给 Python,这意味着一旦 Python 代理完成它会为你调用 delete
并在你的地图中留下一个悬空指针.
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.
您还可以使用 $descriptor
来避免必须弄清楚 SWIG 的内部名称修改方案,因此它变成:
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);
这篇关于使用 SWIG 和 Python/C API 来包装返回 std::map 的函数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!