蟒蛇3+;在这个简单的例子中,ctypes回调导致内存泄漏 在使用Python 3代码和C++代码结合CyType的复杂程序上工作时,发现了一个内存泄漏,可以很容易地用下面的示例来复制。 < >我的C++代码使用回调函数创建Python对象。接下来,它调用Python对象上的另一个回调,该回调只返回其参数。第二次回调导致对象的引用计数增加。因此,对象永远不会被垃圾收集

蟒蛇3+;在这个简单的例子中,ctypes回调导致内存泄漏 在使用Python 3代码和C++代码结合CyType的复杂程序上工作时,发现了一个内存泄漏,可以很容易地用下面的示例来复制。 < >我的C++代码使用回调函数创建Python对象。接下来,它调用Python对象上的另一个回调,该回调只返回其参数。第二次回调导致对象的引用计数增加。因此,对象永远不会被垃圾收集,python,c++,memory-leaks,garbage-collection,ctypes,Python,C++,Memory Leaks,Garbage Collection,Ctypes,这是Python代码(文件bug.py): 这是C++代码(文件bug.CPP): 输出为: ref cnt = 1 ref cnt = 2 ref cnt = 1 ref cnt = 1 garbage collect foo 换句话说,对noop函数的调用错误地增加了引用计数,并且Foo对象没有被垃圾收集。如果不调用noop函数,Foo对象将被垃圾收集。预期产出为: ref cnt = 1 ref cnt = 2 ref cnt = 1 ref cnt = 1 garbage coll

这是Python代码(文件bug.py):

这是C++代码(文件bug.CPP):

输出为:

ref cnt = 1
ref cnt = 2
ref cnt = 1
ref cnt = 1
garbage collect foo
换句话说,对noop函数的调用错误地增加了引用计数,并且Foo对象没有被垃圾收集。如果不调用noop函数,Foo对象将被垃圾收集。预期产出为:

ref cnt = 1
ref cnt = 2
ref cnt = 1
ref cnt = 1
garbage collect foo

这是一个已知的问题吗?有人知道变通方法或解决方案吗?这是由ctypes中的错误引起的吗?

您正在传递Python对象。您的一个对象被传递到您的C代码中,而不是传递出去,因此您要负责引用计数。下面是一些有效的方法,但我已将
void*
更改为
PyObject*
,因为它们就是这样的:

#include <Python.h>
#include <iostream>
#include <assert.h>

extern "C" {

  typedef PyObject* (*CreateObjectCallback)();
  typedef PyObject* (*NoopCallback)(PyObject* arg);

  __declspec(dllexport) PyObject* test(CreateObjectCallback create, NoopCallback noop)
  {
    // Create the object, with one reference.
    PyObject* object = create();
    std::cerr << "ref cnt = " << object->ob_refcnt << std::endl;

    // Passing object back to Python increments its reference count
    // because the parameter of the function is a new reference.
    // That python function returns an object (the same one), but
    // now you own deleting the reference.
    PyObject* object2 = noop(object);
    Py_DECREF(object2);

    std::cerr << "ref cnt = " << object->ob_refcnt << std::endl;

    // Your return the created object, but now that Python knows
    // it is a Python object instead of void*, it will decref it.
    return object;
  }
}
输出:

ref cnt = 1
ref cnt = 1
garbage collect foo

这正是我一直在寻找的答案,甚至更多关于PyDLL和decorators的问题;非常感谢。是否有任何关于ctypes.py_对象如何处理引用计数的公共文档?例如,我并不清楚将Python对象作为参数传递给回调会永久增加引用计数;我希望从回调返回会再次减少引用计数。@ygramoel
lib.test()
创建了一个对象(ref1),并且
noop
被传递给了该对象(ref2…参数名引用了它)
noop
将ref返回给调用方。调用方负责在处理完对象后取消引用该对象
noop
是由
lib.test
从C调用的,因此
lib.test
必须减小它
lib.test
将它创建的对象返回给Python。Python代码没有将其分配给变量,因此Python减少了引用计数。如果Python代码将其分配给一个变量,它将保持引用计数,并且
Foo
不会被垃圾收集。@ygramoel。
import ctypes

CreateObjectCallback = ctypes.CFUNCTYPE( ctypes.py_object )
NoopCallback = ctypes.CFUNCTYPE( ctypes.py_object, ctypes.py_object )

lib = ctypes.PyDLL('test')

lib.test.restype = ctypes.py_object
lib.test.argtypes = [ CreateObjectCallback, NoopCallback ]

class Foo:
    def __del__(self):
        print("garbage collect foo");

@CreateObjectCallback
def create():
    return Foo()

@NoopCallback
def noop(object):
    return object

lib.test(create,noop)
ref cnt = 1
ref cnt = 1
garbage collect foo