Информационные технологии (часть 2)

10. Сборка кроссплатформенных программ

Программы для сборки

Для сборки под Windows потребуются инструменты Build Tools для Visual Studio 2019, которые можно найти на странице https://visualstudio.microsoft.com/ru/downloads/. Также потребуется программа для конфигурации сборки cmake. Для сборки в командной строке предварительно нужно запустить командный файл для Visual Studio Developer Command Prompt (в 2019 версии это команда C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\Common7\Tools\vsdevcmd).

Для сборки под Linux рекомендуется запускать следующий контейнер docker:

sudo docker run -it -v 𝘴𝘩𝘢𝘳𝘦𝘥_𝘧𝘰𝘭𝘥𝘦𝘳:/usr/src/myapp rikorose/gcc-cmake

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

В общей папке для компьютеров с операционными системами Linux и Windows (один или оба компьютера – виртуальные машины) создайте папку с проектом и разместите в ней следующие исходные коды:

  • в папке src файлы get_os_name.c и main.c;
  • в папку include файл get_os_name.h.

Содержимое файла get_os_name.c:

#include "get_os_name.h"

char* get_os_name(){
    #if defined(_WIN32)
        return "windows";
    #elif defined(__linux__)
        return "linux";
    #elif defined(__APPLE__)
        return "Apple";
    #elif defined(BSD)
        return "BSD"; 
    #endif
}

Содержимое файла get_os_name.h:

#ifndef __GET_OS_NAME_H__
#define __GET_OS_NAME_H__
char* get_os_name();
#endif

Содержимое файла main.c:

#include "stdio.h"
#include "get_os_name.h"

int main () {
    printf("hello %s\n", get_os_name());
}

Соберите пример под Windows:

cl -I "include" src\main.c src\get_os_name.c

и под Linux:

gcc -I "include" src/main.c src/get_os_name.c

Проверьте его работоспособность в обеих системах.

Сборка с помощью cmake

Создайте новый каталог для изучения примера сборки с помощью cmake и скопируйте в него папки src и include вместе с файлами.

Добавьте в новый каталог файл CMakeLists.txt:

cmake_minimum_required(VERSION 3.5)
project(print_os_name)

add_executable(print_os_target src/main.c src/get_os_name.c)

target_include_directories(print_os_target PRIVATE ${PROJECT_SOURCE_DIR}/include)

В первой строчке конфигурации (программы для cmake) мы задаем минимальную версию cmake. Во второй – имя проекта.

Функция add_executable добавляет в проект задачу (target, цель) собрать исполняемый файл (программу). Первым аргументом этой функции является имя задачи, последующие аргументы – файлы исходных кодов.

Функция target_include_directories задает для задачи каталог с включаемыми (include, заголовочными) файлами. Первый аргумент – задача. Далее указывается область действия этой функции (PRIVATE – только указанная задача, PUBLIC – весь проект). После чего указывается каталог.

В последней строке показано использование переменных cmake. Переменная PROJECT_SOURCE_DIR равна пути к каталогу проекта.

Сборка под Windows

Создайте каталог wbuild и перейдите в него.

Выполните в нем команду:

cmake ..

Программа cmake при запуске требует один аргумент – каталог проекта, в котором должен находиться файл CMakeLists.txt. Двоеточие как в Windows, так и в Linux означает родительский каталог по отношению к текущему.

При выполнении cmake пытается найти в операционной системе средства для сборки программ. После этого найденные средства для сборки тестируются. Далее генерируются конфигурационные файлы для сборки программы с помощью найденных средств сборки. Если на каком-либо этапе произойдет ошибка, то будет выдано соответствующее предупреждение.

При успешном выполнении cmake создаст в каталоге wbuild файл с именем каждой задачи и расширением .vcxproj. В терминах Visual Studio это называется проектом (project).

Также в этом каталоге будет создан файл с именем проекта и расширением .sln. В Visual Studio это называется решением (solution). Оно содержит в себе все проекты, соответствующие задачам в cmake, а также некоторые дополнительные проекты.

Соберите программу с помощью программы:

msbuild 𝘱𝘳𝘰𝘫𝘦𝘤𝘵 или 𝘴𝘰𝘭𝘶𝘵𝘪𝘰𝘯

По умолчанию будет создана отладочная версия программы, которую можно найти в папке Debug. Можно также создать рабочую версию, указав msbuild ключ -p:Configuration=Release (результат будет в папке Release).

Сборка под Linux

Создайте каталог lbuild и перейдите в него.

Выполните в нем команду:

cmake ..

Соберите программу с помощью программы:

make

Команда make собирает проект в текущей папки (понятие проекта в make и cmake совпадают). В результате получится готовая программа, имя которой совпадает с именем проекта. Запустите и проверьте правильность ее работы.

Статические библиотеки

Код функций из статической библиотеки помещается в файл программы во время ее сборки.

Создайте новый каталог для изучения примера со статической библиотекой и скопируйте в него папки src и include вместе с файлами.

Добавьте в новый каталог файл CMakeLists.txt:

cmake_minimum_required(VERSION 3.5)
project(print_os_name)

# Create a library
add_library(get_os_library STATIC src/get_os_name.c)
target_include_directories(get_os_library PUBLIC ${PROJECT_SOURCE_DIR}/include)

# Create an executable
add_executable(print_os_name src/main.c)
target_link_libraries(print_os_name PRIVATE get_os_library)

В данном примере будет две задачи: создание библиотеки и готовой программы.

Задача создания библиотеки создается функцией add_library. Первый ее аргумент – название задачи, обычно совпадающее с именем библиотеки. Далее указывается тип библиотеки (статическая), после чего указываются файлы исходных кодов.

Функция target_include_directories, как и в предыдущем примере, задает каталоги с заголовочным файлами. В отличие от предыдущего примера указывается область действия PUBLIC, чтобы подключить эти заголовочные файлы и ко второй задаче (создание программы).

Функция add_executable используется так же, как и в предыдущем примере.

Функция target_link_libraries подключает ко второй задаче библиотеку, создаваемую в первой задаче.

Соберите пример под Windows и под Linux (используя те же команды, что и в предыдущем примере).

Проверьте правильность работы программ.

Определите, какие файлы получились в результате выполнения каждой задачи под каждой ОС.

Попробуйте удалить файлы, полученные в результате выполнения задач по созданию библиотеки, и запустить программы без этих файлов.

Динамические библиотеки

Код функций из динамической библиотеки помещается в отдельный от программы файл. Функции из таких библиотек компонуются к основной программе во время работы программы (поэтому и называются динамическими). Достоинством динамических библиотек является возможность использовать их одновременно из нескольких программ (отсюда второе название – разделяемые (shared) библиотеки). Их недостаток – необходимость контролировать их наличие в операционной системе и версию.

Создайте новый каталог для изучения примера со статической библиотекой и скопируйте в него папки src и include вместе с файлами.

Добавьте в новый каталог файл CMakeLists.txt:

cmake_minimum_required(VERSION 3.5)
project(print_os_name)

# Extra lib files for WIN32
if(WIN32)
    set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
endif()

# Create a library
add_library(get_os_library SHARED src/get_os_name.c)
target_include_directories(get_os_library PUBLIC ${PROJECT_SOURCE_DIR}/include)

# Create an executable
add_executable(print_os_name src/main.c)
target_link_libraries(print_os_name PRIVATE get_os_library)

Отличие конфигураций статически и динамически присоединяемых библиотек заключается в использовании ключевых слов STATIC и SHARED в функции add_library. Для Linux отличия заканчиваются.

Если сборка ведется под Windows (что проверяется командой if(WIN32)), переменную CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS нужно установить в значение ON с помощью функции set. Это указывает cmake сгенерировать дополнительные файлы.

В Windows для создания программ, использующие динамические библиотеки, требуются дополнительные файлы для сборки программы. Код самих функций помещается в файлы с расширением .dll, которые используются во время работы. Во время сборки требуются файлы с описанием функций в файле .dll и кодом для доступа к этим функциям.

Соберите пример под Windows и под Linux (используя те же команды, что и в предыдущем примере).

Проверьте правильность работы программ.

Определите, какие файлы получились в результате выполнения каждой задачи под каждой ОС.

Определите, какие библиотечные файлы необходимы для запуска программ.