综述
ctypes 是 Python 的外部函数库。它提供了与 C 兼容的数据类型,并允许调用 DLL 或共享库中的函数。可使用该模块以纯 Python 形式对这些库进行封装。
详细内容
载入动态链接库
ctypes 导出了 cdll 对象,在 Windows 系统中还导出了 windll 和 oledll 对象用于载入动态连接库。
通过操作这些对象的属性,你可以载入外部的动态链接库。cdll 载入按标准的 cdecl 调用协议导出的函数,而 windll 导入的库按 stdcall 调用协议调用其中的函数。 oledll 也按 stdcall 调用协议调用其中的函数,并假定该函数返回的是 Windows HRESULT 错误代码,并当函数调用失败时,自动根据该代码甩出一个 OSError 异常。
这是一些 Windows 下的例子。注意:msvcrt 是微软 C 标准库,包含了大部分 C 标准函数,这些函数都是以 cdecl 调用协议进行调用的。
>>> from ctypes import * >>> print(windll.kernel32) <WinDLL 'kernel32', handle ... at ...> >>> print(cdll.msvcrt) <CDLL 'msvcrt', handle ... at ...> >>> libc = cdll.msvcrt >>>
Windows会自动添加通常的 .dll 文件扩展名
在 Linux 下,必须使用 包含 文件扩展名的文件名来导入共享库。因此不能简单使用对象属性的方式来导入库。因此,你可以使用方法 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 ...> >>>
操作导入的动态链接库中的函数
>>> 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 >>>
有时候,dlls的导出的函数名不符合 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 >>>
调用函数
如果你用 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) >>>
基础数据类型
ctypes 定义了一些和C兼容的基本数据类型:
ctypes 类型 | C 类型 | Python 类型 |
---|---|---|
c_bool | _Bool | bool (1) |
c_char | char | 单字符字节对象 |
c_wchar | wchar_t | 单字符字符串 |
c_byte | char | 整型 |
c_ubyte | unsigned char | 整型 |
c_short | short | 整型 |
c_ushort | unsigned short | 整型 |
c_int | int | 整型 |
c_uint | unsigned int | 整型 |
c_long | long | 整型 |
c_ulong | unsigned long | 整型 |
c_longlong | __int64 或 long long | 整型 |
c_ulonglong | unsigned __int64 或 unsigned long long | 整型 |
c_size_t | size_t | 整型 |
c_ssize_t | ssize_t 或 Py_ssize_t | 整型 |
c_float | float | 浮点数 |
c_double | double | 浮点数 |
c_longdouble | long double | 浮点数 |
c_char_p | char * (NUL terminated) | 字节串对象或 None |
c_wchar_p | wchar_t * (NUL terminated) | 字符串或 None |
c_void_p | void * | int 或 None |
当给指针类型的对象 c_char_p, c_wchar_p 和 c_void_p 等赋值时,将改变它们所指向的 内存地址,而 不是 它们所指向的内存区域的 内容 (这是理所当然的,因为 Python 的 bytes 对象是不可变的):
>>> 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'\x00\x00\x00' >>> p = create_string_buffer(b"Hello") # create a buffer containing a NUL terminated string >>> print(sizeof(p), repr(p.raw)) 6 b'Hello\x00' >>> 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'Hello\x00\x00\x00\x00\x00' >>> p.value = b"Hi" >>> print(sizeof(p), repr(p.raw)) 10 b'Hi\x00lo\x00\x00\x00\x00\x00' >>>
create_string_buffer() 函数替代以前的ctypes版本中的 c_buffer() 函数 (仍然可当作别名使用)和 c_string() 函数。create_unicode_buffer() 函数创建包含 unicode 字符的可变内存块,与之对应的C语言类型是 wchar_t。
使用自定义的数据类型调用函数
>>> class Bottles: ... def __init__(self, number): ... self._as_parameter_ = number ... >>> bottles = Bottles(42) >>> printf(b"%d bottles of beer\n", bottles) 42 bottles of beer 19 >>>
ctypes会寻找 _as_parameter_
属性,然后用该属性作为实际的值
它的类型必须是 integer,string,bytes中的一种
制定函数参数类型
可以通过diaoy .argtypes
来制定函数参数的类型以及顺序
>>> printf.argtypes = [c_char_p, c_char_p, c_int, c_double] >>> printf(b"String '%s', Int %d, Double %f\n", b"Hi", 10, 2.2) String 'Hi', Int 10, Double 2.200000 37 >>> >>> 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 %f\n", b"X", 2, 3) X 2 3.000000 13 >>>
若参数为自定义类型,则需要实现 from_param()
接口用于类型转换
Comments | NOTHING