如何在Python中处理swigdirector方法中的空指针

2024-09-24 02:21:42 发布

您现在位置:Python中文网/ 问答频道 /正文

我使用SWIG将C++库包装为Python库。 C++库公开了抽象类,以便用户从中继承,因此我们使用SWIG中的处理来处理。它基本上可以工作(有一些调整)。在

一个问题是,C++类有以下两种方法:

class Base {
  void* getObject();
  void  doSomething(void* o);
}

用户需要实现这些方法,然后用户在getObject()中返回的对象将传递给doSomething()方法。在

问题是,在使用SWIG时,Python中的doSomething()方法会收到一个包装类型为“void*”的SwigPyObject,因此我们不能像我们希望的那样使用原始的Python对象方法。 由于是Python(或者是Python),所以不能选择强制转换。在

有人有什么见解吗?在

我到处都发现了一些相关的问题,但似乎没有一个能完全解决我的问题,而且我已经尝试了很多方法来解决它,但是没有成功。在

如果你需要更多的细节请告诉我,我会提供的。在

非常感谢!在


Tags: 对象方法用户类型base抽象类细节class
1条回答
网友
1楼 · 发布于 2024-09-24 02:21:42

首先,我们想让你的代码变得真实和可运行。我自己写的测试.hh基于您展示的小代码,我们可以稍微练习一下这个设计:

class Base {
public:
  void runMe() {
    std::cerr << "Getting object\n";
    void *result = getObject();
    std::cerr << "Got: " << result << "\n";
    doSomething(result);
    std::cerr << "Did a thing\n";
  }

  virtual ~Base() {}
protected:
  virtual void* getObject() = 0;
  virtual void  doSomething(void* o) = 0;
};

我们最初可以这样概括:

^{pr2}$

并生成一个测试用例来展示我们希望它在Python中如何工作:

import test

class Foobar(test.Base):
    def getObject(self):
        return [1,2,3]

    def doSomething(self, thing):
        print(thing)

f=Foobar()
f.runMe()

但这在现阶段还行不通,因为我们还没有告诉SWIG如何在Python中有意义地处理void*。在

这里的基本思想是,我们希望让void*在接口内用作PyObject*。我们可以用directorin和directorout类型映射配对来实现。大体上,我们需要解决两个问题:

  1. 如何使参考计数正常工作而不泄漏?在
  2. 如果我们得到的void*不是一个PyObject*,会发生什么?在

如果我们一开始假设getObject()调用和doSomething()调用之间存在1:1的映射,那么引用计数就相当简单,我们可以在接口中编写两个类型映射,保留对PyObject的引用,然后在需要时将其从void*转换回来(注意,我们在这里也完全避开了问题2添加1:1限制)。在

因此,通过这两个类型映射,我们的界面变成:

%module(directors="1") test

%{
#include "test.hh"
%}

%feature("director") Base;

%typemap(directorout) void *getObject %{
  Py_INCREF($1);
  $result = $1;
%}

%typemap(directorin) void *o %{
  $input = static_cast<PyObject*>($1);
  // Director call will decref when we're done here - it assumes ownership semantics, not borrowed
%}

%include "test.hh"

当我们这样测试时:

swig -Wall -python -py3 -c++ test.i
g++ -Wall -Wextra  -shared -o _test.so -I/usr/include/python3.5 test_wrap.cxx -std=c++11 -fPIC
python3 run.py 
Getting object
Got: 0x7fce97b91c48
[1, 2, 3]
Did a thing

但是,如果我们将这里的语义改为不完全是1:1,那么我们就有了一个问题,例如将runMe设为:

void runMe() {
  std::cerr << "Getting object\n";
  void *result = getObject();
  std::cerr << "Got: " << result << "\n";
  doSomething(result);
  std::cerr << "Second time\n";
  doSomething(result);
  std::cerr << "Did a thing\n";
}

因为第一次调用doSomething完成后,引用变小,所以现在该段发生错误。在

在这个阶段,显然要做的事情是在directorin类型映射中添加对Py_INCREF的调用,但是这并不是全部内容—我们现在将永远不调用释放getObject()的结果,它在runMe()的末尾超出了范围。在

我倾向于通过在Base接口中添加另一个调用来解决这个问题:

virtual void cleanupThing(void* o) {} // Default nothing, not mandatory

有了它,我们就可以让你的SWIG接口完全在Python控制器内部实现(如果需要,还可以隐藏)。方法是使用一些%rename%ignore以及一些宏观的骗局:

因此,通过对SWIG接口的以下修改,我们现在可以正确地处理runMe的第二个化身:

%module(directors="1") test

%{
#include "test.hh"
%}

%feature("director") PyBase;

%typemap(directorout) void *getObject %{
  Py_INCREF($1);
  $result = $1;
%}

%typemap(directorin) void *o %{
  $input = static_cast<PyObject*>($1);
  Py_INCREF($input); // Not borrowed now
  // Director call will decref when we're done here
%}

// Python won't even know cleanupThing existed because we use it internally in the Python binding    
%ignore PyBase::cleanupThing;
%feature("nodirector") PyBase::cleanupThing;
// This is a sleight of hand trick with SWIG so we can add another type into the hierarchy without anyone really noticing
%rename(Base) PyBase;

%{
  class PyBase : public Base {
    void cleanupThing(void *o) {
      Py_DECREF(o);
    }
  };
%}

#define Base PyBase

%include "test.hh"

runMe引发对cleanupThing的调用:

void runMe() {
  std::cerr << "Getting object\n";
  void *result = getObject();
  std::cerr << "Got: " << result << "\n";
  doSomething(result);
  std::cerr << "Second time\n";
  doSomething(result);
  std::cerr << "Did a thing\n";
  cleanupThing(result);
}

当run now提供:

Getting object
Got: 0x7ff65dccfd08
[1, 2, 3]
Second time
[1, 2, 3]
Did a thing

(如果语义比简单地来回传递给同一个实例的局部变量复杂,则存在其他可能的解决方案)。在

相关问题 更多 >