我有一些调用Python函数的C代码。这个Python函数接受一个地址,并使用WINFUNCTYPE最终将它转换为Python可以调用的函数。 C函数作为参数发送到Python函数最终将调用另一个Python函数。正是在最后一步导致崩溃。所以简而言之,我从C - > Python - > C - >蟒蛇。最后一个C - > Python导致崩溃。我一直试图理解这个问题,但我一直无法理解。
有人可以指出我的问题吗?
使用Visual Studio 2010编译的C代码,并使用args“c:\ ... \ crash.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代码:
from ctypes import *
def func1(cb):
print "Hello from func1!"
cb_proto = WINFUNCTYPE(None)
print "C callback: " + hex(cb)
call_me = cb_proto(cb)
print "Calling callback from func1."
call_me()
print "Goodbye from func1!"
def func2():
print "Hello and goodbye from func2!"
输出:
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
编辑:修正了abarnert指出的错误。输出不受影响 编辑:在解决该错误的代码中添加(在cfunc1中获取GIL锁)。再次感谢abarnert。
答案 0 :(得分:5)
问题是这段代码:
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)
(或在Py_INCREF(py_func)
行后添加pyfunc =
)。
实际上,你通常会找回一个特殊的&#34;死的&#34;对象,因此您可以非常轻松地对此进行测试:在PyObject_Print(py_func, stdout)
行之后和py_func =
行之后放置Py_DECREF
,您可能会看到<function func2 at 0x10b9f1230>
之类的内容第一次,<refcnt 0 at 0x10b9f1230>
第二次和第三次(你不会看到第四次,因为它会在你到达之前崩溃)。
我没有方便的Windows框,但将wmain
,wchar_t
,PyUnicode_FromWideChar
,WINFUNCTYPE
等更改为main
, char
,PyString_FromString
,CFUNCTYPE
等等,我能够构建并运行您的代码,并且我在同一个地方遇到了崩溃......并且修复工作正常。
另外......你不应该在cfunc1
内举行GIL吗?我不经常写这样的代码,所以也许我错了。而且我没有按原样使用代码崩溃。显然,产生一个运行cfunc1
的线程会崩溃,而PyGILState_Ensure
/ Release
会解决这个崩溃...但这并不能证明你需要任何东西单线程案例。所以也许这不相关......但是如果你在修复第一个崩溃后再次崩溃(在线程的情况下,我看起来像Fatal Python error: PyEval_SaveThread: NULL tstate
),请看看。
顺便说一句,如果您是Python扩展和嵌入的新手:大量无法解释的崩溃,就像这一次,是由手动引用计数错误引起的。这就是boost::python
等等存在的原因。并不是说用普通的C API做到这一点是不可能的,只是它很容易弄错,你将不得不习惯于调试这样的问题。
答案 1 :(得分: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_Ensure / Release的唯一好处是告诉Python在运行和执行操作之前将线程状态设置为有效的东西。没有GIL可以获取(未初始化,因为我从未调用过PyEval_InitThreads())。
测试我的理论:在main函数中,我使用PyThreadState_Swap(NULL)来获取线程状态对象的副本。我在回调期间恢复它,一切正常。如果我将线程状态保持为null,即使没有使用Python,我也会得到几乎相同的访问冲突 - &gt; C回调。在cfunc1中,我恢复了线程状态,并且在Python期间没有更多问题cfunc1本身 - &gt; C回调。
当cfunc1返回到Python代码时会出现问题,但这可能是因为我搞乱了线程状态,而WINFUNCTYPE对象期待完全不同的东西。如果你保持线程处于状态而不将其设置回null,那么Python就坐在那里什么都不做。如果将其还原为null,则会崩溃。但是,它确实成功执行了cfunc1,所以我不确定我是否太在意。
我最终可能会在Python源代码中找到100%肯定,但我确信会满意。