杰瑞科技汇

Python如何调用Windows API?

核心概念

无论使用哪种方法,调用 Windows API 的基本流程都是相似的:

Python如何调用Windows API?-图1
(图片来源网络,侵删)
  1. 找到函数所在的 DLL 文件:Windows API 函数都存储在动态链接库中,user32.dll(界面相关)、kernel32.dll(系统核心功能)、shell32.dll(Shell 相关)等。
  2. 声明函数:告诉 Python 这个函数的名称、它所在的 DLL、以及它需要什么类型的参数和返回值。
  3. 调用函数:像调用普通 Python 函数一样调用它,并传入正确的参数。
  4. 处理结果:获取并解析函数返回的值。

使用 ctypes (最常用、最直接)

ctypes 是 Python 的标准库之一,它允许你调用 C 语言兼容的动态链接库,这是最基础、最灵活的方法,不需要安装任何第三方包。

优点:

  • 无需安装:Python 自带。
  • 功能强大:几乎可以调用所有 Windows API。
  • 学习曲线平缓:概念相对直接。

缺点:

  • 代码冗长:需要手动定义函数原型、数据结构等。
  • 容易出错:类型错误(如传错参数类型)会导致程序崩溃。

示例:获取当前窗口的标题

这个例子将调用 user32.dll 中的 GetWindowTextW 函数来获取活动窗口的标题。

import ctypes
from ctypes import wintypes
# 1. 定义函数原型
# 告诉 Python GetWindowTextW 函数在哪里,它的参数和返回类型是什么
user32 = ctypes.WinDLL('user32', use_last_error=True) # use_last_error=True 可以获取错误码
# 定义函数参数和返回值类型
user32.GetWindowTextW.argtypes = [
    wintypes.HWND,    # 窗口句柄
    wintypes.LPWSTR,  # 缓冲区指针
    ctypes.c_int      # 缓冲区最大长度
]
user32.GetWindowTextW.restype = wintypes.INT # 返回值为拷贝的字符数
# 2. 获取当前活动窗口的句柄
# GetForegroundWindow 是 user32.dll 中的另一个函数
GetForegroundWindow = user32.GetForegroundWindow
GetForegroundWindow.argtypes = []
GetForegroundWindow.restype = wintypes.HWND
# 3. 调用函数
hwnd = GetForegroundWindow()
if not hwnd:
    print(f"无法获取窗口句柄,错误码: {ctypes.get_last_error()}")
else:
    # 创建一个足够大的缓冲区 (256个字符)
    buffer_size = 256
    buffer = ctypes.create_unicode_buffer(buffer_size)
    # 调用 GetWindowTextW
    length = user32.GetWindowTextW(hwnd, buffer, buffer_size)
    if length > 0:
        window_title = buffer.value
        print(f"当前活动窗口标题是: {window_title}")
    else:
        print(f"无法获取窗口标题,错误码: {ctypes.get_last_error()}")

使用 pywin32 (功能最全面)

pywin32 是一个第三方库,它为 Windows 提供了丰富的 Python 接口,它不仅仅是 API 的简单封装,还提供了很多高级对象,win32guiwin32com 等。

优点:

  • 高度封装:将复杂的 API 调用简化成了 Pythonic 的函数调用。
  • 功能全面:除了 Win32 API,还包含 COM、ADI 等技术。
  • 文档和社区支持好:使用广泛,容易找到教程和解决方案。

缺点:

  • 需要安装pip install pywin32
  • 可能版本不匹配:需要确保 pywin32 的版本与你的 Python 版本兼容。

示例:同样获取当前窗口的标题

使用 pywin32,代码会简洁得多。

Python如何调用Windows API?-图2
(图片来源网络,侵删)
import win32gui
# 获取当前活动窗口的句柄
hwnd = win32gui.GetForegroundWindow()
# 通过句柄获取窗口标题
# GetWindowText 是 win32gui 模块中封装好的函数
window_title = win32gui.GetWindowText(hwnd)
if window_title:
    print(f"当前活动窗口标题是: {window_title}")
else:
    print("无法获取窗口标题")

可以看到,代码量大大减少,可读性也更高。


使用 pywin32ctypes 的混合方式

有时,pywin32 可能没有封装你需要的特定 API 函数,这时,你可以在 pywin32 的基础上,直接使用 ctypes 来调用未封装的函数。

import win32gui
import ctypes
from ctypes import wintypes
# 我们想调用 user32.dll 中的 GetWindowRect 函数
# pywin32 没有直接提供这个函数,所以用 ctypes
# 1. 定义 ctypes 函数原型
user32 = ctypes.WinDLL('user32', use_last_error=True)
GetWindowRect = user32.GetWindowRect
GetWindowRect.argtypes = [wintypes.HWND, ctypes.POINTER(wintypes.RECT)]
GetWindowRect.restype = wintypes.BOOL
# 2. 使用 pywin32 获取句柄
hwnd = win32gui.GetForegroundWindow()
# 3. 定义 RECT 结构体
class RECT(ctypes.Structure):
    _fields_ = [
        ("left", wintypes.LONG),
        ("top", wintypes.LONG),
        ("right", wintypes.LONG),
        ("bottom", wintypes.LONG)
    ]
# 4. 调用 ctypes 函数
rect = RECT()
if GetWindowRect(hwnd, ctypes.byref(rect)):
    print(f"窗口句柄: {hwnd}")
    print(f"窗口位置和大小: 左={rect.left}, 上={rect.top}, 右={rect.right}, 下={rect.bottom}")
    width = rect.right - rect.left
    height = rect.bottom - rect.top
    print(f"窗口宽度: {width}px, 高度: {height}px")
else:
    print(f"GetWindowRect 调用失败,错误码: {ctypes.get_last_error()}")

使用 windll (简化版 ctypes)

ctypes 提供了 windllcdll 两个模块。windll 专门用于调用 Windows API(使用 stdcall 调用约定),它比 ctypes.WinDLL 更简洁。

from ctypes import wintypes
# 直接从 windll 导入 user32
user32 = ctypes.windll.user32
# 定义参数和返回值
user32.GetWindowTextW.argtypes = [wintypes.HWND, wintypes.LPWSTR, wintypes.INT]
user32.GetWindowTextW.restype = wintypes.INT
# 获取句柄
hwnd = user32.GetForegroundWindow()
# 调用
buffer = ctypes.create_unicode_buffer(256)
length = user32.GetWindowTextW(hwnd, buffer, 256)
if length > 0:
    print(f"当前活动窗口标题是: {buffer.value}")
else:
    print("无法获取窗口标题")

进阶:处理复杂参数和结构体

很多 API 函数需要传入结构体指针,RECTPOINTMSG 等,处理这些需要用到 ctypes.Structure

Python如何调用Windows API?-图3
(图片来源网络,侵删)

示例:模拟鼠标点击

这个例子需要调用 mouse_event 函数,它需要 MOUSEINPUT 结构体。

import ctypes
import time
# 定义常量
MOUSEEVENTF_LEFTDOWN = 0x0002
MOUSEEVENTF_LEFTUP = 0x0004
# 定义结构体 MOUSEINPUT
class MOUSEINPUT(ctypes.Structure):
    _fields_ = [
        ("dx", ctypes.c_long),
        ("dy", ctypes.c_long),
        ("mouseData", ctypes.c_ulong),
        ("dwFlags", ctypes.c_ulong),
        ("time", ctypes.c_uint),
        ("dwExtraInfo", ctypes.POINTER(ctypes.ULONG)),
    ]
# 定义结构体 INPUT (mouse input)
class INPUT(ctypes.Structure):
    class _INPUT(ctypes.Union):
        _fields_ = [("mi", MOUSEINPUT)]
    _anonymous_ = ("_input",)
    _fields_ = [
        ("type", ctypes.c_ulong),
        ("_input", _INPUT),
    ]
# 定义函数原型
user32 = ctypes.windll.user32
SendInput = user32.SendInput
SendInput.argtypes = [ctypes.c_uint, ctypes.POINTER(INPUT), ctypes.c_int]
SendInput.restype = ctypes.c_uint
def simulate_click(x, y):
    # 设置鼠标位置 (这里假设已经在目标位置,dx, dy 为 0)
    # 如果要移动鼠标,需要使用 SetCursorPos
    mi = MOUSEINPUT(dx=0, dy=0, mouseData=0, dwFlags=MOUSEEVENTF_LEFTDOWN, time=0, dwExtraInfo=None)
    # 创建 INPUT 结构体数组
    inputs = (INPUT * 1)(INPUT(type=0, mi=mi)) # 0 表示 MOUSE input
    # 发送按下事件
    SendInput(1, inputs, ctypes.sizeof(INPUT))
    time.sleep(0.1) # 短暂延时,模拟真实点击
    # 修改标志为抬起
    mi.dwFlags = MOUSEEVENTF_LEFTUP
    inputs = (INPUT * 1)(INPUT(type=0, mi=mi))
    # 发送抬起事件
    SendInput(1, inputs, ctypes.sizeof(INPUT))
# --- 调用 ---
print("将在 3 秒后模拟鼠标左键点击...")
time.sleep(3)
simulate_click(0, 0) # 在当前位置点击
print("点击完成!")

总结与推荐

方法 优点 缺点 推荐场景
ctypes 无需安装,功能强大,灵活 代码繁琐,易出错 快速调用简单 API,或作为学习底层原理的工具。
pywin32 封装好,代码简洁,功能全面 需要安装,版本兼容性 绝大多数 Windows 自动化任务的首选
pywin32 + ctypes 结合两者优势,非常灵活 需要同时了解两者 pywin32 没有你需要的特定 API 时。
windll ctypes.WinDLL 更简洁 功能与 ctypes 类似 喜欢更简洁写法的开发者。

给你的建议:

  • 如果你是初学者:直接从 pywin32 开始,它能让你用最少的代码完成最多的工作,并且能让你更专注于业务逻辑而不是繁琐的 API 定义。
  • 如果你需要调用非常冷门或最新的 APIpywin32 可能没有提供封装,这时你不得不使用 ctypes 来手动定义函数原型。
  • 如果你想深入理解 Windows API 与 Python 的交互:从 ctypes 开始是最好的方式,它能让你明白一切工作的底层原理。
分享:
扫描分享到社交APP
上一篇
下一篇