Итак, в прошлой теме мы рассмотрели динамическую линковку с общей (shared) библиотекой (объектом). Чтобы уже добить эту тему, осталось рассмотреть вариант динамической загрузки библиотек.
Библиотеки Linux (по типу использования):
- Статические
- Общие (shared)
- Динамическая линковка (компановка)
- Динамическая загрузка
Динамическая загрузка библиотеки Си
Для реализации динамической загрузки библиотеки будем использовать интерфейс динамической загрузки (Dynamic Loading API). Имеется стандарт на реализацию для POSIX-совместимых систем, а также есть отдельные системо-специфичные возможности. Мы будем придерживать функций, описанных для POSIX.
Интерфейс динамической загрузки даёт приложению пользователя возможность использовать совместно используемые библиотеки. Функций будет не много, но этого вполне хватает для большинства задач.
libdl
– набор функций в системной динамической библиотеке,
при помощи которых можно загрузить произвольную библиотеку в память,
просмотреть какие символы (в том числе функции) есть в этой библиотеке,
а затем вызвать их исполнение.
Не будем изменять традиции, используем нашу библиотеку для работы со связным списком.
Нам понядобятся dlopen()
и dlsym()
из <dlfcn.h>
:
dlopen
- Дает программе доступ к ELF-библиотеке;dlsym
- Возвращает адрес функции из библиотеки, загруженной при помощиdlopen
;dlerror
- Возвращает текстовое описание последней возникшей ошибки;dlclose
- Закрывает доступ к библиотеке.
#include <dlfcn.h>
#include "linked_list.h"
// Помните, что функции - это указатели?
// Так вот, это как раз мы и используем ниже.
void (* print_list)(node_t *head);
void (* add)(node_t * head, char val);
int load_library() {
// Открываем произвольный файл, реализующий интерфейс `linked_list.h`.
// `RTLD_LAZY` - используем ленивый биндинг – подгружаем только символы
// и соответствующие адреса. То есть берём адреса функций, но пока не
// как функции.
void * lib = dlopen("./liblinkedlist.so", RTLD_LAZY);
if (lib == NULL) {
return 1;
}
// Присваиваем адреса нужных символов нашим указателям (функциям)
add = dlsym(lib, "add");
print_list = dlsym(lib, "print_list");
if (add == NULL) {
return 2;
}
if (print_list == NULL) {
return 3;
}
return 0;
}
int main() {
int err = load_library();
if (err) {
printf("Library load error");
return err;
}
// Подгрузили - пользуемся!
char c;
node_t head = { .next = NULL, .val = ' ' };
while ((c = getc(stdin)) != EOF) {
add(&head, c);
}
print_list(&head);
return 0;
}
→ clang main.c
→ ./a.out
Таким образом мы можем реализовать подключаемые плагины для Си-шной программы. Можем подгрузить код, которого даже не было написано на момент запуска нашей программы!