概述
ctype是Python的外部函数库。它提供了C兼容的数据类型,并允许在DLL或共享库中调用函数。ctype是Python封装的API函数库。(刚开始以为是针对windows系统函数库,后来发现这是一种c语言调用解决方案。可以调用所有c语言实现的函数库。)
ctype教程
注意:本教程中的代码示例使用doctest(Python的测试模块方法)
来确保它们实际工作。由于某些代码示例在Linux、Windows或Mac OS X下表现不同,它们在注释中包含doctest(Python的测试模块方法)
指令。
注意:一些代码示例引用ctype c_int类型。在size of(Long)=size of(Int)的平台上,它是c_long的别名。因此,如果您希望c_int是打印的,那么您不应该对c_long感到困惑-它们实际上是相同的类型。
加载动态链接库
ctypes
导出cdll库,并在 Windows windll 和 oledll 对象上加载动态链接库。
过访问ctypes对象的属性加载库。cdll 加载使用 cdecl 标准调用约定导出函数的库,而 windll 库使用 stdcall 调用约定调用函数。oledll 还使用 stdcall 调用约定,并假设函数返回一个Windows HRESULT
错误代码。错误代码用于在函数调用失败时自动引发OSError异常。
在版本3.3中更改:用于引发WindowsError的Windows错误,该错误现在是OSError的别名。
下面是Windows的一些示例。注意,msvcrt是包含大多数标准C函数的MS标准C库,并使用cdecl调用约定:
>>> from ctypes import *
>>> print(windll.kernel32)
<WinDLL 'kernel32', handle ... at ...>
>>> print(cdll.msvcrt)
<CDLL 'msvcrt', handle ... at ...>
>>> libc = cdll.msvcrt
>>>
Windows会自动附加通常的.dll文件后缀。
注意:通过cdll.msvcrt访问标准C库将使用该库的过时版本,该版本可能与Python使用的版本不兼容。 尽可能使用本机Python功能,或者导入并使用msvcrt模块。
在Linux上,需要指定文件名(包括加载库的扩展名),因此不能使用属性访问来加载库。应该使用DLL加载器的LoadLibrary()方法,或者通过调用构造函数创建CDLL的实例来加载库:
>>> cdll.LoadLibrary("libc.so.6")
<CDLL 'libc.so.6', handle ... at ...>
>>> libc = CDLL("libc.so.6")
>>> libc
<CDLL 'libc.so.6', handle ... at ...>
>>>
从加载的dll访问函数
函数作为dll对象的属性进行访问:
>>> from ctypes import *
>>> libc.printf
<_FuncPtr object at 0x...>
>>> print(windll.kernel32.GetModuleHandleA)
<_FuncPtr object at 0x...>
>>> print(windll.kernel32.MyOwnFunction)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "ctypes.py", line 239, in __getattr__
func = _StdcallFuncPtr(name, self)
AttributeError: function 'MyOwnFunction' not found
>>>
请注意,win32系统dll如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来访问所需的版本,然后分别使用字节或字符串对象来调用它。
有时,dll导出的函数名称不是有效的Python标识符,例如“?? 2 @ YAPAXI @ Z”。 在这种情况下,您必须使用getattr()来检索函数:
>>> getattr(cdll.msvcrt, "??2@YAPAXI@Z")
<_FuncPtr object at 0x...>
>>>
在Windows上,某些dll不按名称导出函数,而是按顺序导出函数。 可以通过使用序号索引dll对象来访问这些函数:
>>> cdll.kernel32[1]
<_FuncPtr object at 0x...>
>>> cdll.kernel32[0]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "ctypes.py", line 310, in __getitem__
func = _StdcallFuncPtr(name, self)
AttributeError: function ordinal 0 not found
>>>
调用函数
您可以像调用任何其他Python一样调用这些函数。 此示例使用time()函数,该函数返回自Unix纪元以来的系统时间(以秒为单位),以及返回win32模块句柄的GetModuleHandleA()函数。
此示例使用NULL指针调用这两个函数(None应该用作NULL指针):
>>> print(libc.time(None))
1150640792
>>> print(hex(windll.kernel32.GetModuleHandleA(None)))
0x1d000000
>>>
注意如果ctypes检测到传递了无效数量的参数,则在调用该函数后可能会引发ValueError。 不应该依赖这种行为。 它在3.6.2中已弃用,将在3.7中删除。
使用cdecl调用约定调用stdcall函数时会引发ValueError,反之亦然:
>>> cdll.kernel32.GetModuleHandleA(None)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: Procedure probably called with not enough arguments (4 bytes missing)
>>>
>>> windll.msvcrt.printf(b"spam")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
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 "<stdin>", line 1, in <module>
OSError: exception: access violation reading 0x00000020
>>>
但是,有足够的方法可以使用ctypes来破坏Python,所以无论如何你应该小心。 faulthandler模块可以帮助调试崩溃(例如,由错误的C库调用产生的分段错误)。
None
, integers, bytes objects 和(unicode)字符串是在这些函数调用中可以直接用作参数的唯一原生Python对象。 None作为C NULL指针传递,bytes objects 和 strings 作为指针传递给包含其数据的内存块(char *或wchar_t *)。 Python整数作为平台默认的C int类型传递,它们的值被屏蔽以适合C类型。
在我们继续使用其他参数类型调用函数之前,我们必须了解有关ctypes数据类型的更多信息。
基本数据类型
ctypes定义了许多原始的C兼容数据类型:
ctypes type | C type | Python type |
---|---|---|
c_bool | _Bool | bool (1) |
c_char | char | 1-character bytes object |
c_wchar | wchar_t | 1-character string |
c_byte | char | int |
c_ubyte | unsigned char | int |
c_short | short | int |
c_ushort | unsigned short | int |
c_int | int | int |
c_uint | unsigned int | int |
c_long | long | int |
c_ulong | unsigned long | int |
c_longlong | __int64 or long long | int |
c_ulonglong | unsigned __int64 or unsigned long long | int |
c_size_t | size_t | int |
c_ssize_t | ssize_t or Py_ssize_t | int |
c_float | float | float |
c_double | double | float |
c_longdouble | long double | float |
c_char_p | char * (NUL terminated) | bytes object or None |
c_wchar_p | wchar_t * (NUL terminated) | string or None |
c_void_p | void * | int or None |
构造函数接受具有真值的任何对象。
所有这些类型都可以通过使用正确类型和值的可选初始化程序调用它们来创建:
>>> c_int()
c_long(0)
>>> c_wchar_p("Hello, World")
c_wchar_p(140018365411392)
>>> 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_wchar_p(s)
>>> print(c_s)
c_wchar_p(139966785747344)
>>> print(c_s.value)
Hello World
>>> c_s.value = "Hi, there"
>>> print(c_s) # the memory location has changed
c_wchar_p(139966783348904)
>>> print(c_s.value)
Hi, there
>>> print(s) # first object 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 b'x00x00x00'
>>> p = create_string_buffer(b"Hello") # create a buffer containing a NUL terminated string
>>> print(sizeof(p), repr(p.raw))
6 b'Hellox00'
>>> print(repr(p.value))
b'Hello'
>>> p = create_string_buffer(b"Hello", 10) # create a 10 byte buffer
>>> print(sizeof(p), repr(p.raw))
10 b'Hellox00x00x00x00x00'
>>> p.value = b"Hi"
>>> print(sizeof(p), repr(p.raw))
10 b'Hix00lox00x00x00x00x00'
>>>
create_string_buffer()函数替换了c_buffer()函数(仍然可用作别名),以及早期ctypes版本中的c_string()函数。 要创建包含C类型wchar_t的unicode字符的可变内存块,请使用create_unicode_buffer()函数。
调用函数,继续
请注意,printf打印到真正的标准输出通道,而不是sys.stdout,因此这些示例仅在控制台提示符下工作,而不是在IDLE或PythonWin中:
>>> printf = libc.printf
>>> printf(b"Hello, %sn", b"World!")
Hello, World!
14
>>> printf(b"Hello, %Sn", "World!")
Hello, World!
14
>>> printf(b"%d bottles of beern", 42)
42 bottles of beer
19
>>> printf(b"%f bottles of beern", 42.5)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ArgumentError: argument 2: exceptions.TypeError: Don't know how to convert parameter 2
>>>
如前所述,除了整数,字符串和字节对象之外的所有Python类型都必须包装在它们对应的ctypes类型中,以便它们可以转换为所需的C数据类型:
>>> printf(b"An int %d, a double %fn", 1234, c_double(3.14))
An int 1234, a double 3.140000
31
>>>
使用您自己的自定义数据类型调用函数
您还可以自定义ctypes参数转换,以允许将您自己的类的实例用作函数参数。 ctypes查找_as_parameter_属性并将其用作函数参数。 当然,它必须是整数,字符串或字节之一:
>>> class Bottles:
... def __init__(self, number):
... self._as_parameter_ = number
...
>>> bottles = Bottles(42)
>>> printf(b"%d bottles of beern", bottles)
42 bottles of beer
19
>>>
如果您不想将实例的数据存储在_as_parameter_实例变量中,则可以定义一个属性,该属性可根据请求使该属性可用。
指定必需的参数类型(函数原型)
可以通过设置argtypes属性来指定从DLL导出的函数所需的参数类型。
argtypes必须是C数据类型的序列(printf函数在这里可能不是一个好例子,因为它取决于格式字符串需要可变数量和不同类型的参数,另一方面,这对于实验这个非常方便 特征):
>>> printf.argtypes = [c_char_p, c_char_p, c_int, c_double]
>>> printf(b"String '%s', Int %d, Double %fn", b"Hi", 10, 2.2)
String 'Hi', Int 10, Double 2.200000
37
>>>
指定格式可以防止不兼容的参数类型(就像C函数的原型一样),并尝试将参数转换为有效类型:
>>> printf(b"%d %d %d", 1, 2, 3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ArgumentError: argument 2: exceptions.TypeError: wrong type
>>> printf(b"%s %d %fn", b"X", 2, 3)
X 2 3.000000
13
>>>
如果已经定义了传递给函数调用的自己的类,则必须为它们实现from_param()类方法,以便能够在argtypes序列中使用它们。 from_param()类方法接收传递给函数调用的Python对象,它应该执行类型检查或确保此对象可接受的任何操作,然后返回对象本身,其_as_parameter_属性或任何您想要传递的对象 在这种情况下作为C函数参数。 同样,结果应该是整数,字符串,字节,ctypes实例或具有_as_parameter_属性的对象。
返回类型
默认情况下,假定函数返回C int类型。 可以通过设置函数对象的restype属性来指定其他返回类型。
这是一个更高级的示例,它使用strchr函数,它需要一个字符串指针和一个char,并返回一个指向字符串的指针:
>>> strchr = libc.strchr
>>> strchr(b"abcdef", ord("d"))
8059983
>>> strchr.restype = c_char_p # c_char_p is a pointer to a string
>>> strchr(b"abcdef", ord("d"))
b'def'
>>> print(strchr(b"abcdef", ord("x")))
None
>>>
如果要避免上面的ord(“x”)调用,可以设置argtypes属性,第二个参数将从单个字符Python字节对象转换为C char:
>>> strchr.restype = c_char_p
>>> strchr.argtypes = [c_char_p, c_char]
>>> strchr(b"abcdef", b"d")
'def'
>>> strchr(b"abcdef", b"def")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ArgumentError: argument 2: exceptions.TypeError: one character string expected
>>> print(strchr(b"abcdef", b"x"))
None
>>> strchr(b"abcdef", b"d")
'def'
>>>
如果外部函数返回一个整数,您还可以使用可调用的Python对象(例如函数或类)作为restype属性。 将使用C函数返回的整数调用callable,并且此调用的结果将用作函数调用的结果。 这对于检查错误返回值并自动引发异常非常有用:
>>> GetModuleHandle = windll.kernel32.GetModuleHandleA
>>> def ValidHandle(value):
... if value == 0:
... raise WinError()
... return value
...
>>>
>>> GetModuleHandle.restype = ValidHandle
>>> GetModuleHandle(None)
486539264
>>> GetModuleHandle("something silly")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in ValidHandle
OSError: [Errno 126] The specified module could not be found.
>>>
WinError是一个函数,它将调用Windows FormatMessage()api来获取错误代码的字符串表示,并返回异常。 WinError接受一个可选的错误代码参数,如果没有使用,它会调用GetLastError()来检索它。
请注意,errcheck属性提供了更强大的错误检查机制; 有关详细信息,请参阅参考手册
传递指针(或:通过引用传递参数)
有时,C api函数需要将指向数据类型的指针作为参数,可能要写入相应的位置,或者数据太大而无法通过值传递。 这也称为通过引用传递参数。
ctypes导出byref()函数,该函数用于通过引用传递参数。 使用pointer()函数可以实现相同的效果,虽然指针()在构造真实的指针对象时做了很多工作,因此如果你不需要Python中的指针对象,使用byref()会更快。 本身:
>>> i = c_int()
>>> f = c_float()
>>> s = create_string_buffer(b'