用 ctypes 替换共享库中的函数指针
Replace a function pointer in a shared library with ctypes
我正在尝试通过 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)中定义的函数是CFunctionType
类型的,而C中定义的函数指针是_FuncPtr
类型的。虽然应用了CAUXDLL
中的replacement,但是在调用main函数的时候好像没有效果。
除了阅读 https://docs.python.org/3/library/ctypes.html#module-ctypes, I found other questions (e.g. or python cytpes creating callback function - Segmentation fault (core dumped)),但我找不到如何将 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
,但似乎没有公开的方法来更改函数指针。我只能读取和调用它,所以我认为没有辅助函数是不可能的。
下面的例子改变了一个 int
全局变量,但是 CFUNCTYPE
实例没有 value
成员来改变它。我添加了一个 C 助手来设置全局变量以解决该问题,并添加了回调的默认值以验证它在更改之前是否被正确访问。
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
来访问它们的值,因此我尝试使用 POINTER(CFUNCTYPE(None))
和 plot.contents = plotxy
但这并没有正确分配全局变量和 C崩溃了。
我什至尝试添加一个实际的全局指针到函数指针:
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
作为函数参数以外的任何东西的功能似乎都没有实现。
我正在尝试通过 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)中定义的函数是CFunctionType
类型的,而C中定义的函数指针是_FuncPtr
类型的。虽然应用了CAUXDLL
中的replacement,但是在调用main函数的时候好像没有效果。
除了阅读 https://docs.python.org/3/library/ctypes.html#module-ctypes, I found other questions (e.g. 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
,但似乎没有公开的方法来更改函数指针。我只能读取和调用它,所以我认为没有辅助函数是不可能的。
下面的例子改变了一个 int
全局变量,但是 CFUNCTYPE
实例没有 value
成员来改变它。我添加了一个 C 助手来设置全局变量以解决该问题,并添加了回调的默认值以验证它在更改之前是否被正确访问。
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
来访问它们的值,因此我尝试使用 POINTER(CFUNCTYPE(None))
和 plot.contents = plotxy
但这并没有正确分配全局变量和 C崩溃了。
我什至尝试添加一个实际的全局指针到函数指针:
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
作为函数参数以外的任何东西的功能似乎都没有实现。