Одно из главных преимуществ Qt — возможность писать единую базу кода для Windows, Linux и macOS. На практике, однако, «однажды написал — везде работает» срабатывает не всегда: различия в файловых системах, поведении компиляторов и системных API требуют аккуратной архитектуры. Разберём типичные проблемы и способы их обхода.
Базовая структура кросс-платформенного проекта
cmake_minimum_required(VERSION 3.16)
project(CrossPlatformApp LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_AUTOMOC ON)
find_package(Qt6 REQUIRED COMPONENTS Core Widgets)
add_executable(CrossPlatformApp main.cpp)
# Платформо-зависимый код подключаем отдельными файлами
if (WIN32)
target_sources(CrossPlatformApp PRIVATE platform_win.cpp)
elseif (APPLE)
target_sources(CrossPlatformApp PRIVATE platform_mac.mm)
elseif (UNIX)
target_sources(CrossPlatformApp PRIVATE platform_linux.cpp)
endif()
target_link_libraries(CrossPlatformApp PRIVATE Qt6::Core Qt6::Widgets)
Здесь главная идея — разделить общий код и платформо-специфичный, не размазывая #ifdef по всему проекту, а локализуя его в отдельных файлах.
Пример: абстракция над платформенным кодом
// platform.h — общий интерфейс
#pragma once
#include <QString>
namespace Platform {
QString configDirectory();
void showNativeNotification(const QString &title, const QString &message);
}
// platform_win.cpp
#include "platform.h"
#include <QStandardPaths>
namespace Platform {
QString configDirectory() {
return QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation);
}
void showNativeNotification(const QString &title, const QString &message) {
// На Windows можно использовать системные toast-уведомления через WinRT API
qDebug() << "[Windows]" << title << message;
}
}
// platform_linux.cpp
#include "platform.h"
#include <QProcess>
namespace Platform {
QString configDirectory() {
return QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation);
}
void showNativeNotification(const QString &title, const QString &message) {
QProcess::startDetached("notify-send", {title, message});
}
}
В большинстве случаев QStandardPaths уже абстрагирует различия в путях между ОС, и платформо-зависимый код нужен только для специфичных вещей — системных уведомлений, интеграции с Dock/Taskbar и т.п.
Подводные камни
1. Различия в путях файловой системы.
Windows использует обратный слэш \, разделители дисков C:\, а пути могут быть длиной больше 260 символов только при специальном префиксе. Qt внутри всегда использует /, и QDir::toNativeSeparators() нужно вызывать только при выводе пути пользователю или при вызове внешних не-Qt API:
QString path = "C:/Users/user/file.txt";
QString nativePath = QDir::toNativeSeparators(path); // "C:\Users\user\file.txt" на Windows
2. Регистронезависимость файловой системы.
На Windows и (по умолчанию) macOS файловая система регистронезависима, на Linux — регистрозависима. Код, который работает на Windows с файлом «Photo.JPG», может не найти файл «photo.jpg» на Linux. Всегда явно контролируйте регистр при сравнении и поиске файлов.
3. Переводы строк (line endings).
Windows использует \r\n, Unix-системы — \n. При работе с текстовыми файлами используйте QIODevice::Text флаг при открытии, чтобы Qt сам нормализовал переводы строк:
QFile file("data.txt");
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
QTextStream in(&file);
// \r\n и \n обрабатываются одинаково
}
4. Различия в системе шрифтов и DPI.
На macOS интерфейс рассчитан на Retina-дисплеи «из коробки», тогда как на Windows и Linux пользователь может вручную менять масштаб. Тестируйте интерфейс при масштабах 100%, 150%, 200%.
5. Поведение модальных диалогов и меню.
На macOS системное меню приложения находится наверху экрана, а не в окне. Используйте QMenuBar без явного родителя — Qt сам разместит меню правильно на каждой платформе:
QMenuBar *menuBar = new QMenuBar(nullptr); // без родителя — Qt сам решит, где разместить
QMenu *fileMenu = menuBar->addMenu("Файл");
6. Различия компиляторов.
MSVC, GCC и Clang по-разному трактуют некоторые тонкости стандарта C++ (например, порядок инициализации статических членов в разных единицах компиляции). Включайте предупреждения максимально строго на всех трёх платформах в CI:
if (MSVC)
target_compile_options(CrossPlatformApp PRIVATE /W4)
else()
target_compile_options(CrossPlatformApp PRIVATE -Wall -Wextra -Wpedantic)
endif()
7. Динамические библиотеки и deployment.
На Windows нужно явно копировать Qt DLL рядом с .exe (или использовать windeployqt), на Linux — настраивать RPATH или AppImage, на macOS — использовать macdeployqt для формирования .app-бандла с правильными @rpath-ссылками.
# Windows
windeployqt.exe MyApp.exe
# macOS
macdeployqt MyApp.app -dmg
Совет
Настройте CI с тремя параллельными джобами (Windows/Linux/macOS) с самого начала проекта, а не в конце разработки. Платформо-специфичные ошибки компиляции и поведения проще находить и фиксить, когда между их появлением и обнаружением проходят минуты, а не недели.
================================================================================