GetSystemMetrics 返回逻辑像素而非物理分辨率,受DPI缩放影响;应使用EnumDisplayMonitors+GetMonitorInfo配合GetDpiForMonitor计算物理尺寸,或用QueryDisplayConfig获取稳定物理分辨率。

GetSystemMetrics 为什么返回错误的分辨率?
调用 GetSystemMetrics(SM_CXSCREEN) 和 GetSystemMetrics(SM_CYSCREEN) 时,返回值可能被 DPI 缩放干扰 —— 比如系统设为 125% 缩放,实际是 1920×1080 的屏,它却返回约 1536×864。这不是 bug,而是 GDI 默认以“逻辑像素”汇报尺寸。
- 仅适用于简单场景(如全屏窗口初始大小),不反映物理像素
- 在高 DPI 设置下必须配合
SetProcessDpiAwareness或清单文件启用 DPI 感知 - 若未声明 DPI 感知,Windows 会自动缩放你的坐标系,
GetSystemMetrics就按缩放后值返回
EnumDisplayMonitors + GetMonitorInfo 获取真实物理分辨率
要拿到每个显示器的原生像素尺寸(含多屏、不同缩放比、主副屏差异),必须用监视器枚举 API。关键不是“屏幕”,而是“监视器” —— Windows 把每个物理显示设备抽象为一个 HMONITOR。
- 先用
EnumDisplayMonitors遍历所有活动监视器 - 对每个
HMONITOR调用GetMonitorInfo,填入MONITORINFOEX结构体 -
mi.rcMonitor给出该屏在虚拟桌面坐标系中的矩形(已考虑 DPI 缩放) - 真正物理像素需进一步调用
GetDpiForMonitor+ 计算:例如rcMonitor.right - rcMonitor.left是逻辑宽度,除以缩放比例才是物理像素
MONITORINFOEX mi{ sizeof(mi) };
GetMonitorInfo(hMonitor, &mi);
int logical_w = mi.rcMonitor.right - mi.rcMonitor.left;
UINT dpi_x, dpi_y;
GetDpiForMonitor(hMonitor, MDT_EFFECTIVE_DPI, &dpi_x, &dpi_y);
int physical_w = MulDiv(logical_w, 96, dpi_x); // 假设 96 是参考 DPI
GetDisplayConfigBufferSizes 和 QueryDisplayConfig 更现代但更复杂
Windows 7+ 推荐用 QueryDisplayConfig 获取显示拓扑(包括旋转、缩放、主副屏状态、连接状态),但它不直接返回分辨率,而是一组 DISPLAYCONFIG_MODE_INFO,其中 width/height 字段才是未经缩放的物理像素尺寸。
- 需要先调用
GetDisplayConfigBufferSizes获取所需缓冲区大小 - 再分配内存并调用
QueryDisplayConfig,传入DISPLAYCONFIG_CURRENT_SOURCE | DISPLAYCONFIG_CURRENT_TARGET - 遍历返回的
DISPLAYCONFIG_MODE_INFO数组,筛选infoType == DISPLAYCONFIG_MODE_INFO_TYPE_TARGET的项 - 每个项的
modeInfo.targetMode.sourceSize.cx/cy即该显示器当前输出的物理宽高
这个 API 不受进程 DPI 感知状态影响,结果稳定,但代码量明显增加,适合需要精确控制多显示器布局的程序(如远程桌面、录屏工具)。
立即学习“C++免费学习笔记(深入)”;
容易忽略的兼容性与权限细节
部分 API 在低版本 Windows 或无管理员权限时行为受限:
-
GetDpiForMonitor要求 Windows 8.1+;Win7 只能靠GetDeviceCaps(HORZSIZE/VERTSIZE)+ 屏幕对角线估算 DPI,误差大 - 如果程序没有 manifest 文件声明
dpiAware=true,即使调用了SetProcessDpiAwareness,在 Win10 1703+ 上也可能被系统忽略 -
EnumDisplayMonitors默认只枚举“可见”监视器 —— 如果某屏被禁用或断开,它不会出现,但QueryDisplayConfig仍可查到其存在(DISPLAYCONFIG_DEVICE_INFO_GET_MONITOR_CAPS)
物理分辨率不是固定值:用户可能热插拔显示器、切换投影模式、更改缩放设置。获取一次不够,关键路径上应监听 WM_DISPLAYCHANGE 消息做动态更新。










