2020.6.22更新:
增加了部分案例,并在python2和python3下都进行了调试。
——————————————————————————————————————————
因为工作需求,最近要使用python在linux环境下调用c/c++的动态库,执行动态库中的函数。这种没接触过的内容,自然首先开启百度谷歌大法。经过一番搜索,尝试使用python的ctypes模块。

一、初识
首先自然是查询文档了。
附文档链接:
python2.7 ctypes文档
python3.6 ctypes文档
python2.7文档描述:“ctypes is a foreign function library for Python. It provides C compatible data types, and allows calling functions in DLLs or shared libraries. It can be used to wrap these libraries in pure Python.”
大意是ctypes是python的一个外部函数库,提供了c的兼容数据类型,允许调用DLL或者共享库中的函数。通过该模块能以纯python的代码对这些库进行封装(最后这句话不理解 =_=,看别人的文章,说是这样你就可以直接 import xxx 来使用这些函数库了)。
可知ctypes只提供了c的兼容,因此若是c++代码,需要使其以c的方式进行编译。
(想了解如何用c的方式编译c++代码,见链接:c方式编译c++)

二、使用
注明:本文均采用python2.7版本
新增了python3的调试,除了str编码内容,其余无需变动

看了文档,就要开始使用了。根据我遇到的使用场景,主要有3个步骤需要处理。
0_0
1.动态库的引入
这一步操作很简单,只需调用方法LoadLibrary,查看文档:
LoadLibrary(name):Load a shared library into the process and return it. This method always returns a new instance of the library.
加载动态库到进程中并返回其实例对象,该方法每次都会返回一个新的实例。
举个栗子,编写如下代码:

# -*- coding: utf-8 -*-
from ctypes import *#引入动态库libDemo.so
library = cdll.LoadLibrary("./libDemo.so")

2.函数的声明和调用
因为ctypes只能调用C编译成的库,因此不支持重载,需要在程序中显式定义函数的参数类型和返回类型。
不过在介绍前,需要先了解ctypes的基本数据类型

该表格列举了ctypes、c和python之间基本数据的对应关系,在定义函数的参数和返回值时,需记住几点:

  • 必须使用ctypes的数据类型
  • 参数类型用关键字argtypes定义,返回类型用restype定义,其中argtypes必须是一个序列,如tuple或list,否则会报错
  • 若没有显式定义参数类型和返回类型,python默认为int型

举个栗子:

/******C端代码*********/
#include<stdio.h>
#include<string.h>
#include<stdlib.h>#include "demo.h"int hello()
{printf("Hello world\n");return 0;
}

编写c端代码demo.c, 用GCC指令编译成动态库libDemo.so :gcc -fPIC -shared -o libDemo.so demo.c

编写python端代码linkTest.py进行调用:

# -*- coding: utf-8 -*-
from ctypes import *#引入动态库libDemo.solibrary = cdll.LoadLibrary("./libDemo.so")library.hello()

执行结果:

3.C语言和python之间数据类型的转换
这部分内容是当初学习的重点,踩了不少坑。

3.1 基本数据类型
这部分已经在上面提到过了,根据对应表格进行转换使用即可。
样例代码在上述demo.c和linkTest.py中继续添加:
C端代码:

......(省略上述代码)
int basicTest(int a, float b)
{printf("a=%d\n", a);printf("b=%f\n", b);return 100;
}

python端代码

def c_basic_test():library.basicTest.argtypes = [c_int, c_float]library.basicTest.restype = c_void_pa = c_int(10)b = c_float(12.34)library.basicTest(a, b)

执行结果:

3.1-增加 字符指针及数组类型

c代码:

void arrayTest(char * pStr, unsigned char *puStr)
{int i = 0;printf("%s\n", pStr);for (i = 0; i < 10; i++) {printf("%c ", puStr[i]);}printf("\n");
}

python代码:
该样例有几个注意点:
<1>使用了ctypes的create_string_buffer和from_buffer_copy函数。
create_string_buffer函数会分配一段内存,产生一个c_char类型的字符串,并以NULL结尾。
同理有个类似函数create_unicode_buffer函数,返回的是c_wchar类型

from_buffer_copy函数则是创建一个ctypes实例,并将source参数内容拷贝进去

<2>python2与python3的字符差异:
python2默认都是ascii编码,python3中str类型默认是unicode类型,而ctypes参数需传入bytes-like object(提示这么说的)。因此python3中的字符串都需转换编码,如样例所示。

def c_array_test():# 以下两方法皆可,但需显式说明c_ubyte数组的大小,个人感觉不方便,求指导# library.arrayTest.argtypes = [c_char_p, c_ubyte*16]library.arrayTest.argtypes = [c_char_p, POINTER(c_ubyte * 16)]library.arrayTest.restype = c_void_p# python2# str_info = create_string_buffer("Fine,thank you")# python3,str都是unicode格式,需转为其余编码,可使用encode函数或b前缀# 以下两种方法皆可str_info = create_string_buffer(b"Fine,thank you")str_info = create_string_buffer("Fine,thank you".encode('utf-8'))# 调用类型需配套u_str_info = (c_ubyte * 16).from_buffer_copy(b'0123456789abcdef')# library.arrayTest(str_info, u_str_info)library.arrayTest(str_info, byref(u_str_info))

执行结果:

3.2 指针

  • 一级指针

指针是c语言中的重要内容,难免要经常使用。因为我处理的主要是api接口的转换,涉及的指针处理就是定义指针类型、获取地址或值,而ctypes中都提供了相应的函数。
在上面的对应表格中,我们可以看到char * 和 void * 已经有专用类型了,直接使用即可,对于其他类型的指针,ctypes提供了两种定义方式:pointerPOINTER
查询文档:
ctypes.POINTER(type)
This factory function creates and returns a new ctypes pointer type. Pointer types are cached and reused internally, so calling this function repeatedly is cheap. type must be a ctypes type.

ctypes.pointer(obj)
This function creates a new pointer instance, pointing to obj. The returned object is of the type POINTER(type(obj)).

大意是POINTER必须传入ctypes类型,创建出新的ctypes 指针类型(pointer type),而pointer传入一个对象,创建出一个新的指针实例。可见POINTER创建出了pointer,我一般选择POINTER来使用(具体差别还没太多研究。。=_=).

传输地址,ctypes提供了byref函数:
ctypes.byref(obj[, offset])
Returns a light-weight pointer to obj, which must be an instance of a ctypes type. offset defaults to zero, and must be an integer that will be added to the internal pointer value.

The returned object can only be used as a foreign function call parameter. It behaves similar to pointer(obj), but the construction is a lot faster.

大意是返回一个指向ctypes实例对象的轻量级指针,函数中还可以通过参数(必须是int)来设置偏移地址,这个返回的对象只能用于外部函数调用的参数。

有了这几个函数,指针就能实现啦。见实例:
C端代码

void pointerTest(int * pInt, float * pFloat)
{*pInt = 10;*pFloat = 12.34;
}

python端代码

def c_pointer_test():library.pointerTest.argtypes = [POINTER(c_int), POINTER(c_float)]library.pointerTest.restype = c_void_pint_a = c_int(0)float_b = c_float(0)library.pointerTest(byref(int_a), byref(float_b))print("out_a:", int_a.value)print("out_b:", float_b.value)

可见我们在python中预设的值都在函数中被更改了,指针有效。

  • 补充样例:外部传输字符空间,在函数内部进行操作

c代码:

void mallocTest(char *pszStr)
{strcpy(pszStr, "Happay Children's Day!");
}

python代码:

def c_malloc_test():library.mallocTest.argtypes = [c_char_p]library.mallocTest.restype = c_void_pword = (c_char * 32)()library.mallocTest(word)print("out_word:", word.value)

执行成功!

  • 二级指针

在实际应用中,我处理的二级指针是在c端接口传送一个char*指针的地址作为参数,在接口中给char * 指针分配空间和赋值。
根据指针的功能,很容易实现,见实例。
c端接口代码

/**函数定义*/
void doublePointTest(int ** ppInt, char ** ppStr)
{printf("before int:%d\n", **ppInt);**ppInt = 10086;*ppStr = (char*)malloc(10 * sizeof(char));strcpy(*ppStr, "Happy  National Day!");
}
//释放函数
void freePoint(void *pt) {if (pt != NULL) {free(pt);pt = NULL;}
}/**函数调用,运行会输出接口中复制的字符串*/
int useDoublePoint()
{int num_e = 10;int * pInt = &num_e;char *pStr_b = NULL;doublePointTest(&pInt, &pStr_b);printf("after int:%d\n", *pInt);printf("out str:%s\n", pStr_b);//必须释放动态内存freePoint(pStr_b);
}

python端代码

# 函数定义及测试
def c_double_point_and_free_test():library.doublePointTest.argtypes = [POINTER(POINTER(c_int)), POINTER(c_char_p)]library.doublePointTest.restype = c_void_plibrary.freePoint.argtypes = [c_void_p]library.freePoint.restype = c_void_pint_a = c_int(10)# 注意POINTER和pointer的区别,因为POINTER必须指向ctypes类型,此处只能用pointerint_pt = pointer(int_a)word_pt = c_char_p()library.doublePointTest(byref(int_pt), byref(word_pt))print("out_word:", word_pt.value)# contents是指针内容print("out_value:", int_pt.contents.value)library.freePoint(word_pt)

执行结果:

另一个c端接口,需要传入一个char* 指针的地址,出参为这个指针指向的内容和内容长度。该接口用于处理图片的rgb值,因此内容都是0-255的值,样例代码如下

......(省略上述代码)
#define LENGTH 8
void doubleUnsignedPointerTest( char ** ppChar, int *num )
{int i;*num = LENGTH;*ppChar = (char*)malloc(LENGTH*sizeof(char));for(i=0;i<LENGTH;i++){(*ppChar)[i] = i;}
}

C端调用代码

 int num = 0, i;doubleUnsignedPointerTest(&pChar, &num);for(i=0;i<num;i++){printf("pChar[%d]:%d\n", i, pChar[i]);    }freePointer(pChar);

这时python端若使用c_char_p来构造类似上述的代码,在输出时会提示超出范围异常(out of range)。
查询了文档,原因在于:
class ctypes.c_char_p
Represents the C char * datatype when it points to a zero-terminated string…

(大意是c_char_p指针必须指向’\0’结尾的字符串。因此输出时遇到了第一个’\0’就中断了)

所以根据对应表格,使用POINTER嵌套构造了双重指针,使用后无误
python端代码

 ......(省略上述代码)library.doubleUnendPointerTest.argtypes = [POINTER(POINTER(c_byte)), POINTER(c_int)]library.doubleUnendPointerTest.restype = c_void_pnum = c_int(0)word_pt = POINTER(c_byte)()   #若使用c_char类型,在下面输出时会以字符类型输出,有乱码,因此选择c_bytelibrary.doubleUnendPointerTest(byref(word_pt), byref(num))print "num:", num.valuefor i in range(8):print "key:", i, " value:", word_pt[i]

执行结果:

3.3 结构体、共用体
结构体、共用体是c中常用类型,使用前需要先定义其成员类型,在python中也是同样的处理。查看文档:
Structures and unions must derive from the Structure and Union base classes which are defined in the ctypes module. Each subclass must define a fields attribute. fields must be a list of 2-tuples, containing a field name and a field type.
The field type must be a ctypes type like c_int, or any other derived ctypes type: structure, union, array, pointer.

大意是结构体和共用体必须继承Sturcture和Unino类,定义其成员必须使用_field_属性。该属性是一个list,其成员都是2个值的tuple,分别是每个结构体/共用体成员的类型和长度,而且定义类型必须使用ctype类型或由ctype组合而成的新类型。
以此写个样例:
c端代码

//结构体
typedef struct _rect
{int index;char info[16];
}Rect;

<1>读取结构体
C代码:

int readRect(Rect rect)
{printf("value=============\n");printf("index:%d\ninfo:%s\n", rect.index, rect.info);return 0;
}

python代码:


# 结构体
class Rect(Structure):_fields_ = [('index', c_int),('info', c_char * 16)]def c_read_rect():library.readRect.argtypes = [Rect]library.readRect.restype = c_void_prect_a = Rect(10, b"Hello")library.readRect(rect_a)

<2>读取结构体,传参为指针
C代码:

int readRectPoint(Rect * pRect)
{printf("point==============\n");printf("index:%d\n", pRect->index);printf("info:%s\n", pRect->info);return 0;
}

python代码:

def c_read_rect_point():library.readRectPoint.argtypes = [POINTER(Rect)]library.readRectPoint.restype = c_void_prect_a = Rect(10, b"Hello")library.readRectPoint(byref(rect_a))

<3>类似,可以传输结构体数组给动态库,实质是传输结构体数组指针,也就是首元素指针
C代码:

void readRectArray(Rect *pRectArray)
{int i;for(i=0;i<5;i++){printf("pRectArray.index:%d\n", pRectArray[i].index);printf("pRectArray.info:%s\n", pRectArray[i].info);}
}

python代码:

def c_read_rect_array():library.readRectArray.argtypes = [POINTER(Rect)]library.readRectArray.restype = c_void_prect_array = (Rect * 5)()for i in range(5):# python2# rect_array[i] = Rect(i, "Hello_" + str(i))# python3rect_array[i] = Rect(i, bytes("Hello_"+str(i), encoding='utf-8') )# 以下两方法皆可# library.readRectArray(rect_array)library.readRectArray(byref(rect_array[0]))

执行结果:

<4> 从动态库中获取结构体数组内容
C代码:

/**函数定义*/
Rect * obtainRectArray(int *pArrayNum)
{int num = 5;*pArrayNum = num;Rect *pArray = (Rect*)malloc(num * sizeof(Rect));for (int i = 0; i < num; i++) {pArray[i].index = i;sprintf(pArray[i].info,"%s_%d", "Hello", i);}return pArray;
}
//必须释放内存
void freeRect(Rect *pRect)
{free(pRect);
}/**c中调用方式*/
void testObtainRectArray(int *num)
{int num = 0;Rect *pstRectArray = obtainRectArray(&num);for (int i = 0; i < num; i++) {printf("index:%d\n", pstRectArray[i].index);printf("info:%s\n", pstRectArray[i].info);}freeRect(pstRectArray);
}

python代码:
ctypes的pointer有一个contents方法,通过该方法可以获取指针的内容。但在这个样例中,contents只能获取首元素的内容,后来才发现竟然能循环读取= =

def c_obtain_rect_array_and_free():library.obtainRectArray.argtypes = [POINTER(c_int)]# library.obtainRectArray.restype = Array()library.obtainRectArray.restype = POINTER(Rect)library.freeRect.argtypes = [POINTER(Rect)]library.freeRect.restype = c_void_pnum = c_int(10)rect_pt = library.obtainRectArray(byref(num))num = num.valueprint("num:", num)# 这种赋值方法是真的没想到,找了好久= = 之前一直在rect_pt.contents上面绕# rect_pt.contents只能输出首元素的内容,如rect_pt.contents.indexrect_array = [rect_pt[i] for i in range(num)]for item in rect_array:print("index:", item.index)print("info:", item.info)library.freeRect(rect_pt)

执行结果:

三、结语

通过文档,python调用C动态库还是比较容易实现的,只是第一次使用没有经验,摸索了一段时间。其实文档还有很多内容,待后续再慢慢学习吧。
附源码:https://pan.baidu.com/s/10dUqfrVQYYDDFTgNAa0Pmw
提取码: yctx

四、补充
1,动态库so中的函数,如fun_add,可能使用了c++的函数模块,直接增加extern "C"无法编译,可以再封装一层函数,如fun_c_add,该函数直接调用fun_add函数,则fun_c_add可以增加extern "C"编译通过,python也可调用;

2,特殊的函数指针,如unsigned char *,转化为python,函数的定义参数格式是POINTER(c_ubyte),较为复杂,可以用c_void_p类型代替。

Python调用c/c++动态库(一)相关推荐

  1. Rust应用调用C语言动态库

    外部功能接口FFI 虽然高级(脚本)编程语言的功能丰富,表达能力强,但对底层的一些特殊操作的支持并不完善,就需要以其他编程语言来实现.调用其他编程语言的接口,被称为Foreign Function I ...

  2. QT 调用Bartender C#动态库接口

    一.制作可供程序调用的条码标签 ********** 测试使用Bartender 软件版本信息 ********* 1.条码设置 (1)打开bartender建立一个模板文件 (2)标签界面布局 a. ...

  3. C#调用C/C++动态库dll异常:对 PInvoke 函数调用导致堆栈不对称问题

    结论:如果你是用C#调用C的动态库,如果出现"对 PInvoke 函数调用导致堆栈不对称问题",建议优先调整CallingConvention的值,建议改为CallingConve ...

  4. C#总结:C#调用C++的动态库Dll遇到的问题[动态库调用/结构体指针调用/union共同体定义]

    记录使用C#调用C++的生成的DLL手柄键盘驱动库包括****.sys(驱动文件)和****.dll(库文件)的全部问题. C#调用C++的库有两种:静态调用和动态调用 静态调用,使用.net 提供的 ...

  5. Golang 中通过 cgo 调用 C++ 的动态库的功能封装

    将C++warpper 文件写在go中: https://github.com/winlinvip/go-fdkaac/blob/master/fdkaac/dec.go https://github ...

  6. JAVA如何调用C/C++动态库

    一.调用方式: JAVA调用C/C++动态库有很多方法,常用的有JNI(Java Native Interface).JNA(Java Native Access). JNI:早在JAVA1.1版本就 ...

  7. go语言调用c语言动态库及交叉编译

    实现基础:CGO编程 C/C++经过几十年的发展,已经积累了庞大的软件资产,它们很多久经考验而且性能已经足够优化.Go语言必须能够站在C/C++这个巨人的肩膀之上,有了海量的C/C++软件资产兜底之后 ...

  8. C# VC6调用VC6的动态库DLL

    C# VC6调用VC6的动态库DLL 一 VC创建动态库 1. DLL的创建 启动VC6.0, 新建一个"Win32 Dynamic-Link Library"工程,选择" ...

  9. C#调用C/C++动态库Dll时几个注意事项:PInvoke错误

    经常需要封装一些C/C++函数放入动态库中给C#程序调用,通常情况下直接写成如下形式即可: C#封装调用: [DllImport("depressor.dll")] //错误调用方 ...

最新文章

  1. 阿里AI摘图像识别竞赛WebVision桂冠,万物识别准确率创世界纪录
  2. linux yum 本地源配置
  3. 停止linux下正在执行的ping命令
  4. 银泰抛弃传统数据库转投阿里云PolarDB 投入产出比增长2倍以上
  5. 优秀的软件测试人员必需具备的素质
  6. 二次开发-如何在PHPEMS-发送短信验证码(以easy-sms为例)
  7. 李航《统计学习方法》第四章课后答案链接
  8. 【转】CString 操作指南
  9. 学会了这条 Curl 命令实用小技巧,网站故障秒排除!
  10. 用计算机用语说唯美的话,好听唯美的说说句子
  11. 《线性代数》(同济版)——教科书中的耻辱柱
  12. 如何设计百度 豆丁 道客巴巴 下载器
  13. 利用python实现压韵(双压版)
  14. 分享一些光纤模块接口类型有用信息给大家
  15. 2021-1-1今日新闻简报 每天精选12条最新时事热点新闻摘要和1条微语
  16. jBPM4的运行期环境
  17. qq快捷登陆 php代码,qq互联--qq快捷登陆
  18. Python薅羊毛脚本
  19. JVM上篇_15-垃圾回收相关算法_尚硅谷
  20. 比尔·盖茨:关于新冠疫苗你需要知道的事

热门文章

  1. LaTeX 花体字母和数字
  2. iconfont 图标转为字体_阿里巴巴Iconfont矢量图转为字体图标的方法
  3. 【SpringBoot】整合消息中间件
  4. 《PR基础教程入门篇-学习笔记》-009
  5. 字符串合并python_Python合并字符串的3种方法
  6. 温室温度预测方案总结(万文详解)
  7. JAVA常用工具类(实用高效)
  8. WiFi6模组RW6852S-50
  9. 远程控制手机时,解决隐私屏黑屏的问题
  10. HTML网页表单学习(全方面详解)