В один прекрасный вечер, понадобилось мне написать небольшое приложение, требования к которому, на первый взгляд, выглядели не такими уж и сложными:
- работа с кое-какими железяками;
- наличие GUI;
- умение работать в Windows XP и выше (не спрашивайте, зачем);
- один исполняемый файл (для Windows);
- крайне желательна версия под macOS;
- проверка наличия обновлений на удалённом сервере по HTTPS.
С учётом того, что всю предыдущую сознательную жизнь с С++ я сталкивался случайно и мимоходом, решение данной задачи (а если быть точнее, то настройка окружения для этого) — оказалось неплохим таким квестом, в конце которого ждал тортик решил поделиться с Вами наработанным опытом, вдруг кому пригодится.
Люблю эту картинку.
Disclaimer: статья от чайника для чайников. За задетые чувства профессионалов C++ и магистров жизненного цикла продуктов я не отвечаю.
Вообще, с начала разработки приложения, особо не задумывался над тем, как именно я буду его собирать. Ну хотя бы потому, что с самого начала в требованиях не было ни Windows XP, ни наличия только одного исполняемого файла в дистрибутиве. Совсем недолго поразмышляв, решил писать на С++ с использованием Qt в качестве библиотеки для GUI. Была у меня ещё мыслишка писать на чистом C с использованием libui (как-то он мне больше импонирует), но поставленные временные рамки такой роскоши не позволяли, да и трудное отрочество на PHP не даёт так быстро и просто отказаться от использования классов. А если ко всему вышеизложенному добавить то, что последние несколько лет я заядлый хакинтошер, то начал и долгое время продолжал разрабатывать всё это дело в Sublime Text из-под macOS, совершенно не думая о Windows: "Ну это же C++, это же полная кроссплатформенность," — думал я. Ошибался. Не всё так просто оказалось.
Итак, долго ли коротко, продукт начал приобретать рабочие очертания и пришло время демонстрировать его заказчику. Устанавливаю, значит, виртуалку с Windows 10, туда — Visual Studio 2017 и последний Qt Creator, быстренько компилирую проект, докидываю ему необходимых Qt-библиотечек, отправляю заказчику и довольный потираю ручки от радости завершённого этапа разработки. Но через пять минут получаю: "не запускается."
И маленький скриншотик из Windows XP, на котором маленький месседжбокс радостно возвещает о начале больших проблем:
После небольшой перепалки в попытках объяснить нецелесообразность поддержки XP, и получения указания о её обязательной поддержке, начинаю выяснять, как заставить вот это вот всё там работать. Примерный список пройденных граблей такой:
- нашёл в установщике Visual Studio галочку "Windows XP support for Visual C++", установил, ничего не поменялось;
- погуглил про поддержку операционных систем со стороны Qt, выяснил, что максимальная версия, поддерживающая Windows XP — 5.6;
- там же выясняется, что 5.6 из коробки работает только с MSVC 2013 и 2015.
Сроки горели, заказчик лютовал, пришлось на всё плюнуть и после некоторых мытарств с разными версиями, остановиться на среде из Windows XP + VS2010 + Qt 5.0. Немало прослезился от неподдерживаемости многих современных штук из C++, но делать было нечего, некогда пилу точить — переписал эти ваши удобные битсеты на самодельные костыли над вектором, enum classes на обычные enum, разрулил пару сотен выползших warning'ов, понаблюдал очередной рассвет, скомпилировал, сдал, — да так и провёл большую часть оставшейся разработки, разрабатывая в macOS и периодически внося необходимые изменения для работы с компилятором MSVC 2010.
Близился конец разработки, вроде бы заказчику уже более-менее всё нравится, но вот приходит небольшое сообщение: "слушай, сделай один экзешник, чото как-то много дллок." Далее снова следует небольшая перепалка, предложение сделать инсталлятор, который намажет все нужные файлы во все нужные места, отрицание предложений и указания о необходимости одного абсолютно портабельного исполнимого файла.
Ну, надо так надо. Убедив, что на сборку необходимого мне понадобится какое-то время, начал разбираться с данным вопросом. Ну и дабы не нагружать тебя, читатель, моими злоключениями и отвратительной манерой письма и далее, лишь опишу тебе то, что получилось в текущем виде. Впрочем, и тут есть куда совершенствоваться.
Итак, дано:
- Виртуалка Windows 7;
- Устанавливаем Visual Studio 2015 (Update 2 и выше), не забываем про Windows XP support при установке;
- Устанавливаем исходники Qt 5.6.3;
- Устанавливаем Active Perl (в интернетах по схожим вопросам писали, что и Strawberry срабатывал), Python, Ruby, jom. Не забываем добавить всё это добро в %PATH%;
- Качаем и распаковываем исходники OpenSSL (1.0.2o на момент написания), допустим, в C:OpenSSL-src.
1. Компилируем OpenSSL.
Открываем cmd.exe и меняем директорию на корень распакованных исходников с OpenSSL:
cd C:OpenSSL-src
Инициализируем нужное окружение (в моих примерах jom и Python не добавлены в глобальный %PATH%):
"C:Program Files (x86)Microsoft Visual Studio 14.0VCvcvarsall.bat" x86
set PATH=C:jom;C:Python27;C:Program Files (x86)Microsoft SDKsWindowsv7.1ABin;%PATH%
set INCLUDE=C:Program Files (x86)Microsoft SDKsWindowsv7.1AInclude;%INCLUDE%
set LIB=C:Program Files (x86)Microsoft SDKsWindowsv7.1ALib;%LIB%
set CL=/D_USING_V110_SDK71_
Конфигурируем будущую сборку:
perl Configure VC-WIN32 no-asm no-async --prefix=C:openssl --openssldir=C:openssl
OpenSSL рекомендует использовать одинаковые значения --prefix и --openssldir, их значения по умолчанию у разных версий разные, поэтому лучше указать их явно. При компиляции OpenSSL может использовать ассемблерные алгоритмы (поддерживается только NASM), и я пару дюжин раз пытался компилировать с ними, но у меня так ничего не вышло, потому используется ключ no-asm. Также, параметр no-async рекомендован для WinXP (а также CentOS 5, BSD 5, Vista и прочих старых операционных систем) при компиляции OpenSSL версии 1.1.0 и выше, но у меня он тоже присутствует, хотя, может быть, он тут и не нужен на самом деле.
Генерируем мейкфайл:
msdo_nt
И вот теперь, чтобы результат собрался с поддержкой Windows XP, необходимо внести некоторые изменения в msnt.mak (статическая версия) или msnt-dll.mak (динамическая версия). Открываем его в любимом текстовом редакторе и добавляем в начало файла инициализацию окружения:
PATH=C:Program Files (x86)Microsoft SDKsWindowsv7.1ABin;$(PATH)
INCLUDE=C:Program Files (x86)Microsoft SDKsWindowsv7.1AInclude;$(INCLUDE)
LIB=C:Program Files (x86)Microsoft SDKsWindowsv7.1ALib;$(LIB)
Далее, добавляем к CFLAG:
-D_USING_V110_SDK71_ -I"C:Program Files (x86)Microsoft SDKsWindowsv7.1AInclude" -DPSAPI_VERSION=1
Не уверен насчёт необходимости -DPSAPI_VERSION=1, но лишним не будет.
Изменяем/меняем/добавляем ключ /subsystem в переменных LFLAGS, MKLIB и MLFLAGS на следующий:
/subsystem:console,5.01
Компилируем и устанавливаем результат в конечную папку:
nmake -f msnt.mak
nmake -f msnt.mak install
Чтобы убедиться, что собранные библиотеки запустятся на WinXP, надо открыть их (в случае динамической сборки с .dll) в любом hex-реадкторе и посмотреть в районе байтов 130..150 на наличие байтов 05 00 01:
2. Компилируем Qt.
Запускаем новую консоль, снова инициализируем окружение:
cd C:QtQt5.6.35.6.3Src
"C:Program Files (x86)Microsoft Visual Studio 14.0VCvcvarsall.bat" x86
set PATH=C:jom;C:Python27;C:Program Files (x86)Microsoft SDKsWindowsv7.1ABin;%PATH%
set INCLUDE=C:opensslinclude;C:Program Files (x86)Microsoft SDKsWindowsv7.1AInclude;%INCLUDE%
set LIB=C:openssllib;C:Program Files (x86)Microsoft SDKsWindowsv7.1ALib;%LIB%
set OPENSSL_LIBS="-LC:/openssl/lib -llibeay32 -lssleay32 -lgdi32"
set CL=/D_USING_V110_SDK71_
Обратите внимание на наличие и правильность путей к библиотекам и хэдерам OpenSSL.
Конфигурирем Qt:
configure -prefix C:QtQt5.6.35.6.3msvc2015-static -static -static-runtime -target xp -platform win32-msvc2015 -debug-and-release -qmake -opensource -nomake examples -no-compile-examples -nomake tests -qt-pcre -no-icu -no-sql-sqlite -no-nis -no-cups -no-iconv -no-dbus -qt-zlib -qt-libpng -opengl desktop -no-directwrite -I "C:Program Files (x86)Microsoft SDKsWindowsv7.1AInclude" -L "C:Program Files (x86)Microsoft SDKsWindowsv7.1ALib" -l Gdi32 -I C:opensslinclude -L C:openssllib -openssl -openssl-linked OPENSSL_LIBS="-lssleay32 -llibeay32 -lgdi32"
Важные ключи для статической сборки: -static, -static-runtime и -openssl-linked.
А для нормальной работы с Windows XP: -target xp, -opengl desktop и -no-directwrite.
Опять же, не уверен насчёт нужности и важности -l Gdi32 в данном списке, ибо компилировал всё это дело не менее двадцати раз, но дела оно не испортит.
Компилируем:
jom
jom install
Можно и nmake пробовать, но jom чуточку быстрее, да и с nmake у меня так ни разу ничего и не вышло.
3. Добавляем поддержку Windows XP в проект Qt
Редактируем .pro:
DEFINES += _ATL_XP_TARGETING
DEFINES += PSAPI_VERSION=1
QMAKE_LFLAGS_WINDOWS = /SUBSYSTEM:WINDOWS,5.01
На этом, собственно, всё. Кажется конечно, что ничего особенного, но смею Вас заверить, что проходить этот путь с нуля и без подготовки — очень даже весело.
Автор: iloveYou