python ctypes 探究 ---- python 与 c 的交互
近几天使用 python 与 c/c++ 程序交互,网上有推荐swig但效果都不理想,所以琢磨琢磨了 python 的 ctypes 模块。同时,虽然网上有这方面的内容,但是感觉还是没说清楚。这里记录下来做备用,同时也给广大 python with c/c++ 派留给方便。如果你觉得我写的不好,可以参考官方文档里对 ctypes 的介绍,那里说不一定有你想要的。
如有错误,请指正:)。
测试环境: win 8.1, Visual Studio 2010, Python 3.5
一、介绍
python 与 c/c++ 交互的主要目的一是为了速度,二大概就是用做脚本了。
说是 python 与 c/c++ 交互,但实际上是 python 与 c 交互, 因为 python 本身只支持 C API。但是我们可以通过调整达到 python 与 c++ 工程协作的目的。下面主要说明 python 使用 ctypes 模块与 c 交互的要点和疑难点。
二、使用 ctypes 可以做到什么?
python 可以通过使用 ctypes 模块调用 c 函数,这其中必定包括可以定义 c 的变量类型(包括结构体类型、指针类型)。
官方给的定义是 “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.” —— 引自 Python 3.5 chm 文档。其大意就是——ctypes 是一个为 Python 准备的外部函数库。它提供兼容C的数据类型,并允许调用DLL或共享库中的函数。通过它,可以使用纯粹的 Python 包装这些函数库(这样你就可以直接 import xxx 来使用这些函数库了)。
口说无凭,我们需要一个具体的例子,下面我们引入一个 cpp 文件来说明以下所有问题:
现有 test.cpp 文件如下:
#if 1
#define DLL_API __declspec(dllexport)
#else
#define DLL_API __declspec(dllimport)
#endif #include <stdio.h>
#include <stdlib.h> // Point 结构体
struct Point
{
float x, y;
}; static Point* position = NULL; extern "C" { DLL_API int add(int a, int b)
{
return a + b;
} DLL_API float addf(float a, float b)
{
return a + b;
} DLL_API void print_point(Point* p)
{
if (p)
printf("position x %f y %f", p->x, p->y);
}
}
可以看见这里有三个函数,包括一个形参带指针的函数。学会用 Python 成功调用上面的三个函数就是我的本文的目标了。对于windows平台把他生成为 dll 文件就行(其他平台为 .so)。下面我们在解释器中写出出测试用的 Python 代码。
如果你不理解上面的 cpp 文件,那还是先看看其他关于 dll 的文章吧:
1. Dll的分析与编写(一) http://www.cnblogs.com/hicjiajia/archive/2010/08/27/1809997.html
2. extern "C"的用法解析 http://www.cnblogs.com/rollenholt/archive/2012/03/20/2409046.html
三、ctypes 怎么样调用 c 的函数库?
首先,需要 ctypes 加载需要被调用的函数库(废话)。
使用 ctypes.CDLL ,其定义如下(引自 Python 3.5 chm 文档 )
ctypes.
CDLL
(name, mode=DEFAULT_MODE, handle=None, use_errno=False, use_last_error=False)
另外,在 windows 平台上会忽略 modes 参数。对于 windows 平台来说还可以调用 ctypes.WinDLL,与上面的 CDLL 几乎一样,唯一不同点是它假定库中函数遵循 Windows stdcall 调用约定,其他参数的意义见官方文档。
如果要调用 test.dll 中的 add 函数可以写作 :
>>> from ctypes import *
>>> dll = CDLL(“test.dll”) # 调用 test.dll
>>> dll.add(10, 30) # 调用 add 函数
40
可以看见返回了 40,是不是很简单?。这是就是我们预期的结果。下面我们再调用 addf 这是 add 的 float 版本,有些人可能会问为什么不直接写 DLL_API float add(float a, float b) ? 用函数的重载就好了,为什么不这么做?注意,我们使用了 extern“C”声明函数,所以不支持函数的重载。
接下来我们调用 addf , 猜猜会发生什么?
>>> dll.addf(10, 30)
9108284
哦,这是不是有点出乎你的意料?为什么会这样?
四、c 类型与 Python 类型, 参数类型、返回类型
之所以会调用 addf 函数“失败”倒不是 Python 出了问题。原因是你没有“告诉” Python 这个函数的“容貌”(更正式的说法是“描述”)——函数的形参类型和返回类型。那么为什么我们调用 add 成功了呢?因为 Python 默认函数的参数类型和返回类型为 int 型。理所当然地 Python 以为 addf 返回了一个 int 类型的值。
也就是说,在 ctypes 读取 dll 时只知道存在这个函数,但是并不知到函数的形参类型和返回值的类型。你可能会疑惑为什么 Python 这么麻烦,还要告诉它共享库中函数的“容貌”。这就不能怪它了,事实上,就是 Microsoft 自己开发的 C# 语言在调用 dll 的时候都需要告诉 C# 这个函数是什么样子的。这解释起来有点烦,还是来专注于我们对 ctypes 用法的研究吧。
那么,对于 Python 来说 c 的类型都有哪些呢?下面就是一张 Python 中的类型对应 c 类型的表(截图自 Python 3.5 chm 文档)
然后,怎么告诉 Python 一个外来函数的形参类型和返回的值的类型呢?
这就要需要给函数的两个属性 restype 和 argtypes 赋值了。它们分别对应返回类型和参数类型。对于 addf 它的返回值类型是 float, 对应到 Python 里就是 c_float。下面我们进行赋值:
>>> dll.addf.restype = c_float # addf 返回值的类型是 flaot
如果函数的返回值是 void 那么你可以赋值为 None。另外,在不是太低的版本中,可以使用 Python 内置类型(上表中最右边的一列)“描述”库函数的返回类型,但是,不可以用 Python 内置类型来描述库函数的参数。
由于函数的参数不是固定的数量,所以需要使用列表或者是元组来说明:
>>> dll.addf.argtypes = (c_float, c_float) # addf 有两个形参,都是 float 类型
或者是下面这样,但是,你知道的,查找元组的效率略高:)
>>> dll.addf.argtypes = [c_float, c_float] # addf 有两个形参,都是 float 类型
该做的都做完了,现在再来调用 addf:
>>> dll.addf(8, 3)
11.0
>>> dll.addf(8.3, 3.1)
11.399999618530273
这就是我们想要的结果。
五、更多地关于 ctypes 类型的创建和使用
我们也可以创建一个 ctypes 的类型(c_int、c_float、c_char……)并给他赋值,例子如下:
>>> i = c_int(45) # 定义一个 int 型变量,值为 45
>>> i.value # 打印变量的值
45
>>> i.value = 56 # 改变该变量的值为 56
>>> i.value # 打印变量的新值
56
没错,你要通过 ctypes 的 value 属性给一个 ctypes 类型赋值——赋一个 Python 内置类型的值。
其他的 ctypes 的函数,如 sizeof(i)(是不是感觉很贴心就像 c 一样),就不一一介绍了。自行参见文献第三条和官方文档吧。
六、结构体、共用体
这是调用 print_point 库函数的必要成分之一。
如果要在 Python 中定义一个 c 类型的结构体,需要定义一个类,例如 Structu Point 就这么做:
>>> class Point(Structure):
... _fields_ = [("x", c_float), ("y", c_float)]
...
>>>
这就定义好了。其中有两个要点:
1. 类必须继承自 ctypes.Structure
2. 描述这个结构体的“容貌”
第一点很简单, class XXX(Structure) 就 OK。
要做到第二点,则必须在自定义的 c 结构体类中定义一个名为 _fields_ 的属性,并赋值给如上的一个列表。
然后就可以这样使用了:
>>> p = Point(2,5) # 定义一个 Point 类型的变量,初始值为 x=2, y=5 也可以直接写 p = Point()
>>> p.y = 3 # 修改值
>>> print (p.x, p.y) # 打印变量
2 3
而对于共用体只要类继承自 ctypes.Union 就成,其他与上面相同。
七、指针
这就是最后一节了,虽然是指针,不过别紧张,且听我娓娓道来。
如何创建一个 ctypes 的指针呢?这里有三个跟指针有个的 ctypes 里的函数,掌握了他们你自然就会了(可能 pointer POINTER 会有点绕,仔细看看就好)。
函数 | 说明 |
byref(x [, offset]) | 返回 x 的地址,x 必须为 ctypes 类型的一个实例。相当于 c 的 &x 。 offset 表示偏移量。 |
pointer(x) | 创建并返回一个指向 x 的指针实例, x 是一个实例对象。 |
POINTER(type) | 返回一个类型,这个类型是指向 type 类型的指针类型, type 是 ctypes 的一个类型。 |
byref 很好理解,传递参数的时候就用这个,用 pointer 创建一个指针变量也行,不过 byref 更快。
而 pointer 和 POINTER 的区别是,pointer 返回一个实例,POINTER 返回一个类型。甚至你可以用 POINTER 来做 pointer 的工作:
>>> a = c_int(66) # 创建一个 c_int 实例
>>> b = pointer(a) # 创建指针
>>> c = POINTER(c_int)(a) # 创建指针
>>> b
<__main__.LP_c_long object at 0x00E12AD0>
>>> c
<__main__.LP_c_long object at 0x00E12B20>
>>> b.contents # 输出 a 的值
c_long(66)
>>> c.contents # 输出 a 的值
c_long(66)
pointer 创建的指针貌似没方法修改指向的 ctypes 类型值。
该说的都说了,接下来就要调用 print_point 函数了:
>>> dll.print_point.argtypes = (POINTER(Point),) # 指明函数的参数类型
>>> dll.print_point.restype = None # 指明函数的返回类型
>>>
>>> p = Point(32.4, -92.1) # 实例化一个 Point
>>> dll.print_point(byref(p)) # 调用函数
position x 32.400002 y -92.099998>>>
当然你非要用慢一点的 pointer 也行:
>>> dll.print_point(pointer(p)) # 调用函数
position x 32.400002 y -92.099998>>>
至于为什么输出的后面出现了畸形 “y -92.099998>>>” ,去翻一翻上面的 c 代码你就知道了。
参考文献
更多关于 ctypes 类型的用法可以参加下面的书籍、文档和网页:
1. 《Python参考手册》
2. Python 3.5 官方文档 “python350.chm”
3. http://www.ibm.com/developerworks/cn/linux/l-cn-pythonandc/
python ctypes 探究 ---- python 与 c 的交互的更多相关文章
- [转]python ctypes 探究 ---- python 与 c 的交互
近几天使用 python 与 c/c++ 程序交互,网上有推荐swig但效果都不理想,所以琢磨琢磨了 python 的 ctypes 模块.同时,虽然网上有这方面的内容,但是感觉还是没说清楚.这里记录 ...
- Python ctypes 在 Python 2 和 Python 3 中的不同 // 使用ctypes过程中问题汇总
In Python 2.7, strings are byte-strings by default. In Python 3.x, they are unicode by default. Try ...
- Python搭建Web服务器,与Ajax交互,接收处理Get和Post请求的简易结构
用python搭建web服务器,与ajax交互,接收处理Get和Post请求:简单实用,没有用框架,适用于简单需求,更多功能可进行扩展. python有自带模块BaseHTTPServer.CGIHT ...
- 使用 ctypes 进行 Python 和 C 的混合编程
Python 和 C 的混合编程工具有很多,这里介绍 Python 标准库自带的 ctypes 模块的使用方法. 初识 Python 的 ctypes 要使用 C 函数,需要先将 C 编译成动态链接库 ...
- Python下探究随机数的产生原理和算法
资源下载 #本文PDF版下载 Python下探究随机数的产生原理和算法(或者单击我博客园右上角的github小标,找到lab102的W7目录下即可) #本文代码下载 几种随机数算法集合(和下文出现过的 ...
- 总结:Python学习 和 Python与C/C++交互
本篇仅仅是Python的学习和Python和C++数据对接过程中的一些总结. 由于工作的需要,用一周的时间学习 Python. Python是基于C实现的一门解释型语言,由于其易用性,俘获了不少开发者 ...
- 使用ctypes在Python中调用C++动态库
使用ctypes在Python中调用C++动态库 入门操作 使用ctypes库可以直接调用C语言编写的动态库,而如果是调用C++编写的动态库,需要使用extern关键字对动态库的函数进行声明: #in ...
- 聊聊Python ctypes 模块(转载)
https://zhuanlan.zhihu.com/p/20152309?columnSlug=python-dev 作者:Jerry Jho链接:https://zhuanlan.zhihu.co ...
- [Python]ctypes+struct实现类c的结构化数据串行处理
1. 用C/C++实现的结构化数据处理 在涉及到比较底层的通信协议开发过程中, 往往需要开发语言能够有效的表达和处理所定义的通信协议的数据结构. 在这方面是C/C++语言是具有天然优势的: 通过str ...
随机推荐
- Spark基础脚本入门实践2:基础开发
1.最基本的Map用法 val data = Array(1, 2, 3, 4, 5)val distData = sc.parallelize(data)val result = distData. ...
- HTML编辑器 -- KindEditor
KindEditor 是一套开源的在线HTML编辑器,主要用于让用户在网站上获得所见即所得编辑效果,开发人员可以用 KindEditor 把传统的多行文本输入框(textarea)替换为可视化的富文本 ...
- UIAutomatorViewer增加xpath查看
原来的UIAutomatorViewer是没有xpath查看路径的,下载如下jar包: https://pan.baidu.com/s/1jpr6m0OOce7CtnDE_lIJ9A 密码:cja0 ...
- 用Eclipse导入Maven工程
步骤一 : 选择 “Import”操作 有两个途径可以选择 “Import”操作; 1>“File”--> "Import..." 2> 在 "Pro ...
- 开源API测试工具 Hitchhiker v0.10 - 中文版
Hitchhiker 是一款开源的支持多人协作的 Restful Api 测试工具,支持自动化测试, 数据对比,压力测试,支持脚本定制请求,可以轻松部署到本地,和你的team成员一起协作测试Api. ...
- Linux学习笔记之五————Linux常用命令之用户、权限管理
一.引言 用户是Unix/Linux系统工作中重要的一环,用户管理包括用户与组账号的管理. 在Unix/Linux系统中,不论是由本机或是远程登录系统,每个系统都必须拥有一个账号,并且对于不同的系统资 ...
- 课程三(Structuring Machine Learning Projects),第二周(ML strategy(2)) —— 1.Machine learning Flight simulator:Autonomous driving (case study)
[中文翻译] 为了帮助您练习机器学习的策略, 在本周我们将介绍另一个场景, 并询问您将如何行动.我们认为, 这个工作在一个机器学习项目的 "模拟器" 将给一个任务, 告诉你一个机器 ...
- Build step 'Execute shell' marked build as failure解决
今天jenkins构建时运行脚本报错如下: Build step 'Execute shell' marked build as failure 脚本没问题后来看了下原因是磁盘空间不足导致报错,清除下 ...
- vue-01
1, vue优势 虚拟daom, 易用, 灵活, 高效 2, 介绍 渐进式框架 3, 兼容性 es5的星特性, 不支持ie8 4, 新版本内置 webpack
- WCF返回表datatable时的解决
在WCF中有时返回值类型是一张表,就会遇到反序列化的问题.解决该问题我归纳了两种方法: 1.根据Model层的类,在服务器端将Table转化成List,在客户端再将List转化为table /// 将 ...