Подключение внешних библиотек

Рассмотрим пример подключения внешней библиотеки OpenCV и сборку программы в терминале, а также в IDE Visual Studio и CLion, используя разные системы сборки.

Все дальнейшие примеры проводились для компилятора clang под Windows. При использовании дргих компиляторов ключи будут другими. Под Unix-системами пути до бибилотеки после её установки и расширения файлов библиотек будут другими.

OpenCV

Популярная библиотека для работы с изображениями. Будем рассматривать пример подключения библиотеки в программу, написанную на C. Для этого используется OpenCV версии 3.4 (sources), т.к. начиная с версии 4.0 C API было удалено из исходников библиотеки.

Структура баблиотеки выглядит следующим образом:

Общая структура библиотеки может поставляться в виде исходники (sources) + собранная версия библиотеки (build). OpenCV как раз можно скачать в таком варианте.

opencv
Mode   Name
----   ----
d----- build
d----- sources
-a---- LICENSE.txt
-a---- LICENSE_FFMPEG.txt
-a---- README.md.txt

Нас интересует собранная версия, поэтому рассмотрим подробнее струкутуру директории build:

opencv\build
Mode   Name
----   ----
d----- bin
d----- etc
d----- include
d----- java
d----- python
d----- x64
-a---- LICENSE
-a---- OpenCVConfig-version.cmake
-a---- OpenCVConfig.cmake
-a---- setup_vars_opencv3.cmd

Чаще всего в библиотеках/фреймворках встречаются директории include, bin, lib, x86, x64, docs, 3rdparty, samples и прочие.

Рассмотрим структуру поддиректорий include, bin, x64

opencv\build\include
Mode   Name
----   ----
d----- opencv
d----- opencv2

Как можно заметить, заголовочные файлы не обязаны все лежать в одной директории, а могут быть расположенны по разным поддиректориям. Также в OpenCV версии 3 видно, что рядом могут лежать заголовочные с C и C++ API.

В OpenCV исполняемые файлы и файлы динамических библиотек самой OpenCV располагаются не в корневой bin директории. Такое также встречается, когда bin директорий в проекте несколько и в таком случае необходимые файлы динамический библиотек следует искать в директориях x86 и x64. Последняя в сброке OpenCV имеется.

vc14 и vc15 – директории, содержащие сборки под разными версиями Visual Studio. vc15 – сборка под VS 2017 и позднее, vc14 – VS 2015. Структура в директориях идентичная, так что будем рассматривать vc15.

Можно заметить, что и dll, и lib файлы представлены в двух вариациях, отличающихся суффиксом d. В OpenCV файлы библиотек с суффиксом d собраны с сохранением отладочной информации и их следует использовать при сборке программы в debug-конфигурации. Без суффикса – релизная сборка.

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

main.c
#include <stdio.h>

#ifdef OPENCV
#include <opencv/cv.h>
#include <opencv/highgui.h>
#include <opencv2/imgcodecs/imgcodecs_c.h>

#define WINDOW_NAME "Example"
#endif

int main(int argc, char* argv[])
{
    printf("sizeof(size_t) = %zu\n", sizeof(size_t));

    if (argc != 2)
    {
        fprintf(stderr, "ERROR! Usage: %s image-file", argv[0]);
        return -1;
    }

#ifdef OPENCV
    IplImage* img = cvLoadImage(argv[1], CV_LOAD_IMAGE_COLOR);
    cvNamedWindow(WINDOW_NAME, CV_WINDOW_AUTOSIZE);
    cvShowImage(WINDOW_NAME, img);
    cvWaitKey(0);

    cvReleaseImage(&img);
    cvDestroyWindow(WINDOW_NAME);
#endif
    return 0;
}

Подключение внешних библиотек в терминале

Попробуем собрать программу без указания особых ключей компиляции:

> clang -O2 .\main.c -o main.exe

При запуске программы получим:

> .\main.exe
sizeof(size_t) = 8
ERROR! Usage: ..\cmd_test\main.exe image-file

> .\main.exe "..\chat.png"
sizeof(size_t) - 8

Попробуем указать сборку в другую битность (по умолчанию x64):

> clang -O2 .\main.c -o main.exe -m64

> .\main.exe
sizeof(size_t) = 4
ERROR! Usage: ..\cmd_test\main.exe image-file

Действительно, программа собрана как 32-битная. Т.к. в нашем распоряжении имеются собранные файлы библиотек только в варианте x64, то в дальнейшем будем собирать программу 64-битную.

Как можно заметить, в коде нигде явно не объявлен макрос OPENCV и соотственно код, связанный с использованием библиотеки был отброшен на этапе препроцессинга.

Объявим макрос OPENCV на этапе сборки программы:

> clang -O2 .\main.c -o main.exe -m64 -D OPENCV

.\main.c:4:10: fatal error: 'opencv/cv.h' file not found
    4 | #include <opencv/cv.h>
      |          ^~~~~~~~~~~~~
1 error generated.

Ошибка гласит, что не удалось найти заголовочный файл. Мы не указали include directories, из-за чего на этапе препроцессинга поиск заголовочных файлов проводился только в директориях, заданных по умолчанию (директории компилятора и системные директории).

Исправим это. Примечание: в текущем примере директория opencv лежит относительно main.c по ..\library\opencv.

add -I
> clang -O2 .\main.c -o main.exe -m64 -D OPENCV -I "..\library\opencv\build\include\"

main-f71563.o : error LNK2019: unresolved external symbol cvLoadImage referenced in function mainmain-f71563.o : error LNK2019: unresolved external symbol cvNamedWindow referenced in function main
main-f71563.o : error LNK2019: unresolved external symbol cvShowImage referenced in function main       
main-f71563.o : error LNK2019: unresolved external symbol cvWaitKey referenced in function main
main-f71563.o : error LNK2019: unresolved external symbol cvReleaseImage referenced in function main    
main-f71563.o : error LNK2019: unresolved external symbol cvDestroyWindow referenced in function main   
main.exe : fatal error LNK1120: 6 unresolved externals
clang: error: linker command failed with exit code 1120 (use -v to see invocation)

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

add -l .lib
> clang -O2 .\main.c -o main.exe -m64 -D OPENCV -I "..\library\opencv\build\include\" -L "..\library\opencv\build\x64\vc15\lib\" -lopencv_world3415.lib 

clang: error: no such file or directory: '.lib'

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

add -l
> clang -O2 .\main.c -o main.exe -m64 -D OPENCV -I "..\library\opencv\build\include\" -L "..\library\opencv\build\x64\vc15\lib\" -lopencv_world3415

Ура, никаких ошибок линковки не возникло. Попробуем запустить программу:

add -l
> .\main.exe ; "0x"+$LastExitCode.ToString("X")

0xC0000135

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

> Start-Process .\main.exe ; "0x"+$LastExitCode.ToString("X")

В обоих случаях удаётся найти сообщение об ошибке – не найден файл динамической библиотеки.

В таком случае есть 2 варианта:

  1. Скопировать .dll-файл рядом с exe-файлом.

  2. Добавить директорию, в которой лежит необходимый .dll-файл в переменные окружения. В таком случае не нужно каждый раз копировать dll к exe-файлу.

В этом примере подложим dll файл к exe:

При запуске видно, что всё открылось успешно и в консоли также видно данные, выводимые в программе.

Подключение внешних библиотек в системе сборки MSBuild (Visual Studio)

Система сборки MSBuild имеет консольный и пользовательский графический интерфейс. Рассмотрим настройку через графический интерфейс в среде разработки Visual Studio.

Добавим готовый файл main.c из начального примера в проект.

Все параметры и данные для сборки настраиваются через свойства решений и проектов в IDE Vusial Studio. Если в решении несколько проектов, то системе сборки MSBuild можно задать правила и порядок сборки проектов, что может быть полезно, если имеется зависимость между проектами в решении.

В нашем случае будет 1 проект в 1 решении и далее мы будем рассматривать только настройки проекта (ПКМ по проекту – Properties).

В первую очередь настоятельно рекомендуется установить общие для всех конфигураций и платформ настройки, установив Configurations и Platforms в All. Затем уже устанавливать параметры, различные для конфигураций и платформ. Это сильно сэкономит времени и нервов :)

В первой вкладке General можно настроить используемый toolset, название выходного файла, стандарт языка и пр.

Toolset – набор инструментов платформы, состоящий из компилятора C++ (cl.exe) и компоновщика (link.exe), а также стандартных библиотек C/C++.

По умолчанию будет доступен 1 toolset той VS, которая была установлена (например 2022). Через Visual Studio Installer можно поставить toolset более ранних VS, а также поставить clang (LLVM-Clang на скрине). Помимо этого компилятор Intel имеет интеграцию в VS и после установки также будет доступен в выпадающем списке.

Target name – имя выходного файла. Аналог -o у clang-а.

Стандарт языка позволяет явно задать под какой стандарт будет собираться программа. аналог -std= у clang-а.

Во вкладке Отладка можно настроить рабочую директорию и аргументы командной строки.

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

На скриншотах выше вы могли заметить значения вида $(ProjectDir) и пр. Это макросы VS, которые будут раскрываться при сборке программы. Во что раскрываются макросы можно посмотреть при редактированнии полей:

Попробуем собрать и запустить нашу программу в Release x64 и Debug x86.

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

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

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

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

Если укажем preprocessor defines, то картина изменится:

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

Укажем include directories:

В разделе C/C++/general помимо include directories можно изменить ключ компиляции warning levels для отлавливания большего числа предупреждений и нужно отключить SDL checks для наступления счастья.

Теперь при попытке сборки ожидаемо будут возникать ошибки линковки:

error LNK
Build started at 16:33...
1>------ Build started: Project: OPENCV_VSProject, Configuration: Release x64 ------
1>main.c
1>main.obj : error LNK2001: unresolved external symbol cvWaitKey
1>main.obj : error LNK2001: unresolved external symbol cvReleaseImage
1>main.obj : error LNK2001: unresolved external symbol cvLoadImage
1>main.obj : error LNK2001: unresolved external symbol cvDestroyWindow
1>main.obj : error LNK2001: unresolved external symbol cvShowImage
1>main.obj : error LNK2001: unresolved external symbol cvNamedWindow
1>..\OPENCV_VSProject\x64\Release\OPENCV_VSProject.exe : fatal error LNK1120: 6 unresolved externals
1>Done building project "OPENCV_VSProject.vcxproj" -- FAILED.
========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========
========== Build completed at 16:33 and took 00.281 seconds ==========

Которые исправим указанием файлов статических бибилотек и директорий, где они расположены. Обратите внимание, что здесь мы явно указываем платформу x64, т.к. собрана библиотека у нас только в этом варианте; а при задании файла мы не забудем для сборки в debug указать debug-версию библиотеки, а для release – release-файл.

Теперь сборка будет проходить успешно:

build succeeded
Build started at 16:38...
1>------ Build started: Project: OPENCV_VSProject, Configuration: Release x64 ------
1>Generating code
1>1 of 5 functions (20.0%) were compiled, the rest were copied from previous compilation.
1>  0 functions were new in current compilation
1>  0 functions had inline decision re-evaluated but remain unchanged
1>Finished generating code
1>OPENCV_VSProject.vcxproj -> ..\OPENCV_VSProject\x64\Release\OPENCV_VSProject.exe
========== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped ==========
========== Build completed at 16:38 and took 00.311 seconds ==========

А для корректного старта необходимо в корень проекта или к exe-файлам подложить dll файлы (подсказка: в корень проекта проще).

Подключение внешних библиотек в системе сборки CMake (Visual Studio)

Visual Studio 2022 поддерживает CMake и позволяет писать CMake-проекты.

Также помимо запуска под Windows есть возможность настроить запуск под WSL (есл установлен) или же настроить запуск с удаленного хоста.

По умолчанию создано несколько стандартных конфигураций сборки программы:

По умолчанию все проекты CMake в VS создаются как C++-проекты и при необходимости необходимо самостоятельно изменить в свойствах проекта язык с C++ (CXX) на C.

Свойства проекта представлены в виде CMakeLists.txt файла:

# CMakeList.txt : CMake project for OPENCV_CmakeProject, include source and define
# project specific logic here.
#
cmake_minimum_required (VERSION 3.8)

# Enable Hot Reload for MSVC compilers if supported.
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()

add_executable(OPENCV_CmakeProject OPENCV_CmakeProject.c)

Укажем явно, что мы хотим собирать проект под C17 и, например, что поддержка этого стандарта нам необходима для сборки (более ранние версии не подойдут):

set(CMAKE_C_STANDART 17)
set(CMAKE_C_STANDART_REQUIRED True)

Дополним его необходимыми сведениями про OpenCV.

  1. Заведём для примера переменную, содержащую все файлы проекта, которые будут подаваться на компиляцию

set(SOURCES "OPENCV_CmakeProject.c")
  1. Укажем макрос

add_definitions(-DOPENCV)
  1. Дополним ключи компиляции по умолчанию ключом про уровень предупреждений (по умолчанию /W3)

set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /W4")
  1. Укажем include directories

include_directories("../library/opencv/build/include")
  1. Укажем при сборке в x64 library directories

if(CMAKE_SIZEOF_VOID_P EQUAL 8)
	link_directories("../library/opencv/build/x64/vc15/lib")
endif()
  1. Модифицируем аргументы конструкции add_executable

add_executable(OPENCV_CmakeProject ${SOURCES})
  1. Дополним сведения о файлах статических библиотек

if(CMAKE_SIZEOF_VOID_P EQUAL 8)
	if (CMAKE_BUILD_TYPE MATCHES "Debug")
		target_link_libraries(OPENCV_CmakeProject "opencv_world3415d.lib")
	elseif (CMAKE_BUILD_TYPE MATCHES "Release")
		target_link_libraries(OPENCV_CmakeProject "opencv_world3415.lib")
	endif()
endif()

CMAKE_BUILD_TYPE нам известен из файла CMakePresets.json, в котором указаны компилтор и подсистема сборки (по умолчанию Ninja), а также переменные каждой конфигурации, которые можно смело использовать в CMakeLists.txt.

Примечение: при изменении CMakeLists.txt они автоматически сохраняются и сразу происходит регенерация кеша сборки:

Там содержаться логи регенерации с ошибками (если они есть). В противном случае будет CMake generation finished.. Из полезного: Command line:, в которой можно посмотреть параметры, с которым вас по итогу собирают, и Working directory: – рабочая директория проекта (та, где лежит сформированный выходной файл).

Чтобы задать аргументы командной строки необходимо:

  1. Переключить отображение в Solution Explorer на Project View

  1. Перейти в Add Debug Configurations (ПКМ по проекту)

  1. Прописать параметр args в открывшемся launch.json:

{
  "version": "0.2.1",
  "defaults": {},
  "configurations": [
    {
      "type": "default",
      "project": "CMakeLists.txt",
      "projectTarget": "OPENCV_CmakeProject.exe",
      "name": "OPENCV_CmakeProject.exe",
      "args": [
        "../../../../chat.png"
      ]
    }
  ]
}

После проделанных манипуляций при указании на верхней панели конфигураций x64 Release и x64 Debug будет собираться программа, запускаться с переданным аргументом и показываться картинка на экране, а приx86 Release и x86 Debug – ошибка линковки.

Подключение внешних библиотек в системе сборки CMake (CLion)

Сам файл CMakeLists.txt почти ничем не будет отличаться, за исключением:

  • cmake_minimum_required (VERSION 3.8) вероятно будет боьшей версии

  • # Enable Hot Reload for MSVC compilers if supported. и связанный с этим комментарием if будет отсутствовать.

  • флаги компиляции следует указывать те, которые нужны для используемого toolchain – если toolchain не VS, то /W4 можно заменить на -Wall -Wextra -Wpedantic

Подробнее про toolchain в CLion: clang-clion

Last updated