python代码⽤c语⾔封装_python调⽤C语⾔接⼝
python调⽤C语⾔接⼝
注:本⽂所有⽰例介绍基于linux平台
在底层开发中,⼀般是使⽤C或者C++,但是有时候为了开发效率或者在写测试脚本的时候,会经常使⽤到python,所以这就涉及到⼀个问题,⽤C/C++写的底层库,怎么样直接被python来调⽤?
python作为⼀门胶⽔语⾔,当然有办法来处理这个问题,python提供的⽅案就是ctypes库。
ctypes
ctypes是python的外部函数库,它提供了C语⾔的兼容类型,⽽且可以直接调⽤⽤C语⾔封装的动态库。
如果各位有较好的英语⽔平,可以参考ctypes官⽅⽂档,但是我会给出更详细的⽰例,以便各位更好地理解。
库的封装
C代码如果要能够被python调⽤,⾸先我们先得把被调⽤C接⼝封装成库,⼀般是封装成动态库。编译动
态库的指令是这样的:
gcc --shared -fPIC -o target.c libtarget.so
在这⾥,
--shared -fPIC 是编译动态库的选项。
-o 是指定⽣成动态库的名称
在linux下,⼀般的命名规则是:静态库为lib.a,动态库为lib.so
target.c为⽬标⽂件,在编译时常有更复杂的调⽤关系和依赖,这⾥就不详说,有兴趣的朋友可以去了解了解gcc编译规则。
在python中导⼊库
既然库已经封装好了,那肯定是就想把它⽤起来。我们可以在python中导⼊这个库,以导⼊libtarget.so为例:
import ctypes
target = cdll.LoadLibrary("./libtarget.so")
顺带提⼀下,如果在windows环境下,动态库⽂件是.dll⽂件,例如导⼊libtarget.dll:
import ctypes
target = windll.LoadLibrary("./libtarget.dll")
在这⾥,可以将target看成是动态库的⽰例,直接可以以变量target来访问动态库中的内容。
LoadLibrary("./libtarget.so")表⽰导⼊同⽬录下的libtarget.so⽂件。
细⼼的朋友已经发现了,在导⼊时,linux环境下使⽤的是cdll,⽽windows环境下使⽤的是windll。
这⾥涉及到C语⾔的调⽤约定,gcc使⽤的调⽤约定是cdecl,windows动态库⼀般使⽤stdcall调⽤约定,既然是调⽤约定,就肯定是关于调⽤时的规则,他们之间的主要区别就是cdecl调⽤时由调⽤者清除被调⽤函数栈,⽽stdcall规定由被调⽤者清除被调⽤函数栈。
关于这个就不在这⾥赘述了,有兴趣的朋友可以看看我另外⼀篇博客:栈帧结构以及函数调⽤约定
hello world!
学会了封装动态库,学会了导⼊库,接下来我们就要动⼿写⼀个hello_world,毕竟学会了hello_world就算是⼊门了。
代码如下:
target.c:
#include
void hello_world(void)
{
printf("hello downey!!\r\n");
}
编译动态库:
gcc -fPIC --shared target.c -o libtarget.so
test.py:
from ctypes import *
test = cdll.LoadLibrary("./libtarget.so")
test.hello_world()
执⾏python脚本:
python test.py
输出结果:
hello downey!!
虽然这些代码都是⾮常简单,但是我还是准备梳理⼀下流程:
在target.c中我们定义了函数hello_world(),然后将其封装成动态库。
在test.py中导⼊libtarget.so动态库,然后调⽤动态库中的hello_world()函数,结果显⽽易见,执⾏了hello_world().
是不是⾮常简单,是的,python调⽤C程序就是这么简单,但是可别忘了,⼊门简单可并不代表真正使⽤起来简单!
我们可以想⼀想,上⾯的hello_world()函数没有参数和返回值,如果是⼀个带参数或者带返回值的C函数呢,python该怎么调⽤?
python的内建类型中可没有C语⾔那么多花⾥胡哨的类型,在python中怎么去区分int,short,char这些类型呢?
类型转换
针对上⾯的问题,python定义了⼀系列兼容C语⾔的类型
如图所⽰,这个图算是很清晰地将python与C类型对应关系展现了出来。我们将要使⽤的就是最左边⼀列的ctypes type,以替代C库中的各种类型。
函数带参⽰例
对于程序员⽽⾔,看图⽚看⽂档永远没有看代码来得直接,所以在这⾥先上⼀段演⽰代码,看看在C库中的类型是怎么被替换的,但是凡事讲究个循序渐进,我们先来⼀个简单的,普通变量版的,代码
如下:
较为简单的⽰例
target.c:
#include
char hello_world(int num)
{
printf("hello %d!!\r\n",num);
return (char)num+1;
}
test.py:
1 from ctypes import *
2 test = cdll.LoadLibrary("./libtarget.so")
3 test.stype = c_char
4 c = test.hello_world(48)
5 print(type(c))
6 print(c)
输出:
hello 48!!
1
C语⾔代码我就不多解释,我们主要来关注python部分:
第1、2⾏不⽤解释了吧
第3⾏:这条指令的作⽤是指定函数的返回值,python解释器并不能⾃动识别C函数的返回值,所以我们需要⼈为地指定,如果不指定,将会是默认的int型。
第4⾏调⽤函数,并传⼊参数48,第五⾏打印返回值的类型,第六⾏打印返回值。
我们再来看输出部分:
第⼀⾏是hello_world()函数的输出。
第⼆⾏打印出来的返回值类型明显是不对的,明明指定了返回值类型为c_char,为什么在这⾥变成了str(字符串)类型,⽽且在第三⾏的输出中输出了1,⽽不是49。原因有以下⼏点:
在python中,内置的类型有int, float,list, tuple等等,但并不包含char类型,既然程序中c是python中的变量,必然将会被转换,⽽且与C不⼀样的是,所有变量都是对象。
如果是需要转换,那会遵循什么规则呢?我们只好从官⽅⽂档中答案,原⽂是这样的:
Represents the C char datatype, and interprets the value as a single character. The constructor accepts an optional string initializer, the length of the string must be exactly one character.
翻译就是,c_char代表C中的char,在python中被视为单个字符,构造函数接受可选的字符串初始值设定项,字符串的长度必须恰好是⼀个字符。通俗地说,就是⼀个字符的字符串。
为什么输出1⽽不是49,这个就很简单了,⼗进制的49就是字符1,既然是被视为字符,当然以字符显⽰
其实在这⾥,博主选取了⼀个⽐较特殊的例⼦,就是char在python中转换的特殊性,各位朋友可以思考下⾯两个问题:
如果在hello_world函数中,将返回值从char改成short,输出是什么?(当然test.py中的第三⾏也要将c_char改为c_short)
接上题,如果将返回值从char改为float,输出将是什么?
⾃⼰动⼿试试,如果在test.py中不指定函数返回值类型,输出将会是什么?
进阶版
如果你看完了上⾯那个简单版的函数参数转换,我们进⼊进阶版的。在这个进阶版的⽰例中,将引⼊数组,指针,结构体。不说了,直接上码:
target.c:
#include
#include
typedef struct{
char *ptr;
float f;
char array[10];
}target_struct;
target_struct* hello_world(target_struct* target)
{
// printf("hello %s.%d!!\r\n",name,num[0]);
static char temp = 0x30;
target->ptr = &temp;
target->f = 3.1;
memset(target->array,1,sizeof(target->array));
return target;
}
test.py:
1 from ctypes import *
2 test = cdll.LoadLibrary("./libtarget.so")
3 class test_struct(Structure):
4 _fields_ = [('ptr',c_char_p),
5 ('c',c_float),
6 ('array',c_char*10)]
7 struct = test_struct(c = 0.5)
8 test.stype =POINTER(test_struct)
9 ret_struct = test.hello_world(pointer(struct))
10 print ts.ptr
11 print ts.c
输出:
3.0999*******
对于target.c不多说,⼤家肯定看得懂,我们还是主要来对照分析⼀下test.py的内容:
第1、2⾏不⽤解释,⼤家都懂
第3-6⾏才是重头戏,这就是python中对结构体的⽀持,新建⼀个类,继承Structure,将C中结构体内容⼀⼀对应地在类中进⾏声明,你可以将这个类看成是对应C库中的结构体,_fields_是字典类型,key要与C库中结构体相对应,value则是指定相应类型,在⽰例中⼤家应该能看懂了。
第7⾏,构造⼀个对应C中结构体的类,可以传⼊对应参数进⾏构造。
第8⾏,指定返回值类型为test_struct指针类型,这⾥的类型由POINTER()修饰,表⽰是指针类型。
第9⾏,调⽤hello_world()函数,传⼊struct类,pointer(struct)就是将struct转为指针类型实例。因为在C中的接⼝就是传⼊
target_struct类型,返回target_struct类型,所以ret_struct也是target_struct*类型
第10、11⾏,打印函数返回值,查看执⾏结果。对于⼀个指针类型的变量,如果我们要获取它的值,可以使⽤.contents⽅法,例如
ts返回结构体类⽰例,然后再访问结构体类中的元素。
输出结果,因为在hello_world中元素ptr指向变量的值为0x30,所以输出1,⽽float类型c被赋值为3.1,但是输出3.0999*******,这其实并不是bug,只能算是python中对浮点数的取值精度问题,这⾥就不展开讨论了。python新手代码例子
⼩结
经过这两个⽰例,我相信⼤家对ctypes的使⽤有了⼀个⼤概的认识,但是我建议⼤家看过之后⾃⼰多尝试尝试,这样才有更深的体会,这⾥再做⼀个总结:
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论