Python编程.利用ctypes调用Windows API

时间:2021-11-04 16:12:12

Python是一种容易入门的编程语言,对于已经掌握C/C++或Java等任意一门编程语言的程序员来说,通过Python官网的Tutorial文档,可以在较短时间内掌握基本语法。 

Ctypes是我喜欢的Python特性之一,它让你的Python程序可以方便地调用已有的C语言编写的动态链接库。在Windows操作系统上,利用ctypes提供的帮助,Python程序直接调用Windows API变得非常容易。 

比如,mouse_event函数用于制造一个虚拟的鼠标事件,如果我们需要模拟一个鼠标移动事件,只需要执行以下代码: 

import ctypes
ctypes.windll.user32.mouse_event(MOUSEEVENTF_ABSOLUTE|MOUSEEVENTF_MOVE, 100, 200, 0, 0)

其中MOUSEEVENTF_ABSOLUTE和MOUSEEVENTF_MOVE的值,根据MSDN文档,分别是0x8000和0x0001。此段代码执行后,鼠标指针将出现在(100,200)坐标点上。

由于种种原因,SendInput函数被设计用来替代mouse_event函数。在Windows 7之前,mouse_event函数仍能顺利完成它的功能。不过,在Windows 8以后,Windows系统增强了对Touch输入模式的支持,当Touch发生时,鼠标指针将自动隐藏,此时调用mouse_event函数将不能让鼠标指针显示出来,但SendInput函数却可以。

Python程序调用SendInput函数略微麻烦,因为我们要创建一个INPUT结构体数组,填充它的内容,并把它的地址作为参数传递给SendInput函数。当你发现INPUT结构体内又包含了union以及其他结构体时,情况就变得更麻烦些。不过ctypes提供了足够的手段来完成这个任务。(以下代码非原创,借鉴了开源Project)

import ctypes

LONG = ctypes.c_long
DWORD = ctypes.c_ulong
ULONG_PTR = ctypes.POINTER(DWORD)
WORD = ctypes.c_ushort

class MOUSEINPUT(ctypes.Structure):
_fields_ = (('dx', LONG),
('dy', LONG),
('mouseData', DWORD),
('dwFlags', DWORD),
('time', DWORD),
('dwExtraInfo', ULONG_PTR))

class KEYBDINPUT(ctypes.Structure):
_fields_ = (('wVk', WORD),
('wScan', WORD),
('dwFlags', DWORD),
('time', DWORD),
('dwExtraInfo', ULONG_PTR))

class HARDWAREINPUT(ctypes.Structure):
_fields_ = (('uMsg', DWORD),
('wParamL', WORD),
('wParamH', WORD))

class _INPUTunion(ctypes.Union):
_fields_ = (('mi', MOUSEINPUT),
('ki', KEYBDINPUT),
('hi', HARDWAREINPUT))

class INPUT(ctypes.Structure):
_fields_ = (('type', DWORD),
('union', _INPUTunion))

到此,我们通过继承ctypes.Structure和ctypes.Union这个两类,得到了最终的INPUT类,我们将用它来表示INPUT结构体。也许我们还需要几个辅助函数来帮我们构造INPUT结构体:

def MouseInput(flags, x, y, data):
return MOUSEINPUT(x, y, data, flags, 0, None)

def KeybdInput(code, flags):
return KEYBDINPUT(code, code, flags, 0, None)

def HardwareInput(message, parameter):
return HARDWAREINPUT(message&0xFFFFFFFF, parameter&0xFFFF, parameter>>16&0xFFFF)

INPUT_MOUSE = 0
INPUT_KEYBOARD = 1
INPUT_HARDWARD = 2

def Input(structure):
if isinstance(structure, MOUSEINPUT):
return INPUT(INPUT_MOUSE, _INPUTunion(mi=structure))
if isinstance(structure, KEYBDINPUT):
return INPUT(INPUT_KEYBOARD, _INPUTunion(ki=structure))
if isinstance(structure, HARDWAREINPUT):
return INPUT(INPUT_HARDWARE, _INPUTunion(hi=structure))
raise TypeError('Cannot create INPUT structure!')

def Mouse(flags, x=0, y=0, data=0):
return Input(MouseInput(flags, x, y, data))

def Keyboard(code, flags=0):
return Input(KeybdInput(code, flags))

def Hardware(message, parameter=0):
return Input(HardwareInput(message, parameter))

现在我们只要调用Mouse()、Keyboard()和Hardware()函数,即可构造出一个INPUT结构体。

是时候调用SendInput函数了。

def SendInput(*inputs):
nInputs = len(inputs)
LPINPUT = INPUT * nInputs
pInputs = LPINPUT(*inputs)
cbSize = ctypes.c_int(ctypes.sizeof(INPUT))
return ctypes.windll.user32.SendInput(nInputs, pInputs, cbSize)

通常ctypes会自动完成参数的类型转换,不过如果有必要,我们也可以手动设置SendInput的参数类型。比如:

ctypes.windll.user32.SendInput.argtypes = [ ctypes.c_uint, ctypes.POINTER(INPUT), ctypes.c_int ]

现在我们可以发送一个虚拟Move事件测试一下,看看鼠标指针是不是出现在预期的位置:

if __name__ == '__main__':
SendInput(Mouse(MOUSEEVENTF_ABSOLUTE|MOUSEEVENTF_MOVE, 100, 200))