注:本文全部示例介绍基于linux平台html
在底层开发中,通常是使用C或者C++,可是有时候为了开发效率或者在写测试脚本的时候,会常用到python,因此这就涉及到一个问题,用C/C++写的底层库,怎么样直接被python来调用?python
python做为一门胶水语言,固然有办法来处理这个问题,python提供的方案就是ctypes库。linux
ctypes是python的外部函数库,它提供了C语言的兼容类型,并且能够直接调用用C语言封装的动态库。
若是各位有较好的英语水平,能够参考ctypes官方文档,可是我会给出更详细的示例,以便各位更好地理解。程序员
C代码若是要可以被python调用,首先咱们先得把被调用C接口封装成库,通常是封装成动态库。编译动态库的指令是这样的:编程
gcc --shared -fPIC -o target.c libtarget.so
在这里,windows
--shared -fPIC 是编译动态库的选项。数组
-o 是指定生成动态库的名称框架
在linux下,通常的命名规则是:静态库为lib.a,动态库为lib.so模块化
target.c为目标文件,在编译时常有更复杂的调用关系和依赖,这里就不详说,有兴趣的朋友能够去了解了解gcc编译规则。函数
既然库已经封装好了,那确定是就想把它用起来。咱们能够在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就算是入门了。
代码以下:
target.c:
#include <stdio.h> 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!!
虽然这些代码都是很是简单,可是我仍是准备梳理一下流程:
是否是很是简单,是的,python调用C程序就是这么简单,可是可别忘了,入门简单可并不表明真正使用起来简单!
咱们能够想想,上面的hello_world()函数没有参数和返回值,若是是一个带参数或者带返回值的C函数呢,python该怎么调用?
python的内建类型中可没有C语言那么多花里胡哨的类型,在python中怎么去区分int,short,char这些类型呢?
针对上面的问题,python定义了一系列兼容C语言的类型
如图所示,这个图算是很清晰地将python与C类型对应关系展示了出来。咱们将要使用的就是最左边一列的ctypes type,以替代C库中的各类类型。
对于程序员而言,看图片看文档永远没有看代码来得直接,因此在这里先上一段演示代码,看看在C库中的类型是怎么被替换的,可是凡事讲究个按部就班,咱们先来一个简单的,普通变量版的,代码以下:
target.c:
#include <stdio.h> 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.hello_world.restype = c_char 4 c = test.hello_world(48) 5 print(type(c)) 6 print(c)
输出:
hello 48!! <type 'str'> 1
C语言代码我就很少解释,咱们主要来关注python部分:
咱们再来看输出部分:
若是是须要转换,那会遵循什么规则呢?咱们只好从官方文档中找答案,原文是这样的:
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.为何输出1而不是49,这个就很简单了,十进制的49就是字符1,既然是被视为字符,固然以字符显示
其实在这里,博主选取了一个比较特殊的例子,就是char在python中转换的特殊性,各位朋友能够思考下面两个问题:
若是你看完了上面那个简单版的函数参数转换,咱们进入进阶版的。在这个进阶版的示例中,将引入数组,指针,结构体。不说了,直接上码:
target.c:
#include <stdio.h> #include <string.h> 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.hello_world.restype =POINTER(test_struct) 9 ret_struct = test.hello_world(pointer(struct)) 10 print ret_struct.contents.ptr 11 print ret_struct.contents.c
输出:
0 3.09999990463
对于target.c很少说,你们确定看得懂,咱们仍是主要来对照分析一下test.py的内容:
通过这两个示例,我相信你们对ctypes的使用有了一个大概的认识,可是我建议你们看过以后本身多尝试尝试,这样才有更深的体会,这里再作一个总结:
POINTER()和pointer(),这两个方法,一个大写一个小写,你们在上面的例子中有看到,博主刚接触的时候也是一脸懵逼,后来查了一下官方文档,而后本身尝试了一遍,终于理解了它们之间的区别,这里贴上官方说明:
POINTER():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.
pointer():This function creates a new pointer instance, pointing to obj. The returned object is of the type POINTER(type(obj)).
Note: If you just want to pass a pointer to an object to a foreign function call, you should use byref(obj) which is much faster.
对于数组,其实也挺简单,你们能够参考上面示例,我相信你们能看懂。
在参数类型中,还有一种很是特殊的存在——函数指针,在C语言中,咱们常常将函数指针做为参数来实现回调函数,这种作法在各类标准化框架中常常见到,在模块化编程中也是很是实用。
那么问题来了,C库中的函数实现了回调函数,python调用时该怎么作?
按照咱们对C语言的理解,其实函数指针也是指针的一种,咱们能够将一个指针强制转换成函数指针类型而后执行,而后博主就在python中尝试了一下,结果不论是我试图将什么类型的指针转换成函数执行,结果都是这样的:
TypeError: XXXX object is not callable
好吧,我仍是老老实实地使用官方提供的接口,仍是直接上码:
target.c:
#include <stdio.h> typedef void (*callback)(int); void func(callback c1,callback c2,int p1,int p2) { c1(p1); c2(p2); }
test.py:
1 from ctypes import * 2 test = cdll.LoadLibrary("./libtarget.so") 3 def test_callback1(val): 4 print "I'm callback1" 5 print val 6 def test_callback2(val): 7 print "I'm callback1" 8 print val 9 CMPFUNC = CFUNCTYPE(None, c_int) 10 cbk1 = CMPFUNC(test_callback1) 11 cbk2 = CMPFUNC(test_callback2) 12 test.func(cbk1,cbk2,1,2)
输出:
I'm callback1 1 I'm callback1 2
能够看到,在target.c中func函数传入了两个函数指针参数,而后在函数中调用这两个函数。
咱们再来仔细分析python中的调用:
好了,关于python ctypes调用C代码的问题就到此为止了,若是朋友们对于这个有什么疑问或者发现有文章中有什么错误,欢迎留言
我的邮箱:linux_downey@sina.com
原创博客,转载请注明出处!
祝各位早日实现项目丛中过,bug不沾身. (完)