В один прекрасный вечер, понадобилось мне написать небольшое приложение, требования к которому, на первый взгляд, выглядели не такими уж и сложными:


  • работа с кое-какими железяками;
  • наличие 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.0\VC\vcvarsall.bat" x86
set PATH=C:\jom;C:\Python27;C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Bin;%PATH%
set INCLUDE=C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Include;%INCLUDE%
set LIB=C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Lib;%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 и выше, но у меня он тоже присутствует, хотя, может быть, он тут и не нужен на самом деле.


Генерируем мейкфайл:


ms\do_nt

И вот теперь, чтобы результат собрался с поддержкой Windows XP, необходимо внести некоторые изменения в ms\nt.mak (статическая версия) или ms\nt-dll.mak (динамическая версия). Открываем его в любимом текстовом редакторе и добавляем в начало файла инициализацию окружения:


PATH=C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Bin;$(PATH)
INCLUDE=C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Include;$(INCLUDE)
LIB=C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Lib;$(LIB)

Далее, добавляем к CFLAG:


-D_USING_V110_SDK71_ -I"C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Include" -DPSAPI_VERSION=1

Не уверен насчёт необходимости -DPSAPI_VERSION=1, но лишним не будет.


Изменяем/меняем/добавляем ключ /subsystem в переменных LFLAGS, MKLIB и MLFLAGS на следующий:


/subsystem:console,5.01

Компилируем и устанавливаем результат в конечную папку:


nmake -f ms\nt.mak
nmake -f ms\nt.mak install

Чтобы убедиться, что собранные библиотеки запустятся на WinXP, надо открыть их (в случае динамической сборки с .dll) в любом hex-реадкторе и посмотреть в районе байтов 130..150 на наличие байтов 05 00 01:





2. Компилируем Qt.


Запускаем новую консоль, снова инициализируем окружение:


cd C:\Qt\Qt5.6.3\5.6.3\Src
"C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" x86
set PATH=C:\jom;C:\Python27;C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Bin;%PATH%
set INCLUDE=C:\openssl\include;C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Include;%INCLUDE%
set LIB=C:\openssl\lib;C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Lib;%LIB%
set OPENSSL_LIBS="-LC:/openssl/lib -llibeay32 -lssleay32 -lgdi32"
set CL=/D_USING_V110_SDK71_

Обратите внимание на наличие и правильность путей к библиотекам и хэдерам OpenSSL.


Конфигурирем Qt:


configure -prefix C:\Qt\Qt5.6.3\5.6.3\msvc2015-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 SDKs\Windows\v7.1A\Include" -L "C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Lib" -l Gdi32 -I C:\openssl\include -L C:\openssl\lib -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

На этом, собственно, всё. Кажется конечно, что ничего особенного, но смею Вас заверить, что проходить этот путь с нуля и без подготовки — очень даже весело.

Комментарии (29)


  1. firk
    14.05.2018 03:09

    После небольшой перепалки в попытках объяснить нецелесообразность поддержки XP, и получения указания о её обязательной поддержке

    Радует (не сарказм) существование таких ситуаций. Так сказать, актуальные требования задачи одерживают верх над попытками их изменить с неавторизованной на это стороны. И печально наблюдать в реальности ситуации, когда происходит наоборот — исполнитель говорит, что какую-то фичу делать неактуально/некрасиво/не вписывается в его любимый фреймворк и на этом основании ему молчаливо разрешают её не делать.


    1. stychos Автор
      14.05.2018 03:20

      Просто, хорошо бы такие вещи сначала оговаривать. А то, когда сначала задача стоит в виде «да нам плевать, на каких фреймворках, главное вот это вот и это, да побыстрее», а потом начинают вылазить требования в виде WinXP или прочих вещей, из-за которых кое-что приходится переосмысливать и переписывать, срывать и тянуть сроки, то становится немного неуютно. Хотя, конечно, вина тут обоюдная. А конкретно в моём случае сыграло и полное отсутствие опыта. Но на то он и «сын ошибок трудных.»


      1. firk
        14.05.2018 21:55

        Это не было претензией, просто конкретно процитированный отрывок дал ассоциации с похожими явлениями.


        1. stychos Автор
          14.05.2018 22:00

          Так никто и не воспринял, как претензию, и отчасти с Вами согласен — бывает, программисты сильно увлекаются внутренним миром продукта, и это несёт вред внешнему. Просто пояснил, что в данном случае кое-какие основания для этой небольшой перепалки имелись (на мой субъективный взгляд). Всё-таки требования к продукту лучше выяснять на ранних стадиях разработки. И в данном случае моя вина тут тоже присутствует, потому что не знал, с какими весёлостями придётся столкнуться для исполнения требований. Вообще, С++ прям удивил своей динамичностью, столько нового понадобавляли со времён С++11 (а в MSVC2010 укороченный драфт), что если использовать эти вещи сразу, то потом их может начать сильно не хватать.


    1. Antervis
      14.05.2018 05:31

      это хорошо, если требования «должно работать на говне мамонта без видеокарты, драйверов и сервис паков» не приходят на 70+% выполнения. Если речь о внутреннем проекте, может быть дешевле купить 1-2 копии винды и их накатить, чем пилить поддержку win xp


  1. Riateche
    14.05.2018 04:30

    Я в свое время проблему неработоспособности приложения на XP (с тем же сообщением об ошибке, что и у вас, и тоже для приложения, статически слинкованного с Qt) решил запуском следующей команды после сборки проекта:

    editbin file.exe /SUBSYSTEM:WINDOWS,5.01 /OSVERSION:5.1

    Пересобирать Qt не понадобилось.


    1. stychos Автор
      14.05.2018 12:58

      Не знал о такой возможности. В теории, если не используются вызовы из новых ядер, то всё должно работать, спасибо за такой вариант решения проблемы.


  1. shuhray
    14.05.2018 05:51

    Сейчас как раз осваиваю Qt, чтобы нарисовать кнопки и выпадающие меню. Впечатление странное. Чтобы нарисовать кнопку, надо использовать классы, указатели, ссылки, макросы какие-то. Вместо того, чтобы написать «кнопка» и задать параметры. По-моему, это неправильное программирование (но посмотрел GTK+, там ещё хуже, хотя классов нет).


    1. CodeRush
      14.05.2018 08:14
      +2

      Изучайте QML в таком случае, там EcmaScript и CSS. «Классы, указатели, ссылки, макросы какие-то» — это потому, что C++ и мета-объектная система, плюс очень долгая история.


    1. totalkom
      14.05.2018 12:59

      Касательно GTK+, хотелось бы обратить Ваше внимание на оболочку GTKmm для GTK+ и конструктор Glade. Там организация построения интерфейса ориентирована на ООП.


    1. stychos Автор
      14.05.2018 13:00

      Сначала лучше в Qt Creator пробовать (если решили изучить Qt Widgets). Единственное, что может вызвать некоторые затруднения - понять, как работает система сигналов-слотов, слишком магическая она, да.


    1. shuhray
      14.05.2018 15:24

      Причём, дело не в сложности. То, что будет в окне, я нарисовал (открывать Мозиллой)
      mega.nz/#!WhgSXIIA!-QohSfyl0MQS9P_0fBj0InFcadFfvj4IOShLy4SeI5g


      1. khim
        14.05.2018 20:46

        Дело именно в ней. Дело в том, что, операции со строками — это сложно. Нарисовать кнопку — это очень сложно (как и любая банальность в ИТ эта тоже была обсосана Джоелем). Лет 20-30 назад, когда люди это понимали, графические программы с мышкой и прочими чудесами отлично работали на системах с 64 килобайтами (не гагабайтами!) памяти и процессором на 1 мегагерц (не гигагерц!).

        А потом… потом всё это обмотали тоннами скотча и «новое поколение» думает, что от этого кнопки стали простыми, а работа со строками — тривиальной. Но это не так: работа со строками — по прежнему сложная и медленная, а с GUI — ещё сложнее и медленнеей (пусть даже из текста программы этого уже и не видно). Просто если раньше вы старались делать сложные операции однократно (и не делать их при необходимости), то теперь вы их повторяете 10, 100, 1000, 10000 раз… потому что ни кажутся простыми.

        Отсюда тормоза и неумеренное потребление ресурсов современными творениями, но я не об этом. Я о другом: C — был, пожалуй, последним языком, в котором если что-то выглядело просто, то оно таки простым и являлось, а C++ — достаточно тонкая прослойка для того, чтобы, при некотором напряжении мысли в любой программе на C++ можно было бы увидеть эквивалентную программу на C.

        Отсюда — все наблюдаемые вами эффекты…


        1. shuhray
          14.05.2018 21:22

          Ну, я не новое поколение. И рисовать сам умею. Но если я хочу просто вставить кнопку, зачем мне в программе знаки * и &? Я пишу параллельно на javascript (+WebGL) и C++ (+OpenGL). На javascript писать легко, но при этом чувство, будто делаю что-то неприличное. От C++, наоборот, восхищение «Ведь наложили же люди такую кучу! Вот это глыба! Вот это матёрый человечище!»


          1. khim
            14.05.2018 21:47

            Но если я хочу просто вставить кнопку, зачем мне в программе знаки * и &?
            Потому что нужно же указать — выделять память под эту кнопку или не нужно? А если выделить, то нужно указать и кто и когда её освободит.

            От C++, наоборот, восхищение «Ведь наложили же люди такую кучу! Вот это глыба! Вот это матёрый человечище!»
            Вот уж чего C++ не должен вызывать — так это восхищения. Этот язык, в общем-то, на 90% состоит из костылей, которые возникают в попытке всё-таки упростить написание сложных вещей — но при этом и не потерять информацию о том, что они таки сложные.

            Новый подход к снаряду выглялит поинтереснее… но для него пока аналога Qt не написали.

            На javascript писать легко, но при этом чувство, будто делаю что-то неприличное.
            Писать просто, читать сложно. JavaScript (как и любой динамически типизированный язык) «хорошо заходит» если вы программу в одиночку разрабатываете. Но если у вас будет команда человек в 50-100 — начнётся TypeScript или Closure, а там и C++ перестанет языком дьявола казаться… Хотя некоторые вещи в нём иначе, чем совместимостью с тоннами уже написанного кода и не объяснить…


        1. Antervis
          14.05.2018 22:35

          наверно если программа работает недостаточно быстро, надо искать проблему не в одной лишней аллокации при создании одной кнопки? Прагматичный подход — написать чтоб работало и потом оптимизировать, и тенденция к нему прослеживается во всех современных технологиях. А уже оптимизировать на плюсах проще, попросту потому что не надо вручную писать базовые алгоритмы и контейнеры.


          1. khim
            14.05.2018 22:57

            наверно если программа работает недостаточно быстро, надо искать проблему не в одной лишней аллокации при создании одной кнопки?
            Нет, конечно. Это системная болезнь.

            Прагматичный подход — написать чтоб работало и потом оптимизировать, и тенденция к нему прослеживается во всех современных технологиях.
            В результате программы используют не в 1000 раз больше ресурсов, чем нужно для решения задачи, а всего лишь в 100, да.

            И нет — я не говорю, что такой подход плох — отнюдь. Многие вещи, которые на современных фреймворках делаются просто используя подход 80х вы будете писать год, вот только… а оно точно вам надо? Потому что чем дальше в лес тем больше ресурсов уходит не на решение задач, которые мы научились решать уже 10-20-30 лет назад, а на бессмысленные «красивости».

            P.S. Есть и вещи, которые 10-20-30 лет назад не решалишь вообще, никак… но как раз все эти нейровнные сети в современном мире занимают как раз далеко не все ресурсы… большая часть — это то, что мы делали эффективно 20-30 лет назад, а сегодня делаем чуть красивее, но дико неэффективно.


  1. sshmakov
    14.05.2018 07:15
    +2

    По-моему, статическая компиляция Qt нарушает лицензию LGPL, а статическая компиляция OpenSSL нарушает её собственную лицензию.


    1. Wilk
      14.05.2018 07:35
      +4

      Что-то мне подсказывает, что людям с XP и «надо чтобы был ровно один абсолютно портабельный файл» в наивысшей степени безразлично, что и какие лицензии нарушает.


    1. CodeRush
      14.05.2018 08:10
      +2

      Не нарушает ни того, ни другого. Статическая компиляция Qt 5.6.3, у которой лицензия LGPL v2.1, предполагает возможность обновления версии библиотеки, т.е. если пользователь потребует, ему нужно предоставить объектные файлы и инструкцию по сборке с другой версией Qt, чтобы не нарушить лицензию. Т.к. пользователь один и ничего такого он не требует — лицензия не нарушена.
      OpenSSL имеет свою собственную лицензию, похожую на Apache 1.0 (с середины 2017 года — саму Apache 2.0), и проблемы со статической линковкой там были только со старыми версиями и только если линковаться с GPL-кодом.


      1. Urub
        14.05.2018 13:04

        > Т.к. пользователь один и ничего такого он не требует — лицензия не нарушена.

        В статье не указано кто пользователь. Есть некий заказчик, а кому он будет отдавать программу, не ясно. Но судя по методу разработки и хотелок заказчика — при лицензию тут никто и не вспоминает.


    1. stychos Автор
      14.05.2018 13:03

      Я тоже так раньше думал, потому что мельком видел, что для статической компиляции Qt необходимо покупать лицензию, чтобы работало из коробки. Но вот немного поковырявшись, можно найти маны по статической компиляции что на сайте Qt, что на сайте OpenSSL (у вторых ещё и отдельный мейкфайл генерируется). Единственное, что на сайте OpenSSL большой такой дисклеймер о возможной незаконности их библиотек в некоторых странах.


  1. al_sh
    14.05.2018 11:01

    В 5.6 WebKit на Хромиум поменяли. Вот его мне так и не удалось собрать под ХР. Пришлось Собирать вебкит по пятерку.


  1. IGHOR
    14.05.2018 14:36

    В свое время тоже занимался решением такой задачи.
    Последняя версия Qt что работает на Windows XP это Qt 5.7.1


    1. stychos Автор
      14.05.2018 15:28

      Мельком читал даже, что у некоторых людей получалось и с 5.8, но сам не пробовал, остановился на LTS-версии.


    1. Antervis
      14.05.2018 16:32

      по идее, и более поздние должны работать если собирать без некоторых модулей (типа WebGL)


  1. sojuznik
    14.05.2018 18:36

    Microsoft Visual Studio 2017 поддерживает компиляцию для XP. Нет необходимости отказываться от фич C++14 и 17. Для поддержки достаточно включить поддержку XP в хидерах -D_USING_V110_SDK71_ компилятора, а в линковщике указать /SUBSYSTEM:WINDOWS,5.02 для 64-битных сборок или /SUBSYSTEM:WINDOWS,5.01 для 32-битных. Использовать Windows SDK 7.1 не обязательно. Оно уже хорошо работает и с Windows 10 SDK. Насчет совместимости этого со сборкой Qt ничего не могу сказать.


    1. stychos Автор
      14.05.2018 18:39

      Именно это я и имел в виду под словами «Впрочем, и тут есть куда совершенствоваться.» Но к тому моменту я уже упоролся компилироваться и решил остановиться на описанном. А это:

      Использовать Windows SDK 7.1 не обязательно.
      весьма интересный момент. Надо будет попробовать, как время для дальнейших извращений появится.


      1. sojuznik
        14.05.2018 18:53
        +1

        Есть разница между SDK 7.1 и 10: например, разные версии DirectX, кое-какие хидеры и определения отсутствуют. База Win32, конечно, совпадает. Если проект старый и там все старое используется, это конечно может не подойти. А если предполагается разработка нового проекта, но не хочется потерять совместимость с XP, можно попробовать сразу использовать SDK 10. Так и совместимость останется, и можно будет пользоваться возможностями Window 10, не заморачиваясь с хидерами.