Pywin32 & ctypes Cookbook by Eric

1. Writing Prompt

现在请您担任MSFT的Python工程师,请你根据"Pywin32_Funtion"函数的功能,为其编写一个清晰
的文档说明

2. Learning Documentation

Bitmap Functions (Windows GDI) - Win32 apps | Microsoft Learn

3. Search techniques

3.1 Microsoft Q&A

4. 阅读资料

4.1 【Microsoft Learn】《Setting default awareness programmatically》

Note
SetProcessDPIAware函数仅适用于旧版本的Windows。从Windows 8.1开始,推荐使用SetProcessDpiAwareness函数,而在Windows10中推荐使用SetProcessDpiAwarenessContext

目前我在代码中使用了“ctypes.windll.user32.SetProcessDPIAware()”,其目的只是为了精确地获取某个应用程序窗口的截图;现在我期望当前这个脚本不会更改系统的设置、或者影响其它应用程序的运行,有没有什么比较安全的实现方法,使得我可以达到同样的效果,但是目的仅是期望可以使用pywin32获得窗口的精确截图,而不会其它应用程序或系统设置,这个可以做到吗?

注意,我在这段代码中加入了 windll.user32.SetProcessDPIAware() 来确保截图是在 DPI 感知的上下文中进行的。这个调用会影响你的 Python 进程,但通常不会影响其他应用程序或系统设置。

5. Functions

5.1 Pywin32

win32gui.FindWindow(lpClassName, lpWindowName)

描述

查找具有指定类名和窗口名称的顶层窗口,并返回其句柄。

参数
  • lpClassName (str):要查找的窗口的类名。如果为None或空字符串,则不限制类名。
  • lpWindowName (str):要查找的窗口的标题或名称。

Note:
FindWindow需要确切的窗口标题或类名,而不是部分匹配。如果窗口标题有任何额外的字符或空格,FindWindow()将找不到它。如果需要更灵活的匹配选项(例如,部分匹配标题),可以参阅EnumWindows()

win32process.GetWindowThreadProcessId(hWnd, lpdwProcessId) [doc]

描述

用于获取指定窗口的创建线程ID和进程ID。

参数
  • hWnd: 窗口句柄。
  • lpdwProcessId: 指向接收进程 ID 的 DWORD 变量。
返回值

返回一个包含两个元素的元组:

  • thread_id (int): 创建窗口的线程的标识符。这并不一定是当前正在操作窗口的线程。
  • process_id (int): 创建窗口的进程的标识符。

win32api.OpenProcess(dwDesiredAccess, bInheritHandle, dwProcessId) [doc]

描述

用于打开现有进程的句柄的函数。通过此函数获得的进程句柄可以用来查询进程信息、读写内存、等待进程结束或执行其他依赖于进程句柄的操作。

语法
handle = win32api.OpenProcess(wDesiredAccess, bInheritHandle, dwProcessId)

win32gui.GetWindowRect(hwnd)

描述

此函数用于获取指定窗口边界矩形的尺寸。尺寸以屏幕坐标给出,相对于屏幕的左上角。

示例
left, top, right, bottom = win32gui.GetWindowRect(hwnd)

其中,hwnd 是一个整数,表示要获取边界矩形的窗口的句柄。函数返回一个包含四个整数的元组,分别表示窗口边界矩形的左、上、右和下边缘的屏幕坐标。

Note:如果获取的窗口坐标不正确,可能是因为DPI缩放的影响,需要在win32gui.GetWindowRect(hwnd)前面开启 DPI感知处理

import ctypes
ctypes.windll.user32.SetProcessDPIAware()

win32gui.GetWindowDC(hwnd)

描述

win32gui.GetWindowDC()函数用于获取指定窗口的设备上下文(Device Context,简称DC)。DC是一个用于绘制图形和文本的环境,它封装了与绘图设备相关的信息。

参数
  • hwnd:【整数值】,窗口句柄(Handle)。用作窗口唯一的标识符。
释放
  • win32gui.ReleaseDC(): 释放通过GetWindowDC获取的DC。

win32ui.CreateDCFromHandle(hwindc)

描述

此函数用于从现有的设备上下文(Device Context, DC)句柄创建一个PyCDC对象。设备上下文是用于在设备上执行绘图操作的Windows GDI对象。通过使用此函数,可以将现有的设备上下文句柄封装为Python对象,从而允许使用更高级的面向对象的方法进行绘图操作。

参数

  • hwindc: 设备上下文句柄。这是一个整数值,代表现有的设备上下文句柄,可以通过win32gui.GetWindowDC(hwnd)等函数获取。

释放

  • PyCDC.DeleteDC():删除与设备上下文关联的所有资源。

win32gui.DeleteObject(handle)

描述

win32gui.DeleteObject()函数用于删除指定句柄的图形对象,并释放与该对象关联的所有系统资源。该对象可以是位图、画笔、字体、调色板、区域或设备上下文对象。

参数

  • handle[int] 要删除的图形对象的句柄。该句柄必须是通过GDI函数(如CreateBitmap, CreateFont等)创建的有效句柄。

参见

  • win32ui.CreateBitmap(): 创建位图对象。

win32api.GetLastError()

描述

用于获取最后一次Win32API调用产生的错误代码。

返回值

该函数返回一个整数,表示最后一个Win32API调用的错误代码。如果上一个API调用成功,函数通常返回0。

5.2 Ctypes

ctypes.windll.kernel32.OpenProcess(dwDesiredAccess, bInheritHandle, dwProcessId) [doc]

描述

ctypes.windll.kernel32.OpenProcess() 函数用于打开一个指定的进程,并获取对该进程的句柄。

参数
  • dwDesiredAccess: 指定对进程的访问权限。该参数可以是以下值的组合:

    • PROCESS_QUERY_INFROMATION: 查询进程信息权限
    • PROCESS_QUERY_LIMITED_INFROMATION: 查询有限的进程信息权限
    • PROCESS_DUP_HANDLE: 复制进程句柄权限
    • PROCESS_CREATE_THREAD: 创建进程线程权限
    • PROCESS_VM_OPERATION: 虚拟内存操作权限
    • PROCESS_VM_READ: 读取虚拟内存权限
    • PROCESS_VM_WRITE: 写入虚拟内存权限
    • PROCESS_SUSPEND_RESUME: 暂停/恢复进程执行权限
    • PROCESS_TERMINATE: 终止进程权限
  • bInheritHandle: 指示是否将句柄继承到子进程。如果为 True,则子进程将继承对该进程的句柄。

  • dwProcessId: 要打开的进程的 ID。

返回值

如果函数成功,则返回一个指向该进程的句柄。如果函数失败,则返回 0

所需权限

执行OpenProcess()不一定需要以管理员身份运行脚本。具体所需的权限取决于带查询进程的权限:如果尝试查询的进程具有比当前脚本更高的权限,或者这个进程的安全设置不允许低权限的用户查询信息,则可能会收到一个访问被拒绝的错误。

ctypes.windll.gdi32.GetDIBits(hDC, hBmp, startScan, scanLines, lpvBits, lpbi, usage)

参数
  • hDC (ctypes.wintypes.HDC): 设备上下文(Device Context)的句柄,用于指定图形对象如何在输出设备上呈现。
  • hBmp (ctypes.wintypes.HBITMAP): 要查询的位图(Bitmap)的句柄。
  • startScan (int): 要开始扫描的起始行。
  • scanLines (int): 从起始行开始要扫描的行数。
  • lpvBits (ctypes.POINTER): 用于接收位图像素数据的缓冲区的指针。
  • lpbi (ctypes.POINTER(BITMAPINFO)): 指向一个 BITMAPINFO 结构的指针,该结构描述了如何检索位图数据。
  • usage (int): 指定颜色表如何被使用。通常,这应是 DIB_RGB_COLORS(实际像素值)或 DIB_PAL_COLORS(调色板索引)。
返回值

返回一个整数,表示从位图中复制的扫描行数。如果函数失败,返回值为0

ctypes.windll.user32.PrintWindow(hwnd, hdcBlt, nFlags) [doc]

参数
  • hwnd (ctypes.example): 窗口的句柄,指定要被捕获内容的窗口。
  • hdcBlt: 设备上下文的句柄,指定用于绘制窗口内容的目标设备上下文。
  • nFlags: 绘图选项。这个参数可以是0,表示默认行为(复制整个窗口),或者是以下标志之一:
    • PW_CLIENTONLY: 仅复制窗口的客户区,不包括非客户区,如边框、标题栏和滚动条。

Note:

  • 在默认情况下,PrintWindow将会返回实际窗口的图像数据,而不会受到DPI设置的影响;
  • 这里的默认情况,是指待测试程序在窗口处理中并没有自定义接收WM_PRINT行为。
跟 WM_PRINT message 的关系

PrintWindow是通过在内部发送WM_PRINTWM_PRINTCLIENT消息给目标窗口来实现窗口截图;具体来说,PrintWindow函数通过参数nFlags来决定内部发送 WM_PRINT 或 WM_PRINTCLIENT 消息:

  • 如果nFlags参数为0,PrintWindow会发送一个 WM_PRINT 消息给窗口
  • nFlags参数中设置为PW_CLIENTONLY标志,则PrintWindow会发送 WM_PRINTCLIENT 消息,这个消息只请求窗口绘制其客户区的内容,而不包括窗口的非客户区

关于nFlags可选项的讨论和说明请参考【Clarification on the nFlags Parameter Default Value for PrintWindow Function - Microsoft Q&A】

Note
需要注意的是,窗口是否响应WM_PRINT消息以及如何响应可能取决于该窗口的具体实现,某些应用程序可能不支持或者不完全支持这些消息,这可能会影响PrintWindow函数的输出结果。

错误信息

对于捕获高权限

6. Class

4.1 PyCDC

PyCDC.CreateCompatibleDC(self)

对象

src_dc:一个已存在的设备上下文(Device Context)的句柄。

功能

CreateCompatibleDC() 方法用于在 Windows GDI(图形设备接口)环境中创建一个与指定设备上下文(DC)兼容的内存设备上下文(Memory DC)。

PyCDC.BitBlt(self)

Note:
BitBlt的全称是 Bit-Block Transfer,即“位块传输”。

描述

BitBlt()函数用于执行位块传输的光栅操作。该函数将源设备上下文(source device context, DC)中的像素复制到目标设备上下文(destination device context, DC)。

参数
  • hdcDest: 目标设备上下文的句柄。
  • xDest: 目标矩形的左上角的x坐标。
  • yDest: 目标矩形的左上角的y坐标。
  • wDest: 目标矩形的宽度。
  • hDest: 目标矩形的高度。
  • hdcSrc: 源设备上下文的句柄。
  • xSrc: 源矩形的左上角的x坐标。
  • ySrc: 源矩形的左上角的y坐标。
  • rop: 光栅操作代码。定义如何将源和目标矩形的颜色组合以达到最终效果。例如,win32con.SRCCOPY表示直接复制源矩形到目标矩形。

4.2 PyCBitmap

-------------Legacy------------------------------

4.2.2 GetBitmapBits(flag: bool) -> bytes (Obsolete)

[Note]: This function is provided only for compatibility with 16-bit versions of Windows. Applications should use the GetDIBits function.

参数
  • flag (bool): 指定是否应该创建一个与位图兼容的设备上下文(Device Context, DC)。
返回值
  • bytes: 返回一个包含位图像素数据的字节串。

BITMAPINFOHEADER [wincpp]

class BITMAPINFOHEADER(ctypes.Structure):
     _fields_ = [
         ("biSize", ctypes.c_uint32),
         ("biWidth", ctypes.c_int),
         ('biHeight', ctypes.c_int),
         ('biPlanes', ctypes.c_ushort),
         ('biBitCount', ctypes.c_ushort),
         ('biCompression', ctypes.c_uint32),
         ('biSizeImage', ctypes.c_uint32),
         ('biXPelsPerMeter', ctypes.c_int),
         ('biYPelsPerMeter', ctypes.c_int),
         ('biClrUsed', ctypes.c_uint32),
         ('biClrImportant', ctypes.c_uint32)
     ]

7. 权限查询

7.1 使用ctypes查询权限

    h_process = ctypes.windll.kernel32.OpenProcess(win32con.PROCESS_QUERY_INFORMATION, False, pid)
    if h_process == 0:
        error_code = ctypes.windll.kernel32.GetLastError()
        if error_code == win32con.ERROR_ACCESS_DENIED:
            print("Access denied. You might need to run the script as an administrator.")
        else:
            print(f"Failed to open process for PID {pid}: error code {error_code}")
    else:
        print(f"Successfully opened process with handle {h_process}")
        # 当你完成了进程句柄的使用后,确保关闭它
        ctypes.windll.kernel32.CloseHandle(h_process)

8. Capturing Image

8.1 需求描述

我们期望使用python来捕获窗口画面:
- 需要确保窗口在最小化时也可以工作

8.2 学习资料

5.2.1 BitBlt is good choice for capturing window

GPT4-32k:
For capturing window content in Windows, BitBlt is one of the most commonly used and efficient functions provided by the Windows API, especially for windows that are visible and not overlapped by other windows. There isn’t an alternative function that is universally faster for capturing window content because BitBlt is already a low-level, high-performance function that does a straight pixel copy.

The minor intellectual error lies in the use of the word “necessary” in the statement. While PrintWindow is indeed useful for capturing minimized or obscured windows, it is not the only option available. There are other techniques, such as using the Windows Desktop Duplication API or capturing the window’s underlying pixel data directly, that can also be employed in such situations.

5.3 示例代码

# 已知hwnd为窗口句柄
# 获取dc对象
hdc = win32gui.GetWindowDC(hwnd)
src_dc = win32ui.CreateDCFromHandle(hdc)
mem_dc = src_dc.CreateCompatibleDC()
# 开启DPI感知
ctypes.windll.user32.SetProcessDPIAware()
rect = win32gui.GetWindowRect(hwnd)
width = rect[2] - rect[0]
height = rect[3] - rect[1]
bmp = win32ui.CreateBitmap()
bmp.CreateCompatibleBitmap(src_dc, width, height)
mem_dc.SelectObject(bmp)
mem_dc.BitBlt((0, 0), (width, height), src_dc, (0, 0), win32con.SRCCOPY)

class BITMAPINFOHEADER(ctypes.Structure):
    _fields_ = [
        ('biSize', ctypes.c_uint32),
        ('biWidth', ctypes.c_int),
        ('biHeight', ctypes.c_int),
        ('biPlanes', ctypes.c_ushort),
        ('biBitCount', ctypes.c_ushort),
        ('biCompression', ctypes.c_uint32),
        ('biSizeImage', ctypes.c_uint32),
        ('biXPelsPerMeter', ctypes.c_int),
        ('biYPelsPerMeter', ctypes.c_int),
        ('biClrUsed', ctypes.c_uint32),
        ('biClrImportant', ctypes.c_uint32)
    ]

# 获取位图数据
bi = BITMAPINFOHEADER()
bi.biSize = ctypes.sizeof(BITMAPINFOHEADER)
bi.biWidth = width
bi.biHeight = -height  # 负数表示自上而下的DIB
bi.biPlanes = 1
bi.biBitCount = 32
bi.biCompression = 0  # BI_RGB,无压缩
# 创建用于存储像素数据的NumPy数组
image_data = np.zeros((height, width, 4), dtype=np.uint8)
# 定义Windows API函数和结构体
GetDIBits = ctypes.windll.gdi32.GetDIBits
GetDIBits(hdc, bmp.GetHandle(), 0, height, image_data.ctypes, ctypes.byref(bi), 0)
# 截图数据存储在image_data中

# 完成操作后释放资源
mem_dc.DeleteDC()
src_dc.DeleteDC()
win32gui.ReleaseDC(hwnd, hdc)
win32gui.DeleteObject(bmp.GetHandle())

Troubleshooting

(1)截图时发现获得的图像截取不全,图像的右下角缺失

原因:未开启DPI感知

在高DPI屏幕上,如果不考虑DPI因素,直接使用窗口大小可能不正确,因为获得的“rect”很可能不是实的际屏幕尺寸;而需要按DPI缩放比例修正。
需要在使用rect = win32gui.GetWindowRect(hwnd)之前开启DPI感知

import ctypes
ctypes.windll.user32.SetProcessDPIAware()

(2)截图时发现获得的图像是一个很小的“全黑”矩形(远远小于窗口尺寸)而且什么内容都没有,或者是截图

(3)使用win32gui.SendMessage发送鼠标点击消息没有生效,并且调试发现使用pyWinhook也无法捕获鼠标点击事件

原因:没有足够的权限

经过测试之后,我们发现如果期望使用win32gui.SendMessage发送鼠标点击消息,需要当前程序【以管理员身份运行】;

错误提示

我们在代码中添加了win32api.GetLastError()的代码来检查执行消息发送后是否有错误码;

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值