Как собрать библиотеку c из исходников
Перейти к содержимому

Как собрать библиотеку c из исходников

  • автор:

C++ подключение библиотеки собранной из исходников

Помогите разобраться с подключением библиотеки. Я собрал библиотеку из исходников, в результате получил некоторую иерархию файлов и папок, официальная инструкция по установке библиотеки на этом заканчивается. Если библиотека состоит из папок lib, include, bin, то как подключить ее понятно. Но тут получается более сложная структура файлов и папок. Как подключить библиотеку в подобных случаях? Пытаюсь подключить библиотеку Wt, компилятор MinGW, если это важно.

Отслеживать
задан 24 авг 2017 в 10:08
13 3 3 бронзовых знака

Все правильно сделали, осталось сделать install. В случае генератора MSVC, надо собрать проект INSTALL .

24 авг 2017 в 11:39

Из другого CMake-проекта делаете find_library() / find_directory() , чтобы найти эти либы и хедеры, и include_directories() / target_link_libraries() их.

24 авг 2017 в 12:01

Для Wt вообще готовый модуль есть. Если текст ошибки не сказал ничего полезного, то откройте файл FindWt.cmake , там будет написано какие переменные надо установить.

Руководство по CMake для разработчиков C++ библиотек

В этой статье я расскажу о том, как правильно писать современные CMakeLists.txt файлы для C++ библиотек. Идеи, используемые в ней, основаны на докладе Крейга Скотта (разработчик CMake) и докладе Роберта Шумахера (разработчик vcpkg) c CppCon 2019. Поскольку мне достаточно часто приходится разрабатывать С++ библиотеки, я создал для себя небольшой шаблон cpp-lib-template, который будет использоваться в этой статье в качестве примера.

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

Введение

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

Когда мы говорим о пользователях библиотеки, то в первую очередь думаем о других разработчиках, которые захотят использовать функциональность библиотеки в своих проектах. Существует два способа, которыми библиотека может интегрироваться в проект:

  • С помощью find_package — в этом случае библиотека должна предоставить package configuration file, который импортирует в CMakeLists.txt приложения таргет библиотеки. Этот файл, вместе с собранной библиотекой, ее публичными заголовочными файлами и некоторой другой информацией устанавливается в директорию, которая потом указывается в переменной CMAKE_PREFIX_PATH при сборке проекта.
  • Как подпроект, добавленный в качестве git submodule или с помощью CMake модуля FetchContent — в этом случае приложение использует обычный (не импортированный) таргет библиотеки ( find_package не вызывается), и сборка библиотеки становится этапом сборки самого проекта.

Еще одна категория пользователей библиотеки — мейнтейнеры различных пакетных менеджеров (например, vcpkg, conan и другие), которым нужно собирать библиотеку под десятки различных платформ и конфигураций. Для них важно, чтобы сборкой библиотеки можно было управлять извне, без необходимости внесения патчей в ее CMakeLists.txt.

Исходя из вышесказанного, хорошо сделанная библиотека должна удовлетворять следующим требованиям:

  • Единообразно интегрироваться и через find_package , и через add_subdirectory / FetchContent , т.е. импортированный таргет и обычный должны быть эквивалентны. В англоязычных источниках это требование часто формулируют как «build interface should match install interface».
  • В ее CMakeLists.txt не должны хардкодиться опции и флаги компиляции/компоновки кроме тех, которые абсолютно необходимы для сборки библиотеки. В противном случае мейнтейнерам менеджеров пакетов будет проблематично упаковывать библиотеку, так как с вероятностью, стремящейся к 1, на какой-то из платформ некоторые захардкоденные значения окажутся невалидны и придется делать патч для CMakeLists.txt .

Структура директорий

В своих библиотеках я придерживаюсь структуры директорий, представленной ниже. На мой взгляд, она является наиболее распространенной, кроме того, интуитивно разделяет файлы библиотеки на основные компоненты: публичные заголовки ( include/ ), исходники ( src ), утилиты для сборки ( cmake ), примеры ( examples ) и тесты ( tests ).

repository root: cmake/ examples/ include// src/ tests/ CMakeLists.txt CMakePresets.json . 

Пример библиотеки

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

#include namespace mylib < int add(int a, int b) < return a + b; >> // namespace mylib

Соответствующий заголовочный файл:

#pragma once #include namespace mylib

Наконец, файл mylib/export.h :

#pragma once #ifndef MYLIB_STATIC_DEFINE # include #else # include #endif

Файлы export_shared.h и export_static.h генерируются CMake и будут рассмотрены ниже.

Определение проекта

Первое, что нужно сделать в CMakeLists.txt — это создать проект для библиотеки:

cmake_minimum_required(VERSION 3.14) project(mylib VERSION 1.0.0 DESCRIPTION "Template for C++ library built with CMake" LANGUAGES CXX) add_library(mylib) # initialized below add_library(mylib::mylib ALIAS mylib) string(COMPARE EQUAL "$" "$" is_top_level)

Я предпочитаю заранее определить таргет для библиотеки, а инициализировать его ниже в коде, когда определены все необходимые параметры. Здесь же мы определим алиас для библиотеки, который должен иметь то же имя, что и импортируемый таргет. Это позволит пользователям библиотеки легко переключаться между ее подключением через find_package , в результате которого создается импортированный таргет с именем mylib::mylib , и подключением через add_subdirectory / FetchContent , который делает доступным алиас mylib::mylib в их проекте. Таким образом, приложение, использующее нашу библиотеку, в обоих случаях может линковаться к библиотеке с помощью команды:

target_link_libraries(app PRIVATE mylib::mylib)

Переменная is_top_level используется в дальнейшем в нескольких местах для определения, собирается ли библиотека как stand-alone проект или как подпроект. Версии CMake, начиная с 3.21, предоставляют переменную PROJECT_IS_TOP_LEVEL для этой же цели. Можно использовать и ее, но тогда в cmake_minimum_required придется указать версию не ниже 3.21.

Опции сборки

Сразу после определения проекта для библиотеки я рекомендую указать все опции, которые вы предоставляете для управления ее сборкой. Эта информация важна для пользователей библиотеки и поэтому лучше размещать ее в самом начале в одном месте. Типичными опциями являются:

include(cmake/utils.cmake) include(GNUInstallDirs) # MYLIB_SHARED_LIBS determines static/shared build when defined option(MYLIB_BUILD_TESTS "Build mylib tests" OFF) option(MYLIB_BUILD_EXAMPLES "Build mylib examples" OFF) option(MYLIB_INSTALL "Generate target for installing mylib" $) set_if_undefined(MYLIB_INSTALL_CMAKEDIR "$/cmake/mylib-$" CACHE STRING "Install path for mylib package-related CMake files") if(DEFINED MYLIB_SHARED_LIBS) set(BUILD_SHARED_LIBS $) endif()

Опции MYLIB_BUILD_* определяют, собирать или нет соответствующий компонент библиотеки. По умолчанию они выключены, потому что пользователи библиотеки, как правило, заинтересованы только в сборке самой библиотеки.

Если библиотека может быть собрана как статически, так и динамически, крайне не рекомендуется использовать кастомную переменную для определения типа сборки, потому что общепринятым стандартом является использование переменной BUILD_SHARED_LIBS , которая обрабатывается самим CMake. Следующий код является примером того, как делать не стоит:

if(SOMELIB_BUILD_AS_STATIC) add_library(somelib STATIC) else() add_library(somelib SHARED) endif()

BUILD_SHARED_LIBS , однако, влияет на все библиотеки, попадающие в сборку, а пользователям в редких случаях может понадобиться собрать несколько библиотек иначе, чем указано в BUILD_SHARED_LIBS . Поэтому среди опций библиотеки mylib есть MYLIB_SHARED_LIBS , которая может использоваться для переопределения значения BUILD_SHARED_LIBS .

Переменная MYLIB_INSTALL определяет, нужно ли генерировать таргет для установки mylib. Ее значение по умолчанию определяется тем, собирается ли mylib как отдельный проект, или как подпроект другого проекта.

Наконец, переменная MYLIB_INSTALL_CMAKEDIR позволяет указать, куда устанавливать файл конфигурации пакета (package configuration file), и предназначена в основном для мейнтейнеров менеджеров пакетов. Функция set_if_undefined определена в файле cmake/utils.cmake и аналогична set , но устанавливает значение только если переменная еще не определена (напомню, что мы не хотим переопределять любые переменные, которые установлены через командную строку CMake или в проектах верхнего уровня).

Экспорт символов

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

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

GCC и Clang по умолчанию экспортируют все символы, которые есть в библиотеке (в том числе те, что не являются частью API), что, как мы только что выяснили, негативно сказывается на скорости загрузки. CMake позволяет легко отключить это поведение, установив следующие переменные (замечу, что здесь я тоже использую функцию set_if_undefined , чтобы дать возможность пользователю при необходимости переопределить эти значения):

set_if_undefined(CMAKE_CXX_VISIBILITY_PRESET hidden) set_if_undefined(CMAKE_VISIBILITY_INLINES_HIDDEN ON)

MSVC по умолчанию ничего не экспортирует, однако CMake предоставляет переменную CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS , которая позволяет получить поведение, аналогичное GCC и Clang. Очевидно, что использовать ее не стоит.

Для экспорта конкретного символа из библиотеки разные компиляторы предоставляют разные директивы. Чтобы не углубляться в особенности компиляторов, можно воспользоваться функцией CMake generate_export_header . Эта функция создает файл, содержащий определение макроса MYLIB_EXPORT , который нужно указывать для экспортируемых символов. Процесс генерации файла элементарен:

include(GenerateExportHeader) set(export_file_name "export_shared.h") if(NOT BUILD_SHARED_LIBS) set(export_file_name "export_static.h") endif() generate_export_header(mylib EXPORT_FILE_NAME include/mylib/$)

В результате в зависимости от типа сборки библиотеки CMake создаст в билд-директории один из двух файлов: export_shared.h или export_static.h .

Объясню, почему используются разные имена в зависимости от типа сборки. Это нужно, чтобы статическую и динамическую версию библиотеки при желании можно было установить в одну директорию. Для этого файлы должны иметь разные имена, чтобы не быть перезаписанными файлом для другого типа сборки. При этом выбор нужного можно сделать в отдельном файле ( mylib/export.h , см. выше) с помощью идентификатора MYLIB_STATIC_DEFINE , который будет определяться только для статического таргета mylib.

Исходники библиотеки

Исходники библиотеки инициализируются с помощью следующих команд:

set(public_headers include/mylib/export.h include/mylib/mylib.h) set(sources $ src/mylib.cpp) source_group(TREE "$" FILES $) list(APPEND public_headers "$/include/mylib/$") list(APPEND sources "$/include/mylib/$")

Я предпочитаю использовать отдельную переменную для хранения публичных заголовков, потому что это лучше отражает разницу между этими файлами и может использоваться потом при создании install-таргета (см. ниже). Функция source_group заставляет CMake сгенерировать такую же структуру директорий в IDE, как и в самом репозитории библиотеки.

Таргет библиотеки

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

include(CMakePackageConfigHelpers) target_sources(mylib PRIVATE $) target_compile_definitions(mylib PUBLIC "$>>:MYLIB_STATIC_DEFINE") target_include_directories(mylib PUBLIC "$/include>" "$/include>" "$>" PRIVATE "$/src") set_target_properties(mylib PROPERTIES PUBLIC_HEADER "$" SOVERSION $ VERSION $)

Пояснения здесь требует, пожалуй, только момент связанный определением MYLIB_STATIC_DEFINE для статического таргета. Это нужно, чтобы файл mylib/export.h включал файл mylib/export_static.h , если используется статическая версия библиотеки, и файл mylib/export_shared.h в противном случае.

Обратите также внимание, что мы не пытаемся установить какие-то флаги компиляции или свойства таргета здесь. Как уже говорилось, это в дальнейшем создаст проблемы сторонним разработчикам, которые будут упаковывать вашу библиотеку под свою платформу. Например, во многих библиотеках, которые могут собираться под Windows, в свойствах таргета библиотеки часто указываются _POSTFIX , чтобы бинарные файлы библиотеки, собранные под разные конфигурации (static/shared, debug/release), получали разные имена и могли устанавливаться в одну директорию. Если это сделать в CMakeLists.txt библиотеки, отказаться от выбранной разработчиком схемы будет проблематично. Вместо этого, постфиксы можно задать на этапе конфигурирования проекта с помощью переменных CMAKE__POSTFIX , указанных явно в командной строке или в пресете (preset), о которых мы поговорим позже.

Install-таргет библиотеки

Поскольку мы держим в уме тот факт, что библиотека может собираться как подпроект, мы предоставляем специальную переменную MYLIB_INSTALL , с помощью которой можно отключить генерацию install-таргета. Кроме того, самому CMake также можно указать, чтобы он не генерировал таргет для install команд с помощью переменной CMAKE_SKIP_INSTALL_RULES . По этой причине, код для создания install-таргета помещен внутрь условия:

if(MYLIB_INSTALL AND NOT CMAKE_SKIP_INSTALL_RULES) . endif()

Сам код для создания install-таргета имеет такой вид:

configure_package_config_file(cmake/mylib-config.cmake.in mylib-config.cmake INSTALL_DESTINATION "$") write_basic_package_version_file(mylib-config-version.cmake COMPATIBILITY SameMajorVersion) install(TARGETS mylib EXPORT mylib_export RUNTIME COMPONENT mylib LIBRARY COMPONENT mylib NAMELINK_COMPONENT mylib-dev ARCHIVE COMPONENT mylib-dev PUBLIC_HEADER COMPONENT mylib-dev DESTINATION "$/mylib") set(targets_file "mylib-shared-targets.cmake") if(NOT BUILD_SHARED_LIBS) set(targets_file "mylib-static-targets.cmake") endif() install(EXPORT mylib_export COMPONENT mylib-dev FILE "$" DESTINATION "$" NAMESPACE mylib::) install(FILES "$/mylib-config.cmake" "$/mylib-config-version.cmake" COMPONENT mylib-dev DESTINATION "$")

В этом коде все достаточно традиционно, отмечу лишь, что в команде install(TARGETS) пути, куда нужно устанавливать основные файлы, можно опустить. В этом случае CMake по умолчанию использует те, которые предоставляются модулем GnuInstallDirs (что правильно, так как их легко переопределить на этапе конфигурации при необходимости).

Кроме того, я рекомендую явно указывать компоненты библиотеки с помощью параметра COMPONENT . В любой библиотеке можно выделить как минимум два компонента:

  • runtime — то, что нужно, чтобы приложение, использующее библиотеку, в принципе могло запуститься ( so или dll файл); в своих библиотеках я как правило называю этот компонент также, как и саму библиотеку, т.е.
  • development — то, что нужно пользователю библиотеки (заголовочные файлы, библиотека импорта, файл конфигурации пакета и т.д.); для этого компонента я использую имя -dev

Разделение на компоненты позволяет в дальнейшем выполнять установку только необходимых файлов. Например, следующая инструкция установит только runtime-компонент библиотеки mylib:

cmake --install . --component mylib

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

При создании install-таргета под Windows дополнительно указывается, как будут устанавливаться pdb-файлы, нужные для отладки библиотеки:

if(MSVC) set(pdb_file "") set(pdb_file_destination "") if(BUILD_SHARED_LIBS) set(pdb_file "$") set(pdb_file_destination "$") else() # TARGET_PDB_FILE does not work for pdb file generated by compiler # during static library build, need to determine it another way set(pdb_file "$/$$.pdb") set(pdb_file_destination "$") endif() install(FILES "$" COMPONENT mylib-dev CONFIGURATIONS Debug RelWithDebInfo DESTINATION "$" OPTIONAL) endif()

Здесь нужно учесть один момент. Дело в том, что msvc при компиляции генерирует pdb-файл для каждого объектного файла .obj (compiler pdb-файл), а потом компоновщик объединяет их в один pdb-файл (linker pdb-файл) для итогового исполняемого файла (например, .dll ). Когда вы собираете статическую библиотеку (которая по сути своей является таким же объектным файлом), компоновщик не вызывается вообще, только компилятор, который создает compiler pdb-файл.

CMake предоставляет простой способ получить linker pdb-файл: выражение-генератор TARGET_PDB_FILE . К сожалению, с помощью него вы не сможете получить compiler pdb-файл, когда собираете статическую версию библиотеки. В качестве хоть какого-то варианта решения этой проблемы, я предполагаю, что compiler pdb-файл лежит по тому же пути, что и статическая библиотека и имеет то же имя. Тем не менее, это не обязательно должно быть так, поэтому команда для установки pdb-файлов отмечена как OPTIONAL . Если вы можете предложить лучший вариант для обработки compiler pdb-файлов, поделитесь им в комментариях.

Файл конфигурации пакета

Файл конфигурации пакета предоставляется разработчиком библиотеки (я обычно помещаю его в директорию cmake ) и устанавливается в одну директорию с файлами, сгенерированными командой install(EXPORT) и содержащими определения импортированных таргетов библиотеки ( mylib-shared-targets.cmake и mylib-static-targets.cmake ). В этом файле как правило делаются две вещи:

  • включается mylib-shared-targets.cmake или mylib-static-targets.cmake
  • с помощью find_dependency находятся все зависимости импортированного таргета

Наша тривиальная библиотека ни от чего не зависит, поэтому ее файл конфигурации имеет такой вид:

macro(import_targets type) if(NOT EXISTS "$/mylib-$-targets.cmake") set($_NOT_FOUND_MESSAGE "mylib $ libraries were requested but not found") set($_FOUND OFF) return() endif() include("$/mylib-$-targets.cmake") endmacro() if(NOT TARGET mylib::mylib) set(type "") if(DEFINED MYLIB_SHARED_LIBS) if(MYLIB_SHARED_LIBS) set(type "shared") else() set(type "static") endif() elseif(BUILD_SHARED_LIBS AND EXISTS "$/mylib-shared-targets.cmake") set(type "shared") elseif(EXISTS "$/mylib-static-targets.cmake") set(type "static") else() set(type "shared") endif() import_targets($) check_required_components(mylib) message("-- Found $ mylib (version $<$_VERSION>)") endif()

Единственное, что заслуживает в нем внимания, это простой алгоритм, по которому определяется, какой таргет (для статической или динамической версии библиотеки) импортировать. Вкратце алгоритм можно описать так: MYLIB_SHARED_LIBS > BUILD_SHARED_LIBS > static > shared.

Другие таргеты

Большинство проектов C++ библиотек содержат примеры использования и тесты. Как правило эти компоненты размещаются в отдельных директориях (обычно /examples и /tests ), в которые я рекомендую поместить CMakeLists.txt для их сборки, чтобы не засорять основной CMakeLists.txt в корне библиотеки. В основной CMakeLists.txt остается добавить лишь вызов add_subdirectory для нужных директорий:

if(MYLIB_BUILD_TESTS) enable_testing() add_subdirectory(tests) endif() if(MYLIB_BUILD_EXAMPLES) add_subdirectory(examples) endif()

Таргет для тестов

Я считаю полезным, когда с тестами библиотеки можно работать не только как с частью библиотеки, но и как с самостоятельным проектом. Например, это позволяет легко собрать тесты для экземпляра библиотеки, установленного где-то в системе. Поэтому файл tests/CMakeLists.txt начинается с определения проекта для тестов, а также содержит опциональный вызов enable_testing и find_package(mylib) , если тесты собираются как stand-alone проект:

cmake_minimum_required(VERSION 3.14) project(mylib-tests) include("../cmake/utils.cmake") string(COMPARE EQUAL "$" "$" is_top_level) if(is_top_level) enable_testing() find_package(mylib REQUIRED) endif()

Я использую googletest, который предпочитаю подключать с помощью CMake модуля FetchContent. Документация googletest содержит подробные инструкции как это сделать, поэтому я не буду останавливаться на этом в своем руководстве (тем более, что вы возможно используете другой фреймворк).

Само определение таргета для тестов тривиально и следует тем же принципам, что и определение таргета библиотеки (замечу, что gtest_main это библиотека фреймворка googletest):

set(sources add_test.cpp) source_group(TREE "$" FILES $) add_executable(mylib-tests) target_sources(mylib-tests PRIVATE $) target_link_libraries(mylib-tests PRIVATE mylib::mylib gtest_main)

В Unix-подобных ОС в бинарном файле есть специальная запись rpath, в которой можно прописать путь к используемым динамическим библиотекам. Когда вы собираете библиотеку и тесты где-то в своей билд-директории, CMake в исполняемый файл mylib-tests добавляет rpath, указывающий на файл библиотеки. Таким образом при запуске mylib-tests динамический компоновщик сумеет найти библиотеку mylib , несмотря на то, что она не установлена по системному пути и не прописана в переменной среды PATH .

К сожалению, в Windows нет аналогичного rpath механизма. Поэтому при сборке тестов для динамической библиотеки возникает проблема с тем, что тесты не запускаются из IDE или с помощью CTest, поскольку DLL-файл библиотеки находится в билд-директории, о которой динамический загрузчик как правило ничего не знает. Чтобы обойти это ограничение, в файле cmake/utils.cmake определена функция win_copy_deps_to_target_dir , которая копирует dll-файл (а также pdb, если мы собираем дебаг-версию) в директорию с исполняемым файлом mylib-tests . В tests/CMakeLists.txt эта функция вызывается после определения таргета следующим образом:

if(NOT is_top_level) win_copy_deps_to_target_dir(mylib-tests mylib::mylib) endif()

Замечу, что когда mylib-tests собирается как stand-alone проект, я предпочитаю не копировать зависимости в его билд-директорию, поскольку dll-файл может находится в директории, куда не будет доступа на копирование.

Рассмотрим функцию win_copy_deps_to_target_dir поподробнее:

function(win_copy_deps_to_target_dir target) if(NOT WIN32) return() endif() set(has_runtime_dll_genex NO) if(CMAKE_MAJOR_VERSION GREATER 3 OR CMAKE_MINOR_VERSION GREATER_EQUAL 21) set(has_runtime_dll_genex YES) add_custom_command(TARGET $ POST_BUILD COMMAND $ -P "$/cmake/silent_copy.cmake" "$" "$" COMMAND_EXPAND_LISTS) endif() foreach(dep $) get_target_property(dep_type $ TYPE) if(dep_type STREQUAL "SHARED_LIBRARY") if(NOT has_runtime_dll_genex) add_custom_command(TARGET $ POST_BUILD COMMAND $ -P "$/cmake/silent_copy.cmake" "$" "$" "$" COMMAND_EXPAND_LISTS) else() add_custom_command(TARGET $ POST_BUILD COMMAND $ -P "$/cmake/silent_copy.cmake" "$" "$" COMMAND_EXPAND_LISTS) endif() endif() endforeach() endfunction()

Эта функция создает кастомную команду для указанного таргета, которая будет запускаться после его сборки. Собственно копирование выполняется CMake скриптом cmake/silent_copy.cmake , который после парсинга своих аргументов вызывает:

# парсинг аргументов в переменные 'files' и 'dest'. execute_process(COMMAND $ -E copy_if_different $ "$" ERROR_QUIET)

Возможно у вас возникнет вопрос, почему потребовалось вынести конструкцию $ -E copy_if_different в отдельный скрипт вместо того, чтобы просто указать ее при определении кастомной команды. Дело в том, что в режиме -E CMake выдаст ошибку, если список содержит несуществующие файлы. А это может произойти если, например, библиотека собиралась без отладочной информации (т.е. pdb-файл не был сгенерирован). Можно правильно обработать такие ситуации, но мне показалось проще написать скрипт, который просто игнорирует ошибки, связанные с отсутствующими файлами.

Также обратите внимание, что начиная с версии 3.21 CMake предоставляет выражение генератора TARGET_RUNTIME_DLLS , которое возвращает абсолютные пути ко всем динамическим библиотекам, от которых зависит таргет (напрямую или транзитивно). Это выражение позволяет значительно упростить использование функции win_copy_deps_to_target_dir , поскольку можно не передавать в нее явным образом зависимости таргета — функция сумеет определить их самостоятельно.

В заключение нам остается только сообщить CTest, какие тесты есть в нашем проекте. Для фреймворка googletest это можно выполнить следующим образом:

include(GoogleTest) gtest_discover_tests(mylib-tests)

Функция gtest_discover_tests определенным образом запускает исполняемый файл mylib-tests , чтобы узнать имена всех тестов, которые в нем содержатся (при этом сами тесты на этом этапе не выполняются), а затем интегрировать их в CTest.

Таргеты для примеров

Таргеты для примеров библиотеки (в директории /examples ) как правило отличаются только именами, поэтому достаточно рассмотреть один:

cmake_minimum_required(VERSION 3.14) project(mylib-add LANGUAGES CXX) include("../../cmake/utils.cmake") string(COMPARE EQUAL "$" "$" is_top_level) if(is_top_level) find_package(mylib REQUIRED) endif() set(sources main.cpp) source_group(TREE "$" FILES $) add_executable(mylib-add) target_sources(mylib-add PRIVATE $) target_link_libraries(mylib-add PRIVATE mylib::mylib) if(NOT is_top_level) win_copy_deps_to_target_dir(mylib-add mylib::mylib) endif()

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

Пресеты (presets)

Пресеты (извините, не подобрал хорошего перевода) являются относительно новой возможностью CMake (появились в CMake 3.19), позволяющей вынести параметры сборки из CMakeLists.txt . Как уже говорилось в начале статьи, делать это нужно для того, чтобы ваши проекты без проблем собирались под разные платформы и тулчейны.

Пресет представляет из себя простой json-файл, в котором задаются различные параметры, влияющие на сборку проекта (опции конфигурации, флаги компилятора и т.д.). Существует два типа пресетов:

  • CMakePresets.json для хранения глобальных настроек — этот файл обычно хранится в репозитории проекта
  • CMakeUserPresets.json для локальных настроек разработчика — этот файл не нужно хранить в репозитории (у каждого разработчика он как правило свой), и поэтому он указывается в .gitignore

Многие IDE (например, CLion) поддерживают пресеты, позволяя выбирать нужный в своем GUI. Подробнее о пресетах можно почитать в официальной документации, здесь я лишь приведу простой пример для библиотеки mylib:

< "version": 3, "cmakeMinimumRequired": < "major": 3, "minor": 14, "patch": 0 >, "configurePresets": [ < "name": "dev", "description": "Base preset for library developers", "binaryDir": "$/build", "hidden": true, "cacheVariables": < "MYLIB_BUILD_TESTS": "ON", "MYLIB_BUILD_EXAMPLES": "ON" >>, < "name": "dev-win", "description": "Windows preset for library developers", "hidden": false, "inherits": ["dev"], "cacheVariables": < "CMAKE_CXX_FLAGS": "/W4 /EHsc /w14242 /w14254 /w14263 /w14265 /w14287 /w14289 /w14296 /w14311 /w14545 /w14546 /w14547 /w14549 /w14555 /w14640 /w14826 /w14928 /WX" >>, < "name": "dev-linux", "description": "Linux preset for library developers", "hidden": false, "inherits": ["dev"], "cacheVariables": < "CMAKE_CXX_FLAGS": "-Wall -Wextra -Wpedantic -Wshadow -Wconversion -Wsign-conversion -Wcast-align -Wcast-qual -Wnull-dereference -Woverloaded-virtual -Wformat=2 -Werror" >> ] >

Ссылки

В заключение приведу некоторые полезные ссылки:

  • cpp-lib-template — шаблон для C++/CMake библиотеки, который использовался в качестве примера в этой статье
  • cmake-init — «правильный» генератор C++/CMake проектов
  • Craig Scott Deep CMake for Library Authors
  • Robert Schumacher Don’t Package Your Libraries, Write Packagable Libraries!
  • Alex Reinking Building a Dual Shared and Static Library with CMake
  • Ulrich Drepper How To Write Shared Libraries
  • Eli Bendersky Load-time relocation of shared libraries, Position Independent Code (PIC) in shared libraries

Пошаговое руководство. Создание и использование собственной библиотеки динамических ссылок (C++)

В этом пошаговом руководстве показано, как с помощью интегрированной среды разработки (IDE) Visual Studio создать собственную библиотеку динамической компоновки (DLL), написанную на Microsoft C++ (MSVC). Далее в нем показано, как использовать библиотеку DLL из другого приложения C++. Библиотеки DLL (также называемые общими библиотеками в операционных системах на основе UNIX) являются одним из наиболее полезных компонентов Windows. Их можно использовать для обмена кодом и ресурсами, а также для уменьшения размера своих приложений. Библиотеки DLL могут упростить обслуживание и расширение приложений.

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

В этом пошаговом руководстве рассматриваются следующие задачи:

  • Создание проекта библиотеки DLL в Visual Studio.
  • Добавление экспортированных функций и переменных в библиотеку DLL.
  • Создание проекта «Консольное приложение» в Visual Studio.
  • Использование в консольном приложении функций и переменных, импортированных из библиотеки DLL.
  • Запустите готовое приложение.

Как и в случае со статически связанной библиотекой, библиотека DLL экспортирует переменные, функции и ресурсы по имени. Клиентское приложение импортирует имена для использования этих переменных, функций и ресурсов. В отличие от статически компонуемой библиотеки, Windows соединяет импорт в вашем приложении с экспортом в библиотеку DLL во время загрузки или выполнения, а не во время компоновки. Для выполнения этих подключений Windows требуются дополнительные сведения, которые не являются частью стандартной модели компиляции C++. Чтобы предоставить эти дополнительные сведения, компилятор MSVC реализует некоторые специальные расширения Майкрософт для C++. Мы рассмотрим эти расширения далее.

В этом пошаговом руководстве создаются два решения Visual Studio. Первое решение создает библиотеку DLL, а второе — клиентское приложение. Библиотека DLL использует соглашение о вызовах языка C. Ее можно вызывать из приложений, написанных на других языках программирования, при условии, что платформа, соглашения о вызовах и соглашения о связывании совпадают. Клиентское приложение использует неявную компоновку, в рамках которой Windows связывает приложение с библиотекой DLL во время загрузки. Эта компоновка позволяет приложению вызывать функции, предоставляемые библиотекой DLL, точно так же, как функции в библиотеке статической компоновки.

В этом пошаговом руководстве не рассматриваются некоторые общие ситуации. В нем не рассматривается использование библиотеки DLL для C++ другими языками. Он не показывает, как создавать DLL-библиотеки только для ресурсов или как использовать явную компоновку для загрузки библиотек DLL во время выполнения, а не во время загрузки. Уверяем вас, все это можно выполнять с помощью MSVC и Visual C++.

Несмотря на то что код библиотеки DLL написан на языке C++, мы использовали интерфейсы в стиле C для экспортированных функций. Существует две основные причины: во-первых, многие другие языки поддерживают импорт функций в стиле C. Клиентское приложение не должно быть записано на языке C++. Во-вторых, это позволяет избежать некоторых распространенных ошибок, связанных с экспортируемыми классами и функциями-членами. При экспорте классов легко диагностировать ошибки, так как все, что упоминалось в объявлении класса, должно иметь экземпляр, который также экспортирован. Это ограничение применяется к библиотекам DLL, но не к статическим библиотекам. Если классы имеют обычный стиль данных, вы не должны столкнуться с этой проблемой.

Ссылки на дополнительные сведения о DLL см. в статье Создание библиотек DLL на C/C++ в Visual Studio. Дополнительные сведения о явной и неявной компоновке см. в разделе Определение подходящего метода связывания. Дополнительные сведения о создании библиотек DLL C++ для использования с языками, в которых применяются соглашения о компоновках языка C, см. в статье Экспорт функций на языке C++ для использования в исполняемых модулях, исходный код которых написан на языке C. Дополнительные сведения о том, как создавать библиотеки DLL для использования с языками .NET, см. в статье Вызов функций библиотек DLL из приложений Visual Basic.

Необходимые компоненты

  • Компьютер под управлением Microsoft Windows 7 или более поздних версий. Мы рекомендуем использовать последнюю версию Windows для более удобной разработки.

Visual Studio Installer, Desktop development with C++ workload.

  • копия Visual Studio. Сведения о скачивании и установке Visual Studio см. в этой статье. Когда вы запускаете установщик, убедитесь, что установлена рабочая нагрузка Разработка классических приложений на C++. Не беспокойтесь, если вы не установили эту рабочую нагрузку при установке Visual Studio. Вы можете снова запустить установщик и установить ее сейчас.
  • копия Visual Studio. Сведения о скачивании и установке Visual Studio 2015 см. в разделе Установка Visual Studio 2015. Для установки компилятора и средств используйте выборочную установку C++, так как они не установлены по умолчанию.
  • Базовые значения об использовании интегрированной среды разработки Visual Studio. Если вы уже использовали классические приложения для Windows, вы, вероятно, справитесь. Общие сведения см. в обзоре возможностей интегрированной среды разработки Visual Studio.
  • Основные навыки владения языком C++. Не волнуйтесь, мы не будем делать ничего сложного.

В данном пошаговом руководстве подразумевается, что вы используете Visual Studio 2017 версии 15.9 или более поздней. В некоторых более ранних версиях Visual Studio 2017 имелись дефекты в шаблонах кода или использовались разные диалоговые окна пользовательского интерфейса. Чтобы избежать проблем, используйте Visual Studio Installer для обновления Visual Studio 2017 до версии 15.9 или более поздней.

Создание проекта библиотеки DLL

Этот набор задач позволяет создать проект для библиотеки DLL, добавить код и выполнить его сборку. Для начала запустите IDE Visual Studio и выполните вход, если это необходимо. Инструкции немного отличаются в зависимости от используемой версии Visual Studio. Убедитесь, что в элементе управления в верхнем левом углу этой страницы выбрана правильная версия.

Создание проекта библиотеки DLL в Visual Studio 2019

Screenshot of the Create a new project dialog with the Dynamic Link Library template highlighted.

  1. В строке меню выберите Файл>Создать>Проект, чтобы открыть диалоговое окно Создание проекта.
  2. В верхней части диалогового окна для параметра Язык установите значение C++, для параметра Платформа — значение Windows, а для параметра Тип проекта — значение Библиотека.
  3. В отфильтрованном списке типов проектов щелкните Библиотека динамической компоновки (DLL), а затем нажмите кнопку Далее.
  4. На странице Настроить новый проект введите MathLibrary в поле Имя проекта. Примите заданные по умолчанию Расположение и Имя решения. Для параметра Решение задайте Создать новое решение. Снимите флажок Разместить решение и проект в одном каталоге, если он установлен.
  5. Нажмите кнопку Создать, чтобы создать проект.

После создания решения созданный проект вместе с исходными файлами отобразится в окне обозревателя решений в Visual Studio.

Screenshot of the Solution Explorer window with the Math Library project highlighted.

Создание проекта библиотеки DLL в Visual Studio 2017

Screenshot of the New Project dialog box showing Math Library in the Name text box.

  1. В строке меню последовательно выберите пункты Файл>Создать>Проект, чтобы открыть диалоговое окно Новый проект.
  2. На панели слева в диалоговом окне Новый проект выберите Установленные >Visual C++ >Классическое приложение для Windows. В центральной области выберите Библиотека динамической компоновки (DLL). В поле Имя введите MathLibrary. Примите заданные по умолчанию Расположение и Имя решения. Для параметра Решение задайте Создать новое решение. Установите флажок Создать каталог для решения, если он снят.
  3. Нажмите кнопку ОК, чтобы создать проект.

После создания решения созданный проект вместе с исходными файлами отобразится в окне обозревателя решений в Visual Studio.

Screenshot of the Solution Explorer window with the Math Library highlighted.

Создание проекта библиотеки DLL в Visual Studio 2015 и более ранних версий

  1. В строке меню выберите Файл >Создать >Проект.
  2. В левой области диалогового окна Новый проект разверните узлы Установленные>Шаблоны и выберите Visual C++, а затем в центральной области щелкните Консольное приложение Win32. В поле Имя введите MathLibrary. Примите заданные по умолчанию Расположение и Имя решения. Для параметра Решение задайте Создать новое решение. Установите флажок Создать каталог для решения, если он снят. Screenshot of the New Project dialog box showing Math Library in the Name text box.
  3. Нажмите кнопку ​​ОК, чтобы закрыть диалоговое окно Новый проект, и запустите мастер приложений Win32. Screenshot of the Win32 Application Wizard Overview page.
  4. Нажмите кнопку Далее. На странице Параметры приложения в поле Тип приложения выберите пункт DLL. Screenshot of the Win32 Application Wizard Application Settings Page.
  5. Нажмите кнопку Готово , чтобы создать проект.

Когда мастер завершит создание решения, вы сможете увидеть созданный проект вместе с исходными файлами в окне обозревателя решений в Visual Studio.

Screenshot of the Solution Explorer window with the Math Library highlighted.

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

Добавление файла заголовка в библиотеку DLL

  1. Чтобы создать файл заголовка для функций, последовательно щелкните Проект>Добавить новый элемент.
  2. В диалоговом окне Добавление нового элемента в левой области щелкните Visual C++. В центральной области выберите Заголовочный файл (.h). Укажите MathLibrary.h в качестве имени для файла заголовка. Screenshot of the Add New Item dialog with the C plus plus Header File template selected, and MathLibrary.h entered in the Name textbox.
  3. Нажмите кнопку ​​Добавить, чтобы создать пустой файл заголовка, который отображается в новом окне редактора. Screenshot of the empty MathLibrary.h file in the editor.
  4. Замените все содержимое этого файла заголовка следующим кодом:
// MathLibrary.h - Contains declarations of math functions #pragma once #ifdef MATHLIBRARY_EXPORTS #define MATHLIBRARY_API __declspec(dllexport) #else #define MATHLIBRARY_API __declspec(dllimport) #endif // The Fibonacci recurrence relation describes a sequence F // where F(n) is < n = 0, a // < n = 1, b // < n >1, F(n-2) + F(n-1) // for some initial integral values a and b. // If the sequence is initialized F(0) = 1, F(1) = 1, // then this relation produces the well-known Fibonacci // sequence: 1, 1, 2, 3, 5, 8, 13, 21, 34, . // Initialize a Fibonacci relation sequence // such that F(0) = a, F(1) = b. // This function must be called before any other function. extern "C" MATHLIBRARY_API void fibonacci_init( const unsigned long long a, const unsigned long long b); // Produce the next value in the sequence. // Returns true on success and updates current value and index; // false on overflow, leaves current value and index unchanged. extern "C" MATHLIBRARY_API bool fibonacci_next(); // Get the current value in the sequence. extern "C" MATHLIBRARY_API unsigned long long fibonacci_current(); // Get the position of the current value in the sequence. extern "C" MATHLIBRARY_API unsigned fibonacci_index(); 

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

Обратите внимание на операторы препроцессора в верхней части файла. Новый шаблон проекта библиотеки DLL добавляется _EXPORTS к определенным макросам препроцессора. В этом примере Visual Studio определяет MATHLIBRARY_EXPORTS , когда создается проект DLL MathLibrary.

MATHLIBRARY_EXPORTS При определении MATHLIBRARY_API макроса макрос задает __declspec(dllexport) модификатор для объявлений функций. Этот модификатор предписывает компилятору и компоновщику экспортировать функцию или переменную из библиотеки DLL для использования другими приложениями. Если не определено, например, если MATHLIBRARY_EXPORTS файл заголовка включен клиентским приложением, MATHLIBRARY_API применяет __declspec(dllimport) модификатор к объявлениям. Этот модификатор оптимизирует импорт функции или переменной в приложении. Дополнительные сведения см. в статье dllexport, dllimport.

Добавление реализации в библиотеку DLL

  1. В обозревателе решений щелкните узел Файлы решения правой кнопкой мыши и выберите пункты Добавить >Новый элемент. Создайте новый CPP-файл с именем MathLibrary.cpp, аналогично добавлению нового файла заголовка на предыдущем шаге.
  2. В окне редактора выберите вкладку MathLibrary.cpp, если она уже открыта. Если нет, то в обозревателе решений дважды щелкните файл MathLibrary.cpp в папке Исходные файлы проекта MathLibrary.
  3. В редакторе замените содержимое файла MathLibrary.cpp следующим кодом:
// MathLibrary.cpp : Defines the exported functions for the DLL. #include "pch.h" // use stdafx.h in Visual Studio 2017 and earlier #include #include #include "MathLibrary.h" // DLL internal state variables: static unsigned long long previous_; // Previous value, if any static unsigned long long current_; // Current sequence value static unsigned index_; // Current seq. position // Initialize a Fibonacci relation sequence // such that F(0) = a, F(1) = b. // This function must be called before any other function. void fibonacci_init( const unsigned long long a, const unsigned long long b) < index_ = 0; current_ = a; previous_ = b; // see special case when initialized >// Produce the next value in the sequence. // Returns true on success, false on overflow. bool fibonacci_next() < // check to see if we'd overflow result or position if ((ULLONG_MAX - previous_ < current_) || (UINT_MAX == index_)) < return false; >// Special case when index == 0, just return b value if (index_ > 0) < // otherwise, calculate next sequence value previous_ += current_; >std::swap(current_, previous_); ++index_; return true; > // Get the current value in the sequence. unsigned long long fibonacci_current() < return current_; >// Get the current index position in the sequence. unsigned fibonacci_index()
  1. В окне редактора выберите вкладку MathLibrary.cpp, если она уже открыта. Если нет, то в обозревателе решений дважды щелкните файл MathLibrary.cpp в папке Исходные файлы проекта MathLibrary.
  2. В редакторе замените содержимое файла MathLibrary.cpp следующим кодом:
// MathLibrary.cpp : Defines the exported functions for the DLL. #include "stdafx.h" // use pch.h in Visual Studio 2019 and later #include #include #include "MathLibrary.h" // DLL internal state variables: static unsigned long long previous_; // Previous value, if any static unsigned long long current_; // Current sequence value static unsigned index_; // Current seq. position // Initialize a Fibonacci relation sequence // such that F(0) = a, F(1) = b. // This function must be called before any other function. void fibonacci_init( const unsigned long long a, const unsigned long long b) < index_ = 0; current_ = a; previous_ = b; // see special case when initialized >// Produce the next value in the sequence. // Returns true on success, false on overflow. bool fibonacci_next() < // check to see if we'd overflow result or position if ((ULLONG_MAX - previous_ < current_) || (UINT_MAX == index_)) < return false; >// Special case when index == 0, just return b value if (index_ > 0) < // otherwise, calculate next sequence value previous_ += current_; >std::swap(current_, previous_); ++index_; return true; > // Get the current value in the sequence. unsigned long long fibonacci_current() < return current_; >// Get the current index position in the sequence. unsigned fibonacci_index()

Чтобы убедиться, что все работает, скомпилируйте библиотеку динамической компоновки. Чтобы выполнить компиляцию, последовательно выберите Сборка>Собрать решение. Библиотека DLL и связанные выходные данные компилятора помещаются в папку с именем Debug непосредственно под папкой решения. При создании сборки выпуска выходные данные помещаются в папку с именем Release. Результат должен выглядеть следующим образом.

1>------ Build started: Project: MathLibrary, Configuration: Debug Win32 ------ 1>pch.cpp 1>dllmain.cpp 1>MathLibrary.cpp 1>Generating Code. 1> Creating library C:\Users\username\Source\Repos\MathLibrary\Debug\MathLibrary.lib and object C:\Users\username\Source\Repos\MathLibrary\Debug\MathLibrary.exp 1>MathLibrary.vcxproj -> C:\Users\username\Source\Repos\MathLibrary\Debug\MathLibrary.dll ========== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped ========== 
1>------ Build started: Project: MathLibrary, Configuration: Debug Win32 ------ 1>stdafx.cpp 1>dllmain.cpp 1>MathLibrary.cpp 1>Generating Code. 1> Creating library C:\Users\username\Source\Repos\MathLibrary\Debug\MathLibrary.lib and object C:\Users\username\Source\Repos\MathLibrary\Debug\MathLibrary.exp 1>MathLibrary.vcxproj -> C:\Users\username\Source\Repos\MathLibrary\Debug\MathLibrary.dll ========== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped ========== 
1>------ Build started: Project: MathLibrary, Configuration: Debug Win32 ------ 1>MathLibrary.cpp 1>dllmain.cpp 1>Generating Code. 1> Creating library C:\Users\username\Source\Repos\MathLibrary\Debug\MathLibrary.lib and object C:\Users\username\Source\Repos\MathLibrary\Debug\MathLibrary.exp 1>MathLibrary.vcxproj -> C:\Users\username\Source\Repos\MathLibrary\Debug\MathLibrary.dll 1>MathLibrary.vcxproj -> C:\Users\username\Source\Repos\MathLibrary\Debug\MathLibrary.pdb (Partial PDB) ========== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped ========== 

Поздравляем, вы создали библиотеку DLL с помощью Visual Studio! Далее вы создадите клиентское приложение, которое использует функции, экспортируемые из библиотеки DLL.

Создание клиентского приложения, которое использует библиотеку DLL

При создании библиотеки DLL подумайте о том, как клиентские приложения могут его использовать. Чтобы вызвать функции или получить доступ к данным, экспортированным библиотекой DLL, исходный код клиента должен иметь объявления, доступные во время компиляции. Во время компоновки компоновщику требуются сведения для разрешения вызовов функций или доступа к данным. Библиотека DLL предоставляет эти сведения в библиотеке импорта — файле, который содержит сведения о поиске функций и данных вместо фактического кода. Во время выполнения библиотека DLL должна быть доступна клиенту в месте, которое может найти операционная система.

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

Чтобы избежать рассинхронизации, мы рекомендуем вам установить путь включения в своем клиентском проекте, чтобы добавить файлы заголовков библиотеки DLL напрямую из проекта DLL. Кроме того, укажите путь к библиотеке в своем клиентском проекте, чтобы добавить библиотеки импорта DLL из проекта DLL. Наконец, скопируйте встроенную библиотеку DLL из проекта DLL в выходной каталог своей сборки клиента. Этот шаг позволяет вашему клиентскому приложению использовать тот же код библиотеки DLL, который вы создали.

Создание клиентского приложения в Visual Studio

Screenshot of the Create a new project dialog box with the Console App option highlighted.

  1. В строке меню выберите «Файл>нового проекта«>, чтобы открыть диалоговое окно «Создать проект».
  2. В верхней части диалогового окна задайте для параметра Язык значение C++, для параметра Платформа значение Windows, а для Типа проектаКонсоль.
  3. В отфильтрованном списке типов проектов щелкните Консольное приложение, а затем нажмите кнопку Далее.
  4. На странице Настроить новый проект введите MathClient в поле Имя проекта. Примите заданные по умолчанию Расположение и Имя решения. Для параметра Решение задайте Создать новое решение. Снимите флажок Разместить решение и проект в одном каталоге, если он установлен.
  5. Нажмите кнопку Создать, чтобы создать клиентский проект.

Создается минимальный проект консольного приложения. Имя главного исходного файла будет совпадать с ранее введенным именем проекта. В этом примере используется имя MathClient.cpp. Вы можете создать проект, но он еще не использует вашу библиотеку DLL.

Создание клиентского приложения в Visual Studio 2017

Screenshot of the New Project dialog box with Installed ></p>
<ol>
<li>Чтобы создать приложение C++, которое использует созданную вами библиотеку DLL, в строке меню выберите <strong>Файл</strong>><strong>Создать</strong>><strong>Проект</strong>.</li>
<li>В левой области диалогового окна <strong>Новый проект</strong> выберите <strong>Классическое приложение Windows</strong> в разделе <strong>Установленные</strong>><strong>Visual C++</strong>. В центральной области выберите <strong>Консольное приложение Windows</strong>. В поле ввода <strong>Имя</strong> укажите имя для проекта <em>MathClient</em>. Примите заданные по умолчанию <strong>Расположение</strong> и <strong>Имя решения</strong>. Для параметра <strong>Решение</strong> задайте <strong>Создать новое решение</strong>. Установите флажок <strong>Создать каталог для решения</strong>, если он снят. Visual C plus plus > Windows Desktop selected, Windows Console Application highlighted, and Math Client typed in the Name text box.» /></li>
<li>Нажмите кнопку <strong>ОК</strong>, чтобы создать проект клиентского приложения.</li>
</ol>
<p>Создается минимальный проект консольного приложения. Имя главного исходного файла будет совпадать с ранее введенным именем проекта. В этом примере используется имя <strong>MathClient.cpp</strong>. Вы можете создать проект, но он еще не использует вашу библиотеку DLL.</p><div class='code-block code-block-12' style='margin: 8px 0; clear: both;'>
<!-- 12vxworks -->
<script src=

Создание клиентского приложения в Visual Studio 2015

Screenshot of the New Project dialog box with Installed ></p>
<ol>
<li>Чтобы создать приложение C++, которое использует созданную вами библиотеку DLL, в строке меню выберите <strong>Файл</strong>><strong>Создать</strong>><strong>Проект</strong>.</li>
<li>В левой области диалогового окна <strong>Новый проект</strong> щелкните <strong>Win32</strong> в разделе <strong>Установленные</strong>><strong>Шаблоны</strong>><strong>Visual C++</strong>. В центральной области выберите <strong>Консольное приложение Win32</strong>. В поле ввода <strong>Имя</strong> укажите имя для проекта <em>MathClient</em>. Примите заданные по умолчанию <strong>Расположение</strong> и <strong>Имя решения</strong>. Для параметра <strong>Решение</strong> задайте <strong>Создать новое решение</strong>. Установите флажок <strong>Создать каталог для решения</strong>, если он снят. Templates > Visual C plus plus > Win32 selected, Win32 Console Application Visual C plus plus highlighted, and Math Client typed in the Name text box.» /></li>
<li>Нажмите кнопку <strong>​​ОК</strong>, чтобы закрыть диалоговое окно <strong>Новый проект</strong>, и запустите <strong>мастер приложений Win32</strong>. На странице <strong>Обзор</strong> диалогового окна <strong>Мастер приложений Win32</strong> нажмите кнопку <strong>Далее</strong> .</li>
<li>На странице <strong>Параметры приложения</strong> в поле <strong>Тип приложения</strong> выберите пункт <strong>Консольное приложение</strong>, если он еще не выбран.</li>
<li>Нажмите кнопку <strong>Готово</strong> , чтобы создать проект.</li>
</ol>
<p>После завершения работы мастера создается минимальный проект консольного приложения. Имя главного исходного файла будет совпадать с ранее введенным именем проекта. В этом примере используется имя <strong>MathClient.cpp</strong>. Вы можете создать проект, но он еще не использует вашу библиотеку DLL.</p>
<p>Затем, чтобы вызвать функции MathLibrary в исходном коде, ваш проект должен содержать файл <em>MathLibrary.h</em>. Этот файл заголовка можно скопировать в проект клиентского приложения, а затем добавить его в проект как существующий элемент. Этот метод подходит для сторонних библиотек. Однако если вы работаете над кодом для библиотеки DLL и клиента одновременно, файлы заголовков могут выйти из синхронизации. Чтобы избежать этой проблемы, задайте <strong>в проекте путь «Дополнительные каталоги включения»</strong> , чтобы включить путь к исходному заголовку.</p>
<h4>Добавление заголовка библиотеки DLL в путь включения</h4>
<ol>
<li>Щелкните правой кнопкой мыши узел <strong>MathClient</strong> в <strong>обозревателе решений</strong>, чтобы открыть диалоговое окно <strong>Страницы свойств</strong>.</li>
<li>В раскрывающемся списке <strong>Конфигурация</strong> выберите пункт <strong>Все конфигурации</strong>, если он еще не выбран.</li>
<li>В области слева выберите пункт <strong>Свойства конфигурации</strong> ><strong>C/C++</strong> ><strong>Общие</strong>.</li>
<li>На панели свойств щелкните раскрывающийся элемент управления рядом с полем ввода параметра <strong>Дополнительные каталоги включаемых файлов</strong>, а затем щелкните <strong>Правка</strong>. <img decoding=

  • Дважды щелкните в верхней панели диалогового окна Дополнительные каталоги включаемых файлов, чтобы включить элемент управления «Поле ввода». Или щелкните значок папки, чтобы создать новую запись.
  • В элементе управления «Поле ввода» укажите путь к расположению файла заголовка MathLibrary.h. Чтобы перейти к нужной папке, можно выбрать элемент управления с многоточием (. ). Можно также ввести относительный путь от исходных файлов клиента к папке, содержащей файлы заголовков библиотеки DLL. Если вы следовали инструкциям по размещению клиентского проекта в отдельном решении, отличном от библиотеки DLL, относительный путь должен выглядеть следующим образом: ..\..\MathLibrary\MathLibrary Если библиотеки DLL и клиентские проекты находятся в одном решении, относительный путь может выглядеть следующим образом: ..\MathLibrary Если библиотеки DLL и клиентские проекты находятся в других папках, измените относительный путь для соответствия. Или используйте элемент управления «Многоточие» для поиска папки. Screenshot of the Additional Include Directories dialog showing the relative path to the MathLibrary directory.
  • После ввода пути к файлу заголовка в диалоговом окне Дополнительные каталоги включаемых файлов нажмите кнопку ОК. В диалоговом окне Страницы свойств нажмите кнопку OK, чтобы сохранить изменения.
  • Теперь можно добавить файл MathLibrary.h и использовать функции, которые он объявляет, в вашем клиентском приложении. Замените содержимое файла MathClient.cpp, используя следующий код:

    // MathClient.cpp : Client app for MathLibrary DLL. // #include "pch.h" Uncomment for Visual Studio 2017 and earlier #include #include "MathLibrary.h" int main() < // Initialize a Fibonacci relation sequence. fibonacci_init(1, 1); // Write out the sequence values until overflow. do < std::cout while (fibonacci_next()); // Report count of values written before overflow. std::cout

    Этот код может быть скомпилирован, но не скомпилирован. Если вы создаете клиентское приложение, в списке ошибок появится несколько ошибок LNK2019. Это связано с отсутствием некоторых сведений о проекте: вы еще не указали, что проект зависит от библиотеки MathLibrary.lib . И вы не указали компоновщику, как найти файл MathLibrary.lib.

    Чтобы устранить эту проблему, можно скопировать файл библиотеки непосредственно в проект клиентского приложения. Компоновщик сможет найти и использовать его автоматически. Однако если и библиотека, и клиентское приложение находятся в стадии разработки, это может привести к изменениям в одной копии, которые не будут отображаться в другой. Чтобы избежать этой проблемы, можно задать свойство Дополнительные зависимости, чтобы сообщить системе сборки о том, что проект зависит от MathLibrary.lib. Также можно задать путь Дополнительные каталоги библиотек в проекте, включив в него путь к исходной библиотеке при компоновке.

    Добавление библиотеки импорта DLL в проект

    1. Щелкните правой кнопкой мыши узел MathClient в обозревателе решений и выберите Свойства, чтобы открыть диалоговое окно Страницы свойств.
    2. В раскрывающемся списке Конфигурация выберите пункт Все конфигурации, если он еще не выбран. Это гарантирует, что любые изменения свойств применяются к сборкам отладки и выпуска.
    3. В области слева выберите пункт Свойства конфигурации >Компоновщик>Ввод. На панели свойств щелкните раскрывающийся элемент управления рядом с полем ввода параметра Дополнительные зависимости, а затем щелкните Правка. Screenshot of the Property Pages dialog showing the Edit command in the Linker >Input > Additional Dependencies property drop-down.
    4. В диалоговом окне Дополнительные зависимости добавьте MathLibrary.lib в список в верхнем элементе управления "Поле ввода". Screenshot of the Additional Dependencies dialog showing the MathLibrary.lib file.
    5. Нажмите кнопку OK, чтобы вернуться в диалоговое окно Страницы свойств.
    6. В области слева выберите пункт Свойства конфигурации >Компоновщик>Общие. На панели свойств щелкните раскрывающийся элемент управления рядом с полем ввода параметра Дополнительные каталоги библиотек, а затем щелкните Правка. Screenshot of the Property Pages dialog showing the Edit command in the Linker >General > Additional Library Directories property drop-down.
    7. Дважды щелкните в верхней панели диалогового окна Дополнительные каталоги библиотек, чтобы включить элемент управления "Поле ввода". В элементе управления "Поле ввода" укажите путь к расположению файла MathLibrary.lib. По умолчанию он находится в папке с именем Debug непосредственно в папке решения DLL. При создании сборки выпуска файл помещается в папку с именем Release. Можно использовать макрос $(IntDir) , чтобы компоновщик мог найти библиотеку DLL независимо от типа создаваемой сборки. Если вы следовали инструкциям по размещению клиентского проекта в отдельном решении, отличном от проекта DLL, относительный путь должен выглядеть следующим образом: ..\..\MathLibrary\$(IntDir) Если библиотеки DLL и клиентские проекты находятся в других расположениях, измените относительный путь для соответствия. Screenshot of the Additional Library Directories dialog.
    8. Как только вы ввели путь к файлу библиотеки, в диалоговом окне Дополнительные каталоги библиотек нажмите кнопку ОК, чтобы вернуться в диалоговое окно Страницы свойств. Нажмите ОК, чтобы сохранить изменения свойств.

    Ваше клиентское приложение теперь можно компилировать и компоновать, но в нем по-прежнему нет всего необходимого для запуска. Когда операционная система загружает ваше приложение, оно ищет библиотеку DLL MathLibrary. Если она не может найти библиотеку DLL в определенных системных каталогах, в пути среды или локальном каталоге приложения, загрузка завершается сбоем. В зависимости от операционной системы вы увидите сообщение об ошибке следующего вида:

    Screenshot of the error dialog, MathLibrary DLL not found.

    Чтобы избежать этой проблемы, можно скопировать библиотеку DLL в каталог, в котором находится исполняемый файл клиента, в процессе сборки. Можно добавить событие после сборки в ваш проект, чтобы добавить команду, которая копирует библиотеку DLL в выходной каталог вашей сборки. Указанная здесь команда копирует библиотеку DLL только в том случае, если она отсутствует или была изменена. Он использует макросы для копирования в расположения отладки или выпуска на основе конфигурации сборки.

    Копирование библиотеки DLL в событие после сборки

    Screenshot of the Property Pages dialog showing the post build event command line property.

    1. Щелкните правой кнопкой мыши узел MathClient в обозревателе решений и выберите Свойства, чтобы открыть диалоговое окно Страницы свойств.
    2. В раскрывающемся списке Конфигурация выберите пункт Все конфигурации, если он еще не выбран.
    3. В области слева выберите Свойства конфигурации >События сборки >Событие после сборки.
    4. В области свойств щелкните элемент управления "Поле ввода" в поле Командная строка. Если вы следовали инструкциям по размещению клиентского проекта в отдельном решении, отличном от проекта DLL, введите следующую команду: xcopy /y /d "..\..\MathLibrary\$(IntDir)MathLibrary.dll" "$(OutDir)" Если библиотеки DLL и клиентские проекты находятся в других каталогах, измените относительный путь к библиотеке DLL для соответствия.
    5. Нажмите кнопку OK, чтобы сохранить изменения в свойствах проекта.

    Теперь в вашем клиентском приложении есть все, что нужно для сборки и запуска. Соберите приложение, щелкнув команду Сборка>Собрать решение в меню. Окно Вывод в Visual Studio должно иметь примерно следующий вид в зависимости от используемой версии Visual Studio:

    1>------ Build started: Project: MathClient, Configuration: Debug Win32 ------ 1>MathClient.cpp 1>MathClient.vcxproj -> C:\Users\username\Source\Repos\MathClient\Debug\MathClient.exe 1>1 File(s) copied ========== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped ========== 

    Поздравляем, вы создали приложение, которое вызывает функции в вашей библиотеке DLL. Теперь запустите свое приложение, чтобы увидеть, как оно работает. В строке меню щелкните Отладка>Начать без отладки. В Visual Studio открывается командное окно для запуска программы. Последняя часть выходных данных должна выглядеть так:

    Screenshot of the command window output when you start the client app without debugging.

    Для закрытия командного окна нажмите любую клавишу.

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

    При развертывании приложения необходимо также развернуть используемые им библиотеки DLL. Самый простой способ сделать библиотеки DLL, которые вы создаете или добавляете из сторонних источников, доступными — поместить их в тот же каталог, что и ваше приложение. Это также называется локальным развертыванием приложений. Дополнительные сведения о развертывании см. в разделе Deployment in Visual C++.

    Библиотеки в Си. Сборка программы со статической и динамической библиотеками

    Библиотеки позволяют использовать созданный ранее программный код в различных программах. Таким образом, программист может не разрабатывать часть кода для своей программы, а воспользоваться тем, что входит в состав библиотек.

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

    У каждой библиотеки должен быть свой заголовочный файл, в котором должны быть описаны прототипы (объявления) всех функций, содержащихся в этой библиотеке. С помощью заголовочных файлов вы "сообщаете" вашему программному коду, какие библиотечные функции есть и как их использовать.

    При компиляции программы библиотеки подключаются линковщиком, который вызывается gcc . Если программе требуются только стандартные библиотеки, то дополнительных параметров линковщику передавать не надо (есть исключения). Он "знает", где стандартные библиотеки находятся, и подключит их автоматически. Во всех остальных случаях при компиляции программы требуется указать имя библиотеки и ее местоположение.

    Библиотеки бывают двух видов — статические и динамические. Код первых при компиляции полностью входит в состав исполняемого файла, что делает программу легко переносимой. Код динамических библиотек не входит в исполняемый файл, последний содержит лишь ссылку на библиотеку. Если динамическая библиотека будет удалена или перемещена в другое место, то программа работать не будет. С другой стороны, использование динамических библиотек позволяет сократить размер исполняемого файла. Также если в памяти находится две программы, использующие одну и туже динамическую библиотеку, то последняя будет загружена в память лишь единожды.

    Далее будет описан пример, в котором создается библиотека, после чего используется при создании программы.

    Пример создания библиотеки

    Допустим, мы хотим создать код, который в дальнейшем планируем использовать в нескольких проектах. Следовательно, нам требуется создать библиотеку. Ее исходный код разместим в двух файлах.

    Также у нас будет проект, использующая эту библиотеку. Он тоже будет включать два файла.

    В итоге, когда все будет сделано, схема каталогов и файлов будет выглядеть так:

    Схема проекта с библиотекой

    Пусть каталоги library и project находятся в одном общем каталоге, например, домашнем каталоге пользователя. Каталог library содержит каталог source с файлами исходных кодов библиотеки. Также в library будут находиться заголовочный файл (содержащий описания функций библиотеки), статическая ( libmy1.a ) и динамическая ( libmy2.so ) библиотеки. Каталог project будет содержать файлы исходных кодов проекта и заголовочный файл с описанием функций проекта. Также после компиляции с подключением библиотеки здесь будет располагаться исполняемый файл проекта.

    В операционных системах GNU/Linux имена файлов библиотек должны иметь префикс "lib", статические библиотеки - расширение *.a , динамические - *.so .

    Для компиляции проекта достаточно иметь только одну библиотеку: статическую или динамическую. В образовательных целях мы получим обе и сначала скомпилируем проект со статической библиотекой, потом — с динамической. Статическая и динамическая "разновидности" одной библиотеки по-идее должны называться одинаково (различаются только расширения). Поскольку у нас обе библиотеки будут находиться в одном каталоге, то чтобы быть уверенными, что при компиляции проекта мы используем ту, которую хотим, их названия различны ( libmy1 и libmy2 ).

    Исходный код библиотеки

    #include void rect(char sign, int w, int h)  int i, j; for (i = 0; i  w; i++) putchar(sign); putchar('\n'); for (i = 0; i  h-2; i++) { for (j = 0; j  w; j++) { if (j == 0  putchar('\n'); } for (i = 0; i  w; i++) putchar(sign); putchar('\n'); } void diagonals(char sign, int w)  putchar('\n'); } }

    В файле figure.c содержатся две функции — rect и diagonals . Первая принимает в качестве аргументов символ и два числа и "рисует" на экране с помощью указанного символа прямоугольник заданной ширины и высоты. Вторая функция выводит на экране две диагонали квадрата ("рисует" крестик).

    #include void text(char *ch) { while (*ch++ != '\0') putchar('*'); putchar('\n'); }

    В файле text.c определена единственная функция, принимающая указатель на символ строки. Функция выводит на экране звездочки в количестве, соответствующем длине указанной строки.

    void rect(char sign, int width, int height); void diagonals(char sign, int width); void text(char *ch); 

    Заголовочный файл можно создать в каталоге source , но мы лучше сохраним его там, где будут библиотеки. В данном случае это на уровень выше (каталог library ). Тем самым как бы подчеркивается, что файлы исходных кодов после создания из них библиотеки вообще не нужны пользователям библиотек, они нужны лишь разработчику библиотеки. А вот заголовочный файл библиотеки требуется для ее правильного использования.

    Создание статической библиотеки

    Статическую библиотеку создать проще, поэтому начнем с нее. Она создается из обычных объектных файлов путем их архивации с помощью утилиты ar .

    Все действия, которые описаны ниже выполняются в каталоге library (т.е. туда надо перейти командой cd ). Просмотр содержимого каталога выполняется с помощью команды ls или ls -l .

    Получаем объектные файлы:

    gcc -c ./source/*.c

    В итоге в каталоге library должно наблюдаться следующее:

    figures.o mylib.h source text.o

    Далее используем утилиту ar для создания статической библиотеки:

    ar r libmy1.a *.o

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

    Объектные файлы нам не нужны, поэтому их можно удалить:

    rm *.o

    В итоге содержимое каталога library должно выглядеть так:

    libmy1.a mylib.h source

    , где libmy1.a — это статическая библиотека.

    Создание динамической библиотеки

    Объектные файлы для динамической библиотеки компилируются особым образом. Они должны содержать так называемый позиционно-независимый код (position independent code). Наличие такого кода позволяет библиотеке подключаться к программе, когда последняя загружается в память. Это связано с тем, что библиотека и программа не являются единой программой, а значит как угодно могут располагаться в памяти относительно друг друга. Компиляция объектных файлов для динамической библиотеки должна выполняться с опцией -fPIC компилятора gcc :

    gcc -c -fPIC source/*.c

    В отличие от статической библиотеки динамическую создают при помощи gcc указав опцию -shared :

    gcc -shared -o libmy2.so *.o

    Использованные объектные файлы можно удалить:

    rm *.o

    В итоге содержимое каталога library :

    libmy1.a libmy2.so mylib.h source

    Использование библиотеки в программе

    Исходный код программы

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

    #include #include "../library/mylib.h" void data(void) { char strs[3][30]; char *prompts[3] = { "Ваше имя: ", "Местонахождение: ", "Пунк прибытия: "}; int i; for (i = 0; i  3; i++) { printf("%s", prompts[i]); fgets(strs[i], 30, stdin); } diagonals('~', 7); for (i = 0; i  3; i++) { printf("%s", prompts[i]); text(strs[i]); } }

    Функция data запрашивает у пользователя данные, помещая их в массив strs . Далее вызывает библиотечную функцию diagonals() , которая выводит на экране "крестик". После этого на каждой итерации цикла вызывается библиотечная функция text() , которой передается очередной элемент массива; функция text выводит на экране звездочки в количестве равном длине переданной через указатель строки.

    Обратите внимание на то, как подключается заголовочный файл библиотеки: через относительный адрес. Две точки обозначают переход в каталог на уровень выше, т. е. родительский по отношению к project , после чего путь продолжается в каталог library , вложенный в родительский. Можно было бы указать абсолютный путь, например, "/home/pl/c/les22/library/mylib.h". Однако при перемещении каталогов библиотеки и программы на другой компьютер или в другой каталог адрес был бы уже не верным. В случае с относительным адресом требуется лишь сохранять расположение каталогов project и library относительно друг друга.

    #include #include "../library/mylib.h" #include "project.h" int main() { rect('-',75,4); data(); rect('+',75,3); } 

    Здесь два раза вызывается библиотечная функция rect() и один раз функция data() из другого файла проекта. Чтобы сообщить функции main прототип data также подключается заголовочный файл проекта.

    Файл project.h содержит всего одну строчку:

    void data(void);

    Из обоих файлов проекта с исходным кодом надо получить объектные файлы для объединения их потом с файлом библиотеки. Сначала мы получим исполняемый файл, содержащий статическую библиотеку, потом — связанный с динамической библиотекой. Однако с какой бы библиотекой мы не компоновали объектные файлы проекта, компилируются они как для статической, так и динамической библиотеки одинаково:

    gcc -c *.c

    При этом не забудьте сделать каталог project текущим!

    Компиляция проекта со статической библиотекой

    Теперь в каталоге project есть два объектных файла: main.o и data.o . Их надо скомпилировать в исполняемый файл project , объединив со статической библиотекой libmy1.a . Делается это с помощью такой команды:

    gcc -o project *.o -L../library -lmy1

    Начало команды должно быть понятно: опция -o указывает на то, что компилируется исполняемый файл project из объектных файлов.

    Помимо объектных файлов проекта в компиляции участвует и библиотека. Об этом свидетельствует вторая часть команды: -L../library -lmy1 . Здесь опция -L указывает на адрес каталога, где находится библиотека, он и следует сразу за ней. После опции -l записывается имя библиотеки, при этом префикс lib и суффикс (неважно .a или .so ) усекаются. Обратите внимание, что после данных опций пробел не ставится.

    Опцию -L можно не указывать, если библиотека располагается в стандартных для данной системы каталогах для библиотек. Например, в GNU/Linux это /lib/ , /urs/lib/ и др.

    Запустив исполняемый файл project и выполнив программу, мы увидим на экране примерно следующее:

    Выполнение готовой программы

    Посмотрим размер файла project :

    pl@desk:~/c/les22/project$ ls -l project -rwxrwxr-x 1 pl pl 16352 дек 19 02:02 project

    Его размер равен 16352 байт.

    Компиляция проекта с динамической библиотекой

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

    gcc -o project *.o \ > -L../library -lmy2 -Wl,-rpath. /library/

    Здесь в отличии от команды компиляции со статической библиотеки добавлены опции для линковщика: -Wl,-rpath. /library/ . -Wl ‒ это обращение к линковщику, -rpath ‒ опция линковщика, ../library/ ‒ значение опции. Получается, что в команде мы два раза указываем местоположение библиотеки: один раз с опцией -L , а второй раз с опцией -rpath .

    Следует заметить, что если вы скомпилируете программу, используя приведенную команду, то исполняемый файл будет запускаться из командной строки только в том случае, если текущий каталог project . Стоит сменить каталог, будет возникать ошибка из-за того, что динамическая библиотека не будет найдена. Но если скомпилировать программу так:

    gcc -o project *.o -L../library -lmy2 \ > -Wl,-rpath,/home/pl/c/library

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

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

    pl@desk:~/c/les22/library$ ls -l libmy* -rw-rw-r-- 1 pl pl 3616 дек 19 00:16 libmy1.a -rwxrwxr-x 1 pl pl 15592 дек 19 00:33 libmy2.so

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

    Курс с решением задач:
    pdf-версия

    Добавить комментарий

    Ваш адрес email не будет опубликован. Обязательные поля помечены *