Windows 11 上 PrintWindow / WM__PRINT 的性能随机下降

Woj*_*ech 9 python windows gdi+

问题

我正在使用 Windows 11 操作系统上的受阻窗口捕获屏幕截图windll.user32.PrintWindow(这又WM_PRINT根据文档进行调用)。一切都按预期工作,但是从 Windows 10 迁移到 Windows 11 后,性能非常不稳定。在 Win10 上,捕获通常需要不到 30 毫秒,在 Win11 上,有时接近这个时间,但有时屏幕捕获将持续数小时重复接近 300 毫秒(屏幕捕获在循环中运行)。在Win10上运行屏幕截图的几个月里从未出现过这种情况。造成这种缓慢性能的单行代码是对PrintWindow.

有趣的是,只有在捕获特定应用程序时才会出现性能缓慢的情况。这是一个第三方应用程序,我没有它的源代码,我只知道它使用Java。当尝试使用相同的屏幕捕获代码捕获其他应用程序时,性能符合预期 - 大约 20-30 毫秒。

附加信息

  • (WM_PRINT打印整个窗口和仅打印客户区域或)的时间大致相同WM_PRINTCLIENT

  • 时间与捕获窗口的大小呈线性关系。对于其他应用程序而言,情况并非如此 - PrintWindow 花费的时间大致相同(大约 30 毫秒),无论捕获的窗口是否占据全屏或尺寸是否大幅减小。

  • 速度较慢的计算机(i5 9600 12 GB RAM)和较快的计算机(i7 10700 32 GB RAM)都会出现性能缓慢的情况。使用速度较慢的 PC 在 Win10 上运行屏幕捕获(捕获时间不到 30 毫秒)。

  • 当性能变慢时,CPU 和 GPU 不会负担过重(查看任务管理器,它们的使用率不到 3%)。我没有注意到任何关于它何时变慢的模式。

  • 动画的操作系统设置已关闭。另外,窗口没有最小化和恢复,所以据我了解,动画不应该是一个因素。

  • 在捕获的应用程序中添加控件时,屏幕捕获会稍微变慢。然而,将控件减少到最低限度仍然无法让我接近所需的 30 毫秒捕获时间。

我最初的猜测

  • 操作系统对CPU/GPU要求过高。我认为在 i7 10700 上进行的测试与 i5 上的结果相同,证明情况并非如此。

  • 应用程序的消息队列可能负载过重,并且我的 PrintWindow 调用正在排队等待。我认为性能随窗口大小线性缩放表明情况并非如此。我还尝试在调用 PrintWindow 之前调用 RedrawWindow - 没有效果。

可能的解决方案/解决方法:

  • 在不牺牲所需信息的情况下使窗口尽可能小

  • 同时捕获几个区域,然后将它们放在一起

  • 使用 BitBlt 捕获桌面屏幕(窗口必须可见)

所有这些都没有解决核心问题 - 为什么这个特定窗口的绘制速度比所有其他窗口慢得多。任何想法都非常感激。

代码:代码是用Python编写的,但据我所知,PrintWindow的关键行是直接调用Windows dll。无论编程语言如何,请随意添加想法/解决方法。

def capture_screen_from_DC(hwnd):
    l, t, r, b = win32gui.GetWindowRect(hwnd)
    w = r - l
    h = b - t

    hwndDC = win32gui.GetWindowDC(hwnd)
    mfcDC = win32ui.CreateDCFromHandle(hwndDC)
    destDC = mfcDC.CreateCompatibleDC()

    saveBitMap = win32ui.CreateBitmap()
    saveBitMap.CreateCompatibleBitmap(mfcDC, w, h)

    destDC.SelectObject(saveBitMap)

    windll.user32.PrintWindow(hwnd, destDC.GetSafeHdc(), 2)

    bmpinfo = saveBitMap.GetInfo()
    bmpstr = saveBitMap.GetBitmapBits(True)

    im = Image.frombuffer('RGB', (bmpinfo['bmWidth'], bmpinfo['bmHeight']), bmpstr, 'raw', 'BGRX', 0, 1)

    win32gui.DeleteObject(saveBitMap.GetHandle())
    destDC.DeleteDC()
    mfcDC.DeleteDC()
    win32gui.ReleaseDC(hwnd, hwndDC)

    return im
Run Code Online (Sandbox Code Playgroud)

Tib*_*ic4 2

为了提高Java应用程序中PrintWindow的性能,您可以尝试使用以下代码:

def capture_screen_from_DC(hwnd):
  l, t, r, b = win32gui.GetWindowRect(hwnd)
  w = r - l
  h = b - t

  hwndDC = win32gui.GetWindowDC(hwnd)
  mfcDC = win32ui.CreateDCFromHandle(hwndDC)
  destDC = mfcDC.CreateCompatibleDC()

  saveBitMap = win32ui.CreateBitmap()
  saveBitMap.CreateCompatibleBitmap(mfcDC, w, h)

  destDC.SelectObject(saveBitMap)

  # Set the window to be topmost
  win32gui.SetWindowPos(hwnd, win32con.HWND_TOPMOST, 0, 0, 0, 0, win32con.SWP_NOMOVE | win32con.SWP_NOSIZE)
  # Call PrintWindow
  windll.user32.PrintWindow(hwnd, destDC.GetSafeHdc(), 2)
  # Set the window back to normal
  win32gui.SetWindowPos(hwnd, win32con.HWND_NOTOPMOST, 0, 0, 0, 0, win32con.SWP_NOMOVE | win32con.SWP_NOSIZE)

  bmpinfo = saveBitMap.GetInfo()
  bmpstr = saveBitMap.GetBitmapBits(True)

  im = Image.frombuffer('RGB', (bmpinfo['bmWidth'], bmpinfo['bmHeight']), bmpstr, 'raw', 'BGRX', 0, 1)

  win32gui.DeleteObject(saveBitMap.GetHandle())
  destDC.DeleteDC()
  mfcDC.DeleteDC()
  win32gui.ReleaseDC(hwnd, hwndDC)

  return im 
Run Code Online (Sandbox Code Playgroud)

说明:PrintWindow函数是一个同步函数。当窗口不是最顶层时,窗口管理器可能需要等待其他窗口被绘制后才能绘制该窗口。这可能会导致 PrintWindow 函数变慢。当窗口位于最顶层时,窗口管理器不会等待其他窗口被绘制后才可以绘制该窗口。这可能会导致 PrintWindow 函数变快。