正确姿势是先用monitorfromwindow或enumdisplaymonitors获取系统生成的有效hmonitor,再调用getdpiformonitor;需链接shcore.lib、检查windows 8.1+版本、使用mdt_effective_dpi参数。

Windows 下用 GetDpiForMonitor 获取显示器 DPI 的正确姿势
直接调用 GetDpiForMonitor 大概率返回 E_INVALIDARG 或 0,不是函数错了,是它不接受任意 HMONITOR——必须先用 MonitorFromWindow 或 EnumDisplayMonitors 拿到“真实有效”的句柄。
关键点:该函数要求传入的 HMONITOR 必须由系统内部生成(比如通过窗口关联或枚举获得),不能靠 GetMonitorInfo 反推,也不能硬写数值。
- 先确保链接
shcore.lib,否则链接失败;#pragma comment(lib, "shcore.lib")最省事 - 调用前检查 Windows 版本:该 API 在 Windows 8.1+ 才可用,Win7 下会直接调用失败(
GetProcAddress返回 NULL) - 参数
dpiType推荐用MDT_EFFECTIVE_DPI,它反映实际缩放效果;MDT_ANGULAR_DPI和MDT_RAW_DPI在高分屏混合场景下意义模糊 - 示例片段:
HMONITOR hmon = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST); UINT dpiX = 0, dpiY = 0; HRESULT hr = GetDpiForMonitor(hmon, MDT_EFFECTIVE_DPI, &dpiX, &dpiY);
为什么 GetDeviceCaps(HORZRES/VERTRES) + LOGPIXELSX/Y 不可靠
这套老方法在多 DPI 场景下基本失效:它只读取主显示器的 GDI 缩放设置,且完全忽略 per-monitor DPI 启用状态。即使你的应用声明了 dpiAware=true,GetDeviceCaps 仍可能返回 96,而实际显示是 144。
-
GetDeviceCaps(LOGPIXELSX)返回的是“系统级默认 DPI”,不是当前窗口所在屏的实际 DPI - 在 4K 屏 + 150% 缩放 + 笔记本外接 1080p 屏(100%)的典型双屏配置下,两个屏的
LOGPIXELSX值完全一样,但视觉缩放完全不同 - 仅适用于 Win7 或未启用 per-monitor DPI 的遗留程序,新项目应绕开
X11 下没有统一 DPI API,得靠组合推断
X11 本身不提供类似 Windows 的 DPI 查询接口,所谓“获取 DPI”本质是查 xrandr 输出尺寸、解析 EDID、再结合屏幕物理尺寸估算——但多数 Linux 桌面环境根本不填物理尺寸,结果常常是 96 或 0。
立即学习“C++免费学习笔记(深入)”;
- 最现实的做法是读
Xft.dpiX resource:XrmGetResource查"Xft.dpi",这是 GTK/Qt 实际使用的值(通常由桌面环境或用户 ~/.Xresources 设置) -
xrandr --verbose输出里的mm字段若为 0,说明驱动没上报物理尺寸,此时任何基于 mm 的计算都无意义 - Wayland 下更复杂:需走
wp-primary-output或zxdg_output_manager_v1协议,且客户端必须主动订阅事件,无法“查询一次就完事”
跨平台封装时最容易被忽略的边界
很多封装库把 DPI 当成窗口属性缓存,但 Windows 下 WM_DPICHANGED 事件触发后,旧 HMONITOR 可能已失效,下次调用 GetDpiForMonitor 就会崩;X11 下则根本没事件机制,只能轮询或依赖桌面通知。
- 不要缓存
HMONITOR超过一次绘制周期,每次需要时重新用MonitorFromWindow获取 - Windows 上必须响应
WM_DPICHANGED并重设字体、重排布局;漏掉这个消息,窗口会在缩放切换后文字糊成一团 - X11 下若发现
Xft.dpi改变,需重建所有字体对象——XLoadFont不会自动适配新 DPI










