SWIG 的 Python 包装器中临时对象的生命周期(?)

发布于 2024-10-16 23:44:43 字数 1333 浏览 3 评论 0 原文

2 月 12 日编辑

我最近刚刚使用一些 SWIG 生成的 Python 包装器来处理一些 C++ 类,结果出现了奇怪的崩溃。看来 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

似乎在第二个实例中,临时 Bar 对象被销毁在我们读取 theFoovalue 字段之前。在 gdb 中追踪事情,这显然是正在发生的事情。因此,当我们从 Bar().theFoo 读取 .value 时,C++ 已经销毁了(并被其他一些堆分配覆盖).theFoo。代码>.在我的实际情况中,这导致了段错误。

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

Edited 12 Feb

I've just recently come up with an odd crash using some SWIG-generated Python wrappers for some C++ classes. It seems that the combination of SWIG and Python together are somewhat eager to clean up temporary values. So eager, in fact, that they're cleaned up while they're still being used. A significantly condensed version looks like this:

/* 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 run SWIG (1.3.37) on the .i file, and then in Python, have:

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

It seems that in the second instance, the temporary Bar object is destroyed before we ever get to read theFoo's value field. Chasing things around in gdb, this is clearly what's happening. And so by the time we read .value from Bar().theFoo, C++ has already destroyed (and overwritten with some other heap allocation) .theFoo. In my actual situation, this is causing a segfault.

Is there any SWIG directive or trick that I can add to my Example.i file to make Bar().theFoo.value return 1 here?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

忆悲凉 2024-10-23 23:44:43

第二次更新

我们知道基本问题是 python 立即销毁 Bar。当 Bar 在 python 中实现时,python 的 gc 知道仍然存在对 theFoo 的引用,因此不会销毁它。但是当 Bar 在 C++ 中实现时,Python 会调用 C++ 析构函数,它会自动销毁 theFoo 以及 Bar。

所以显而易见的解决方案是防止python 过早地销毁 Bar 。这是一个稍微有点黑客的解决方案,涉及子类化 Bar

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

这会保存对最后创建的 Bar 的引用,以便它不会立即被销毁。当创建新的 Bar 时,旧的将被删除。 (我的旧版本很愚蠢;不需要为此重写 __del__ 。)这是输出(在 Foo< 中使用 cout << "deleting Foo " /code> 的析构函数):

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

我仍然不喜欢这个。将“持久”行为隔离在装饰器中可能会更好;我也尝试过并且有效(如果您想查看代码,请告诉我)。以某种方式告诉 python 处理销毁 theFoo 本身肯定会更好,但我不知道如何做到这一点。

第一次更新

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

# 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 何时被调用。

Second Update:

Well we know that the basic problem is that python destroys Bar immediately. When Bar is implemented in python, python's gc knows that there's still a reference to theFoo, and so does not destroy it. But when Bar is implemented in c++, python calls the c++ destructor, which automatically destroys theFoo along with Bar.

So the obvious solution is to prevent python from destroying Bar prematurely. Here's a slightly hackish solution involving subclassing Bar:

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

This saves a reference to the last Bar created so that it isn't destroyed right away. When a new Bar is created, the old one is deleted. (My old version was silly; no need to override __del__ for this.) Here's the output (with cout << "deleting Foo " in Foo's destructor):

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

I still don't love this. It might be better to sequester the "persistent" behavior in a decorator; I tried that too and it worked (if you want to see the code let me know). It would definitely be better to somehow tell python to handle destroying theFoo itself, but I can't figure out how to do that.

First Update:

The wrap code told me nothing, so I looked in swigexample.py. That also yielded nothing. Things became clearer when I tried duplicating Bar in pure 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

Now we import Bar from pyfoobar:

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

This behavior is coming from Python!

Original Answer:

It seems like there's definitely some garbage collection combat at work here... Here's some related information on SWIG Memory Management. Based on this, it looks like the %newobject directive might be what you're looking for; but I tried several variations and couldn't get it to give python control over theFoo:

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

I'm beginning to suspect that this is intentional; seems like this line from the above link is relevant here:

C is now holding a reference to the
object---you probably don't want
Python to destroy it.

But I'm not certain. I'm going to look at the swigexample_wrap code to see if I can figure out when ~Bar is being called.

红墙和绿瓦 2024-10-23 23:44:43

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

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

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

The solution is to add %naturalvar to your .i file like this:

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

This causes SWIG to return a copy of Foo instead of a reference to it, which solves the problem of aggressive temporary object cleanup that Python does.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文