获取屏幕分辨率看似是个简单需求,但一旦项目要求同时支持Windows、Linux和macOS,事情就变得复杂了。三个平台各自拥有独立的窗口系统和图形接口,C++标准库本身并没有提供任何屏幕相关的API,所以你必须针对每个平台分别编写实现代码,再通过条件编译把它们粘合到一起。本文将分别讲解三套平台API的用法,最后给出一个可以直接复用的跨平台封装。

一、Windows平台:使用GDI的GetSystemMetrics
Windows上最直接的方式是调用GetSystemMetrics函数,它位于user32.dll中,头文件是windows.h。传入SM_CXSCREEN和SM_CYSCREEN即可分别拿到主显示器的宽度和高度,单位是物理像素。
#include <windows.h>
#include <iostream>
int main() {
// 获取主屏幕分辨率(物理像素)
int width = GetSystemMetrics(SM_CXSCREEN);
int height = GetSystemMetrics(SM_CYSCREEN);
std::cout << "Screen: " << width << " x " << height << std::endl;
return 0;
}</code>这个方法简单可靠,但有一个容易被忽视的坑:高DPI屏幕。从Windows 10开始,如果程序没有声明DPI感知,系统会对坐标进行虚拟化缩放。例如在一块4K屏、缩放比例200%的机器上,你拿到的可能是1920x1080而不是真实的3840x2160。解决办法是在程序入口处调用SetProcessDpiAwarenessContext或者通过清单文件声明Per-Monitor DPI Aware。
如果需要枚举所有显示器的分辨率而不仅仅是主屏,可以改用EnumDisplayMonitors配合回调函数,每个显示器回调一次,通过GetMonitorInfo拿到RECT区域的宽高。这种方式在多屏环境下是更稳妥的选择。
二、Linux平台:X11的Display连接与屏幕查询
Linux桌面环境下主流方案是X11。核心流程是先调用XOpenDisplay建立与X服务器的连接,拿到Display*指针后,用DefaultScreen获取默认屏幕编号,再通过XDisplayWidth和XDisplayHeight读取分辨率,最后记得调用XCloseDisplay释放连接。
#include <X11/Xlib.h>
#include <iostream>
int main() {
Display* display = XOpenDisplay(nullptr); // nullptr表示使用DISPLAY环境变量
if (!display) {
std::cerr << "无法连接到X服务器" << std::endl;
return 1;
}
int screen = DefaultScreen(display);
int width = XDisplayWidth(display, screen);
int height = XDisplayHeight(display, screen);
std::cout << "Screen: " << width << " x " << height << std::endl;
XCloseDisplay(display);
return 0;
}编译时必须链接X11库,例如:g++ main.cpp -o main -lX11。如果提示找不到头文件,需要先安装开发包,Debian系发行版对应的是libx11-dev,可以手动到/usr/include/X11/目录下确认头文件是否齐全。
需要说明的是,X11查询到的是逻辑像素,且在多屏拼接场景下XDisplayWidth返回的是整个XScreen的尺寸(可能包含多块显示器拼出来的总区域)。如果项目只针对Wayland桌面,X11接口在XWayland兼容层下通常仍能工作,但严格来说Wayland协议本身不提供全局屏幕查询能力,需要通过各桌面协议扩展或者通过-weston之类的合成器接口获取,这是Linux图形栈的架构性限制。
三、macOS平台:Cocoa的NSScreen
macOS上推荐使用Cocoa框架的NSScreen类。由于它是Objective-C对象,纯C++文件无法直接调用,常见做法是把实现文件后缀改为.mm(Objective-C++),这样可以在同一个文件里混用C++和Objective-C语法。
#import <AppKit/AppKit.h>
#include <iostream>
int main() {
@autoreleasepool {
NSScreen* screen = [NSScreen mainScreen];
NSRect frame = [screen frame];
int width = (int)frame.size.width;
int height = (int)frame.size.height;
std::cout << "Screen: " << width << " x " << height << std::endl;
}
return 0;
}编译时需要链接AppKit框架:clang++ main.mm -o main -framework AppKit。这里同样有Retina屏的问题:frame.size返回的是逻辑点而非物理像素。如果需要真实像素,应该访问backingScaleFactor属性,用逻辑尺寸乘以缩放系数。例如逻辑尺寸1440x900、缩放为2x的MacBook,物理分辨率实际是2880x1800。
多显示器场景下可以用[NSScreen screens]拿到数组,遍历每个NSScreen对象获取各自分辨率,这在做窗口布局或者全屏应用时非常实用。
四、用条件编译封装统一的跨平台接口
三套API各不相同,我们可以用预处理器宏把它们封装成一个统一的函数。CMake或构建脚本负责定义对应平台的宏,源代码里根据宏选择实现分支:
#ifndef SCREEN_SIZE_HPP
#define SCREEN_SIZE_HPP
struct ScreenSize {
int width;
int height;
};
inline ScreenSize getPrimaryScreenSize() {
#if defined(_WIN32)
#include <windows.h>
return { GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN) };
#elif defined(__APPLE__)
// 此分支需放在.mm文件中编译
NSScreen* s = [NSScreen mainScreen];
NSRect r = [s frame];
return { (int)r.size.width, (int)r.size.height };
#elif defined(__linux__)
#include <X11/Xlib.h>
Display* d = XOpenDisplay(nullptr);
if (!d) return { 0, 0 };
int scr = DefaultScreen(d);
ScreenSize size = { XDisplayWidth(d, scr), XDisplayHeight(d, scr) };
XCloseDisplay(d);
return size;
#else
return { 0, 0 };
#endif
}
#endif实际工程中更规范的做法是把声明放在头文件,三个平台的实现分别放在screen_win.cpp、screen_x11.cpp和screen_mac.mm三个文件里,由CMake根据平台决定编译哪个文件、链接哪些库。Windows下什么都不用额外链接,Linux下在target_link_libraries里加上X11,macOS下链接AppKit框架即可。
如果你的项目已经引入了Qt或SFML这类跨平台库,也可以直接用QScreen::geometry()或sf::VideoMode::getDesktopMode()代替手写平台代码,代价是引入一整个框架的依赖。对于只需要屏幕信息的小型工具或者游戏引擎的引导模块,自己封装这三个分支的体积开销最小,也最可控。无论选择哪条路线,理解各平台返回的是逻辑像素还是物理像素,是保证UI在不同DPI环境下正确显示的关键。