在 Python 中简化 Ctype 联合(在 Windows 中发送键盘事件)

Simplifying Ctype union in Python (to send Keyboard events in Windows)

我有以下脚本,每 xx 秒发送一个键盘键(默认为 F15),灵感来自网上找到的一些代码

我想删除与鼠标事件相关的类型和联合,但无法使其工作。特别是,我不知道如何删除 class MOUSEINPUT 和类型的联合 _INPUTunion (删除它们将停止发送键盘键)。

关于如何将脚本 trim 降至最低限度(即仅保留与键盘相关的代码)的任何建议?

以下代码将发送密钥 "C" 以便能够调试。

#!/python

import ctypes
import sys
import time

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 _INPUTunion(ctypes.Union):
    _fields_ = (('mi', MOUSEINPUT), ('ki', KEYBDINPUT))

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


def SendInput(*inputs):
    print(inputs[0].union.mi)
    nInputs = len(inputs)
    LPINPUT = INPUT * nInputs
    pInputs = LPINPUT(*inputs)
    cbSize = ctypes.c_int(ctypes.sizeof(INPUT))
    return ctypes.windll.user32.SendInput(nInputs, pInputs, cbSize)

INPUT_KEYBOARD = 1

def Input(structure):
    if isinstance(structure, KEYBDINPUT):
        return INPUT(INPUT_KEYBOARD, _INPUTunion(ki=structure))
    else:
        raise TypeError('Cannot create INPUT structure (keyboard)!')

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

if __name__ == '__main__':
    nb_cycles = 10
    while nb_cycles != 0:
            time.sleep(2)  # 3 seconds
            # Key "c" for debug, but ideally use 0x7E for "F15"
            SendInput(Keyboard(ord("C")))
            sys.stdout.write(".")
            nb_cycles -= 1

"圣经" 用于这样的任务:[Python.Docs]: ctypes - A foreign function library for Python
我修改了你的代码(发现了一堆问题,其中一些很关键)。

code00.py:

#!/usr/bin/env python

import sys
import time
import ctypes as ct
from ctypes import wintypes as wt


class KEYBDINPUT(ct.Structure):
    _fields_ = [
        ("wVk", wt.WORD),
        ("wScan", wt.WORD),
        ("dwFlags", wt.DWORD),
        ("time", wt.DWORD),
        ("dwExtraInfo", wt.PULONG),
    ]


class INPUT(ct.Structure):
    _fields_ = [
        ("type", wt.DWORD),
        ("ki", KEYBDINPUT),
        ("padding", ct.c_ubyte * 8)
    ]


INPUT_KEYBOARD = 1  # Also defined by win32con if you have pywin32 installed

INPUT_LEN = ct.sizeof(INPUT)
LPINPUT = ct.POINTER(INPUT)

user32_dll = ct.windll.user32
SendInput = user32_dll.SendInput
SendInput.argtypes = [wt.UINT, LPINPUT, ct.c_int]
SendInput.restype = wt.UINT


def send_input(_input):
    return SendInput(1, ct.byref(_input), INPUT_LEN)


def keyboard(code, flags=0):
    return INPUT(INPUT_KEYBOARD, (KEYBDINPUT(code, code, flags, 0, None)))


def main(*argv):
    time.sleep(2)
    nb_cycles = 3
    for _ in range(nb_cycles):
        time.sleep(0.5)  # 3 seconds
        # Key "c" for debug, but ideally use 0x7E for "F15"
        ret = send_input(keyboard(ord("C")))
        #print(ret)
        sys.stdout.write(".")
        sys.stdout.flush()


if __name__ == "__main__":
    print("Python {0:s} {1:d}bit on {2:s}\n".format(" ".join(elem.strip() for elem in sys.version.split("\n")), 64 if sys.maxsize > 0x100000000 else 32, sys.platform))
    rc = main(*sys.argv[1:])
    print("\nDone.")
    sys.exit(rc)

备注:

  • 最初,我定义了只有 1st 2 个成员的 INPUT 结构,但是看了一下[MS.Docs]: INPUT structure 并注意到联合包括:

    • 鼠标输入
    • 键盘输入
    • 硬件输入

    做了一些测试并注意到 MOUSEINPUT 在 3 个 struct 中具有最大的尺寸,它是 8 字节大于 KEYBDINPUT(对于 both 32 位64 位 ),所以我添加了 (dummy) (padding) 成员。通常,我不希望 SendInput 在接收 INPUT 结构时超出 KEYBDINPUT 大小type 设置为 INPUT_KEYBOARD,但 INPUT 大小是 [=14= 所要求的]的cbSize参数

  • def SendInput(*inputs): - 在 Python, 星号 ( *) 在参数之前做了一些与 C 完全不同的事情。检查 [SO]: Expanding tuples into arguments (我没有找到官方文档)。我修改了函数,让它只发送一个这样的结构

  • Always 为函数定义 argtypes(和 restype)通过 ctypes 调用。否则它们将默认为 ctypes.c_int(这可能会导致一些严重的错误,尤其是在 64 位 上: )

  • Win特定类型使用ctypes.wintypes,不要重新发明轮子。更不用说有时可能会出现不匹配(我最近看到的是 ctypes.c_bool vs. wintypes.BOOL)

  • 我将您的函数重命名为 [Python]: PEP 8 -- Style Guide for Python Code 兼容。我还删除了一些不再需要的

  • 其他不值得单独提及的小改动

至于测试,我使用了 记事本 window(或任何其他对用户输入“敏感”的):

  • 启动脚本
  • Alt + Tab测试window(我添加了1st指令(time.sleep(2)) 来自 main 只是为了让用户有时间切换 windows),并注意 cs“神奇地”出现

... 或者只是从控制台启动脚本,按住(并按住)Ctrl,然后注意 KeyboardInterruptCtrl + C)出现.