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 ...
随机推荐
- Data - 关于大数据
历史与趋势 大数据的前世今生:诞生.发展.未来? 如何利用数据赚钱?大数据价值变现的10种商业模式及利弊分析 10大行业大数据应用痛点及解决策略 大数据凉了?不,流式计算浪潮才刚刚开始 概念与定义 关 ...
- Liferay7 BPM门户开发之7: Activiti中的重要概念和主要数据库结构
流程的人员参与角色: Assignee :签收者(即待办人) Candidate:候选人 Owner:拥有者 Starter:启动者 participant:参与者,包含查阅 流程变量的类型: Str ...
- Strom
storm 实时分析概念 离线分析 通常是 需要一段时间的数据积累 积累到一定数量数据后 开始离线分析 无论数据量多大 离线分析 有开始 也有结束 最终得到 ...
- 递归查询区域信息及子区域到advTree
效果: DataTable dtArea = new DataTable(); private void Form1_Load(object sender, EventArgs e) { Node n ...
- Hadoop项目实战-用户行为分析之分析与设计
1.概述 本课程的视频教程地址:<用户行为分析之分析与设计> 下面开始本教程的学习,本教程以用户行为分析案例为基础,带着大家对项目的各个指标做详细的分析,对项目的整体设计做合理的规划,让大 ...
- leetcode — two-sum-ii-input-array-is-sorted
import java.util.ArrayList; import java.util.List; /** * Source : https://oj.leetcode.com/problems/m ...
- win32进程概念之句柄表,以及内核对象.
句柄表跟内核对象 一丶什么是句柄表什么是内核对象. 1.句柄表的生成 我们知道.我们使用CreateProcess 的时候会返回一个进程句柄.以及线程句柄. 其实在调用CreateProcess的时候 ...
- ZooKeeper概念与应用
Zookeeper是开源的分布式协调服务,提供了分布式数据一致性的解决方案. Zookeeper 可用作配置中心和分布式锁服务,在 Dubbo.Kafka.Spark等分布式集群上得到广泛应用. ZN ...
- [转]angular2封装material2对话框组件
本文转自:https://www.jianshu.com/p/da9978e25566 1. 说明 angular-material2自身文档不详,控件不齐,使用上造成了很大的障碍.这里提供一个方案用 ...
- .net实现支付宝在线支付
流程参考<实物商品交易服务集成技术文档2.0.pdf>网关地址http://paytest.rupeng.cn/AliPay/PayGate.ashx 网关参数说明:partner:商户编 ...