近几天使用 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(namemode=DEFAULT_MODEhandle=Noneuse_errno=Falseuse_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(, ) # 调用 add 函数

可以看见返回了 40,是不是很简单?。这是就是我们预期的结果。下面我们再调用 addf 这是 add 的 float 版本,有些人可能会问为什么不直接写 DLL_API float add(float a, float b) ? 用函数的重载就好了,为什么不这么做?注意,我们使用了 extern“C”声明函数,所以不支持函数的重载。

接下来我们调用 addf , 猜猜会发生什么?

>>> dll.addf(, )

哦,这是不是有点出乎你的意料?为什么会这样?

四、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(, )
11.0
>>> dll.addf(8.3, 3.1)
11.399999618530273

这就是我们想要的结果。

五、更多地关于 ctypes 类型的创建和使用

我们也可以创建一个 ctypes 的类型(c_int、c_float、c_char……)并给他赋值,例子如下:

>>> i = c_int()                        # 定义一个 int 型变量,值为
>>> i.value # 打印变量的值 >>> i.value = # 改变该变量的值为
>>> i.value # 打印变量的新值

没错,你要通过 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(,)          # 定义一个 Point 类型的变量,初始值为 x=, y= 也可以直接写 p = Point()
>>> p.y = # 修改值
>>> print (p.x, p.y) # 打印变量

而对于共用体只要类继承自 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()         # 创建一个 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()
>>> c.contents # 输出 a 的值
c_long()

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”

[转]python ctypes 探究 ---- python 与 c 的交互的更多相关文章

  1. python ctypes 探究 ---- python 与 c 的交互

    近几天使用 python 与 c/c++ 程序交互,网上有推荐swig但效果都不理想,所以琢磨琢磨了 python 的 ctypes 模块.同时,虽然网上有这方面的内容,但是感觉还是没说清楚.这里记录 ...

  2. 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 ...

  3. 使用 ctypes 进行 Python 和 C 的混合编程

    Python 和 C 的混合编程工具有很多,这里介绍 Python 标准库自带的 ctypes 模块的使用方法. 初识 Python 的 ctypes 要使用 C 函数,需要先将 C 编译成动态链接库 ...

  4. Python下探究随机数的产生原理和算法

    资源下载 #本文PDF版下载 Python下探究随机数的产生原理和算法(或者单击我博客园右上角的github小标,找到lab102的W7目录下即可) #本文代码下载 几种随机数算法集合(和下文出现过的 ...

  5. 使用ctypes在Python中调用C++动态库

    使用ctypes在Python中调用C++动态库 入门操作 使用ctypes库可以直接调用C语言编写的动态库,而如果是调用C++编写的动态库,需要使用extern关键字对动态库的函数进行声明: #in ...

  6. 聊聊Python ctypes 模块(转载)

    https://zhuanlan.zhihu.com/p/20152309?columnSlug=python-dev 作者:Jerry Jho链接:https://zhuanlan.zhihu.co ...

  7. [Python]ctypes+struct实现类c的结构化数据串行处理

    1. 用C/C++实现的结构化数据处理 在涉及到比较底层的通信协议开发过程中, 往往需要开发语言能够有效的表达和处理所定义的通信协议的数据结构. 在这方面是C/C++语言是具有天然优势的: 通过str ...

  8. 浮生半日:探究Python字节码

    好吧!“人生苦短,请用Python”,作为python爱好者以及安全从业者,而且最近也碰到了一些这方面的问题,懂点python字节码还是很有必要的. Python是一门解释性语言,它的具体工作流程如下 ...

  9. python第六天 函数 python标准库实例大全

    今天学习第一模块的最后一课课程--函数: python的第一个函数: 1 def func1(): 2 print('第一个函数') 3 return 0 4 func1() 1 同时返回多种类型时, ...

随机推荐

  1. foreach 当被循环的变量为空时 不进入循环

    $a = []; foreach($a as $v){ echo 222; } //不会输出222 并且不会报错

  2. 【MOOC EXP】Linux内核分析实验二报告

    程涵  原创博客 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000  [操作系统是如何工作的]   教学内 ...

  3. Teechart使用记录

    一.      Chart 1.1 Series 在该界面可以进行曲线的添加.删除.修改 1.2 General 在该界面 Margins 可以设置整个坐标系外边距. 在这里可是设置放大功能. All ...

  4. javascript 数组对象及其方法

    数组声明:通过let arr = new Array(); 或者 let arr = []; 数组对象可调用的方法: 1)find方法,使用情况是对数组进行筛选遍历,find方法要求某个函数(A)作为 ...

  5. 软件工程实践-git的使用

    ² Github使用心得 其实以前就注册过一个github账号,不过那时只不过是因为在网上看到这个挺对于程序员有着重大作用就顺手去弄了,从未使用过,直到这次软工实践需要我才从新回想起来. 之前的几篇随 ...

  6. [Latex] Travis-CI与Latex构建开源中文PDF

    博主有一本开源书籍,用 latex 排版,托管在Github上.但用 latex 不像是 Markdown,当tex文本更新时,用于最终浏览的PDF文件很难得到及时的更新, 所以博主一直想找到一套工具 ...

  7. 数据平面可编程与SDN关系理解,以及数据平面可编程的理解

    数据平面可编程与SDN关系 狭义 广义 数据平面可编程的理解 狭义 广义

  8. Winform设置开机启动-操作注册表

    #region 设置开机运行 /// <summary> /// 设置开机运行 /// </summary> /// <param name="R_startP ...

  9. K8S 创建rc 时 不适用本地镜像的解决办法

    spec: containers: - name: nginx image: image: reg.docker.lc/share/nginx:latest imagePullPolicy: IfNo ...

  10. POWERSHELL 计划任务的创建,收集DC中失败的登录信息并邮件通知

    (注:本文参考以下前辈文章修改而来,源文章连接:http://itadmindev.blogspot.hk/2011/07/powershell-ad-dc-failed-logins-report. ...