来自 C++ 的 Python 回调调用在 native 站点上失败

标签 python c++ callback shared-libraries ctypes

我有一个 C++ 共享库 (DLL),其中包含一些我从 Python 使用的 C 风格 API。有一个函数将 C 回调作为参数。据我了解,执行此操作需要 Python 扩展模块(一个单独的 DLL)。该模块应将 native “代理”回调传递给 API,然后从该回调调用 Python 对象。我尝试按如下方式使用它(它失败了):

from ctypes import CDLL
from test_extension import set_python_callback

def callback():
    pass

if __name__ == '__main__':
    library = CDLL('test_dll.shared-library')
    set_python_callback(library, callback)
    library.trigger_callback()

我正在使用带有 Python 2.7.6(32 位)和 MinGW 32 位编译器 (mingw32) 的 Windows 8 x64 环境。 Python 扩展由 distutils 构建。必要时可以更改 API 库,但是,特定于 Python 的代码必须保存在单独的库中。

这是我的代码,经过简化。删除了所有错误检查,但是,执行时它没有显示来自 Python API 或 Windows API 的错误。

API 库:

/* typedef void (*callback_t)(); */
static callback_t s_callback = nullptr;

void DLL_PUBLIC set_callback(callback_t callback) {
    s_callback = callback;
}

void DLL_PUBLIC trigger_callback() {
    s_callback(); // <--------------------------(1)
}

Python 扩展模块函数 set_python_callback(library, callback) 采用 ctypes.CDLL 对象和 Python 可调用对象。然后它从前者中提取 native DLL 句柄:

PyObject* handle_object = PyObject_GetAttrString(library, "_handle");
const HMODULE handle = static_cast<const HMODULE>(
    PyLong_AsVoidPtr(handle_object));
const native_set_callback_t native_api_routine =
    reinterpret_cast<const native_set_callback_t>(
        GetProcAddress(handle, "set_callback"));
native_api_routine(common_callback);

C函数common_callback()实现如下:

extern "C" void DLL_PUBLIC common_callback() {
    // <--------------------------------------- (2)
    PyObject *arguments = Py_BuildValue("()");
    PyObject *callback_result = PyEval_CallObject(s_callback, arguments);
    Py_DECREF(arguments);
    Py_XDECREF(callback_result);
}

错误信息是这样的:

Invoking callback from DLL... Traceback (most recent call last):
  File "script.py", line 14, in <module>
    library.trigger_callback()
WindowsError: exception: access violation reading 0x00000028

使用调试打印,我将错误追溯到 (1)(2) 点的任何代码都不会执行。奇怪的是,改变 common_callback() 调用 Python 代码的方式(例如传递 Py_None 而不是空元组)会改变错误消息中的地址。

也许这与调用约定有某种关系,但我不知道它到底错在哪里或如何修复它。

最佳答案

通过为 GIL 注册线程解决了问题 in documentation .

请注意,原始 API DLL、Python 扩展 DLL 和脚本都是在单线程模式下执行的,没有创建额外的线程,无论是否由 Python 管理。但是,肯定存在同步问题,因为使用 Py_AddPendingCall()也工作了。 (不鼓励使用 is;我发现它分析了 signals 模块源,它导致了上面的解决方案。)

我的这个陈述是不正确的:

Using debug print, I traced the error down to (1). Any code at (2) point doesn't execute.

我被以下事实误导了:一些 Python API 函数仍然有效(例如 Py_BuildValuePy_Initialize),但大多数无效(例如 PySys_WriteStdoutPyObject_Call 等)。

关于来自 C++ 的 Python 回调调用在 native 站点上失败,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22282385/

相关文章:

python - 在 flask 中嵌入 Bokeh 图和数据表

C++:如何以有效的方式截断 double ?

c++ - C++中的回调函数

c - pthread 回调中断用户输入

python - 用 pandas 填充最后已知的数据

python - 替换 Pandas 多列中的一系列整数值

python - 如何在 Tkinter 文本小部件绑定(bind)后在 Tkinter 文本小部件中绑定(bind)自身事件?

c++ - 更改命名空间中的值

c++ - 读写迭代器 - C++

c++ - std::bind 和 unique_ptr - 如何只移动?