非DPI感知应用截图异常:GetDC/BitBlt的DPI Bug及解决方法
问题解答
问题1:这是否为Bug?
这不是Bug,是Windows的DPI虚拟化特性。对于未声明DPI感知的应用,系统会自动启用该机制,将应用的坐标和分辨率请求进行缩放转换,让应用误以为运行在96DPI(100%缩放比例)的环境中,以此保证老旧应用的界面不会因为高DPI缩放而出现布局错乱。你看到的缩放后的值,正是这个机制的正常表现。
问题2:如何在非DPI感知应用中实现完整截图?
需要绕过DPI虚拟化获取真实桌面分辨率,再基于真实尺寸创建位图并完成截图,以下是两种可行方案:
方案1:通过枚举显示器获取真实分辨率(支持多显示器)
通过EnumDisplayMonitors枚举显示器,在回调中获取主显示器的真实像素尺寸,再创建对应大小的位图完成截图:
#include <Windows.h> // 显示器枚举回调函数,获取主显示器真实尺寸 BOOL CALLBACK MonitorEnumProc(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) { MONITORINFOEX monitorInfo = {0}; monitorInfo.cbSize = sizeof(MONITORINFOEX); GetMonitorInfoW(hMonitor, &monitorInfo); *(RECT*)dwData = monitorInfo.rcMonitor; return FALSE; // 仅获取主显示器,如需多显示器可返回TRUE继续枚举 } // 获取真实桌面矩形 RECT GetRealDesktopRect() { RECT realRect = {0}; EnumDisplayMonitors(NULL, NULL, MonitorEnumProc, (LPARAM)&realRect); return realRect; } // 完整截图函数 void CaptureFullDesktop() { RECT realRect = GetRealDesktopRect(); int realWidth = realRect.right - realRect.left; int realHeight = realRect.bottom - realRect.top; // 创建真实显示器DC HDC hScreenDC = CreateDCW(L"DISPLAY", NULL, NULL, NULL); HDC hMemDC = CreateCompatibleDC(hScreenDC); HBITMAP hBitmap = CreateCompatibleBitmap(hScreenDC, realWidth, realHeight); HGDIOBJ hOldBitmap = SelectObject(hMemDC, hBitmap); // 执行截图 BitBlt(hMemDC, 0, 0, realWidth, realHeight, hScreenDC, realRect.left, realRect.top, SRCCOPY); // 此处可添加位图保存逻辑(如写入BMP文件) // ... // 释放资源 SelectObject(hMemDC, hOldBitmap); DeleteObject(hBitmap); DeleteDC(hMemDC); DeleteDC(hScreenDC); }
方案2:通过系统DPI计算真实分辨率(单显示器场景)
先获取系统真实DPI,再结合虚拟化后的分辨率计算出真实尺寸:
#include <Windows.h> void CaptureFullDesktopSingleMonitor() { UINT systemDpi = GetDpiForSystem(); // 获取虚拟化后的分辨率 int virtualWidth = GetSystemMetrics(SM_CXSCREEN); int virtualHeight = GetSystemMetrics(SM_CYSCREEN); // 计算真实分辨率 int realWidth = MulDiv(virtualWidth, systemDpi, 96); int realHeight = MulDiv(virtualHeight, systemDpi, 96); HDC hScreenDC = CreateDCW(L"DISPLAY", NULL, NULL, NULL); HDC hMemDC = CreateCompatibleDC(hScreenDC); HBITMAP hBitmap = CreateCompatibleBitmap(hScreenDC, realWidth, realHeight); HGDIOBJ hOldBitmap = SelectObject(hMemDC, hBitmap); BitBlt(hMemDC, 0, 0, realWidth, realHeight, hScreenDC, 0, 0, SRCCOPY); // 保存位图逻辑 // ... // 释放资源 SelectObject(hMemDC, hOldBitmap); DeleteObject(hBitmap); DeleteDC(hMemDC); DeleteDC(hScreenDC); }
注:方案1兼容性更好,支持多显示器场景;方案2仅适用于单显示器环境,需确保系统DPI与主显示器DPI一致。
内容的提问来源于stack exchange,提问作者Александр Кулагин
相关产品推荐
相关产品推荐

