Python 的ctypes模块使用详解
ctypes是 Python 的外部函数库。它提供了与 C 兼容的数据类型,并允许调用 DLL 或共享库中的函数。可使用该模块以纯 Python 形式对这些库进行封装。
ctypes 教程
注意:在本教程中的示例代码使用doctest进行过测试,保证其正确运行。由于
有些代码在Linux,Windows或Mac OS X下的表现不同,这些代码会在 doctest 中包含相关的指令注解。
注意:部分示例代码引用了 ctypes c_int类型。在sizeof(long) == sizeof(int)的平台上此类型是c_long的一个别名。所以,在程序输出c_long而不是你期望
的c_int时不必感到迷惑 --- 它们实际上是同一种类型。
载入动态连接库
ctypes导出了cdll对象,在 Windows 系统中还导出了windll和oledll对象用于载入动态连接库。
通过操作这些对象的属性,你可以载入外部的动态链接库。cdll载入按标准的cdecl调用协议导出的函数,
而windll导入的库按stdcall调用协议调用其中的函数。oledll也按stdcall调用协议调用其中的函数,并假定该函数返回的是Windows HRESULT错误代码,并当函数调用失败时,自动根据该代码甩出一个OSError异常。
在 3.3 版更改: 原来在 Windows 下甩出的异常类型WindowsError现在是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文件扩展名。
注解
通过cdll.msvcrt调用的标准 C 函数,可能会导致调用一个过时的,与当前Python 所不兼容的函数。因此,请尽量使用标准的 Python 函数,而不要使用msvcrt模块。
在 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 ...>
操作导入的动态链接库中的函数
通过操作dll对象的属性来操作这些函数。
from ctypes import *
libc.printf
<_FuncPtr object >
print(windll.kernel32.GetModuleHandleA)
<_FuncPtr object >
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系统的动态库,比如kernel32和user32,通常会同时导出同一个函数的 ANSI 版本和 UNICODE 版本。UNICODE 版本通常会在名字最后以W结尾,而 ANSI 版本的则以A结尾。 win32的GetModuleHandle函数会根据一个模块名返回一个模块句柄,该函数暨同时包含这样的两个版本的原型函数,并通过宏UNICODE 是否定义,来决定宏GetModuleHandle导出的是哪个具体函数。
/* ANSI version */
HMODULE GetModuleHandleA(LPCSTR lpModuleName);
/* UNICODE version */
HMODULE GetModuleHandleW(LPCWSTR lpModuleName);
windll不会通过这样的魔法手段来帮你决定选择哪一种函数,你必须显式的调用GetModuleHandleA或GetModuleHandleW,并分别使用字节对象或字符串对象作参数。
有时候,dlls的导出的函数名不符合 Python 的标识符规范,比如
"??2@YAPAXI@Z"。此时,你必须使用getattr()方法来获得该函数。
getattr(cdll.msvcrt, "??2@YAPAXI@Z")
<_FuncPtr object >
Windows 下,有些 dll 导出的函数没有函数名,而是通过其顺序号调用。对此类函数,你也可以通过 dll 对象的数值索引来操作这些函数。
cdll.kernel32[1]
<_FuncPtr object >
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 时间起点到现在的秒数),而``GetModuleHandleA()`` 函数返回一个 win32 模块句柄。
此函数中调用的两个函数都使用了空指针(用None作为空指针):
print(libc.time(None))
1150640792
print(hex(windll.kernel32.GetModuleHandleA(None)))
0x1d000000
如果你用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)
你必须阅读这些库的头文件或说明文档来确定它们的正确的调用协议。
在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,整型,字节对象和(UNICODE)字符串是仅有的可以直接作为函数参数使用的四种Python本地数据类型。None`作为C的空指针 (NULL),字节和字符串类型作为一个指向其保存数据的内存块指针(char *或wchar_t *)。Python 的整型则作为平台默认的C的int类型,他们的数值被截断以适应C类型的整型长度。在我们开始调用函数前,我们必须先了解作为函数参数的ctypes数据类型。
基础数据类型
ctypes定义了一些和C兼容的基本数据类型:
ctypes 类型  C 类型Python 类型
c_bool_Bool bool (1)
c_char char单字符字节对象
c_wchar wchar_t单字符字符串
c_byte char int
c_ubyte unsigned char int
c_short short int
ctypes 类型  C 类型Python 类型
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或long long int
python的类怎么输出printf
c_ulonglong unsigned __int64或unsigned long long int
c_size_t size_t int
c_ssize_t ssize_t或Py_ssize_t int
c_float float float
c_double double float
c_longdouble long double float
c_char_p char * (以 NUL 结尾) 字节串对象或None c_wchar_p wchar_t * (以 NUL 结尾) 字符串或None
c_void_p void *int 或None
1.构造函数接受任何具有真值的对象。
所有这些类型都可以通过使用正确类型和值的可选初始值调用它们来创建:  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