在C中嵌入Python:尝试在Python-cod调用的C回调中调用Python代码时出错

2024-10-04 13:17:06 发布

您现在位置:Python中文网/ 问答频道 /正文

我有一些调用Python函数的C代码。这个Python函数接受一个地址,并使用WINFUNCTYPE最终将其转换为Python可以调用的函数。作为参数发送给Python函数的C函数最终将调用另一个Python函数。正是在这最后一步导致了崩溃。简而言之,我是从C->;Python->;C->;Python开始的。最后一个C->;Python会导致崩溃。我一直在试图理解这个问题,但一直未能理解。在

有人能指出我的问题吗?在

用visualstudio2010编译并使用参数“C:\”运行的C代码\碰撞.py和“func1”:

#include <stdlib.h>
#include <stdio.h>

#include <Python.h>

PyObject* py_lib_mod_dict; //borrowed

void __stdcall cfunc1()
{
    PyObject* py_func;
    PyObject* py_ret;
    int size;
    PyGILState_STATE gil_state;

    gil_state = PyGILState_Ensure();
    printf("Hello from cfunc1!\n");

    size = PyDict_Size(py_lib_mod_dict);
    printf("The dictionary has %d items!\n", size);
    printf("Calling with GetItemString\n");
    py_func = PyDict_GetItemString(py_lib_mod_dict, "func2"); //fails here when cfunc1 is called via callback... will not even go to the next line!
    printf("Done with GetItemString\n");
    py_ret = PyObject_CallFunction(py_func, 0);

    if (py_ret)
    {
        printf("PyObject_CallFunction from cfunc1 was successful!\n");
        Py_DECREF(py_ret);
    }
    else
        printf("PyObject_CallFunction from cfunc1 failed!\n");

    printf("Goodbye from cfunc1!\n");
    PyGILState_Release(gil_state);
}

int wmain(int argc, wchar_t** argv)
{
    PyObject* py_imp_str;
    PyObject* py_imp_handle;
    PyObject* py_imp_dict; //borrowed
    PyObject* py_imp_load_source; //borrowed
    PyObject* py_dir; //stolen
    PyObject* py_lib_name; //stolen
    PyObject* py_args_tuple;
    PyObject* py_lib_mod;
    PyObject* py_func;
    PyObject* py_ret;

    Py_Initialize();

    //import our python script
    py_dir = PyUnicode_FromWideChar(argv[1], wcslen(argv[1]));
    py_imp_str = PyString_FromString("imp");
    py_imp_handle = PyImport_Import(py_imp_str);
    py_imp_dict = PyModule_GetDict(py_imp_handle); //borrowed
    py_imp_load_source = PyDict_GetItemString(py_imp_dict, "load_source"); //borrowed
    py_lib_name = PyUnicode_FromWideChar(argv[2], wcslen(argv[2]));

    py_args_tuple = PyTuple_New(2);
    PyTuple_SetItem(py_args_tuple, 0, py_lib_name); //stolen
    PyTuple_SetItem(py_args_tuple, 1, py_dir); //stolen

    py_lib_mod = PyObject_CallObject(py_imp_load_source, py_args_tuple);
    py_lib_mod_dict = PyModule_GetDict(py_lib_mod); //borrowed

    printf("Calling cfunc1 from main!\n");
    cfunc1();

    py_func = PyDict_GetItem(py_lib_mod_dict, py_lib_name);
    py_ret = PyObject_CallFunction(py_func, "(I)", &cfunc1);

    if (py_ret)
    {
        printf("PyObject_CallFunction from wmain was successful!\n");
        Py_DECREF(py_ret);
    }
    else
        printf("PyObject_CallFunction from wmain failed!\n");

    Py_DECREF(py_imp_str);
    Py_DECREF(py_imp_handle);
    Py_DECREF(py_args_tuple);
    Py_DECREF(py_lib_mod);

    Py_Finalize();

    fflush(stderr);
    fflush(stdout);
    return 0;
}

Python代码:

^{pr2}$

输出:

Calling cfunc1 from main!
Hello from cfunc1!
The dictionary has 88 items!
Calling with GetItemString
Done with GetItemString
Hello and goodbye from func2!
PyObject_CallFunction from cfunc1 was successful!
Goodbye from cfunc1!
Hello from func1!
C callback: 0x1051000
Calling callback from func1.
Hello from cfunc1!
The dictionary has 88 items!
Calling with GetItemString
PyObject_CallFunction from wmain failed!

我在末尾添加了一个PyErr_Print(),结果是:

Traceback (most recent call last):
  File "C:\Programming\crash.py", line 9, in func1
    call_me()
WindowsError: exception: access violation writing 0x0000000C

编辑:修正了阿巴内特指出的一个错误。输出不受影响。
编辑:在解决错误的代码中添加(获取cfunc1中的GIL锁)。再次感谢阿巴纳特。在


Tags: 函数frompymodlibdictpyobjectimp
2条回答

阿巴内特的回答提供了正确的函数,但是这个解释让我很困扰,所以我很早就回到家里,又找了一些。在

在开始解释之前,我想指出,当我说GIL时,我严格地说的是互斥体、信号量或全局解释器锁用来进行线程同步的任何东西。这不包括Python在获取和发布GIL之前/之后所做的任何其他内务处理。在

单线程程序不会初始化GIL,因为您从不调用PyEval_InitThreads()。因此没有GIL。即使发生了锁定,这也不重要,因为它是单线程的。然而,获取和释放GIL的函数除了获取/释放GIL之外,还会做一些有趣的事情,比如扰乱线程状态。关于WINFUNCTYPE对象的文档明确指出,它在跳转到C之前释放GIL。因此,当在Python中调用C回调时,我怀疑调用了PyEval_SaveThread()之类的东西(可能是错误的,因为至少据我的理解,它只在线程操作中被调用)。这将释放GIL(如果存在的话)并将线程状态设置为NULL,但是在单线程Python程序中没有GIL,因此它真正做的只是将线程状态设置为NULL。这会导致C回调中的大多数Python函数失败。在

实际上,调用PyGILState_sure/Release的唯一好处是告诉Python在运行和执行操作之前将线程状态设置为有效的状态。没有要获取的GIL(未初始化,因为我从未调用PyEval_InitThreads())。在

为了测试我的理论:在main函数中,我使用PyThreadState_Swap(NULL)来获取线程状态对象的副本。我在回调过程中恢复它,一切正常。如果我将线程状态保持为null,那么即使不执行Python->;C回调,也会出现几乎相同的访问冲突。在cfunc1中,我恢复了线程状态,并且在Python->;C回调期间,cfunc1本身不再有问题。在

cfunc1返回Python代码时会出现问题,但这可能是因为我搞乱了线程状态,而WINFUNCTYPE对象期望的是完全不同的东西。如果您保持线程的状态,而在返回时没有将其设置回null,那么Python只会坐在那里不做任何事情。如果你把它恢复为null,它就会崩溃。但是,它确实成功地执行了cfunc1,所以我不确定我是否太在意。在

我可能最终会在Python源代码中找到100%的确定,但我确信足够满意。在

问题是这个代码:

py_func = PyDict_GetItemString(py_lib_mod_dict, "func2"); //fails here when cfunc1 is called via callback... will not even go to the next line!
printf("Done with GetItemString\n");
py_ret = PyObject_CallFunction(py_func, 0);

Py_DECREF(py_func);

正如the docs所说,PyDict_GetItemString返回一个借用的引用。所以,当你第一次打电话到这里时,你借用了引用,并减少了它,导致它被销毁。下次你打电话的时候,你会收到垃圾,然后试着打电话给它。在

所以,要修复它,只需删除Py_DECREF(py_func)(或在pyfunc =行后添加Py_INCREF(py_func))。在

实际上,您通常会返回一个特殊的“dead”对象,因此您可以非常容易地测试这个对象:在py_func =行之后和Py_DECREF行后面放一个<function func2 at 0x10b9f1230>,第二次和第三次可能会看到类似<function func2 at 0x10b9f1230>,第二次和第三次(你不会看到第四个,因为它会在到达之前崩溃)。在

而{cd16}则可以在{cd16}中运行{cd11>,而不是^ cd16}来修复{cd16}的代码。在

还有…你不应该把GIL放在里面吗?我不常写这样的代码,所以也许我错了。我不会因为代码而崩溃。显然,生成一个线程来运行cfunc1会导致崩溃,PyGILState_Ensure/Release解决了这个崩溃……但这并不能证明您在单线程情况下需要任何东西。{你先看看这件事和我的案子没什么关系。在

顺便说一句,如果您是Python扩展和嵌入的新手:大量无法解释的崩溃,就像这次一样,都是由手动引用错误引起的。这就是boost::python等事物存在的原因。这并不是说用纯C API是不可能的,只是它很容易出错,而且您必须习惯于调试这样的问题。在

相关问题 更多 >