15.17。ctypes- 用于Python的外部函数库
2.5版中的新功能。
ctypes是Python的外部函数库。它提供C兼容的数据类型,并允许在DLL或共享库中调用函数。它可以用于在纯Python中包装这些库。
15.17.1。ctypes教程
注意:本教程中的代码示例doctest用于确保它们实际工作。由于某些代码示例在Linux,Windows或Mac OS X下的行为不同,因此它们在注释中包含doctest指令。
注意:某些代码示例引用了ctypes c_int类型。此类型是c_long32位系统上类型的别名。因此,c_long如果您打算如果打印,则不应该感到困惑c_int- 它们实际上是相同的类型。
15.17.1.1。加载动态链接库
ctypes导出cdll,以及Windows windll和oledll 对象,用于加载动态链接库。
您可以通过访问它们作为这些对象的属性来加载库。cdll 加载使用标准cdecl调用约定导出函数的库,而windll库使用stdcall 调用约定调用函数。oledll还使用stdcall调用约定,并假定函数返回Windows HRESULT错误代码。错误代码用于WindowsError在函数调用失败时自动引发异常。
以下是Windows的一些示例。注意,它msvcrt是包含大多数标准C函数的MS标准C库,并使用cdecl调用约定:>>> from ctypes import *
>>> print windll.kernel32
>>> print cdll.msvcrt
>>> libc = cdll.msvcrt
>>>
Windows会.dll自动附加常用的文件后缀。
在Linux上,需要指定包含加载库的扩展名的文件名,因此不能使用属性访问来加载库。LoadLibrary()应该使用dll加载器的 方法,或者你应该通过调用构造函数创建一个CDLL实例来加载库:>>> cdll.LoadLibrary("libc.so.6")
>>> libc = CDLL("libc.so.6")
>>> libc
>>>
15.17.1.2。从加载的dll访问函数
函数作为dll对象的属性进行访问:>>> from ctypes import *
>>> libc.printf
>>> print windll.kernel32.GetModuleHandleA
>>> print windll.kernel32.MyOwnFunction
Traceback (most recent call last):
File "", line 1, in
File "ctypes.py", line 239, in __getattr__
func = _StdcallFuncPtr(name, self)
AttributeError: function 'MyOwnFunction' not found
>>>
请注意,win32系统类似于kernel32并且user32经常导出ANSI以及函数的UNICODE版本。UNICODE版本将导出W并附加到名称,而ANSI版本将导出A 并附加到名称。win32 GetModuleHandle函数返回给定模块名称的 模块句柄,具有以下C原型,并且GetModuleHandle根据是否定义了UNICODE ,使用宏来公开其中一个:/* ANSI version */
HMODULE GetModuleHandleA(LPCSTR lpModuleName);
/* UNICODE version */
HMODULE GetModuleHandleW(LPCWSTR lpModuleName);
windll不会尝试通过魔法选择其中一个,您必须通过指定GetModuleHandleA或GetModuleHandleW 显式访问所需的版本,然后分别使用字符串或unicode字符串调用它。
有时,dll导出的函数名称不是有效的Python标识符,例如"??2@YAPAXI@Z"。在这种情况下,您必须使用 getattr()检索功能:>>> getattr(cdll.msvcrt, "??2@YAPAXI@Z")
>>>
在Windows上,某些dll不按名称导出函数,而是按顺序导出函数。可以通过使用序号索引dll对象来访问这些函数:>>> cdll.kernel32[1]
>>> cdll.kernel32[0]
Traceback (most recent call last):
File "", line 1, in
File "ctypes.py", line 310, in __getitem__
func = _StdcallFuncPtr(name, self)
AttributeError: function ordinal 0 not found
>>>
15.17.1.3。调用函数
您可以像调用任何其他Python一样调用这些函数。此示例使用time()函数,该函数返回自Unix纪元以来以秒为单位的系统时间,以及GetModuleHandleA()返回win32模块句柄的函数。
此示例使用NULL指针调用这两个函数(None应该用作NULL指针):>>> print libc.time(None)
1150640792
>>> print hex(windll.kernel32.GetModuleHandleA(None))
0x1d000000
>>>
ctypes试图保护您不要使用错误的参数数量或错误的调用约定来调用函数。不幸的是,这仅适用于Windows。它通过在函数返回后检查堆栈来完成此操作,因此虽然引发了错误,但函数已被调用:>>> windll.kernel32.GetModuleHandleA()
Traceback (most recent call last):
File "", line 1, in
ValueError: Procedure probably called with not enough arguments (4 bytes missing)
>>> windll.kernel32.GetModuleHandleA(0, 0)
Traceback (most recent call last):
File "", line 1, in
ValueError: Procedure probably called with too many arguments (4 bytes in excess)
>>>
stdcall使用cdecl调用约定调用函数 时会引发相同的异常,反之亦然:>>> cdll.kernel32.GetModuleHandleA(None)
Traceback (most recent call last):
File "", line 1, in
ValueError: Procedure probably called with not enough arguments (4 bytes missing)
>>>
>>> windll.msvcrt.printf("spam")
Traceback (most recent call last):
File "", line 1, in
ValueError: Procedure probably called with too many arguments (4 bytes in excess)
>>>
要找出正确的调用约定,您必须查看C头文件或要调用的函数的文档。
在Windows上,ctypes使用win32结构化异常处理来防止在使用无效参数值调用函数时因常规保护错误而崩溃:>>> windll.kernel32.GetModuleHandleA(32)
Traceback (most recent call last):
File "", line 1, in
WindowsError: exception: access violation reading 0x00000020
>>>
但是,有足够的方法可以使Python崩溃ctypes,所以无论如何你应该小心。
None,整数,长整数,字节字符串和unicode字符串是唯一可以直接用作这些函数调用中的参数的本机Python对象。 None作为C NULL指针传递,字节字符串和unicode字符串作为指针传递给包含其数据( 或)的内存块。Python整数和Python long作为平台默认C 类型传递,它们的值被屏蔽以适合C类型。char *wchar_t *int
在我们继续使用其他参数类型调用函数之前,我们必须了解有关ctypes数据类型的更多信息。
15.17.1.4。基本数据类型
ctypes 定义了许多原始C兼容的数据类型:
ctypes类型
C型
Python类型_Bool
布尔(1)
char
1个字符的字符串
wchar_t
1个字符的unicode字符串
char
INT /长
unsigned char
INT /长
short
INT /长
unsigned short
INT /长
int
INT /长
unsigned int
INT /长
long
INT /长
unsigned long
INT /长
__int64 要么 long long
INT /长
unsigned __int64 要么 unsigned long long
INT /长
float
浮动
double
浮动
long double
浮动
char * (NUL终止)
字符串或 None
wchar_t * (NUL终止)
unicode或 None
void *
int / long或 None构造函数接受具有真值的任何对象。
所有这些类型都可以通过使用正确类型和值的可选初始化程序调用它们来创建:>>> c_int()
c_long(0)
>>> c_char_p("Hello, World")
c_char_p('Hello, World')
>>> c_ushort(-3)
c_ushort(65533)
>>>
由于这些类型是可变的,因此它们的值也可以在以后更改:>>> i = c_int(42)
>>> print i
c_long(42)
>>> print i.value
42
>>> i.value = -99
>>> print i.value
-99
>>>
分配一个新的值,将指针类型的实例c_char_p, c_wchar_p以及c_void_p改变所述存储器位置它们指向,而不是内容的内存块(当然不是,因为Python字符串是不可变的):>>> s = "Hello, World"
>>> c_s = c_char_p(s)
>>> print c_s
c_char_p('Hello, World')
>>> c_s.value = "Hi, there"
>>> print c_s
c_char_p('Hi, there')
>>> print s # first string is unchanged
Hello, World
>>>
但是,您应该小心,不要将它们传递给期望指向可变内存的函数。如果你需要可变的内存块,ctypes有一个create_string_buffer()以各种方式创建它们的 函数。可以使用raw 属性访问(或更改)当前内存块内容; 如果要以NUL终止字符串的形式访问它,请使用以下value 属性:>>> from ctypes import *
>>> p = create_string_buffer(3) # create a 3 byte buffer, initialized to NUL bytes
>>> print sizeof(p), repr(p.raw)
3 '\x00\x00\x00'
>>> p = create_string_buffer("Hello") # create a buffer containing a NUL terminated string
>>> print sizeof(p), repr(p.raw)
6 'Hello\x00'
>>> print repr(p.value)
'Hello'
>>> p = create_string_buffer("Hello", 10) # create a 10 byte buffer
>>> print sizeof(p), repr(p.raw)
10 'Hello\x00\x00\x00\x00\x00'
>>> p.value = "Hi"
>>> print sizeof(p), repr(p.raw)
10 'Hi\x00lo\x00\x00\x00\x00\x00'
>>>
该create_string_buffer()函数替换了c_buffer()函数(仍可作为别名使用),以及c_string()早期ctypes版本中的函数。要创建包含C类型的unicode字符的可变内存块,请wchar_t使用该 create_unicode_buffer()函数。
15.17.1.5。调用函数,续
需要注意的是的printf打印到实际的标准输出通道,不给 sys.stdout,所以这些例子只是在控制台提示符下运行,而不是从内IDLE或PythonWin的:>>> printf = libc.printf
>>> printf("Hello, %s\n", "World!")
Hello, World!
14
>>> printf("Hello, %S\n", u"World!")
Hello, World!
14
>>> printf("%d bottles of beer\n", 42)
42 bottles of beer
19
>>> printf("%f bottles of beer\n", 42.5)
Traceback (most recent call last):
File "", line 1, in
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))
An int 1234, a double 3.140000
31
>>>
15.17.1.6。使用自己的自定义数据类型调用函数
您还可以自定义ctypes参数转换,以允许将您自己的类的实例用作函数参数。 ctypes查找 _as_parameter_属性并将其用作函数参数。当然,它必须是整数,字符串或unicode之一:>>> class Bottles(object):
... def __init__(self, number):
... self._as_parameter_ = number
...
>>> bottles = Bottles(42)
>>> printf("%d bottles of beer\n", bottles)
42 bottles of beer
19
>>>
如果您不想将实例的数据存储在_as_parameter_ 实例变量中,则可以定义property()使数据可用的数据。
15.17.1.7。指定必需的参数类型(函数原型)
可以通过设置argtypes属性来指定从DLL导出的函数所需的参数类型。
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
37
>>>
指定格式可以防止不兼容的参数类型(就像C函数的原型一样),并尝试将参数转换为有效类型:>>> printf("%d %d %d", 1, 2, 3)
Traceback (most recent call last):
File "", line 1, in
ArgumentError: argument 2: exceptions.TypeError: wrong type
>>> printf("%s %d %f\n", "X", 2, 3)
X 2 3.000000
13
>>>
如果已经定义了自己传递给函数调用的from_param()类,则必须为它们实现一个类方法,以便能够在argtypes序列中使用它们。本from_param()类方法接收传给函数的Python对象,它做一个类型检测,或者是需要确保这个对象是可接受的,然后返回对象本身,它_as_parameter_不管你想传递的C函数属性,或在这种情况下的论点。同样,结果应该是整数,字符串,unicode,c