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

Как добавить библиотеку в c

  • автор:

Как написать свою библиотеку на Си

Если ты встал на путь С/С++ разработчика, то скорее всего (помимо использования стандартной библиотеки — libc) рано или поздно вам потребуется занятся разработкой собственных библиотек. Зачем. Причин может быть несколько. Например вы написали свою структуру данных или свой алгоритм, и хотите использовать его повторно или распространять. Так же возможно вы написали несколько утилит и все они используют один и тот же кусок кода (например, как часто это бывает, логгер), и будет логично вынести этот кусок кода в отдельный модуль. Поскольку сопровождать такой код будет проще.

Реализация

И так, давайте начнем с примера. Создадим заголовочный файл somecode.h, который будет содержать объявление некоторой функции. Пусть будет простая функция которая разбивает предложение на слова и печатает каждое слово в новой строке. Простой синтетический пример.

1 2 3 4 5 6
#ifndef __SOMECODE__ #define __SOMECODE__ void print_split(char* str); #endif 

И создадим файл somecode.c, в которой напишем реализацию нашей функции.

1 2 3 4 5 6 7 8 9 10 11 12
#include "somecode.h" #include  #include void print_split(char* str)  const char *word = strtok(str, " "); while (word != NULL)  printf("> %s\n", word); word = strtok(NULL, " "); > > 

Далее создадим файл main.c, где будем вызывать нашу функцию.

1 2 3 4 5 6 7 8 9 10
#include  #include "somecode.h" int main(int argc, char** argv)  if (argc > 1)  print_split(argv[1]); > > 

Давайте для начала скомпилируем это все самым обычным способом для проверки работоспособности.

  • из исходных файлов получаем объектные файлы
  • из объектных файлов получаем исполняемый файл
1 2 3 4 5 6 7 8 9 10 11 12
$ gcc -c -Wall -g -o somecode.o somecode.c $ gcc -c -Wall -g -o main.o main.c $ gcc -Wall -g -o a.out.1 main.o somecode.o $ ls -l $ ls -l total 40 -rwxr-xr-x 1 ainr ainr 19816 Aug 9 21:22 a.out.1 -rw-r--r-- 1 ainr ainr 123 Aug 9 21:20 main.c -rw-r--r-- 1 ainr ainr 3576 Aug 9 21:22 main.o -rw-r--r-- 1 ainr ainr 213 Aug 9 21:21 somecode.c -rw-r--r-- 1 ainr ainr 72 Aug 9 21:20 somecode.h -rw-r--r-- 1 ainr ainr 6224 Aug 9 21:21 somecode.o

Запускаем исполняемый файл и видим, что все работает.

1 2 3 4 5
$ ./a.out.1 "hello my little pony" > hello > my > little > pony

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

$ gcc -Wall -g -shared -fpic -o libsomecode.so somecode.c

Из файла с расширением .c мы получаем файл с расширением .so. И так же обратите внимание, что библиотека имеет префикс lib. Еще мы видим, что появились два дополнительных аргумента -shared и -fpic. С помощью опции -shared мы говорим компилятору, что хотим получить а выходе библиотеку. А опция -fpic говорит компилятору, что объектные файлы должны содержать позиционно-независимый код (position independent code), который рекомендуется использовать для динамических библиотек.

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

1 2 3
$ gcc -Wall -g -o a.out.2 main.c -lsomecode /usr/bin/ld: невозможно найти -lsomecode collect2: error: ld returned 1 exit status

И опс.. мы получили ошибку… Линкер говорит нам, что он не знает где лежит наша библиотека. С помощью опции -L указываем текущую директорию, где лежит наша библиотека и компиляция проходит успешно.

$ gcc -Wall -g -o a.out.2 main.c -lsomecode -L. 

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

$ ./a.out.2 "hello my little pony" ./a.out.2: error while loading shared libraries: libsomecode.so: cannot open shared object file: No such file or directory

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

1 2 3 4 5 6 7 8 9 10 11 12 13
$ ld --verbose | grep SEARCH_DIR | sed "s/\;\ /\n/g" SEARCH_DIR("=/usr/local/lib/x86_64-linux-gnu") SEARCH_DIR("=/lib/x86_64-linux-gnu") SEARCH_DIR("=/usr/lib/x86_64-linux-gnu") SEARCH_DIR("=/usr/lib/x86_64-linux-gnu64") SEARCH_DIR("=/usr/local/lib64") SEARCH_DIR("=/lib64") SEARCH_DIR("=/usr/lib64") SEARCH_DIR("=/usr/local/lib") SEARCH_DIR("=/lib") SEARCH_DIR("=/usr/lib") SEARCH_DIR("=/usr/x86_64-linux-gnu/lib64") SEARCH_DIR("=/usr/x86_64-linux-gnu/lib"); 

Так же есть возможность задавать дополнительные директории с библиотеками с помощью переменной окружения LD_LIBRARY_PATH. С помощью утилиты ldd посмотрим от каких библиотек зависит наша программа.

1 2 3 4 5
$ ldd a.out.2 linux-vdso.so.1 (0x00007ffffe4e7000) libsomecode.so => not found libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fcf3b680000) /lib64/ld-linux-x86-64.so.2 (0x00007fcf3b897000) 

Добавим в LD_LIBRARY_PATH текущую директорию.

$ export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$PWD" 

Видим, что наша библиотека подгрузилась.

1 2 3 4 5
ldd a.out.2 linux-vdso.so.1 (0x00007fffe2115000) libsomecode.so (0x00007f4848a6c000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f4848860000) /lib64/ld-linux-x86-64.so.2 (0x00007f4848a76000) 

И теперь программа запускается и работает.

1 2 3 4 5
$ ./a.out.2 "hello my little pony" > hello > my > little > pony

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

1 2 3 4 5 6 7 8 9 10
$ ls -l total 92 -rwxr-xr-x 1 ainr ainr 19816 Aug 9 21:22 a.out.1 -rwxr-xr-x 1 ainr ainr 17864 Aug 9 21:25 a.out.2 -rwxr-xr-x 1 ainr ainr 18808 Aug 9 21:24 libsomecode.so -rw-r--r-- 1 ainr ainr 123 Aug 9 21:20 main.c -rw-r--r-- 1 ainr ainr 3576 Aug 9 21:23 main.o -rw-r--r-- 1 ainr ainr 213 Aug 9 21:21 somecode.c -rw-r--r-- 1 ainr ainr 72 Aug 9 21:20 somecode.h -rw-r--r-- 1 ainr ainr 6224 Aug 9 21:23 somecode.o

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

1 2 3 4 5 6
$ objdump -t a.out.1 | grep print_split 000000000000119c g F .text 0000000000000061 print_split $ objdump -t a.out.2 | grep print_split 0000000000000000 F *UND* 0000000000000000 print_split $ objdump -t libsomecode.so | grep print_split 0000000000001139 g F .text 0000000000000061 print_split

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

Так что же мы сделали?

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

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
-------------------------------- --------------------------------- | Заголовочный файл | | Реализация | | (somecode.h) | | (somecode.c) | -------------------------------- --------------------------------- | | | | | void print_split(char* string);| | void print_split(char* string) | | | | < | | | | // . | | | | >| | | | | ------------------------------- --------------------------------- | | Компилируем \ / (gcc -shared -fpic -o liblog.so . ) | --------------------------------- | Динамическая библиотека | | (liblog.so) | --------------------------------- | | (gcc . -llog) --------------------------------------------- | | | ----------------------- ----------------------- ----------------------- | Утилита1 | | Утилита2 | | Утилита3 | ----------------------- ----------------------- ----------------------- | | | | | | | #include "somecode.h" | | #include "somecode.h" | | #include "somecode.h" | | | | | | | | print_split(". "); | | print_split(". "); | | print_split(". "); | | | | | | | ----------------------- ----------------------- -----------------------

Примерно те же действия вы будете делать и под windows и под мак, будут немного другие компиляторы, но идея одна.

Как подключить библиотеку в проекте на С++

При использование Visual Studio: самый простой — в любом файле добавить запись:

#pragma comment(lib, "") 

Как альтернатива, можно указать lib-файл в свойствах проекта, для этого перейдите к пункту:

  1. Linker → General → Additional Library Directories — указать каталог с lib-файлов.(напр. D:\ace\lib)
  2. Linker → Input → Additional Dependencies — указать само название lib файла (напр. ace_vc11.lib)

Так же в C/C++ → General → Additional Include Directories можно указать путь к *.h файлам, чтоб в своих исходниках не прописывать полный путь на диске.

Update:

Если библиотека из себя представляет только h-файл(такое возможно), тогда достаточно просто написать:

#include "" 

и далее пользоваться предоставленным функционалом.

Но в основном библиотека представляет из себя *.lib -файл и *.h -файлы, необходимые для сборки своего приложения а также непосредственно *.dll -файл, необходимый для запуска приложения.

Подключение и использование библиотек в Visual Studio

В качестве примера мы рассмотрим подключение библиотеки SDL к нашему проекту в Visual Studio 2017 (работать будет и с более новыми версиями Visual Studio).

Оглавление:

  • Шаг №1: Создаем папку для хранения библиотеки
  • Шаг №2: Скачиваем и устанавливаем библиотеку
  • Шаг №3: Указываем путь к заголовочным файлам библиотеки
  • Шаг №4: Указываем путь к файлам с реализацией библиотеки
  • Шаг №5: Копируем dll-ку в папку с проектом
  • Шаг №6: Тестируем

Шаг №1: Создаем папку для хранения библиотеки

Создаем папку Libs на диске C ( C:\Libs ).

Шаг №2: Скачиваем и устанавливаем библиотеку

Заходим на сайт https://www.libsdl.org/download-2.0.php, пролистываем вниз до «Development Libraries» и скачиваем SDL2-devel-2.0.9-VC.zip (Visual C++ 32/64-bit). После успешного скачивания нужно разархивировать этот архив в папку Libs .

Шаг №3: Указываем путь к заголовочным файлам библиотеки

Открываем свой любой проект в Visual Studio или создаем новый, переходим в «Обозреватель решений» > кликаем правой кнопкой мыши (ПКМ) по названию нашего проекта > «Свойства» :

В «Свойства конфигурации» ищем вкладку «С/С++» > «Общие» . Затем выбираем пункт «Дополнительные каталоги включаемых файлов» > нажимаем на стрелочку в конце > «Изменить» :

В появившемся окне кликаем на иконку с изображением папки, а затем на появившееся троеточие:

Заголовочные файлы находятся в папке include внутри нашей библиотеки, поэтому переходим в нее ( C:\Libs\SDL2-2.0.9\include ) и нажимаем «Выбор папки» , а затем «ОК» :

Шаг №4: Указываем путь к файлам с реализацией библиотеки

Переходим на вкладку «Компоновщик» > «Общие» . Ищем пункт «Дополнительные каталоги библиотек» > нажимаем на стрелочку в конце > «Изменить» :

Опять же, нажимаем на иконку с папкой, а затем на появившееся троеточие. Нам нужно указать следующий путь: C:\Libs\SDL2-2.0.9\lib\x86 . Будьте внимательны, в папке lib находятся две папки: x64 и x86 . Даже если у вас Windows разрядности x64, указывать нужно папку x86 . Затем «Выбор папки» и «ОК» :

После этого переходим в «Компоновщик» > «Ввод» . Затем «Дополнительные зависимости» > нажимаем на стрелочку в конце > «Изменить» :

В появившемся текстовом блоке вставляем:

Затем переходим в «Компоновщик» > «Система» . После этого «Подсистема» > нажимаем на стрелочку вниз > выбираем «Консоль (/SUBSYSTEM:CONSOLE)» > «Применить» > «ОК» :

Шаг №5: Копируем dll-ку в папку с проектом

Переходим в папку x86 ( C:\Libs\SDL2-2.0.9\lib\x86 ), копируем SDL2.dll и вставляем в папку с вашим проектом в Visual Studio. Чтобы просмотреть папку вашего проекта в Visual Studio, нажмите ПКМ по названию вашего проекта > «Открыть содержащую папку» :

Затем вставляем скопированный файл (SDL2.dll) в папку с проектом (где находится рабочий файл .cpp):

Шаг №6: Тестируем

Теперь, чтобы проверить, всё ли верно мы сделали — копируем и запускаем следующий код:

int main ( int argc , char * argv [ ] )
if ( SDL_Init ( SDL_INIT_EVERYTHING ) < 0 ) std :: cout << "SDL initialization failed. SDL Error: " << SDL_GetError ( ) ; std :: cout << "SDL initialization succeeded!" ; std :: cin . get ( ) ;

Если результат следующий:

SDL initialization succeeded!

Значит мы успешно подключили библиотеку SDL к нашему проекту!

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

(137 оценок, среднее: 4,70 из 5)

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

C++11. Нововведения

Комментариев: 13

Спустя время вернулся чтобы порекомендовать ЛУЧШИЙ метод подключения сторонник библиотек в Visual Studio. Рассмотрим на примере весьма полезной библиотеки длинной арифметики gmp, а точнее ее брата близнеца для windows — mpir! Установка vcpkg Для установки любой библиотеки, которую создала более-менее приличная компания вам понадобится только vcpkg. Для установки vcpkg открываем Visual Studio > Git > Клонировать репозиторий . В расположение репозитория указываем https://github.com/microsoft/vcpkg Путь указываем на свое усмотрение, у меня это C:\vcpkg Нажимаем «Клонировать» и ожидаем завершения процесса. Получаем сообщение о том, что один или несколько объектов небыли загружены, ничего страшного. Можно закрыть Visual Studio. Открываем командную строку, для тех кто не знает как это сделать: нажимаем Win+R и в окошке «Выполнить» пишем: cmd В командной строке: cd C:\vcpkg (или путь, который вы указали для vcpkg в Visual Studio). В итоге рабочая папка командной строки должна измениться на путь к вашей vcpkg. (в данном случае C:\vcpkg ). В командной строке пишем: bootstrap-vcpkg.bat — Ожидаем окончания процесса. В командной строке: vcpkg integrate install — Ожидаем окончания процесса. На этом установка vcpkg завершена, теперь мы можем его использовать для установки любой библиотеки (о которой он знает, конечно же, а таких бесконечно много). Установка библиотек Теперь установка самой библиотеки. Открываем Visual Studio, консольный проект и пишем #include , например в моем случае: #include , такой библиотеки в моем Visual Studio еще нет, но vcpkg сразу предлагает ее установить. Возле вашего #include появляется помощник vcpkg и предлагает скопировать команду для установки библиотеки в буфер обмена, нажимаем ее. Открываем командную строку, переходим в рабочую папку vcpkg: ( Win+R , в окошке «Выполнить»: cmd , в командной строке: cd C:\vcpkg или путь к вашему vcpkg) Теперь, когда мы в командной строке находимся в рабочей папке с vcpkg вводим команду из буфера обмена, которую мы получили в Visual Studio, в моем случае для библиотеки GMP она выглядит так: vcpkg install mpir:x64-windows Ожидаем окончания процесса (может занять несколько минут). Открываем Visual Studio, можем использовать #include Прелесть метода заключается в том, что вам не нужно линковать библиотеку или делать какие либо настройки вашего проекта кроме непосредственного подключения библиотеки с помощью #include
Это касается всех проектов и всех библиотек, которые знает vcpkg. Использовал Visual Studio Enterprise 2022.

Сработало только с версией х64, видимо для тех у кого 64-битная IDE нужно делать так же.

Юрий! Статья несомненно хорошая, но не полная и с недочётом.
Недочёт в следующем: не учтён конфигуратор решений. Если в конфигурации будет «Debug», а в изменяемых свойствах стоять «Relase», то всё сделаешь правильно, а файл с библиотекой найден не будет.
Кроме того, хоть пару слов желательно было бы услышать о том, зачем выполнять тот или иной пункт, так как тогда легче было бы повторить всё это в другой ситуации. Но, всё равно, большое спасибо за статью, помогла!

FAQ: Как подключить библиотеку на C/C++? Зачем файлы .h, .lib, .dll?

Этот вопрос довольно часто возникает у начинающих, а также у программистов, которые привыкли работать с языками, имеющими встроенную поддержку импорта из модулей (например, Delphi/Pascal).

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

  1. Включить в исходный текст заголовочные файлы библиотеки (.h или .hpp) директивой #include
  2. Обеспечить, чтобы при сборке программы использовались соответствующие объектные файлы библиотеки (в зависимости от системы они могут иметь расширения .lib, .a, .o, .obj и т.д.) В зависимости от используемого компилятора это делается разными способами, например:
    – добавить файл(ы) в проект как объектные;
    – в MS Visual Studio: добавить имя файла в Linker->Input->Additional Dependencies (если файл в другом каталоге, путь добавить в Linker->General->Additional Library Directories);
    – при использовании make прописать файл в список файлов для линкера (обычно это LIBS= или т.п.).
    А если библиотека представлена в исходных текстах, надо просто добавить все нужные .c (.cpp) файлы в проект программы.

Файлы .lib должны быть совместимы с используемым компилятором и ОС. Поэтому к библиотеке может прилагаться несколько наборов файлов для разных сред.

Файлы .dll (динамическая библиотека Windows) не нужно указывать при компиляции, они используются при выполнении программы (о динамических библиотеках см. ниже).

В чём смысл этих действий, зачем нужны все эти разные файлы?

Файл заголовков (.h) нужен для того, чтобы компилятор мог распознать идентификаторы (имена макросов, типов, переменных, функций), которые определены в библиотеке. Специального средства для импорта имён в стандартном C нет, и для этого используется директива препроцессора #include, которая вставляет включаемый файл как исходный текст, как если бы он был написан вместо директивы #include.

В файлах .h идентификаторы объявляются, но не определяются (decalared but not defined), т.е. под переменные не выделяется пространство, а функции не имеют кода. Это достигается использованием ключевого слова extern для переменных, а для функций даются прототипы, например:

/* mylib.h */
extern int mylib_global_variable;
int mylib_function(int x, int y);

Если эти объявления включить через #include, то компилятор сможет скомпилировать .c файл, в котором упоминаются mylib_global_variable и mylib_function, например:

/* myprog.c */
#include «mylib.h»
int main(void)
mylib_global_variable = 1;
return mylib_function(mylib_global_variable, 2);
>

После компиляции получится объектный файл (например, myprog.o), причем в нём эти идентификаторы будут описаны как внешние. Но линкер не сможет собрать такую программу в готовый исполнимый файл, потому что для этих идентификаторов нет определений, т.е. есть имена, но нет «тела».
При попытке собрать программу будет выдано сообщение об ошибке «undefined external».

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

/* mylib.c */
int mylib_global_variable = 0;
int mylib_function(int x, int y)
return x + y;
>

Тогда при его компиляции получится объектный файл, который можно соединить с нашей основной программой (включить оба .c файла в проект или makefile), и всё готово.

Если же библиотека уже скомпилирована отдельно, то нужно взять готовые объектные файлы (.lib или .a – это по сути контейнеры объектных файлов) и передать их линкеру.

Это в общих чертах всё, что касается статических библиотек, т.е. таких, код которых попадает в исполнимый файл основной программы.

О динамических библиотеках следует сказать отдельно. Динамическая библиотека отличается тем, что её код хранится в отдельном файле (Windows – .dll, в системах типа GNU/Linux и FreeBSD – .so) и загружается операционной системой при запуске программы, либо самой программой по мере надобности.

Рассмотрим оба способа на примере Windows.

Чтобы подключить динамическую библиотеку с загрузкой при запуске, используются файлы .h и .lib – точно так же, как в случае статической библиотеки. Просто в данном случае .lib файл содержит не сам код, а ссылки на импортируемые функции из DLL, так что после компиляции получается .exe файл, в котором есть ссылки на нужную DLL. Файл .dll при компиляции не нужен, зато нужен при запуске (в каталоге с .exe файлом, в системных каталогах Windows, в каталогах, перечисленных в переменной окружения PATH и т.д.).

Также можно загружать динамическую библиотеку «вручную» функциями LoadLibrary и GetProcAddress. Тогда .lib файл не нужен вообще, а .h включить обычно нужно (чтобы иметь описания типов и констант-макросов), но к функциям обращаться нужно будет не по именам из .h, а через указатели соответствующих типов, например:
/* . */
typedef int (*my_funct_ptr_t)(int, int); /* тип указателя на функцию */
HINSTANCE h_dll;
my_func_ptr_t func;
int x = 1, y = 2, z;
h_dll = LoadLibrary(«mylib.dll»);
if (NULL == h_dll) return MY_ERROR_CANNOT_LOAD_DLL;
func = (my_func_ptr_t)GetProcAddress(h_dll, «mylib_function»);
if (NULL == func) return MY_ERROR_FUNCTION_NOT_FOUND;
z = func(x, y);
/* . */

Перейти к другим статьям FAQ

Cтатья создана: 14.08.2014
Последняя редакция: 04.08.2016

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

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