您的位置:首页 > 编程语言 > Python开发

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 回调函数