摘要:调用以回调函数地址为参数的函数这个主题就稍微绕一些了,也就是说在接口中,需要传入回调函数作为参数。这个问题在中也可以解决,并且回调函数可以用定义。代码代码很简单回调函数的传入参数为,返回参数也是。
项目中要对一个用 C 编写的 .so 库进行逻辑自测。这项工作,考虑到灵活性,我首先考虑用 Python 来完成。
研究了一些资料,采用 python 的 ctypes 来完成这项工作。已经验证通过,本文记录一下适配流程。验证采用 cpp 来设计,不过暂时还没有涉及类的内容。以后如果需要再补足。
本文地址:https://segmentfault.com/a/1190000013339754
参考资料 ctypes以下资料是关于 ctypes 的,也就是本文采用的资料:
Python的学习(三十二)---- ctypes库的使用整理
Python Ctypes 结构体指针处理(函数参数,函数返回)
ctypes库
用Python ctypes 建立與C的介面
Python调用C/C++动态链接库的方法详解
【转】python中使用 C 类型的数组以及ctypes 的用法
ctypes
将函数指针转换为可调用对象
Python Ctypes结构体指针处理(函数参数,函数返回)
Can"t install python-dev on centos 6.5
Python 3.5, ctypes: TypeError: bytes or integer address expected instead of str instance
一些 Python 本身的资料由于研究 ctypes 时我用的是 Python 2.7,后来切换到 Python 3 的时候稍微遇到一点适配问题,因此也顺便记录一下我切换过程中参考的一些资料:
python多线程ctrl-c退出问题
Python多线程之怎样优雅的响应中断异常(Ctrl+C)
CentOS7.2 多个python版本共存
Python 2 和 Python 3 有哪些主要区别? - 猪了个去的回答 - 知乎
关于 python ImportError: No module named 的问题
python的模块加载和路径查找
如何获得Python脚本所在目录的位置
关于python中带下划线的变量和函数 的意义
【变量】关于python中的下划线
16.16. ctypes — A foreign function library for Python
其他 python 调用 C 的方法Python 调用 C 还有其他的几个解决方案,比如 cython、SWIG 等等。但是查了不少资料没能解决我的两个关键诉求(结构体参数和回调函数):
Python调用C
Python.h:No such file or directory
环境准备 ctypes 包准备使用 ctypes,需要首先安装 python-dev 包:
Ubuntu: $ sudo apt-get install python-dev -y CentOS: $ sudo yum install python-devel -y
这里主要包含了 ctypes 包。
.so 文件准备将你的 C 代码编译成 .so 文件。这里假设目标文件是 libtest.so,放在工作目录下。
基本参数函数调用首先是最简单的函数调用,并且函数参数为基本数据类型。待调用的函数定义如下:
extern "C" int max(int a, int b) { return (a > b) ? a : b; }
这种情况下,在 Python 中的调用就很简单了。我们需要使用 ctypes 包中的 cdll 模块加载 .so 文件,然后就可以调用库中的函数了。
Python 代码如下:
#!/usr/bin/python3 # -*- coding: UTF-8 -*- from ctypes import * so_file = cdll.LoadLibrary("./libtest.so") # 如果前文使用的是 import ctypes,则这里应该是 ctypes.cdll.LoadLobrary(...) ret = so_file.max(22, 20) print("so_file class:", type(so_file)) print("so_file.max =", ret)
输出:
so_file class:调用以结构体为参数的函数so_file.max = 22
这就稍微复杂点了,因为 C 语言中的结构体在 Python 中并没有直接一一对应。不过不用担心,简单而言,解决方案就是:在 Python 代码中调用 ctypes 的类进行 Python 化的封装。
网上的代码进行了最简化的演示,这里我从这一小节开始,建议读者把一个 .so 文件,封装成 Python 模块。这样一来库的包装更加简洁和清晰。
这里是 C 代码的部分,主要是结构体的声明。用于示例的函数很简单,只是一个 print 功能而已:
typedef struct _test_struct { int integer; char * c_str; void * ptr; int array[8]; } TestStruct_st; extern "C" const char *print_test_struct(TestStruct_st *pTestSt) { if (NULL == pTestSt) { return "C -- parameter NULL"; # "C --" 打头区分这是在 .so 里面输出的 } printf("C -- { "); printf("C -- integer : %d ", pTestSt->integer); printf("C -- cstr : %s ", pTestSt->c_str); printf("C -- ptr : %p ", pTestSt->ptr); printf("C -- array : ["); for (int tmp = 0; tmp < 7; tmp ++) { printf("%d, ", pTestSt->array[tmp]); } printf("%d] ", pTestSt->array[7]); printf("C -- } "); return "success"; }
首先,我们要对结构体进行转换:
from ctypes import * INTARRAY8 = c_int * 8 class PyTestStruct(Structure): "TestStruct_st 的 Python 版本" _fields_ = [ ("integer", c_int), ("c_str", c_char_p), ("ptr", c_void_p), ("array", INTARRAY8) ]
首先对结构体里的 int 数组进行了重定义,也就是 INTARRAY8。
接着,注意一下 _fields_ 的内容:这里就是对 C 数据类型的转换。左边是 C 的结构成员名称,右边则是在 python 中声明一下各个成员的类型。其他的一些类型请参见官方文档。
此外还需要注意一下类似于 c_int, c_void_p 等等的定义是在 ctypes 中的,如果是用 impoer ctypes 的方式包含 ctypes 模块,则应该写成 ctypes.c_int, ctypes.c_void_p。
第三个要注意的是:这个类必须定义为 ctypes.Structure 的子类,否则在进行后续的函数传递时,ctypes 由于不知道如何进行数据类型的对应,会抛出异常
封装 .so 函数class testdll: "用于 libtest.so 的加载,包含了 cdll 对象" def __init__(self): self.cdll = cdll.LoadLibrary("./libtest.so") # 直接加载 .so 文件。感觉更好的方式是写成单例 return def print_test_struct(self, test_struct): func = self.cdll.print_test_struct func.restype = c_char_p func.argtypes = [POINTER(PyTestStruct)] return func(byref(test_struct)).decode()
注意最后一句 func(byref(test_struct)) 中的 byref。这个函数可以当作是 C 中的取地址符 & 的 Python 适配。因为函数参数是一个结构体指针(地址),因此我们需要用上 byref 函数。
Python 调用直接上 Python 代码,很短的(import 语句就不用写了吧,读者自行发挥就好):
test_struct = PyTestStruct() test_struct.integer = 1 test_struct.c_str = "Hello, C".encode() # Python 2.x 则不需要写 encode test_struct.ptr = 0xFFFFFFFF test_struct.array = INTARRAY8() for i in range(0, len(test_struct.array)): j = i + 1 test_struct.array[i] = j * 10 + j so_file = testdll() test_result = so_file.print_test_struct(test_struct) print("test_result:", test_result)
执行结果:
C -- { C -- integer : 1 C -- cstr : Hello, C C -- ptr : 0xffffffff C -- array : [11, 22, 33, 44, 55, 66, 77, 88] C -- } test_result: success
这里可以看到,结构体参数的准备还是很简单的,就是将用 Python 适配过来之后的类中对应名字的成员进行赋值就好了。
注意一下在 Python 3.x 中,str 和 bytes 类型是区分开的,而 char * 对应的是后者,因此需要进行 encode / decode 转换。在 Python 2.x 则不需要。
调用以回调函数地址为参数的函数这个主题就稍微绕一些了,也就是说在 C 接口中,需要传入回调函数作为参数。这个问题在 Python 中也可以解决,并且回调函数可以用 Python 定义。
C 代码C 代码很简单:回调函数的传入参数为 int,返回参数也是 int。C 代码获取一个随机数交给回调去处理。
extern "C" void print_given_num(int (*callback)(int)) { if (NULL == callback) { printf("C -- No number given "); } static int s_isInit = 0; if (0 == s_isInit) { s_isInit = 1; srand(time(NULL)); } int num = callback((int)rand()); printf("C -- given num by callback: %d (0x%x) ", num, num); return; }Python 封装
这里我还是用前面的 testdll 类来封装:
class testdll: "用于 libtest.so 的加载,包含了 cdll 对象" def __init__(self): self.cdll = cdll.LoadLibrary("./libtest.so") return def print_given_num(self, callback): self.cdll.print_given_num(callback) return testCallbackType = CFUNCTYPE(None, c_int, c_int)
最后的 testCallbackType 通过 ctypes 定义了一个回调函数类型,这个在后面的调用中需要使用
在 CFUNCTYPE 后面的第一个参数为 None,这表示回调函数的返回值类型为 void
Python 调用 回调函数准备回调函数用 Python 完成,注意接受的参数和返回数据类型都应该与 .so 中的定义一致。我这里的回调函数中,将 .so 传过来的参数取了一个最低字节返回:
def _callback(para): print("get callback req:", hex(para)) print("return:", hex(para & 0xFF)) return para & 0xFF函数调用
so_file = testdll() cb = testCallbackType(_callback) so_file.print_given_num(cb)
执行结果:
get callback req: 0x4f770b3a return: 0x3a C -- given num by callback: 58 (0x3a)
怎么样,是不是觉得很简单?
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/41427.html
摘要:指针和引用假设动态库中有函数如下第二个参数为结构体指针,第三个参数是一个引用。我这里选择的是然后找到,下载替换掉重编译和输入版本号,这里实用的是为或者参考资料通过在中调用动态链接库文件厚颜无耻加上自己的博客 0x01. 使用的 npm 包 首先要安装 node-gyp, 用来重新编译依赖包。 npm instal -g node-gyp 然后主要用到下面三个包: node-ffi -...
摘要:由设计,作为编程语言的继承者,于年首次发布。表达式表达式是编程语言中的语法实体,可以对其进行评估以确定其值。它是编程语言解释和计算以产生值的常量变量函数和运算符的组合。它在年年年和年被评为年度编程语言,是唯一四次获得该奖项的语言。 ...
摘要:最近了解了提供的一个外部函数库它提供了语言兼容的几种数据类型,并且可以允许调用编译好的库。这里是阅读相关资料的一个记录,内容大部分来自官方文档。注意,提供的接口会在不同系统上有出入,比如为了加载动态链接库,在上提供的是而在上提供的是和。 参考资料 https://docs.python.org/2.7/l... http://www.ibm.com/developerw... c...
阅读 1223·2021-11-11 16:54
阅读 875·2021-10-19 11:44
阅读 1337·2021-09-22 15:18
阅读 2444·2019-08-29 16:26
阅读 2946·2019-08-29 13:57
阅读 3094·2019-08-26 13:32
阅读 1080·2019-08-26 11:58
阅读 2327·2019-08-26 10:37