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 等等。但是查了不少资料没能解决我的两个关键诉求(结构体参数和回调函数):

bVD0IB?w=800&h=3

环境准备

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,放在工作目录下。

bVD0IB?w=800&h=3

基本参数函数调用

首先是最简单的函数调用,并且函数参数为基本数据类型。待调用的函数定义如下:

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

bVD0IB?w=800&h=3

调用以结构体为参数的函数

这就稍微复杂点了,因为 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 中声明一下各个成员的类型。其他的一些类型请参见官方文档。

此外还需要注意一下类似于 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 则不需要。

bVD0IB?w=800&h=3

调用以回调函数地址为参数的函数

这个主题就稍微绕一些了,也就是说在 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

函数调用

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)

怎么样,是不是觉得很简单?

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Python调用C++函数并返回结构,可以通过使用ctypes来实现。下面是一个示例代码,展示了如何在Python调用一个返回结构C++函数: 假设我们有一个C++函数,它返回一个结构类型`MyStruct`: ```c++ #include <iostream> struct MyStruct { int my_int; float my_float; char my_string[256]; }; MyStruct get_struct() { MyStruct s; s.my_int = 123; s.my_float = 3.14; strcpy(s.my_string, "Hello, C++!"); return s; } ``` 现在,我们可以通过使用ctypes来在Python调用这个函数并获取返回的结构。下面是示例代码: ```python import ctypes # 加载C++编译后的动态 lib = ctypes.cdll.LoadLibrary('./libexample.so') # 定义结构类型 class MyStruct(ctypes.Structure): _fields_ = [ ("my_int", ctypes.c_int), ("my_float", ctypes.c_float), ("my_string", ctypes.c_char * 256) ] # 设置函数的返回类型为MyStruct类型 lib.get_struct.restype = MyStruct # 调用C++函数并获取返回的结构 result = lib.get_struct() # 输出结构的成员变量 print(result.my_int) print(result.my_float) print(result.my_string.decode()) ``` 在上面的示例代码中,我们首先使用`cdll.LoadLibrary()`函数加载C++编译后的动态着,我们定义了一个结构类型`MyStruct`,并使用`_fields_`属性来定义结构的成员变量列表。然后,我们使用`restype`属性将C++函数的返回类型设置为`MyStruct`类型。 最后,我们调用C++函数`get_struct()`并获取返回的结构,将其赋值给变量`result`。我们可以通过访问结构对象的成员变量来获取它们的值,使用`.decode()`方法将`my_string`成员变量从`bytes`类型转换为字符串类型并输出。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值