在C++中删除Python分配的对象是否可行?

10

我的程序在C++中管理对Python对象的引用。也就是说,我所有的类都派生自Referenced类,该类包含指向相应Python对象的指针。

class Referenced
{
public:
    unsigned use_count() const
    { 
        return selfptr->ob_refcnt;
    }

    void add_ref() const
    {
        Py_INCREF(selfptr);
    }

    void remove_ref() const
    {
        Py_DECREF(selfptr);
    }

    PyObject* selfptr;
};

我使用intrusive_ptr来持有从Referenced派生的对象。这使得我可以轻松地在C++中保留对所需Python对象的引用,并在必要时访问它们。但是,当从C++中删除Python对象时(即当我调用Py_DECREF(selfptr)时),我的程序会崩溃(仅在Windows上发生),即使selfptr->ob_refcnt == 1。

这种方法可行吗?

更新:我最终找到了程序中的问题。它与对象删除无直接关系。为了检查最初的问题,我实现了一个简单的扩展模块,记住对Python对象的引用,并在需要时释放它。这是它:

#include <Python.h>

static PyObject* myObj;

static PyObject* acquirePythonObject(PyObject* self, PyObject* obj)
{
    printf("trying to acquire python object %p, refcount = %d\n", obj, obj->ob_refcnt);
    myObj = obj;
    Py_INCREF(myObj);
    printf("reference acquired\n");
    return Py_True;
}

static PyObject* freePythonObject(PyObject*, PyObject*)
{
    printf("trying to free python object %p, refcount = %d\n", myObj, myObj->ob_refcnt);
    Py_DECREF(myObj);
    printf("reference removed\n");
    return Py_True;
}

static PyMethodDef moduleMethods[] =
{
    {"acquirePythonObject", acquirePythonObject, METH_O, "hold reference to python object."},
    {"freePythonObject", freePythonObject, METH_NOARGS, "free reference to python object."},
    {NULL, NULL, 0, NULL}
};

PyMODINIT_FUNC initmodule(void)
{
    Py_InitModule("module", moduleMethods);
}

和Python脚本:

import module

class Foo:
    def __init__(self):
        print "Foo is created"

    def __deinit__(self):
        print "Foo is destroyed"

def acquireFoo():
    foo = Foo()
    module.acquirePythonObject(foo)

def freeFoo():
    module.freePythonObject()

if __name__ == "__main__":
    acquireFoo()
    freeFoo()

样例在Windows和Linux系统下无缝运行。以下是输出结果。

Foo is created
trying to acquire python object 0x7fa19fbefd40, refcount = 2
reference acquired
trying to free python object 0x7fa19fbefd40, refcount = 1
Foo is destoryed
reference removed

1
请注意,在Windows下,有些情况下在一个DLL中分配内存,在另一个DLL中释放内存会导致问题。我不知道具体是哪些情况,但值得检查一下,这似乎可能是你的情况。请注意,这是在较差的Windows实现中存在的问题,而不是C++/Python的普遍问题。 - PlasmaHH
@PlasmaHH 我有同感,但如果我理解正确的话,Python本身通过C API函数(http://docs.python.org/2/c-api/memory.html)管理堆,因此分配/释放调用应该在同一个dll中。 - DikobrAz
@PlasmaHH仍然不理解。如果我调用Py_DECREF,我会进入python_27.dll,所以如果它在某个地方调用free,它应该访问与python27.dll链接的c运行时函数。这正是我需要的。我没错吧? - DikobrAz
1
关于上述问题:在Python中,分配和释放通过对象的分配器和解分配器进行,这些分配器和解分配器由PyTypeObject结构中的指针定义。如果它们都指向同一个DLL,那么就不应该有问题。(如果链接正确,也不应该有问题。) - James Kanze
@PlasmaHH 如果你链接正确,那么在动态链接的 C 运行时库中只有一个 free 函数。但当然,Python 不会直接调用 free 函数;它会调用为该类型指定的释放函数。这将始终指向同一函数,在同一 DLL 中。 - James Kanze
显示剩余5条评论
1个回答

1
这种方法可以吗?
基本上可以,但是...
- 我没有看到任何保证`add_ref`/`remove_ref`调用了正确次数的保证(使用RAII将自动执行此操作 - 也许这就是您的intrusive_ptr所做的?) - 如果您尝试过多次`remove_ref`,我不确定Python会提供什么保证。如果您知道引用计数从1->0,您可以设置`selfptr = NULL`,这样就可以捕获该问题: - 要么崩溃,要么明确检查,要么使用`Py_XDECREF` - 更好的方法是改用`Py_CLEAR`
最后......您有任何崩溃转储或诊断信息吗?

  1. 是的,intrusive_ptr 确保了 RAII,我使用类似于 boost::intrusive_ptr 的一个(http://www.boost.org/doc/libs/1_52_0/libs/smart_ptr/intrusive_ptr.html)。
  2. 我在 Referenced 类中有安全检查,只是为了提高可读性而将它们删除了。
- DikobrAz
我终于找到问题所在了,崩溃是由其他第三方模块引起的,它是由对象销毁触发的。虽然我花了很多时间去解决它,甚至写了一个小演示,在cpp中释放python对象。我回家后会把它放在这里,并在Linux上进行验证。 - DikobrAz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接