Python 调用 C 动态链接库,包括结构体参数、回调函数等
2018-02-23 17:13
351 查看
环境准备
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: <class 'ctypes.CDLL'> so_file.max = 22
调用以结构体为参数的函数
这就稍微复杂点了,因为 C 语言中的结构体在 Python 中并没有直接一一对应。不过不用担心,简单而言,解决方案就是:在 Python 代码中调用ctypes的类进行 Python 化的封装。网上的代码进行了最简化的演示,这里我从这一小节开始,建议读者把一个 .so 文件,封装成 Python 模块。这样一来库的包装更加简洁和清晰。
C 代码
这里是 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 -- {\n"); printf("C -- \tinteger : %d\n", pTestSt->integer); printf("C -- \tcstr : %s\n", pTestSt->c_str); printf("C -- \tptr : %p\n", pTestSt->ptr); printf("C -- \tarray : ["); for (int tmp = 0; tmp < 7; tmp ++) { printf("%d, ", pTestSt->array[tmp]); } printf("%d]\n", pTestSt->array[7]); printf("C -- }\n"); return "success"; }
Python 封装
封装结构体
首先,我们要对结构体进行转换: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 中声明一下各个成员的类型。其他的一些类型还请读者 Google 之。此外还需要注意一下类似于
c_int,
c_void_p等等的定义是在
ctypes中的,如果是用
impoer ctypes的方式包含 ctypes 模块,则应该写成
ctypes.c_int,
ctypes.c_void_p。
封装 .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\n"); } 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)\n", 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(c_int, c_int)最后的
testCallbackType通过
ctypes定义了一个回调函数类型,这个在后面的调用中需要使用
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)怎么样,是不是觉得很简单?文章来源:北大青鸟官网 HD089455
相关文章推荐
- 当用 Python ctypes 模块调用动态链接库,如何传入一个结构体指针
- python调用dll动态库传入结构体作为参数
- python调用dll, 复杂结构体做参数。
- python如何调用C, 如何注册成C的回调函数(python后台程序常用方法)
- python调用dll文件时传入参数是int*,返回值是char*,对传入的char*所指的内容的修改
- Python装饰器实例:调用参数合法性验证
- PHP 调用python 传参数被截断
- Python调用C/C++动态链接库的方法
- 自写python3的API调用,并筛选出合适参数的表情
- python中函数参数,函数调用参数传递,可变参数,*expression,**expression
- Delphi 调用 c编写的动态链接库,结构体指针作为参数
- call_user_func与call_user_func_array把第一个参数作为回调函数调用
- Python自定义函数的创建、调用和函数的参数详解
- python中调用动态链接库(C++,linux)
- python调用动态链接库传送protobuf数据
- Memcached 结构分析之各个结构体详解(包括set参数)
- 【python】c++调用python文件设置参数Py_BuildValue()
- c++ 调用 python 实例 涉及 类 多参数 列表作为参数
- Python调用C/C++动态链接库
- python调用C++,传递结构体与结构体指针,以及嵌套结构体