本文介绍了用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();
返回0;
}

Python脚本的来源:

 从sys导入平台
从pathlib导入路径
导入ctypes
import _ctypes


FUNCTYPE = ctypes.WINFUNCTYPE如果是平台=='win32'else ctypes.CFUNCTYPE


def dlclose(obj):
如果平台== 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(无)
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对象位于0x7fb1f0abb1c0>
< _FuncPtr对象位于0x7fb1f0abb640>
< CFunctionType对象位于0x7fb1f0abb1c0>
图为(nil)
python3:test.c:8:c_main:断言'plot!= NULL'失败。
./test.sh:第3行:21171中止了python3 test.py

因此,似乎在Python(plotxy)中定义的函数的类型为 CFunctionType ,而在C中定义的函数指针的类型为 _FuncPtr 。尽管在 CAUXDLL 中进行了替换,但在调用main函数时似乎没有任何作用。


除了读取,我发现了其他问题(例如或),但是我找不到如何将 CFunctionType (plotxy)转换为 _FuncPtr 。 / p>

编辑


我相信这可能与常规使用ctypes无关。这是我已经成功实现的,并且在文档中对此进行了充分的说明。这个问题超越了。我不想执行C函数。我希望Python用Python编写的回调替换现有的函数指针。请注意,可以使用辅助C函数来完成此操作(请参见)。因此,这个问题是关于如何在没有该辅助函数的情况下(如果可能的话)实现它的。

解决方案

从中访问全局变量的方式 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图= dllplot;
API int x = 5;

API int c_main(){
printf( x =%d(来自C)\n,x);
plot();
返回0;
}

API无效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}(来自Python)')
x.value = 7
print('直接从Python调用图:')
plot()
print('calling c_main():')
dll.c_main()
dll.set_cb(plotxy)
print('设置回调后从Python调用图:')
plot()
print('设置回调后从C调用图:')
dll.c_main()

输出:

  x = 5(来自Python)
直接从Python调用图:
默认
调用c_main():
x = 7(来自C)
默认
设置回调后从Python调用图:
plotxy
设置回调后从C调用图:
x = 7(来自C)
plotxy

请注意,全局指针使用 .contents 来访问它们的值,因此我尝试使用 POINTER(CFUNCTYPE(None ))并使用 plot.contents = plotxy ,但这不能正确分配全局变量,C崩溃了。


我什至尝试将实际的全局指针添加到函数指针:

  API CB plot = dllplot; 
API CB * pplot =& plot;

,然后使用:

  PLOT = CFUNCTYPE(None)
PPLOT = POINTER(PLOT)
图= PPLOT.in_dll(dll,'pplot')
plot.contents = plotxy

这让我通过 .contents 分配了函数,但是 c_main 仍称为默认绘图值。因此,将 CFUNCTYPE 用作除函数参数之外的任何功能的功能似乎未实现。


I am trying to replace an existing function pointer in a shared library with a callback defined in Python, through ctypes.

The source of the shared library in 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;
}

The source of the Python script:

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))

The script to test it:

gcc -fPIC -shared -o test.so test.c
python3 test.py

The output I get:

# ./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

So, it seems that the function defined in Python (plotxy) is of type CFunctionType, while the function pointer defined in C is of type _FuncPtr. Although the replacement in CAUXDLL is applied, it seems to have no effect when the main function is called.

Apart from reading https://docs.python.org/3/library/ctypes.html#module-ctypes, I found other questions (e.g. How to use typedef in ctypes or python cytpes creating callback function - Segmentation fault (core dumped)), but I cannot find how to convert CFunctionType (plotxy) to _FuncPtr.

EDIT

I believe this might not be an issue with the regular usage of ctypes. That's something I have successfully achieved and which is sufficiently explained in the docs. This question goes beyond. I don't want to execute the C function. I want Python to replace an existing function pointer with a callback written in Python. Note that it is possible to do it by using a helper C function (see https://github.com/ghdl/ghdl-cosim/blob/master/vhpidirect/shared/pycb/caux.c#L32-L40). Hence this question is about how to achieve it without that helper function (if possible).

解决方案

The way to access global variables from ctypes is to use in_dll, but there doesn't seem to be an exposed way to change a function pointer. I was only able to read it and call it, so I don't think it is possible without a helper function.

The example below alters an int global variable, but CFUNCTYPE instances don't have a value member to alter it. I added a C helper to set the global to work around the issue and a default value of the callback to verify it was accessed correctly before changing it.

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()

Output:

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

Note that global pointers use .contents to access their value, so I experimented with using a POINTER(CFUNCTYPE(None)) and using plot.contents = plotxy but that doesn't assign the global variable correctly and C crashed.

I even tried adding an actual global pointer to function pointer:

API CB plot = dllplot;
API CB* pplot = &plot;

and then using:

PLOT = CFUNCTYPE(None)
PPLOT = POINTER(PLOT)
plot = PPLOT.in_dll(dll,'pplot')
plot.contents = plotxy

That let me assign the function via .contents, but c_main still called the default plot value. So the functionality of using CFUNCTYPE as anything but a function parameter doesn't appear to be implemented.

这篇关于用ctypes替换共享库中的函数指针的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

07-25 06:10