ctypes使用指南
ctypes使用指南
1 ctypes简介
ctypes是Python的外部函数库。它提供了C兼容的数据类型,并且允许调用动态链接库/共享库中的函数。它可以将这些库包装起来给Python使用。
2 ctypes入门
本入门中的代码使用doctest确保可用。不过一些代码在linux/windows/mac os x中的行为可能略有差异,这在其doctest的注释中有所表示。
少数代码示例引用了ctypes的c_int类型。这个类型是32bit系统中c_long类型的别名。所以你在期待c_int而显示c_long时不必疑惑,他们是一样的。
2.1 载入动态链接库
ctypes导出了 cdll,在windows上还有 windll 和 oledll 对象用于载入动态链接库。
如下是Windows的例子,主意msvcrt是MS标准C库,包含了大部分标准C函数,并且使用cdecl调用规范:
<WinDLL 'kernel32', handle ... at ...>
<CDLL 'msvcrt', handle ... at ...>
Linux上需要指定包含扩展名的文件名来载入动态库,所以属性存取方式就失效了。你可以使用 LoadLibrary 方法,或者创建CDLL的实例来载入:
>>> cdll.LoadLibrary("libc.so.6")
<CDLL 'libc.so.6', handle ... at ...>
<CDLL 'libc.so.6', handle ... at ...>
2.2 从载入的动态链接库中访问函数
>>> print windll.kernel32.GetModuleHandleA
>>> print windll.kernel32.MyOwnFunction
Traceback (most recent call last):
File "ctypes.py", line 239, in __getattr__
func = _StdcallFuncPtr(name,self)
AttributeError: function 'MyOwnFunction' not found
HMODULE GetModuleHandleA(LPCSTR lpModuleName);
HMODULE GetModuleHandleW(LPCWSTR lpModuleName);
windll 并不会自动选择调用某个版本,所以你必须指定要调用的,传递的时候也要指定正确的字符串参数类型。
有时动态链接库导出函数并不是有效的Python标识符,例如 "??2@YAPAXI@Z" 。这种情况下,你必须使用getattr 获取函数:
>>> getattr(cdll.msvcrt,"??2@YAPAXI@Z")
在Windows上,有些动态链接库导出函数不是用名字,而是用序号(ordinal)。这些函数通过索引存取:
Traceback (most recent call last):
File "ctypes.py", line 310, in __getitem__
func = _StdcallFuncPtr(name,self)
AttributeError: function ordinal 0 not found
2.3 调用函数
你可以像正常的Python函数一样调用这些函数。这里用 time() 函数示例,返回Unix epoch系统时间,和GetModuleHandleA() 函数,返回win32模块句柄。
这个例子调用函数时附带NULL指针(None作为NULL指针):
>>> print hex(windll.kernel32.GetModuleHandleA(None))
在调用函数时,如果使用了错误的参数数量和调用规范时,ctypes尝试保护调用。不幸的是该功能仅在Windows上有用。它通过检查函数返回栈来实现,所以尽管发生了错误,但是函数还是调用了:
>>> windll.kernel32.GetModuleHandleA()
Traceback (most recent call last):
ValueError: Procedure probably called with not enough argument (4 bytes missing)
>>> windll.kernel.GetModuleHandleA(0,0)
Traceback (most recent call last):
ValueError: Procedure probably called with too many argument (4 bytes in excess)
>>> cdll.kernel32.GetModuleHandleA(None) # doctest: +WINDOWS
Traceback (most recent call last):
ValueError: Procedure probably called with not enough arguments (4 bytes missing)
>>> windll.msvcrt.printf("spam") # doctest: +WINDOWS
Traceback (most recent call last):
ValueError: Procedure probably called with too many arguments (4 bytes in excess)
在Windows,ctypes使用win32结构异常处理,避免无保护的挂掉:
>>> windll.kernel32.GetModuleHandleA(32)
Traceback (most recent call last):
WindowsError: exception: access violation reading 0x00000020
尽管如此,仍然有很多方法用ctypes挂掉Python,所以你必须很小心的使用。
在调用更多的函数之前,必须了解关于ctypes数据类型的知识。
2.4 基本数据类型
ctypes类型 |
C类型 |
Python类型 |
c_char |
char |
1个字符的字符串 |
c_wchar |
wchar_t |
1个字符的unicode字符串 |
c_byte |
char |
int/long |
c_ubyte |
unsigned char |
int/long |
c_short |
short |
int/long |
c_ushort |
unsigned short |
int/long |
c_int |
int |
int/long |
c_uint |
unsigned int |
int/long |
c_long |
long |
int/long |
c_ulong |
unsigned long |
int/long |
c_longlong |
__int64 或 long long |
int/long |
c_ulonglong |
unsigned __int64 或 unsigned long long |
int/long |
c_float |
float |
float |
c_double |
double |
float |
c_char_p |
char * (NUL结尾字符串) |
string或None |
c_wchar_p |
wchar_t * (NUL结尾字符串) |
unicode或None |
c_void_p |
void * |
int/long或None |
对指针类型 c_char_p/c_wchar_p/c_void_p 的赋值将会改变其指向的内存区域地址,而不是改变内存块的值(当然了,因为Python字符串是只读的):
>>> p = create_string_buffer(3) # create a 3 byte buffer, initialized to NUL bytes
>>> print sizeof(p), repr(p.raw)
>>> p = create_string_buffer("Hello") # create a buffer containing a NUL terminated string
>>> print sizeof(p), repr(p.raw)
>>> p = create_string_buffer("Hello", 10) # create a 10 byte buffer
>>> print sizeof(p), repr(p.raw)
10 'Hello\x00\x00\x00\x00\x00'
>>> print sizeof(p), repr(p.raw)
10 'Hi\x00lo\x00\x00\x00\x00\x00'
2.5 调用函数,继续
需要注意的是,printf打印到真实的标准输出,而不是 sys.stdout ,所以这些例子仅在控制台模式有效,而不是IDLE或PythonWin:
>>> printf("Hello, %s\n","World!")
>>> printf("Hello, %S", u"World!")
>>> printf("%d bottles of beer\n", 42)
>>> printf("%f bottles of beer\n", 42.5)
Traceback (most recent call last):
ArgumentError: argument 2: exceptions.TypeError: Don't know how to convert parameter 2
有如前面所说,除了整数、字符串和unicode字符串以外的Python类型必须使用ctypes类型做包装,所以他们可以转换为必须的C数据类型:
>>> printf("An int %d, a double %f\n",1234,c_double(3.14))
Integer 1234, double 3.1400001049
2.6 使用自定义数据类型调用函数
你可以使用自定义ctypes参数转换,允许你自己的类作为函数参数。ctypes寻找对象的 _as_parameter_ 属性,并将其作为函数参数。当然,必须是整数、字符串或unicode
... def __init__(self, number):
... self._as_parameter_ = number
>>> printf("%d bottles of beer\n", bottles)
如果你不想存储实例的数据到 _as_parameter_ 实例变量,你可以定义一个属性确保数据有效。
2.7 指定必须的参数类型(函数原型)
可以通过指定函数的 argtypes 属性来指定函数的参数类型。
argtypes必须是一个C数据类型序列(printf函数在这里不是个好例子,因为它需要依赖于格式化字符串的可变数量和多种类型的参数,反过来说倒是很适合于练手):
>>> printf.argtypes=[c_char_p,c_char_p,c_int,c_double]
>>> printf("String '%s', Int %d, Double %f\n","Hi",10,2.2)
String 'Hi', Int 10, Double 2.200000
>>> printf("%d %d %d", 1, 2, 3)
Traceback (most recent call last):
ArgumentError: argument 2: exceptions.TypeError: wrong type
>>> printf("%s %d %f", "X", 2, 3)
2.8 返回类型
缺省情况假设函数返回C的int类型。其他返回类型可以通过设置函数的 restype 属性来实现。
这里是一个更高级的例子,它使用strchr函数,需要一个字符串指针和一个字符,返回字符串的指针:
>>> strchr("abcdef", ord("d")) # doctest: +SKIP
>>> strchr.restype = c_char_p # c_char_p is a pointer to a string
>>> strchr("abcdef", ord("d"))
>>> print strchr("abcdef", ord("x"))
如果你想要上面的 ord("x") 调用,你可以设置argtypes属性,而第二个参数的Python字符串会转换成C字符:
>>> strchr.argtypes = [c_char_p, c_char]
Traceback (most recent call last):
ArgumentError: argument 2: exceptions.TypeError: one character string expected
>>> print strchr("abcdef", "x")
>>> GetModuleHandle = windll.kernel32.GetModuleHandleA # doctest: +WINDOWS
>>> GetModuleHandle.restype = ValidHandle # doctest: +WINDOWS
>>> GetModuleHandle(None) # doctest: +WINDOWS
>>> GetModuleHandle("something silly") # doctest: +WINDOWS
Traceback (most recent call last):
File "<stdin>", line 3, in ValidHandle
WindowsError: [Errno 126] The specified module could not be found.
需要注意的是强大的错误检查机制是通过 errcheck 属性实现的。具体查看手册了解细节。
2.9 传递指针(或者传递参数引用)
有时C函数需要一个指针指向的数据作为参数,还有可能是想向里面写的位置,或者数据太大不适合传递。这也叫做传递参数引用。
ctypes导出 byref() 函数用于传递参数引用。同样也可以用于指针函数,尽管指针对象可以做很多工作,但是如果你并不需要在Python中使用指针对象的话,使用 byref() 会更快:
>>> s = create_string_buffer('\000' * 32)
>>> print i.value, f.value, repr(s.value)
>>> libc.sscanf("1 3.14 Hello", "%d %f %s",
>>> print i.value, f.value, repr(s.value)
2.10 结构和联合
结构和联合必须继承自ctypes模块的 Structure 和 Union 类。每个子类必须定义 _fields_ 属性,该属性必须是2元素元组的列表,包含字段名和字段类型。
字段类型必须是ctypes类型,例如 c_int ,或者其他派生的ctypes类型:结构、联合、数组、指针。
这里有个POINT结构体的简单例子,包含两个整数叫做x和y,同时展示了如何构造结构体:
Traceback (most recent call last):
ValueError: too many initializers
你还可以构造更多复杂的结构体。结构体可以自包含作为一个字段类型。
这里是一个RECT结构体,它包含了两个POINT结构体分别名为upperleft和lowerright:
... _fields_ = [("upperleft", POINT),
>>> print rc.upperleft.x, rc.upperleft.y
>>> print rc.lowerright.x, rc.lowerright.y
>>> r = RECT(POINT(1, 2), POINT(3, 4))
域描述可以检索到类,这对调试有很大的帮助,因为它们可以提供到有用的信息:
<Field type=c_long, ofs=0, size=4>
<Field type=c_long, ofs=4, size=4>
2.11 结构/联合对齐和字节序
2.12 结构与联合中的位字段
创建结构与联合体时,可以包含位字段。只有整型域才可以使用位字段,位宽可以在_fields_元组的第三个选项中指定:
... _fields_ = [("first_16", c_int, 16),
<Field type=c_long, ofs=0:0, bits=16>
<Field type=c_long, ofs=0:16, bits=16>
2.13 数组
数组就是序列,包含固定数量(fixed number of)的相同类型的实例。
这里有个巧妙的例子,一个结构体包含一个字段有4个POINT:
... _fields_ = ("x", c_int), ("y", c_int)
>>> class MyStruct(Structure):
... ("point_array", POINT * 4)]
>>> print len(MyStruct().point_array)
>>> ii = TenIntegers(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
<c_long_Array_10 object at 0x...>
2.14 指针
指针实例有一个 contents 属性返回指针指向的内容对象,例如上面的例子:
注意ctypes没有OOR(Original Object Return原始对象返回),他在你请求一个属性时构造一个新的、等同的对象:
>>> pi.contents is pi.contents
给指针的contents属性赋值一个新的c_int实例会改变指针指向内容的内存地址:
你也可以使用非0下标访问,但你必须知道你在做什么,比如在C语言:你可以访问或改变任意的内存地址。一般情况下,你仅可以在收到一个C函数返回来的指针,并且你知道它是指向了一个数组时才可以使用这个特性。
指针函数不仅创建了指针实例,它还会先创建指针类型。这些就是指针函数POINTER的工作,它可以接受任何ctypes的类型,并返回一个新的指针:
Traceback (most recent call last):
TypeError: expected c_long instead of int
<ctypes.LP_c_long object at 0x...>
>>> null_ptr = POINTER(c_int)()
当访问或给NULL指针赋值时,会引发python类型检查异常:
Traceback (most recent call last):
ValueError: NULL pointer access
Traceback (most recent call last):
ValueError: NULL pointer access
2.15 类型转换
... _fields_ = [("count", c_int), ("values", POINTER(c_int))]
>>> bar.values = (c_int * 3)(1, 2, 3)
>>> for i in range(bar.count):
可以通过给指针的values属性赋值为None来设置NULL指针:
>>> bar.values = (c_byte * 4)()
Traceback (most recent call last):
TypeError: incompatible types, c_byte_Array_4 instance instead of LP_c_long instance
<ctypes.LP_c_long object at ...>
所以,Bar结构的values域可以这样通过类型转换来赋值:
>>> bar.values = cast((c_byte * 4)(), POINTER(c_int))
2.16 不完全的类型
不完全的类型包含结构体,联合体或者类型未指定的数组。在C语言中,它们可以这样先声明后定义:
struct cell; /* forward declaration */
... _fields_ = [("name", c_char_p),
Traceback (most recent call last):
File "<stdin>", line 2, in cell
NameError: name 'cell' is not defined
因为新类cell在类本身定义时是无效的。在ctypes,我们可以先定义cell类,然后再给它的_fields_属性赋值:
>>> cell._fields_ = [("name", c_char_p),
让我们试一下效果。我们创建两个cell的实例,然后让他们互相指向对方,然后尝试访问指针链表几次:
foo bar foo bar foo bar foo bar
2.17 回调函数
ctypes允许从python回调中创建c回调函数指针。这个常常被称为回调函数。
首先,你必须为回调函数创建一个类,这个类知道调用协议,函数返回值类型,函数接受的参数个数及类型。
CFUNCTYPE工厂函数使用普通cdecl调用协议来为回调函数创建类型。并且,在Windows平台,WINFUNCTYPE工厂函数使用stdcall调用协议来为回调函数创建类型。
这两个工厂函数在调用时,参数表都是使用返回值作为第一个参数,而将回调函数所需要的参数作为剩下的参数。
在这里我将使用一个c标准库里的快排函数作为演示例子,快排是一个借助回调函数进行排序的函数。快排将会用到下面的整型数组:
>>> ia = IntArray5(5, 1, 7, 33, 99)
所以,我们例子所需要的回调函数形参表是两个整型指针,它返回一个整数。首先我们用工厂函数创建回调函数的类型:
>>> CMPFUNC = CFUNCTYPE(c_int, POINTER(c_int), POINTER(c_int))
在真正实现回调函数之前,我们简单打印获取到的参数,然后返回0(一步一步来;-)
>>> cmp_func = CMPFUNC(py_cmp_func)
>>> qsort(ia, len(ia), sizeof(c_int), cmp_func) # doctest: +WINDOWS
py_cmp_func <ctypes.LP_c_long object at 0x00...> <ctypes.LP_c_long object at 0x00...>
py_cmp_func <ctypes.LP_c_long object at 0x00...> <ctypes.LP_c_long object at 0x00...>
py_cmp_func <ctypes.LP_c_long object at 0x00...> <ctypes.LP_c_long object at 0x00...>
py_cmp_func <ctypes.LP_c_long object at 0x00...> <ctypes.LP_c_long object at 0x00...>
py_cmp_func <ctypes.LP_c_long object at 0x00...> <ctypes.LP_c_long object at 0x00...>
py_cmp_func <ctypes.LP_c_long object at 0x00...> <ctypes.LP_c_long object at 0x00...>
py_cmp_func <ctypes.LP_c_long object at 0x00...> <ctypes.LP_c_long object at 0x00...>
py_cmp_func <ctypes.LP_c_long object at 0x00...> <ctypes.LP_c_long object at 0x00...>
py_cmp_func <ctypes.LP_c_long object at 0x00...> <ctypes.LP_c_long object at 0x00...>
py_cmp_func <ctypes.LP_c_long object at 0x00...> <ctypes.LP_c_long object at 0x00...>
我们已经知道怎么访问指针指向的内容了,所以让我们重新定义一下回调函数:
... print "py_cmp_func", a[0], b[0]
>>> cmp_func = CMPFUNC(py_cmp_func)
>>> qsort(ia, len(ia), sizeof(c_int), cmp_func) # doctest: +WINDOWS
有趣的是,在linux上排序函数运行更高效,它仅需要更少的比较的次数:
>>> qsort(ia, len(ia), sizeof(c_int), cmp_func) # doctest: +LINUX
嗯,我们将要完成了!最后一步是要真正去对两个数据进行比较并且返回一个有用的结果:
... print "py_cmp_func", a[0], b[0]
>>> qsort(ia, len(ia), sizeof(c_int), CMPFUNC(py_cmp_func)) # doctest: +WINDOWS
>>> qsort(ia, len(ia), sizeof(c_int), CMPFUNC(py_cmp_func)) # doctest: +LINUX
很有趣地看到,Windows的快排比在linux版本的快排比较的次数多!
确保你在C代码的使用生命周期里保持引用CFUNCTYPE对象。ctypes并不会帮你做这样的事情,如果你没有做保证,它们就会被垃圾回收,然后当你调用这个回调函数时将会导致程序崩溃。
2.18 访问动态链接库导出的值
ctypes可以这样使用in_dll的类方法访问变量值。pythonapi是一个预定义符号可以访问Python C api:
>>> opt_flag = c_int.in_dll(pythonapi, "Py_OptimizeFlag")
如果解析器使用-O命令启动,例子就会打印c_long(1),或者c_long(2)如果指定-OO参数。
Python的导出指针PyImport_FrozenModules也是一个扩展的例子展示指针的访问使用办法。
所以熟悉这个指针证明还是挺有用的。为了限制例子的大小,我们仅展示这个表如果通过ctypes来访问。
>>> class struct_frozen(Structure):
... _fields_ = [("name", c_char_p),
... ("code", POINTER(c_ubyte)),
我们已经定义struct_frozen的数据结构类型,所以我们可以获得指向这张表的指针:
>>> FrozenTable = POINTER(struct_frozen)
>>> table = FrozenTable.in_dll(pythonapi, "PyImport_FrozenModules")
... print item.name, item.size
事实上,标准Python有一个并不怎么出名的静态模块和一个静态包(相对于其他成员来说),它仅用于测试。试试用import __hello__吧。
2.19 意料之外
... _fields_ = ("x", c_int), ("y", c_int)
... _fields_ = ("a", POINT), ("b", POINT)
>>> print rc.a.x, rc.a.y, rc.b.x, rc.b.y
>>> print rc.a.x, rc.a.y, rc.b.x, rc.b.y
嗯,我们当然期望最后一名打印3 4 1 2。到底发生了什么事?这里是上面rc.a, rc.b = rc.b, rc.a这一行的步骤:
记住,检索结构体,联合体及数组并不是使用它们的拷贝,而是检索一个访问顶级对象相关缓冲区的封装对象。
2.20 可变大小的数据类型
ctypes提供了可变数组与结构体的支持(在0.9.9.7版本增加)。
>>> short_array = (c_short * 4)()
Traceback (most recent call last):
这看起来不错,但怎么访问这个数据增加的元素呢?由于type方法仍然只知道有4个元素,当访问其他元素时我们会得到错误:
Traceback (most recent call last):
ctypes中另外一种使用可变数据类型的方法是使用Python的动态语言特性,具体问题具体分析,当已经知道需要的数据大小时,才(重)定义数据类型。
2.21 bug, todo和未完成的东西
没有实现枚举类型。你自己使用c_int作为基类就可以简单实现它。
3 其他感兴趣的话题
3.1 研究各种python封装c扩展方法的性能
写python的c扩展方法有很多,除了ctypes之外,还有原始的python c api、swig、sip、cython、cffi等方法,研究各种封装写法的性能差异有利于程序的性能优化。
3.2 利用PyImport_FrozenModules做一些有趣的事情
4 参考资料
1. http://starship.python.net/crew/theller/ctypes/tutorial.html#bugs-todo-and-non-implemented-things
2. http://gashero.iteye.com/blog/519837
3. http://www.isnowfy.com/introduction-to-python-c-extension/
4. http://blog.csdn.net/linda1000/article/details/12623527
5. http://blog.waterlin.org/articles/using-python-ctypes-to-link-cpp-library.html
6. https://docs.python.org/2/c-api/import.html#PyImport_FrozenModules
ctypes使用指南相关推荐
- linux3.x内核实时性改进,linux 3.x内核优化指南
1.1.Numba的约5分钟指南 Numba是Python的即时编译器,它最适用于使用NumPy数组和函数以及循环的代码.使用Numba的最常用方法是通过其装饰器集合,可以应用于您的函数来指示Numb ...
- python能和c语音交互吗_Python和C语言交互--ctypes,struct
python和c语言进行数据交互,涉及类型转换,字节对齐,字节序大小端转换等.相关模块ctypes,struct,memoryview. 一.ctypes:python和c语言使用结构体数据进行交互 ...
- 百倍加速!Python量化策略的算法性能提升指南
性能问题 Python在2016年里可以说是风靡国内量化投资圈,目前整个生态链已经初具规模: 交易:vn.py.easytrader.at_py 数据:tushare 回测:rqalpha 在线平台: ...
- 百倍加速:Python量化策略的算法性能提升指南
性能问题 Python在2016年里可以说是风靡国内量化投资圈,目前整个生态链已经初具规模: 交易:vn.py.easytrader.at_py 数据:tushare 回测:rqalpha 在线平台: ...
- Python --- ctypes库的使用
ctypes 的官方文档 英文文档:https://docs.python.org/3/library/ctypes.html 中文文档:https://docs.python.org/zh-cn/3 ...
- Robot Framework用户指南
Robot Framework用户指南 版本2.8.6 版权所有©诺基亚解决方案和网络2008-2014 根据知识共享署名3.0 Unported许可授权 目录 1开始 1.1简介 1.2版权和许可 ...
- Python包装用户指南(删减了讨论和新闻部分)
Python包装用户指南 欢迎使用Python Packaging用户指南,这是一系列教程和参考资料,可帮助您使用现代工具分发和安装Python包. 本指南由Python Packaging Auth ...
- python内置库之学习ctypes库(三)--调用Win32API
ctypes库踩坑日记3 1.调用win32的api 2.最好让结构体和程序分开 3.取完数据找到对应信息,创建code.py 4.创建main.py,代码这样看着就很简洁 1.调用win32的api ...
- python内置库之学习ctypes库(二)
ctypes库踩坑日记2 一.自己实现一个dll文件,再用python的ctypes库调用思路1更清晰 二.生成dll文件 三.ctypes库调用 一.自己实现一个dll文件,再用python的cty ...
最新文章
- java map 红黑树_Java集合-TreeMap和红黑树
- 如何在Linux中恢复一个删除了的文件
- 小学听课计算机笔记范文,小学教师听课记录精选10篇【最新】.pdf
- 72万奖金!DCIC 2021数据竞赛正式报名了!
- python 定时器_python 线程之四:定时器(Timer),非阻塞
- 学术界女性有孩子后论文发表率下降约20%,男性则不会
- Maven学习(四)-----Maven中央存储库
- 计算机无故重启是什么原因,事实:无缘无故重新启动计算机有什么问题?计算机无故重启的原因和解决方法...
- EXCEL同一个界面下打开两个表格比较
- java远程获取linux文件_Java远程连接操作linux服务器,scp获取文件
- python pip 下载
- 表格中计算机设置,如何在excel表格中设置下拉菜单?一招教你搞定!
- 推荐几个资源搜索网站
- Spring依赖注入和简单demo
- 110 AddressBook
- mac虚拟摄像头开发
- 二等水准测量记录数据_二等水准测量外业数据整理(往返测).doc
- oracle stdevp函数,SQL Server与oracle两者区别之函数区别
- swift Locale
- 【图像去雾】基于matlab颜色衰减先验图像去雾【含Matlab源码 2036期】
热门文章
- FAQ| 近期常见使用问题汇总
- android 修改 dpi_魅族16th|魔趣100|安卓10.0|归属地|机型修改|性能调整|稳定流畅
- android 小学课程,中小学同步课堂
- MySQL高级篇——索引简介
- 从零开始学USB(二十二、USB接口HID类设备(四)_报表描述符Local类)
- 【基于Swing+Java的连连看小游戏的设计与实现(效果+源代码+论文 获取~~)】
- Python实现100以内的加减法口算练习题
- 软件测试-web端测试方法
- 很好的在线端口扫描网站
- 金蝶EAS客户端隐藏快捷键