我试图通过ctypes用Python中定义的回调替换共享库中的现有函数指针。
C中共享库的源代码:
#include <assert.h> #include <stdio.h> void (*plot)(); int c_main(int argc, void** argv) { printf("plot is %p\n", (void*)plot); assert(plot != NULL); plot(); return 0; }
Python脚本的来源:
from sys import platform from pathlib import Path import ctypes import _ctypes FUNCTYPE = ctypes.WINFUNCTYPE if platform == 'win32' else ctypes.CFUNCTYPE def dlclose(obj): if platform == "win32": _ctypes.FreeLibrary(obj._handle) else: _ctypes.dlclose(obj._handle) def enc_args(args): C_ARGS = (ctypes.POINTER(ctypes.c_char) * len(args))() for idx, arg in enumerate(args): C_ARGS[idx] = ctypes.create_string_buffer(arg.encode("utf-8")) return C_ARGS @FUNCTYPE(None) def plotxy(): print("plotxy") C_ARGS = enc_args([str(Path(__file__))]) CAUXDLL = ctypes.CDLL("./test.so") print(plotxy) print(CAUXDLL.plot) CAUXDLL.plot = plotxy print(CAUXDLL.plot) print(CAUXDLL.c_main(len(C_ARGS), C_ARGS))
测试它的脚本:
gcc -fPIC -shared -o test.so test.c python3 test.py
我得到的输出:
# ./test.sh <CFunctionType object at 0x7fb1f0abb1c0> <_FuncPtr object at 0x7fb1f0abb640> <CFunctionType object at 0x7fb1f0abb1c0> plot is (nil) python3: test.c:8: c_main: Assertion `plot != NULL' failed. ./test.sh: line 3: 21171 Aborted python3 test.py
因此,似乎在Python(plotxy)中定义的函数是type CFunctionType,而在C中定义的函数指针是type _FuncPtr。尽管应用了替换in CAUXDLL,但在调用main函数时似乎没有任何效果。
CFunctionType
_FuncPtr
CAUXDLL
除了阅读https://docs.python.org/3/library/ctypes.html#module- ctypes之外,我还发现了其他问题(例如,如何在ctypes或python cytpes中使用typedef创建回调函数-分段错误(核心已转储)),但找不到如何将CFunctionType(plotxy)转换为_FuncPtr。
编辑
我相信这可能与ctypes的常规用法无关。这是我已经成功实现的,并且在文档中对此进行了充分的说明。这个问题超越了。我不想执行C函数。我希望Python用Python编写的回调替换现有的函数指针。请注意,可以通过使用辅助C函数来做到这一点(请参阅https://github.com/ghdl/ghdl- cosim/blob/master/vhpidirect/shared/pycb/caux.c#L32-L40)。因此,这个问题是关于如何在没有辅助功能的情况下实现它(如果可能)。
从中访问全局变量的方法ctypes是使用in_dll,但是似乎没有公开的方法来更改函数指针。我只能阅读和调用它,所以我认为没有帮助程序功能是不可能的。
ctypes
in_dll
以下示例更改了int全局变量,但是CFUNCTYPE实例没有value成员可以对其进行更改。我添加了一个C帮助程序来设置全局值以解决该问题,并添加一个回调的默认值,以在更改它之前验证它是否已正确访问。
int
CFUNCTYPE
value
test.c:
#include <stdio.h> #define API __declspec(dllexport) typedef void (*CB)(); void dllplot() { printf("default\n"); } API CB plot = dllplot; API int x = 5; API int c_main() { printf("x=%d (from C)\n",x); plot(); return 0; } API void set_cb(CB cb) { plot = cb; }
test.py:
from ctypes import * PLOT = CFUNCTYPE(None) dll = CDLL('./test') dll.c_main.argtypes = () dll.c_main.restype = c_int dll.set_cb.argtypes = PLOT, dll.set_cb.restype = None @PLOT def plotxy(): print("plotxy") x = c_int.in_dll(dll,'x') plot = PLOT.in_dll(dll,'plot') print(f'x={x.value} (from Python)') x.value = 7 print('calling plot from Python directly:') plot() print('calling c_main():') dll.c_main() dll.set_cb(plotxy) print('calling plot from Python after setting callback:') plot() print('calling plot from C after setting callback:') dll.c_main()
输出:
x=5 (from Python) calling plot from Python directly: default calling c_main(): x=7 (from C) default calling plot from Python after setting callback: plotxy calling plot from C after setting callback: x=7 (from C) plotxy
请注意,全局指针用于.contents访问其值,因此我尝试使用aPOINTER(CFUNCTYPE(None))和using,plot.contents = plotxy但未正确分配全局变量,C崩溃了。
.contents
POINTER(CFUNCTYPE(None))
plot.contents = plotxy
我什至尝试将实际的全局指针添加到函数指针:
API CB plot = dllplot; API CB* pplot = &plot;
然后使用:
PLOT = CFUNCTYPE(None) PPLOT = POINTER(PLOT) plot = PPLOT.in_dll(dll,'pplot') plot.contents = plotxy
这使我可以通过分配函数.contents,但c_main仍称为默认绘图值。因此CFUNCTYPE,除了功能参数之外,用作任何功能的功能似乎都没有实现。
c_main