Python C API:创建一些PyObjects后的WindowsError

时间:2017-11-15 14:40:48

标签: python python-2.7 ctypes python-c-api

我一直遇到一个问题,就是让Python C API没有给我错误。

后台:我一直在使用ctypes来运行本机代码(C ++),但直到现在我还没有真正完成Python C API的任何特定操作。我大多只是从Python传递结构并从C ++中填充它们。我使用结构的方式变得很麻烦,所以我决定尝试直接用C ++创建Python对象,然后将它们传递回我的Python脚本。

代码: 我有一个只有一个函数的DLL(Foo.dll):

#define N 223
__declspec(dllexport) void Bar(void)
{
    std::cout << "Bar" << std::endl;

    for (int i = 0; i < N; ++i)
    {
        auto list = PyList_New(0);
        std::cout << "Created: " << i << std::endl;
        //Py_DECREF(list);
    }
}

然后我运行了我正在运行的Python脚本:

import ctypes as C
dll = r"C:\path\to\dll\Foo.dll"
Foo = C.CDLL(dll)
# lists = [[] for _ in range(0)]
Foo.Bar()
print "Done."

会发生什么:如果我将上述DLL中的N定义为222或更低,代码工作正常(内存泄漏除外,但不是'这个问题)。

如果我取消注释//Py_DECREF(list)行,则代码可以正常工作。

但是,通过上面的代码,我得到了这个:

Bar
Created: 0
Created: 1
Created: 2
...(omitted for your sake)
Created: 219
Created: 220
Created: 221
Traceback (most recent call last):
  File "C:\path_to_script\script.py", line 9, in <module>
    Foo.Bar()
WindowsError: exception: access violation reading 0x00000028

事实上,我用字典,列表,元组等得到了相同的结果。如果我创建一个列表然后将空的子列表附加到该列表,我会得到相同的结果。

更奇怪的是,我在实际Python脚本中创建的每个列表都会减少DLL在获取此窗口错误之前可以生成的列表数。

Weirder仍然,如果我在我的python脚本中创建了超过222个列表,那么在创建720 more 列表之前,DLL不会遇到此错误。

**其他细节:**

  • Windows 10
  • 使用Anaconda2 32位Python 2.7发行版
  • (使用该分发中的Python.hpython27.lib
  • python.exe --version:2.7.13 :: Anaconda custom (32-bit)
  • 使用Visual Studio 2017创建DLL

只要我不从我的C ++代码创建许多PyObject,一切似乎都可以正常工作。我可以将PyObject传递给Python代码,并且它可以正常工作......直到我从C ++代码中创建了“太多”对象。

发生了什么事?

1 个答案:

答案 0 :(得分:4)

From the documentation for CDLL

  

Python全局解释器锁在调用这些库导出的任何函数之前发布,之后重新获取。

这使得不安全使用Python C API代码。正如你所发现的那样,它究竟是如何失败的是不可预测的。我猜它是否与分配触发垃圾收集器的运行有关,但我不认为值得花费太多时间来解决确切的原因。

有(至少)两种解决方案可供选择:

  1. 使用ctypes.PyDLL(文档说明与CDLL类似,但不会释放GIL)
  2. 在C ++代码中重新获取GIL - 一种简单的方法是:

    auto state = PyGILState_Ensure();
    // C++ code requiring GIL - probably your entire loop
    PyGILState_Release(state);