python中导入win32api (python调用win32api传递指针)

Python是目前最流行的编程语言之一。其语法简练,功能丰富,用途广泛,深受广大程序员的欢迎。

在这里我们来了解一下,Python如何在不需要第三方库的情况下,调用Win32 API。我们以“读取剪贴板中的文本”为例进行演示。我使用的操作系统是Windows 10 x64 22H2,Python是3.11.4 x64。

C版本的实现

Windows本身是用C、C++编写,所以原生支持C语言编程。要实现读取剪贴板文本的功能,C语言代码片段如下:

OpenClipboard(0);

HANDLE hg = GetClipboardData(CF_TEXT);

char *p = GlobalLock(hg);

printf("%s\n", p);

GlobalUnlock(hg);

CloseClipboard();

Python版本的实现

Python为了实现与C的底层兼容,自带了一个ctypes库,其中包括C中的数据类型和动态库调用。在Windows版本的Python中,

ctypes.windll.kernel32.GlobalLock(hg)

即可以调用该API函数。整个实现代码如下:

import ctypes as C

K = C.windll.kernel32

U = C.windll.user32

CF_TEXT = 1

U.OpenClipboard(0)

hg = U.GetClipboardData(CF_TEXT)

p = K.GlobalLock(hg)

print(C.string_at(p).decode('gbk'))

K.GlobalUnlock(hg)

U.CloseClipboard()

如果你使用的是32位Python,那么上述代码已经可以很好工作了。但是,如果是64位Python,则无法正常运行。

64位程序的问题

在64位程序中,指针、句柄、长度/尺寸等数据类型是64位的,同时又保留了32位及以下的数据类型,只不过传递、返回时将其扩展为64位,所以情况比较复杂。

而ctyps库在调用函数时,默认是没有函数原型的,除了在64位程序中显式的c_char_p、c_size_t等类型外,多数以32位int类型传递和返回,这样就导致一些API调用出现问题。

在上面的代码中,GetClipboardData函数的返回类型是HGLOBAL,其在32位系统中是32位大小,而在64位系统中是64位大小,因此64位程序中返回值是一个被截断的32位值,以这个32位值再调用下一个GlobalLock肯定出错,就算是正确的64位值,调用GlobalLock也会被截断为32位值,同样出错。GlobalUnlock亦然。

没有函数原型还导致另一个问题,就是不检查参数和返回值的类型及数量,可以传递任意类型任意数量的参数。比如:

K.GetTickCount()

K.GetTickCount(1)

都可以得到正确结果,但是在很多其它API调用时将得到错误结果或抛出异常。

那么ctypes是如何解决这个问题的呢?它采用argtypes和restype来声明函数原型。如下:

import ctypes.wintypes as W

U.GetClipboardData.argtypes = W.UINT,

U.GetClipboardData.restype = W.HGLOBAL

K.GlobalLock.argtypes = W.HGLOBAL,

K.GlobalLock.restype = W.LPVOID

K.GlobalUnlock.argtypes = W.HGLOBAL,

K.GlobalUnlock.restype = W.BOOL

请注意,W.UINT, 后面的逗号,argtypes需要一个元组(tuple)。如果argtypes或restype为空,则用None表示。其余API为偷懒可以不声明,因为它们只用到了INT/UINT类型或为空。

声明函数原型的另一个好处是可以进行类型检查和转换。

将这几行代码插入到上节代码第三行之后,现在完善后的Python程序在32位和64位环境中都可以正常运行了。