【问题标题】:Using SWIG and the Python/C API to wrap a function which returns a std::map使用 SWIG 和 Python/C API 包装返回 std::map 的函数
【发布时间】:2014-10-10 21:05:11
【问题描述】:

我想包装一个 C++ 例程,它返回一个 std::map 整数和指向 C++ 类实例的指针。我无法让它与 SWIG 一起使用,我希望能提供任何帮助。我试图通过一个简单的例子将这个问题归结为本质。

标题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 中定义:

/* 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 来尝试适应这个例程,以便我可以在 Python 中将 std::map&lt;int, Test*&gt; 作为字典返回:

%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 生成的包装器代码,并将其链接为共享库,如下所示:

> 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 中执行以下操作:

import test

tests = test.get_tests(3)
print tests

for test in tests.values():
  test.printId()

但是,如果我将其作为脚本 example.py 运行,我会得到以下输出:

> 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 代理?任何帮助将不胜感激!

【问题讨论】:

  • 附带一个问题 - 为什么地图中有 Test* 而不仅仅是 Test

标签: python c++ swig


【解决方案1】:

就目前而言,您看到的问题是由 SWIG 提供的 std_map.i 中的默认行为引起的。它提供了类型映射,试图合理地包装所有 std::map 用法。

其中一个干扰了您自己的输出类型映射,因此如果我们将您的接口文件更改为:

%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 并可能改为使用默认行为,除非有充分的理由不这样做。

顺便说一句,您的电话:

SWIG_NewPointerObj(SWIG_as_voidptr(test), SWIGTYPE_p_Test, SWIG_POINTER_NEW | 0));

可能不符合您的要求 - 它将指针的所有权转移给 Python,这意味着一旦 Python 代理完成了它会为您调用 delete 并在您的地图中留下一个悬空指针。

您还可以使用$descriptor 来避免必须弄清楚 SWIG 的内部名称修改方案,所以它变成:

// No ownership, lookup descriptor:
SWIG_NewPointerObj(SWIG_as_voidptr(test), $descriptor(Test*), 0);

【讨论】:

  • 很棒的答案 - 刚刚学到了很多东西。感谢发布!
猜你喜欢
  • 1970-01-01
  • 2011-09-04
  • 1970-01-01
  • 2020-09-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-11-15
  • 1970-01-01
相关资源
最近更新 更多