python调⽤c++动态库_Python调⽤C动态链接库,包括结构体
参数、回调函数等
项⽬中要对⼀个⽤ C 编写的 .so 库进⾏逻辑⾃测。这项⼯作,考虑到灵活性,我⾸先考虑⽤ Python 来完成。
研究了⼀些资料,采⽤ python 的 ctypes 来完成这项⼯作。已经验证通过,本⽂记录⼀下适配流程。验证采⽤ cpp 来设计,不过暂时还没有涉及类的内容。以后如果需要再补⾜。
参考资料
ctypes
以下资料是关于 ctypes 的,也就是本⽂采⽤的资料:
⼀些 Python 本⾝的资料
由于研究 ctypes 时我⽤的是 Python 2.7,后来切换到 Python 3 的时候稍微遇到⼀点适配问题,因此也顺便记录⼀下我切换过程中参考的⼀些资料:
其他 python 调⽤ C 的⽅法
Python 调⽤ C 还有其他的⼏个解决⽅案,⽐如 cython、SWIG 等等。但是查了不少资料没能解决我的两个关键诉求(结构体参数和回调函数):
环境准备
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 代码
这⾥是 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 ⾥⾯输出的
ubuntu怎么安装python}
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 中声明⼀下各个成员的类型。其他的⼀些类型请参见官⽅⽂档。
此外还需要注意⼀下类似于 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.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(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
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论