Или как я делал GTK приложение в Windows
Современные кроссплатформенные библиотеки, такие как Qt и GTK4, становятся весьма тяжёлыми и неудобными на слабых компьютерах, таких как Raspberry Pi или Orange Pi. Поэтому выбор пал на GTK+3. В отличие от Qt, GTK придерживается простого процедурного программирования вместо объектно-ориентированного подхода. Этот стиль позволяет сосредоточиться на реализации функций без необходимости переключаться между объявлениями и реализациями, что упрощает чтение кода как одно целое. Меньше служебных файлов и больше прямой логики — это ключевые преимущества GTK+3. Однако поддержка в Windows требует дополнительных настроек, о которых мы поговорим дальше.
В Linux всё достаточно однозначно, оттого и проще. Написал “sudo apt get …” - и пакет установился. Пользуйтесь. В Windows же надо всё прописывать ручками при том, что freeware библиотеки, в основном, изначально разрабатываются под Linux. И непонятно, как без опыта это сделать. Обязательно выскочит какая-нибудь ошибка компиляции, что что-нибудь не найдено, а где это найти и как правильно запустить и настроить – это тоже ещё проблема, когда мало что известно.
Для управления зависимостями в проектах с использованием компилятора Visual Studio идеально подходит vcpkg. Хотя он включён в стандартную установку VS, я рекомендую разместить его отдельно для удобства (например, без длинных путей в Program Files). Убедитесь, что vcpkg не конфликтует с теми версиями, которые уже установлены в Visual Studio, для чего его можно найти и удалить в Visual Studio.
После скачивания мастера нужно запустить bootstrap-vcpkg, после чего появится исполняемый модуль vcpkg.
Для полноценной работы необходимо указать переменные среды (environment):
VCPKG_ROOT=c:\path_to_vcpkg
VCPKG_DEFAULT_TRIPLET=x64-windows
VCPKG_ROOT – указывает на папку с установленным vcpkg, а VCPKG_DEFAULT_TRIPLET – указывает на выбранную конфигурацию компилятора по-умолчанию. Для Windows может ещё принимать, к примеру, значение “x86-windows” – для 32-разрядных систем.
следует выполнить интеграцию с Visual Studio:
vcpkg integrate install
Следующим шагом будет установка и компиляция пакетов. Предполагается, что Visual Studio с компилятором C, C++ уже установлен. vcpkg находит их автоматически.
Для установки пакета, например gtk3, нужно написать:
vcpkg install gtk3
где вместо “gtk3” – может быть любой другой необходимый вам пакет.
Для выполнения всех необходимых действий внутри папки с vcpkg будут скачены все необходимые утилиты, такие как git, cmake. После этого скачиваются и компилируются все необходимые пакеты, от которых зависит выбранный пакет. Это занимает приличное время и папка с vcpkg весьма увеличивается в размерах.
Тут следует сказать, что vcpkg работает в двух режимах – “классическом” и “режим манифеста”. В “классическом” режиме все скомпилированные библиотеки и заголовочные файлы появляются в папке installed. При создании своего проекта необходимо все пути к include и lib прописать вручную.
Для автоматизации процесса подключения директорий в проект служит “режим манифеста”. При этом в папке с проектом должны присутствовать json файлы конфигурации vcpkg. Эти файлы могут быть созданы и изменены командами vcpkg. Для “режима манифеста” библиотеки и заголовочные файлы каждой библиотеки находятся в отдельных директориях, а все эти файлы и папки находятся в папке packages. Пакеты для проекта в этом режиме могут скачиваться и компилироваться автоматически. При этом для подключения пакета в файле проекта CMake достаточно указать find_package. Но у меня это не сработало.
Для работы find_package необходимо наличие файлов конфигурации пакета в самом CMake (Modules). А даже для GTK+3 их там не оказалось, и думаю, для многих других пакетов тоже нет.
И хотя “режим манифеста” предлагается как более современный, я предпочёл “классический” режим, как более понятный, но пришлось прописывать вручную пути к файлам include и lib.
Можно, конечно, использовать классические для Visual Studio виды проектов - vcproj, но выполнять конфигурацию каждого нового проекта из диалогового окна достаточно утомительно. Лучше создать один раз файл проекта в виде текста, и копировать его из проекта в проект с небольшими исправлениями. Это значительно облегчает работу и мотивирует при создании какого-нибудь мелкого проекта.
Для работы с С, С++ в Visual Studio и Visual Studio Code предлагается использовать кроссплатформенную систему проектов - CMake.
CMakeLists.txt
, что
упрощает настройку и управление проектами.CMake является метасистемой, которая генерирует “настоящие” файлы сборки (типа Makefile или VS-проекты), которые затем используются компилятором. Это позволяет абстрагироваться от деталей конкретной среды разработки.
Так, к примеру, пусть проект содержит один исходный файл “box.c”, тогда CMakeList.txt может выглядеть так:
cmake_minimum_required (VERSION 3.25)
project (boxgtk3)
add_executable(boxgtk3 box.c)
Здесь “cmake_minimum_required” – задаёт минимально необходимую версию самого CMake, что позволяет контролировать использование дополнительных возможностей, “project” – задаёт имя проекта, что определяет некоторые пути, и “add_executable” – задаёт компилируемый модуль “boxgtk3” из исходного файла “box.c”.
Но, как вы догадываетесь, если бы всё было так просто, то это была бы песня. Но как минимум, необходимо задавать пути для дополнительных включаемых файлов (include) и библиотек. Можно, конечно, вводить условия в скрипт CMakeList.txt, но тогда этот скрипт будет походить на малопонятное заклинание, но есть другой вариант решения проблемы.
Важным дополнением, которое появилось с версии 3.19 является наличие preset-ов – дополнительных файлов настроек, в которых могут быть описаны параметры конфигураций вида “Windows debug, Windows Release, Linux”. Для каждой конфигурации могут быть указаны свои пути поиска включаемых файлов и библиотек. Этот файл называется CMakePreset.json. В нём перечислен набор параметров для каждой операционной системы, вида сборки и разрядности. Это гораздо лучшее решение, чем менять основной скрипт в зависимости от системы, что и делает систему CMake похожей на “классическую” систему проектов Visual Studio.
Рассмотрим файл CMakePreset.json поподробнее:
{
"version": 5, // версия json. c 5 версии доступны комментарии
"cmakeMinimumRequired": { // Минимальная требуемая версия CMake - 3.19, так как
// поддержка Presets появилась с этой версии.
"major": 3,
"minor": 19,
"patch": 0
},
"configurePresets": [ // набор пресетов
{
"name": "base", // имя пресета. это базовый пресет, который служит основой для всех остальных
"hidden": true, // это флаг скрытого пресета, не доступного для построения
"generator": "Ninja", // Используем генератор Ninja для создания файлов сборки
"binaryDir": "${sourceDir}/build/${presetName}"
// Каталог с промежуточными файлами сборки будет иметь имя текущего пресета в структуре build/
},
{
"name": "windows-base", // базовый пресет для Windows
"inherits": "base", // наследуемый от базового
"hidden": true, // скрытый
"toolchainFile": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake",
// Используем toolchain файл vcpkg для управления зависимостями. Включение его в "классическом"
// режиме позволяет копировать все необходимые динамические библиотеки в каталог с исполняемым модулем
"cacheVariables": { // указание переменных для CMake
"CMAKE_C_COMPILER": "cl.exe", // указание компилятора C - MSVC
"CMAKE_CXX_COMPILER": "cl.exe", // указание компилятора C++ - MSVC
"CMAKE_RUNTIME_OUTPUT_DIRECTORY": "${sourceDir}/${presetName}"
// Итоговые исполняемые файлы сохраняются в корне проекта рядом с исходниками. В Windows удобнее
// исполняемые файлы помещать в другую папку, отличную от build, таким образом, все промежуточные
// файлы можно удалить, просто удалив папку build
},
"condition": {
"type": "equals",
"lhs": "${hostSystemName}",
"rhs": "Windows"
}
// Пресет активен только на Windows-системах.
},
{
"name": "x64-debug", // конфигурация для построения в отладочной конфигурации режима x64
"displayName": "x64 Debug",
"inherits": "windows-base",
"architecture": { // указание архитектуры
"value": "x64",
"strategy": "external"
},
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug",
// Режим сборки: отладочный
// Собственные переменные
"CMAKE_PREFIX_PATH": "$env{VCPKG_ROOT}/installed/x64-windows/include;
$env{VCPKG_ROOT}/installed/x64-windows/debug/lib",
// Путь к заголовкам и библиотекам для debug-зависимостей в x64 конфигурации
// CMAKE_PREFIX_PATH -- директории для поиска файлов командами CMake - find_package(),
// find_program(), find_library(), find_file() и find_path()
// фактически - путь include
// вторая часть "$env{VCPKG_ROOT}/installed/x64-windows/debug/lib" нужна для поиска
// одного файла - glibconfig.h
"CMAKE_LIBRARY_PATH": "$env{VCPKG_ROOT}/installed/x64-windows/debug/lib"
// путь к библиотекам
}
},
{ // X64 Release configuration (наследует настройки от Debug)
"name": "x64-release",
"displayName": "x64 Release",
"inherits": "x64-debug",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release",
"CMAKE_PREFIX_PATH": "$env{VCPKG_ROOT}/installed/x64-windows/include;
$env{VCPKG_ROOT}/installed/x64-windows/lib",
"CMAKE_LIBRARY_PATH": "$env{VCPKG_ROOT}/installed/x64-windows/lib",
"ISGUI": "WIN32" // Специфический флаг, указывающий на включение GUI-функциональности
// для Windows платформы
}
},
{ // Конфигурация 32-разрядного режима x86
"name": "x86-debug",
"displayName": "x86 Debug",
"inherits": "windows-base",
"architecture": {
"value": "x86",
"strategy": "external"
},
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug", // отладочная конфигурация
"CMAKE_PREFIX_PATH": "$env{VCPKG_ROOT}/installed/x86-windows/include;
$env{VCPKG_ROOT}/installed/x86-windows/debug/lib",
"CMAKE_LIBRARY_PATH": "$env{VCPKG_ROOT}/installed/x86-windows/debug/lib"
}
},
{
"name": "x86-release", // release -- конфигурация под x86
"displayName": "x86 Release",
"inherits": "x86-debug",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release",
"CMAKE_PREFIX_PATH": "$env{VCPKG_ROOT}/installed/x86-windows/include;
$env{VCPKG_ROOT}/installed/x86-windows/lib",
"CMAKE_LIBRARY_PATH": "$env{VCPKG_ROOT}/installed/x86-windows/lib",
"ISGUI": "WIN32" // Специфический флаг, указывающий на включение
// GUI-функциональности для Windows платформы
}
},
{ // базовая конфигурация для linux-платформ
"name": "linux-base",
"hidden": true,
"inherits": "base",
"installDir": "/usr/local/bin", // в Linux имеет смысл указать директорию установки
"cacheVariables": {
"CMAKE_LIBRARY_PATH": "/usr/lib" // библиотеки в Linux хранятся в одном месте
},
"condition": { // указание операционной системы - Linux
"type": "equals",
"lhs": "${hostSystemName}",
"rhs": "Linux"
},
"vendor": {
"microsoft.com/VisualStudioRemoteSettings/CMake/2.0": {
"remoteSourceRootDir": "$env{HOME}/.vs/$ms{projectDirName}"
}
}
},
{ // конфигурация для x86, x64 процессоров
"name": "linux-debug-x86_64",
"displayName": "Linux Debug x86_64",
"inherits": "linux-base",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug",
"CMAKE_PREFIX_PATH": "/usr/include;/usr/lib/x86_64-linux-gnu"
// указание путей поиска h-файлов и файла glibconfg.h
}
},
{
"name": "linux-release-x86_64",
"displayName": "Linux Release x86_64",
"inherits": "linux-base",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release",
"CMAKE_PREFIX_PATH": "/usr/include;/usr/lib/x86_64-linux-gnu"
}
},
{ // конфигурация для linux arm64, используемой на Raspberry Pi
"name": "linux-debug-arm64",
"displayName": "Linux Debug Arm64",
"inherits": "linux-base",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug",
"CMAKE_PREFIX_PATH": "/usr/include;/usr/lib/aarch64-linux-gnu"
}
},
{
"name": "linux-release-arm64",
"displayName": "Linux Release Arm64",
"inherits": "linux-base",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release",
"CMAKE_PREFIX_PATH": "/usr/include;/usr/lib/aarch64-linux-gnu"
}
}
]
}
При работе с Visual Studio Code, в отличии от Visual Studio, - он начал ругаться на комментарии, поэтому их пришлось потом убрать.
Основной файл CMakeList.txt для проекта выглядит так:
cmake_minimum_required (VERSION 3.25)
# Включение горячей перезагрузки для компиляторов MSVC, если поддерживается.
if (POLICY CMP0141)
cmake_policy(SET CMP0141 NEW)
set(CMAKE_MSVC_DEBUG_INFORMATION_FORMAT "$<IF:$<AND:$<C_COMPILER_ID:MSVC>,
$<CXX_COMPILER_ID:MSVC>>,$<$<CONFIG:Debug,RelWithDebInfo>:EditAndContinue>,
$<$<CONFIG:Debug,RelWithDebInfo>:ProgramDatabase>>")
endif()
include(gtk3-config.cmake)
project (boxgtk3)
set(SRCLIST_BOX box.c)
if(ISGUI)
list(APPEND SRCLIST_BOX winmain.c)
endif()
add_executable(boxgtk3 ${ISGUI} ${SRCLIST_BOX})
cmake_minimum_required (VERSION 3.25) - указание версии. поскольку поддержка пресетов начинается с версии 3.19, а в дистрибутиве Linux - Debian оказалась версия 3.25, то её и решил выставить
Включение горячей перезагрузки… - эту лабуду вставил Visual Studio.
include(gtk3-config.cmake) - включение всех путей к include и lib для gtk3 решил вынести в отдельный файл (щас покажу).
project (boxgtk3) - объявление имени проекта, это понятно
и далее, изначально была строчка:
add_executable(boxgtk3 box.c)
Но оказалось, что при запуске оконного приложения GTK в Windows, запускается чёрная консоль. Пришлось сделать танец с бубном, чтобы от неё избавиться.
Для того, чтобы избавиться от консоли в проекте CMake в Windows, надо указать тип приложения WIN32, и дописать функцию запуска WinMain:
add_executable(boxgtk3 WIN32 box.c winmain.c)
Но так как для режима отладки консоль всё-же может пригодиться,
поэтому решено было сделать костыли в конфигурации Release из
CMakePresets.json ввести дополнительную переменную ISGUI, которая равна
WIN32, в зависимости от которой, к списку исходников SRCLIST добавляется
winmain.c:
#include <Windows.h>
#include <gmodule.h>
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nShowCmd)
{
** argv = g_win32_get_command_line();
gchar(g_strv_length(argv), argv);
main(argv);
g_strfreev}
данный файл является только точкой входа для оконного приложения в Windows и только запускает функцию main с параметрами.
И собственно файл для включения путей include и lib в весь проект:
# gtk-3 directories
find_path(GTK_INCLUDE gtk/gtk.h PATH_SUFFIXES gtk-3.0 REQUIRED)
find_path(GLIB_INCLUDE glib.h PATH_SUFFIXES glib-2.0 REQUIRED)
find_path(GLIB_CONFIG_INCLUDE glibconfig.h PATH_SUFFIXES glib-2.0/include REQUIRED)
find_path(CAIRO_INCLUDE cairo.h PATH_SUFFIXES cairo REQUIRED)
find_path(PANGO_INCLUDE pango/pango.h PATH_SUFFIXES pango-1.0 REQUIRED)
find_path(HARFBUZZ_INCLUDE hb.h PATH_SUFFIXES harfbuzz REQUIRED)
find_path(GDK_PIXBUF_INCLUDE gdk-pixbuf/gdk-pixbuf.h PATH_SUFFIXES gdk-pixbuf-2.0 REQUIRED)
find_path(ATK_INCLUDE atk/atk.h PATH_SUFFIXES atk-1.0 REQUIRED)
set(GTK_INCLUDE ${GTK_INCLUDE} ${GLIB_INCLUDE} ${GLIB_CONFIG_INCLUDE}
${CAIRO_INCLUDE} ${PANGO_INCLUDE} ${HARFBUZZ_INCLUDE} ${GDK_PIXBUF_INCLUDE}
${ATK_INCLUDE})
include_directories(${GTK_INCLUDE})
link_directories(${CMAKE_LIBRARY_PATH})
link_libraries(glib-2.0 gtk-3 cairo gobject-2.0 gio-2.0)
Сначала ищутся все необходимые include файлы. Список путей заносится в переменную GTK_INCLUDE, подключается путь с библиотеками link_directories, и собственно библиотеки link_libraries. Список библиотек может быть неполным, поскольку данному приложению более и не понадобилось. По необходимости следует дополнить список библиотек.
В директории, обозначенной в preset как “binaryDir” формируются непосредственно make-файлы и файл c переменными CMakeCache.txt. Из IDE конфигурация выполняется автоматически. Данные файлы можно считать временными и затем удалить. Иногда даже проще удалить все эти файлы и чтоб сделать конфугурацию заново, например, при изменении переменных CMake.
Для выполнения этапа конфигурации из командной строки следует написать:
cmake --config <preset>
Например:
cmake --config linux-release-x86_64
В IDE построение делается нажатием соответствующей кнопочки, а если
вдруг возникла острая производственная необходимость надо
сделать построение из командной строки, то можно написать примерно
следующее:
cmake --build build/linux-release-x86
Где ‘build/linux-release-x86’ - указание пути к файлам построения. Можно “зайти”(сделать текущей) в нужную директрию и выполнить
cmake --build .
После чего появится исполняемый модуль (или модули).
Есть ещё третий этап - установка (install), но по-видимомому это работает только с “make install”, впрочем для начала данный этап не особо важен, поэтому пропустим его.
Получившееся приложение выполнет добавление в контейнер вводимых строк и имеет вид: