我有一些调用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锁)。再次感谢阿巴纳特。在
阿巴内特的回答提供了正确的函数,但是这个解释让我很困扰,所以我很早就回到家里,又找了一些。在
在开始解释之前,我想指出,当我说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%的确定,但我确信足够满意。在
问题是这个代码:
正如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是不可能的,只是它很容易出错,而且您必须习惯于调试这样的问题。在相关问题 更多 >
编程相关推荐