8

2月12日编辑

我最近刚刚为一些 C++ 类使用一些 SWIG 生成的 Python 包装器出现了一个奇怪的崩溃。看来 SWIG 和 Python 结合在一起有点急于清理临时值。事实上,如此渴望,以至于它们在仍在使用时就被清理干净了。一个显着压缩的版本如下所示:

/* Example.hpp */
struct Foo {
    int value;
    ~Foo();
};

struct Bar {
    Foo theFoo;
    Bar();
};

/* Example.cpp */
#include "Example.hpp"
Bar::Bar()  {theFoo.value=1;}
Foo::~Foo() {value=0;}

/* Example.i */
%module Example
%{
#include "Example.hpp"
%}
%include "Example.hpp"

我在 .i 文件上运行 SWIG (1.3.37),然后在 Python 中,有:

Python 2.4.3 (#1, Sept 17 2008, 16:07:08)
[GCC 4.1.2 20071124 (Red Hat 4.1.2-41)] on linux2
Type "help", "copyright", "credits", or "license" for more information.
>>> from Example import Bar
>>> b=Bar()
>>> print b.theFoo.value      # expect '1', since Bar's constructor sets this
1
>>> print Bar().theFoo.value  # expect '1', since we're still using the Foo object
26403424

似乎在第二种情况下,临时对象在我们到达 read的字段Bar之前就被销毁了。在 gdb 中追逐东西,这显然是正在发生的事情。因此,当我们从 中读取时,C++ 已经销毁(并被其他一些堆分配覆盖)。在我的实际情况下,这会导致段错误。theFoovalue.valueBar().theFoo.theFoo

是否有任何 SWIG 指令或技巧可以添加到我的Example.i文件中以在此处Bar().theFoo.value返回1

4

2 回答 2

2

第二次更新

好吧,我们知道基本问题是 pythonBar立即破坏。当Bar在 python 中实现时,python 的 gc 知道还有对 的引用theFoo,因此不会破坏它。但是当用Barc++实现时,python调用了c++的析构函数,它会自动销毁theFooBar.

所以显而易见的解决方案是防止pythonBar过早地破坏。这是一个涉及子类化的稍微有点骇人听闻的解决方案Bar

class PersistentBar(swigexample.Bar):
    lastpbar = None
    def __init__(self):
        super(PersistentBar, self).__init__()
        PersistentBar.lastpbar = self

这保存了对最后Bar创建的引用,因此它不会立即被销毁。当一个新Bar的被创建时,旧的被删除。(我的旧版本很傻;无需为此重写__del__。)这是输出(带有cout << "deleting Foo "inFoo的析构函数):

>>> from test import persistentBar
>>> persistentBar().theFoo.value
1
>>> persistentBar().theFoo.value
deleting Foo 1
>>> persistentBar().theFoo.value
deleting Foo 1

我还是不喜欢这个。在装饰器中隔离“持久”行为可能会更好;我也试过了,它奏效了(如果你想看代码,请告诉我)。以某种方式告诉 python 处理自我毁灭肯定会更好theFoo,但我不知道该怎么做。

第一次更新

包装代码什么也没告诉我,所以我查看了 swigexample.py。那也一无所获。Bar当我尝试在纯 python 中复制时,事情变得更加清晰:

# pyfoobar.py
class Foo(object):
    def __init__(self):
        self.value = -1

class Bar(object):
    def __init__(self):
        self.theFoo = Foo()
        self.theFoo.value = 1
    def __del__(self):
        self.theFoo.value = 0

现在我们从 pyfoobar 导入 Bar:

>>> from pyfoobar import Bar
>>> b = Bar()
>>> b.theFoo.value
1
>>> Bar().theFoo.value
0

这种行为来自 Python!

原答案

似乎这里肯定有一些垃圾收集战斗在起作用……这里有一些关于SWIG 内存管理的相关信息。基于此,看起来 %newobject 指令可能是您正在寻找的;但我尝试了几种变体,但无法让 python 控制theFoo

>>> from swigexample import Bar
>>> b = Bar()
>>> b.theFoo.value
1
>>> b.theFoo.thisown
False
>>> Bar().theFoo.value
0
>>> Bar().theFoo.thisown
False

我开始怀疑这是故意的;似乎上述链接中的这一行与此处相关:

C 现在持有对该对象的引用——您可能不希望 Python 销毁它。

但我不确定。我将查看 swigexample_wrap 代码,看看我是否能弄清楚何时~Bar被调用。

于 2011-02-12T03:53:09.503 回答
1

解决方案是将 %naturalvar 添加到您的 .i 文件中,如下所示:

%naturalvar Bar::theFoo;
%include "Example.hpp"

这会导致 SWIG 返回 Foo 的副本而不是对其的引用,这解决了 Python 所做的激进临时对象清理的问题。

于 2018-08-31T00:42:53.877 回答