This is an old revision of the document!


Установка и использование MSYS2 и Mingw-w64 под Windows

Текст был первоналаьно сделан для сайта “Железные призраки прошлого”, но затем невозбранно перенесен на эту Wiki и теперь живет тут.

Введение

В целях ретрокомпьютинга иногда возникает желание “что-нибудь скомпилировать под 32-битный Windows”. Ага, ага. Вот и у меня однажды возникала такая же ситуация. Когда-то раньше мне довелось пользоваться компилятором Mingw и оболочкой MSYS, так что я попробовал их найти и установить. Оказалось, что там “всё не так, как раньше”. Вобщем, сейчас ситуация такая: “простой” MinGW и “классический” MSYS обновляться перестали и они зачахли где-то в районе 2015 года.

ПРИМЕЧАНИЕ: Надеюсь, читатели понимают разницу между Cygwin и Mingw. Кратко: Cygwin пытается воссоздать наиболее полную “среду” UNIX/POSIX на Windows, со всеми её фишками, типа fork(), особенностей файловой системы, сигналами, псевдотерминалами и т.д. в то время как Mingw - это (изначально) просто перенос компилятора GCC на Windows, без вот этого всего. А MSYS - это набор утилит для сборки, главные из которых - пожалуй make и bash. Подробности.

Оказывается, нынче текущая версия MSYS - это MSYS2. Это такой странный гибрид из Cygwin и старого MSYS. Подобно Cygwin он делает замену путей в стиле UNIX, маскирует расширение *.exe, поддерживает псевдотерминалы, UNIX-сигналы и еще много чего. Это, с одной стороны, облегчает перенос и build-инг всяких нетривиальных UNIX программ, а с другой стороны, все msys-программы, в отличии от старого MSYS теперь тянут за собой DLL-ку: msys-2.0.dll .

А компилятор с тулзами нынче называется Mingw-w64. Это вовсе не значит, что он 64-битный, это просто такое имя. Он существует во всех возможных комбинациях: 32-битный (т.е. работающий на 32-бит Windows) для билда 32-битных программ, 64-битный для билдинга 64-битных программ, и все промежуточные варианты, то есть 32-битный для построения 64-битных и наоборот, 64-битный для 32-бит.

Вы еще не запутались ? Держитесь, это только начало… :)

MSYS2 теперь поддерживает два вида терминалов (т.е. программ для отрисовки “текстового” экрана) - один новомодный Mintty, утащенный с того же Cygwin, который типа лучше поддерживает механизмы терминала UNIX, но опять же, тянет за собой всякое… Другой - почти классическая Windows консоль почти без обвеса (ConEmu) но зато с закладками (Tabs). На самом деле, есть еще третий вид терминала (defterm), но он не совсем поддерживается… Подробности. Чтобы запустить MSYS2 c нужным терминалом, надо стартовый msys2_shell.cmd запустить с нужным ключиком. По-умолчанию запускается Mintty.

Да, сама MSYS2 тоже бывает 32-битная и 64-битная. К счастью на сайте MSYS2 есть нужный инсталлятор, (msys2-x86_64 или msys2-i686) - это такой огромный файл .EXE, у которого внутри целый архив соответствующих программ. MSYS2 64-битная по умолчанию ставится в C:\msys64, а 32-битная - в C:\msys32. Можете попробовать поменять каталог, но тогда никто ничего не гарантирует… Но вот беда - инсталлятор старый и первое, что нужно сделать - это обновиться: pacman -Syuu Этот pacman первым делом обновит сам себя, так что ОБЯЗАТЕЛЬНО надо прибить всё окно! НЕ выходя!!! И запустить pacman -Syuu еще раз.

Вы еще тут ? Я еще держусь, но с трудом… :)

ПРИМЕЧАНИЕ: С мая 2020 32-битную MSYS2 стали потихоньку сворачивать. Она еще поддерживается, но пакеты для нее выходят крайне редко, а начальный инсталлятор для 32-битной версии MSYS2 убрали с главной стравницы сюда. Следите за новостями. Таким образом, для инструментальной машины лучше использовать 64-битную ОС и 64-битную MSYS2. Увы.

Компилятор и его запуск

Ну вот например поставили мы MSYS2 (без разницы, 64-битную или 32-битную) и обновились. Давайте попробуем, наконец, скомпилить HelloWorld под Windows консоль. А вот хрен вам! Оказывается, теперь есть НЕ ОДИН компилятор, точнее не одна build-система, а несколько. Из них первая - для компиляции программ под САМУ MSYS2 (вы же помните, что она тащит за собой теперь как минимум DLL msys-2.0.dll и работает с UNIX-путями), а вторая build-система - для обычных “голых” Windows программ, как в старом добром Mingw. Компилятор и там и там - Mingw-w64, но по-разному настроенный. Да, не забываем что всё это может существовать в 32-бит, 64-бит и все комбинации между ними! Так что на самом деле у нас бывает (как минимум!) ТРИ build системы: msys2, mingw32 и mingw64. Все три ставить не обязательно. Например, если нам надо только собирать “чистые” 32-битные программы на 32-битной винде то можно поставить 32-битную MSYS2 без её собственных компиляторов и build-систему mingw32. (как понимаете, на самом деле это будет 32-битный компилятор по имени Mingw-w64 работающий под 32-битной MSYS2 и который умеет делать 32-битные “чистые” программы под Windows. Надеюсь крыша у вас еще не поехала…)

Итак, давайте сначала установим тулзы для сборки: pacman --needed -S base-devel Это установит всякие полезные для сборки утилиты в нашу MSYS2 (любой битности). Установщик pacman знает про себя, 32-бита он или 64, берёт правильные пакеты и ставит в правильный каталог. Репозиторий для любой MSYS2 всегда называется просто msys2 :) Компилятор для самой MSYS2 в base-devel не входит, да он нам и не нужен.

MSYS2 Menu А вот теперь аккуратнее! Чтобы установить build-систему, которая делает 32-бит программы надо установить группу mingw-w64-i686-toolchain. То есть компилятор называется Mingw-w64 (помним, что это просто название такое). Ставится он на нашу текущую MSYS2 и будет генерить 32-битные программы (хвостик -i686). Делаем pacman -S --needed mingw-w64-i686-toolchain. Пакеты компилятора скачаются (из репозитория mingw32) и будут установлены. Вот только никакого компилятора не появится!

Чтобы воспользоваться нужным компилятором, надо запустить нашу MSYS2 особым образом. Если вы откроете кнопку Пуск –> Все программы –> “MSYS2 32bit” то увидите три разных “среды”: “MSYS2 MinGW 32bit”, “MSYS2 MinGW 64bit” и “MSYS2 MSYS” (а в новых добавились еще clang и ucrt). По крайней мере, на Windows7 так. На Windows10 эти программы расположены в главном меню под буквой “M”. Вот эти три разные режима запуска и делают всю магию. Альтернативный путь - запускать напрямую программы из папки C:\msys32\……exe или скрипт C:\msys32\msys2_shell.cmd с ключами -mingw32, -mingw64 или -msys2. Это точно то же самое.

Давайте запустим “MSYS2 MinGW 32bit”. Теперь у нас доступен компилятор gcc, причем он полностью настроен, с указанием папки include, линкера и пути к библиотекам. Ура!

Пишем программы

Создадим Си-шный файлик hello.c со стандартным библиотечным вызовом printf():

#include <stdio.h>

int main () {
   printf("Hello, World!\n");
   return 0;
}

откомпилим его:

$ gcc -o hello hello.c

и запустим ПРЯМО ИЗ CMD.EXE (то есть из другого окна CMD, не из MSYS2):

C:\TEMP\TEST>hello
Hello, World!

Всё работает. Есть и недостатки - полученный EXE-шник имеет размер почти 300Kb! Воспользуемся командой strip и удалим из него все лишнее. Получится EXE-шник около 16К, что гораздо лучше. Посмотрим, что у нас получилось (Windows 7):

$ file hello.exe
hello.exe: PE32 executable (console) Intel 80386, for MS Windows

$ ldd hello.exe
        ntdll.dll => /c/Windows/SYSTEM32/ntdll.dll (0x772c0000)
        kernel32.dll => /c/Windows/system32/kernel32.dll (0x768e0000)
        KERNELBASE.dll => /c/Windows/system32/KERNELBASE.dll (0x75130000)
        msvcrt.dll => /c/Windows/system32/msvcrt.dll (0x76ae0000)

Как и требовалось, это “чистое” автономное 32-битное консольное Win32 приложение, которое использует только стандартные библиотеки 32-битного Windows (можно поспорить насчет стандартности MSVCRT.DLL, в котором и располагается printf()), но по факту, она есть практически везде. На 64-битном Windows вывод ldd будет несколько другой, поскольку там другая архитектура запуска 32-битных программ WindowsOnWindows (WoW). Скорее всего, эта программа будет работать даже в Windows 95, поскольку формат PE-файла не изменился (не пробовал).

В данном пакете сразу включен Windows SDK, поэтому можно писать программы на Win32 API, как невинные так и более серьезные, типа CreateFile( L“\\\\.\\PhysicalDrive0”…

Давайте теперь напишем оконную программку под Windows (точнее под Win32). Вытащим пример из старика Петзольда (Charles Petzold. Programming Windows):

#include <windows.h>

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    PSTR szCmdLine, int iCmdShow)
{
     MessageBox (NULL, TEXT ("Hello, Windows!"), TEXT ("HelloMsg"), 0) ;
     return 0 ;
}

откомпилируем:

$ gcc -mwindows -o hellowin hellowin.c

Все работает. Чтобы указать, что у нас оконная, а не консольная программа, используется ключ -mwindows . Первоначально ключ -m служил для указания модели машины или процессора, а тут его приспособили для указания “подсистемы”. Если в программе есть WinMain(), то даже -mwindows указывать не обязательно, компилятор сам понимает, но на всякий случай укажем.

ПРИМЕЧАНИЕ1: Не стоит ждать многого от штатной библиотеки MSVCRT.DLL . Это далеко не полная реализация libc. Кроме явного отсутствия “юниксовых” возможностей типа fork() и т.д. расхождения могут быть совсем неожиданные (и в разных версиях Windows - разные). Например, printf() не поддерживает формат %zd для типа size_t и другие неожиданные вещи. Компилятор Mingw-w64 знает много таких особенностей и по мере возможностей дает предупреждения. Подробности.

ПРИМЕЧАНИЕ2: Компиляторы gcc, а особенно g++ иногда создают код, который зависит от библиотек libgcc и libstdc++. Чтобы полученное приложение не требовало соответствующих DLL, в командную строку можно добавить опции -static-libgcc и -static-libstdc++. Библиотеки же, которые являются “стандартными” для Windows, Mingw-w64 всегда делает динамическими, даже несмотря на присутствие опции -static в опциях компилятора.

Консольные полноэкранные приложения

TODO. Параграф не дописан!

При портировании UNIX-приложений на MSYS2 часто возникают сложности с работой “полноэкранных” текстовых приложений, т.е. требущих управления экраном.

Тут надо немного углубиться в теорию. С точки зрения UNIX мы имеем две абстракции:

  1. Стандартные потоки STDIN/STDOUT/STDERR. Это псевдо-файлы, которые передаются при старте UNIX программы, с номерами файловых десткрапторов 0,1, и 2. Довольно часто, это не терминал, а вывод из предыдущей программы (pipe) или реальный файл.
  2. Терминал TTY. У терминала в UNIX имеется множество функций и атрибутов. Например, имеется API управления дисциплиной линии (всякие RTS/CTS), различные режимы ввода (с эхо или без) termio/termios, возможность переключения raw/cooked режимов, специальные функции управления заданиями (job control, например обработка CTRL-C, CTRL-Z) и т.д. Кроме того, у каждой разновидности терминала имеется база, описывающая его систему команд, в основном через ESC-последовательности (termcap/terminfo).

На Windows совершенно другая идеология управления консолью (WinAPI Console). Поэтому и возникает необходимость в эмуляторе терминала и промежуточной библиотеке типа msys-2.0.dll или cygwin.dll, которая имитирует (с разной степенью успешности) поведение UNIX системы. Тем или иным способом в оболочках типа MSYS2 и CYGWIN эта проблема решена. Но с “автономными” консольными Windows приложениями неколько сложнее. На это накладывается взаимодействие c MSVCRT.DLL, которая имитирует файловые потоки STDIN/STDOUT/STDERR и также вносит свои особенности.

Пишем простое консольное приложение на чистом WinAPI

Как же быть ?

Можно предложить такое неожиденное решение. Как правило, управление терминалом в UNIX-программах редко делают напрямую, на нижнем уровне через API терминала termio/termios. Как правило, используют готовые библиотеки, например Termcap или Curses. Такую UNIX программу намного легче портировать в консольное “автономное” Windows приложение.

Одна из популярных библиотек, требующих прямого управления консолью - readline. Библиотека GNU Readline используеся довольно широко. Самый известный пример - bash.

pacman -S libreadline-devel

pacman -S mingw-w64-i686-ncurses

SDL

Если вы хотите быть современным молодым динамичным программистом, то лучше писать на каком-нибудь Framework :) (как етто по рюсcки? Каркас?). Например, очень большое число разных эмуляторов винтажных систем написано на SDL.

По SDL в сети гуляет просто огромное число примеров, туториалов, введений и т.д. SDL рисует, поёт, поддерживает джойстик и сеть, может быть даже варит кофе… Текущая версия: SDL2. Давайте поставим себе SDL2 вместе со всеми её запчастями :
pacman -S mingw-w64-i686-SDL2 mingw-w64-i686-SDL2_gfx mingw-w64-i686-SDL2_image mingw-w64-i686-SDL2_mixer mingw-w64-i686-SDL2_net mingw-w64-i686-SDL2_ttf
(набирать в одну строку или покомпонентно)

Напишем небольшую программу под SDL:

#include <SDL2/SDL.h> 

int main(int argc, char *argv[]) 
{ 
   SDL_Event e;
   int quit = 0;
   SDL_Window* win;
   SDL_Surface* surf;

   SDL_Init(SDL_INIT_EVERYTHING);
   win = SDL_CreateWindow("Hello SDL!", 
                          SDL_WINDOWPOS_CENTERED, 
                          SDL_WINDOWPOS_CENTERED, 
                          320, 240, 0);
   surf = SDL_GetWindowSurface( win );
   SDL_FillRect( surf, NULL, 
               SDL_MapRGB( surf->format, 0, 0, 0xFF ) );
   SDL_UpdateWindowSurface( win );
   while( !quit )
      {    
      while( SDL_PollEvent( &e ) != 0 )
         {
            if( e.type == SDL_QUIT )
               {
                  quit = 1;
               }
         }
      }
   SDL_DestroyWindow(win);
   SDL_Quit();
   return 0; 
}

Программа просто создает маленькое (320×240) окошко с заголовком, закрашивает его синим (R=0, G=0, B=0xFF) и ждет, когда из нее выйдут.

Компиляция чуть сложнее.

  • Первое: SDL ПЕРЕОПРЕДЕЛЯЕТ функцию main(). То есть наша main() вызывается ИЗ системы SDL, а стартует программа начиная с функции SDL_main(). Это надо явно описать.
  • Второе: Поскольку мы не используем WinMain() напрямую, но хотим программу под Windows и не хотим,чтобы болталось черное окно консоли, то нам нужно ЯВНО добавлять ключ -mwindows .
  • Третье: При сборке нужно указать, что нам необходима библиотека SDL.

Получается целая куча ключей командной строки.

Можно выписать ключи на бумажку или создать make-файл. Однако всё проще. Многие Frameworks имеют специальные конфиг-файлы, в которых указаны всё необходимые настройки и нужно просто их использовать. SDL2 - не исключение.

$ sdl2-config --cflags --libs
-I/mingw32/include/SDL2 -Dmain=SDL_main
-L/mingw32/lib -lmingw32 -lSDL2main -lSDL2 -mwindows

Давайте просто подключим вывод в свою командную строку (это делается с помощью обратного апострофа: ` . Читается как “Использовать ВЫВОД команды”. Подробности в man bash секция “Command Substitution” )

$ gcc -o hellosdl hellosdl.c `sdl2-config --cflags --libs`

Все откомпилировалось. НО! Если мы попытаемся теперь запустить эту программу из CMD.EXE (просто из Windows), мы получим ошибку:
“Запуск программы невозможен, так как на компьютере отсутствует SDL2.dll .
Попробуйте переустановить программу.”

Это неудивительно, так как полученная программа требует для своей работы SDL2.DLL, а она пока доступна только “изнутри” среды MSYS2. Можно пойти двумя путями: первый - установить SDL2.DLL “в систему”, второй: просто положить SDL2.DLL в каталог с программой. Тогда за счет известной особенности Windows “use local DLL” программа при запуске найдет нужную DLL и подключит. Скачаем нужный runtime (например 32-битный) на сайте https://www.libsdl.org/download-2.0.php и просто положим DLL в каталог с программой. Теперь всё запускается из под CMD.EXE и рисует синее окошко.

ПРИМЕЧАНИЕ: Обратите внимание, что MSYS2-шная SDL2.DLL тянет за собой кучу других DLL. Это происходит потому, что авторы MSYS2 выбрали такой путь: всё что можно - выносить в отдельные библиотеки. Ну ОК. Это хорошо работает для экономии памяти MSYS2, но сильно мешает создавать “автономные” приложения Windows на Mingw. Используйте “официальную” SDL2.DLL
Второе. Можно даже саму SDL2.DLL вкомпилить статически. Можно. Но сейчас SDL2 употребляется настолько часто, что автор считает допустимым использовать такие “инфраструктурные” компоненты, типа QT или той же SDL2 в форме DLL и ставить их “в систему”. Дело вкуса.

OpenGL

Другая самая известная библиотека - это 3D библиотека OpenGL. По ней также существуют тысячи примеров и туториалов. Не вдаваясь в тонкости OpenGL и его реализации на разных машинах и видеокартах, попробуем создать OpenGL Windows-приложение. Возьмем, например, официальные примеры с официального сайта OpenGL. Например, нарисуем красный кубик: cube.c.

Тут все-таки надо сказать, что OpenGL за время своего существования сменил несколько версий. Поэтому, при чтении литературы иногда надо обращать внимание, про какую версию OpenGL идет речь. Вдобавок OpenGL за это время оброс несколькими побочными библиотеками, которые формально не входят в спецификацию, но все к ним настолько привыкли, что используют их не задумываясь. Самые часто применяемые - это GLU и GLUT. Библиотека GLUT, например, обеспечивает связку с операционной системой, в частности создает окно нужным способом: через X Window на UNIX системах, или через GDI на Windows. Большинство примеров с сайта OpenGL написаны с применением GLU и GLUT. В Windows имеются библиотеки OPENGL32.DLL и GLU32.DLL но нету GLUT.

Как было написано выше, хотелось бы создать максимально независимое Windows-приложение и не таскать за собой libfreeglut.dll. К сожалению, в текущем MSYS2 не очень понятно (мне по крайней мере) разделены библиотеки, например статическая libglut.a содержится в “базовом” пакете mingw-w64-i686-crt-git, а реализация freeglut несет с собой libfreeglut_static.a (именно с таким именем, нарушая обычное наименование библиотек Mingw-w64). В любом случае - установим пакет freeglut: pacman -S mingw-w64-i686-freeglut и откомпилируем наш кубик:

$ gcc -o cube -DFREEGLUT_STATIC cube.c -static -mwindows -lglut -lfreeglut_static -lgdi32 -lwinmm -lglu32 -lopengl32

Для сборки независимого EXE-шника пришлось добавить всю эту цепочку библиотек. Не завидую авторам скриптов ./configure

ПРИМЕЧАНИЕ: Здесь собирается 32-битное приложение. Сборка 64-битных тут пока не рассматривается. Опция -mwindows нужна, чтобы не создавалось черное окно консоли для Windows-приложений. Библиотека -lwinmm нужна для поддержки джойстика (!) в GLUT. Библиотеку -lgdi32 в принципе можно не добавлять, она и так унаследуется.

Другая популярная библиотека-“связка” для управления окнами для OpenGL - это GLFW Установим ее: pacman -S mingw-w64-i686-glfw . Текущая версия: glfw3. Это надо учитывать, когда читаете туториалы, иногда речь идет о старых версиях без упоминания деталей.

Пример возьмем с официального сайта GLFW: Getting started. Там же есть краткая инструкция по сборке: Building programs that use GLFW. Особых сложностей не заметно, за исключением тонкостей сборки со статической и динамической библиотекой GLFW3.DLL.

Скомпилируем:

$ gcc -o glfwtest -DGLFW_DLL glfwtest.c -mwindows -lglfw3 -lgdi32 -lopengl32

Большинство опций нам уже знакомо: -mwindows для создания не-консольного приложения, подключение библиотек, новое - только определение -DGLFW_DLL согласно рекомендациям по сборке. Получившееся приложение требует GLFW3.DLL , которую для Windows можно скачать с официального сайта GLFW. Обратите внимание, что подобно SDL2.DLL - это “инфраструктурная” библиотека. Использовать ли ее - дело вкуса. Можно скомпилировать GLFW статически:

$ gcc -o glfwtest glfwtest.c -mwindows -static -lglfw3 -lgdi32 -lopengl32

Исполняемый файл увеличится примерно на 200К, за счет включения GLFW внутрь приложения, но зависимости будут только от “стандартных” DLL имеющихся в Windows.

OpenGL через SDL

Наконец, существует возможность - использовать уже знакомую нам SDL для “склейки” операционной системы и OpenGL. Это намного удобнее т.к. в SDL имеются функции ввода, управления манипуляторами, звуком, сетью и т.д. и самое главное - обе библиотеки кросс-платформенные.

<to be continued…>

Дополнительное чтение

Navigation