在 Python ctypes 回调中发生异常时避免未定义的行为?
Avoiding undefined behavior when an exception occurs in a Python ctypes callback?
这个 C 共享库 test.so
定义了 cfunc()
,它接受一个回调函数:
int cfunc(int (*callback)(void)) {
return callback();
}
此 Python 代码调用 cfunc()
使用 Python 函数 callback()
:
from ctypes import *
lib = cdll.LoadLibrary('./test.so')
@CFUNCTYPE(c_int)
def callback():
raise RuntimeError
print(lib.cfunc(callback))
因为 callback()
引发异常,所以它永远不会 return 成为一个值。我相信这会导致 ctypes
触发未定义的行为,尽管我还没有仔细研究源代码来确定。至少,由 cfunc()
编辑的值 return 似乎表明它未初始化。
有什么办法可以避免这种未定义的行为吗?
将 callback()
的名义主体包裹在 try
子句中会在 except
子句中留下例外的可能性。
我可以将 int *
传递给 callback()
并使用引用的 int
来表示是否引发了异常。但是,我认为未定义的行为仍然发生在我无法控制的代码中。
有没有办法让ctypes
CFunctionType
对象初始化return值? ctypes
文档似乎没有提及任何内容。
有多种可能性,一种是将纯 python 函数包装到 try..except
中,然后才使用 ctypes
:[=29= jit C 函数]
import ctypes
def SafeCFunctype(py_fun):
@ctypes.CFUNCTYPE(ctypes.c_int)
def safe():
try:
return py_fun()
except:
print("Exception!")
return -1
return safe
为简单起见,我选择 return -1
并记录到标准输出,以防发生错误,但还有其他选项可以指示错误。
现在:
@SafeCFunctype
def callback1():
raise RuntimeError()
callback1() # results in
# Exception!
# -1
然而,py_fun
的代码并不是唯一可能发生错误的地方 - 当 python 对象转换为 c- 时,胶水代码中也可能出现异常整数,例如:
@SafeCFunctype
def callback2():
return None
callback2()
# Exception ignored in: <function SafeCFunctype.<locals>.safe at 0x7f31f41dd840>
# -97323988
为确保转换有效(或异常发生在 try..catch
内部,我们可以执行以下操作:
import ctypes
def SafeCFunctype(py_fun):
@ctypes.CFUNCTYPE(ctypes.c_int)
def safe():
try:
# throws if conversion not possible:
return ctypes.c_int(py_fun())
except:
print("Exception!")
return -1
return safe
现在转换(和可能的异常)发生在 ctypes.c_int(py_fun())
内部,我们可以肯定,创建的 CFunction 中的样板代码不会出错:
@SafeCFunctype
def callback2():
return None
callback2() # results in
# Exception!
# -1
即没有随机结果。
对于像 return -1
这样的简单代码,我们可以非常确定,不会引发进一步的异常 except
子句。
我没有看到任何方法来检测包装回调中是否引发了 python 异常,因为 ctypes
会检测到引发了异常,打印警告然后清除错误。
这是上面的例子:我使用 ffi
来 jit 一个 C-wrapper,它将调用 ctypes
提供的 callback
并检查 Python-错误:
import cffi
code_template = r"""
int safe_call(void){{
typedef int (*functor)(void);
// this is awfull, but that is how it works:
functor fun = *((functor *){callback_addressof});
int res = fun();
if(PyErr_Occurred()){{
PyErr_Clear();
printf("Error detected\n");
return -1;
}}
else{{
printf("No error detected\n");
return res;
}}
}}
"""
def create_safe_functor(functor):
ffibuilder = cffi.FFI()
ffibuilder.cdef("int safe_call(void);", override=True)
code = code_template.format(callback_addressof=ctypes.addressof(functor))
print(code)
built_module=ffibuilder.verify(source=code)
safe_functor = built_module.safe_call
return safe_functor
现在
fun = create_safe_functor(callback)
fun()
# RuntimeError:
# No error detected
# -234235
这意味着 C 函数不再能够检测到错误。
这个 C 共享库 test.so
定义了 cfunc()
,它接受一个回调函数:
int cfunc(int (*callback)(void)) {
return callback();
}
此 Python 代码调用 cfunc()
使用 Python 函数 callback()
:
from ctypes import *
lib = cdll.LoadLibrary('./test.so')
@CFUNCTYPE(c_int)
def callback():
raise RuntimeError
print(lib.cfunc(callback))
因为 callback()
引发异常,所以它永远不会 return 成为一个值。我相信这会导致 ctypes
触发未定义的行为,尽管我还没有仔细研究源代码来确定。至少,由 cfunc()
编辑的值 return 似乎表明它未初始化。
有什么办法可以避免这种未定义的行为吗?
将 callback()
的名义主体包裹在 try
子句中会在 except
子句中留下例外的可能性。
我可以将 int *
传递给 callback()
并使用引用的 int
来表示是否引发了异常。但是,我认为未定义的行为仍然发生在我无法控制的代码中。
有没有办法让ctypes
CFunctionType
对象初始化return值? ctypes
文档似乎没有提及任何内容。
有多种可能性,一种是将纯 python 函数包装到 try..except
中,然后才使用 ctypes
:[=29= jit C 函数]
import ctypes
def SafeCFunctype(py_fun):
@ctypes.CFUNCTYPE(ctypes.c_int)
def safe():
try:
return py_fun()
except:
print("Exception!")
return -1
return safe
为简单起见,我选择 return -1
并记录到标准输出,以防发生错误,但还有其他选项可以指示错误。
现在:
@SafeCFunctype
def callback1():
raise RuntimeError()
callback1() # results in
# Exception!
# -1
然而,py_fun
的代码并不是唯一可能发生错误的地方 - 当 python 对象转换为 c- 时,胶水代码中也可能出现异常整数,例如:
@SafeCFunctype
def callback2():
return None
callback2()
# Exception ignored in: <function SafeCFunctype.<locals>.safe at 0x7f31f41dd840>
# -97323988
为确保转换有效(或异常发生在 try..catch
内部,我们可以执行以下操作:
import ctypes
def SafeCFunctype(py_fun):
@ctypes.CFUNCTYPE(ctypes.c_int)
def safe():
try:
# throws if conversion not possible:
return ctypes.c_int(py_fun())
except:
print("Exception!")
return -1
return safe
现在转换(和可能的异常)发生在 ctypes.c_int(py_fun())
内部,我们可以肯定,创建的 CFunction 中的样板代码不会出错:
@SafeCFunctype
def callback2():
return None
callback2() # results in
# Exception!
# -1
即没有随机结果。
对于像 return -1
这样的简单代码,我们可以非常确定,不会引发进一步的异常 except
子句。
我没有看到任何方法来检测包装回调中是否引发了 python 异常,因为 ctypes
会检测到引发了异常,打印警告然后清除错误。
这是上面的例子:我使用 ffi
来 jit 一个 C-wrapper,它将调用 ctypes
提供的 callback
并检查 Python-错误:
import cffi
code_template = r"""
int safe_call(void){{
typedef int (*functor)(void);
// this is awfull, but that is how it works:
functor fun = *((functor *){callback_addressof});
int res = fun();
if(PyErr_Occurred()){{
PyErr_Clear();
printf("Error detected\n");
return -1;
}}
else{{
printf("No error detected\n");
return res;
}}
}}
"""
def create_safe_functor(functor):
ffibuilder = cffi.FFI()
ffibuilder.cdef("int safe_call(void);", override=True)
code = code_template.format(callback_addressof=ctypes.addressof(functor))
print(code)
built_module=ffibuilder.verify(source=code)
safe_functor = built_module.safe_call
return safe_functor
现在
fun = create_safe_functor(callback)
fun()
# RuntimeError:
# No error detected
# -234235
这意味着 C 函数不再能够检测到错误。