Ключевые слова:lib, gcc, (найти похожие документы)
From: Андрей Киселев <[email protected]>
Newsgroups: gazette.linux.ru.net
Subject: Добавление модулей расширения (плагинов) к программе.
Оригинал: http://gazette.linux.ru.net/lg84/bradley.html
Автор: Tom Bradley <http://gazette.linux.ru.net/authors/bradley.html>
Перевод: Андрей Киселев <[email protected]>
_________________________________________________________________
0. Введение
Прошли те времена, когда программы создавались как нечто законченное,
не имеющее возможности для расширения. Сегодня от программ требуется
большая универсальность и возможность расширения. Самый простой способ
увеличения гибкости и расширяемости программы заключается в добавлении
поддержки дополнительных модулей -- плагинов (от англ. plugin, прим.
перев.). В качестве примеров программ с поддержкой дополнительных
модулей (плагинов) можно назвать WEB-браузеры и медиапроигрыватели. В
браузерах плагины обеспечивают поддержку Java, Flash и QuickTime,
внедренных в WEB-страницы. В медиапроигрывателях, таких как XMMS, с
помощью плагинов выполняется поддержка воспроизведения файлов
различных форматов, визуальных эффектов и т.д.. Цель этой статьи --
расказать о том, как организовать поддержку сменных модулей --
плагинов в ваших программах. Маленькое замечание: в пределах этой
статьи я использую слова "модуль" и "плагин" как взаимозаменяемые
понятия.
1. Работа с плагинами
В распоряжении разработчика имеется библиотека dl (Dynamic Loader --
Динамический Загрузчик), которая предоставляет всего четыре функции.
Здесь я дам лишь краткое описание этих функций. За более подробной
информацией обращайтесь к справочному руководству -- man.
dlopen
Производит загрузку модуля в память.
dlclose
Выгружает модуль из памяти.
dlsym
Возвращает адрес искомой функции в модуле.
dlerror
Возвращает сообщение об ошибке, которая могла возникнуть при
вызове dlopen и dlsym[DEL: . :DEL]
2. Пример простой программы с поддержкой плагинов.
Ниже показан код программы loader, которая принимает название плагина
как аргумент командной строки.
main.c (та же программа в виде отдельного файла http://gazette.linux.ru.net/lg84/misc/bradley/main.c.txt)
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <dlfcn.h>
#define PATH_LENGTH 256
int main(int argc, char * argv[])
{
char path[PATH_LENGTH], * msg = NULL;
int (*my_entry)();
void * module;
/* сборка имени модуля и полного пути к нему в одну строку */
getcwd(path, PATH_LENGTH);
strcat(path, "/");
strcat(path, argv[1]);
/* загрузка модуля и разрешение имен перед возвратом из dlopen */
module = dlopen(path, RTLD_NOW);
if(!module) {
msg = dlerror();
if(msg != NULL) {
dlclose(module);
exit(1);
}
}
/* попытка получить адрес функции "entry" */
my_entry = dlsym(module, "entry");
msg = dlerror();
if(msg != NULL) {
perror(msg);
dlclose(module);
exit(1);
}
/* вызов функции "entry" в модуле */
my_entry();
/* close module */
if(dlclose(module)) {
perror("error");
exit(1);
}
return 0;
}
Этот пример достаточно прост. После загрузки модуля, функция dlsym, по
таблице имен модуля, отыскивает адрес функции "entry" в модуле. Адрес
функции запоминается в локальной переменной, после чего эта функция
вызвается на исполнение. Затем модуль выгружается из памяти.
Объявление указателя на функцию, возможно нуждается в дополнительном
пояснении.
int (*my_entry)()
объявляет указатель на функцию, не имеющую входных параметров и
возвращающую результат типа int. В данном примере в указателе
запоминается адрес функции "entry" в модуле:
int entry()
Сборка программы выполняется командой:
$ gcc -o loader main.c -ldl
3. Два простых модуля расширения (плагина)
Теперь, когда у нас уже есть программа, поддерживающая модули
расширения, можно создать несколько плагинов. Нет никаких ограничений,
накладываемых на функции в модуле. В своем примере я объявляю функции,
не имеющие входных параметров, и возвращающие результат типа int. Вы
можете объявлять свои функции со своим набором входных параметров и
возвращаемым значением, требуемого вам типа. Совсем не обязательно
давать функциям имена "entry". Я использую это имя лишь для простоты
восприятия. Кроме того, в модуль может быть включено значительно
большее число функций. Ниже приведен пример исходных текстов двух
простых модулей, в каждом из которых определена функция с именем
"entry":
module1.c (текст модуля в виде отдельного файла http://gazette.linux.ru.net/lg84/misc/bradley/module1.c.txt)
int entry()
{
printf("Я - первый модуль!\n");
return 0;
}
module2.c (текст модуля в виде отдельного файла http://gazette.linux.ru.net/lg84/misc/bradley/module2.c.txt)
int entry()
{
printf("Я - второй модуль!\n");
return 0;
}
Компиляция модулей:
$ gcc -fPIC -c module1.c
$ gcc -shared -o module1.so module1.o
$ gcc -fPIC -c module2.c
$ gcc -shared -o module2.so module2.o
Несколько замечаний по компиляции. Во-первых, флаг `-fPIC' ("Position
Independent Code") сообщает компилятору о необходимости относительной
(от англ. relative) адресации. Это означает, что скомпилированный код
может быть размещен в любой области памяти, а загрузчик сам
"побеспокоится" об адресах во время загрузки модуля. Во-вторых, флаг
`-shared' (общедоступный, разделяемый) говорит компилятору о том, что
этот код должен быть собран таким образом, чтобы было возможно связать
его с любым другим исполняемым кодом. Другими словами .so - файлы
(shared object) ведут себя подобно библиотекам, только не могут быть
связаны с программой с помощью ключа компиляции `-l' (да простит меня
читатель за подобное сравнение, но *.so файлы очень напомнают мне
динамически загружаемые библиотеки *.dll в операционной системе MS
Windows. прим. перев.).
4. Запуск программы Loader
Ниже показан пример запуска нашей программы loader и результат ее
выполнения:
$ ./loader module1.so
Я - первый модуль!
$ ./loader module2.so
Я - второй модуль!
5. Функции инициализации и финализации плагина
Содержание этого раздела предполагает использование специфических
особенностей компилятора gcc. Если вы используете другой компилятор,
то вам следует обратиться к документации за разрешением проблем
совместимости.
Ключевое слово `__attribute__' позволяет определить массу полезных
атрибутов для функций, однако я остановлюсь только на двух из них --
`constructor' и `destructor'. За дополнительной информацией по
атрибутам обращайтесь к info gcc. Формат ELF (Executable and Linkable
Format -- формат исполняемых и связываемых модулей) предполагает
наличие двух секций -- .init и .fini, в которых может содержаться код,
исполняемый до и после загрузки модуля (для обычных программ это
означает -- "до и после исполнения функции main()"). Код, размещаемый
в этих секциях может выполнять действия по инициализации переменных
модуля, выделению/освобождению ресурсов и пр.. Например, модуль может
иметь ряд переменных, определяющих правила взаимодействия с
программой, значения которых считываются из главной программы сразу
после загрузки модуля. Эти переменные могут содержать точки входа
(команды), которые поддерживаются плагином. В моем примере модули
имеют лишь по одной точке входа -- функции "entry", вы можете
определить большее количество функций. Ниже приведен пример
использования атрибутов:
__attribute__ ((constructor)) void init()
{
/* этот код вызывается сразу после загрузки модуля функцией dlopen() */
}
__attribute__ ((destructor)) void fini()
{
/* этот код вызывает непосредственно перед выгрузкой модуля функцией dlclose(
) */
}
Имена init() и fini() не являются обязательными, я использую их лишь
для большей ясности понимания назначения этих функций. Однако имеется
ряд имен, зарезервированных gcc. Вот некоторые из них -- _init, _fini,
_start и _end. Полный список имен функций и переменных в модуле можно
посмотреть с помощью утилиты nm. Атрибуты `constructor' и `destructor'
сообщают компилятору о том, что этот код должен располагаться в
секциях .init и .fini соответственно.
6. Заключение
Библиотека dl делает поддержку сменных модулей - плагинов в программе
достаточно простой задачей. Приведенный здесь пример демонстрирует
возможность импорта единственной функции из плагина, но он может быть
легко распространен на случай значительно большего количества функций
и обращения к ним так, как будто они являются частью превоначальной
программы.
Copyright (C) 2002, Tom Bradley. Copying license http://www.linuxgazette.com/copying.html
Published in Issue 84 of Linux Gazette, November 2002
Процесс сборки тропы (path) до библиотеки подвержен переполнению буфера пользовательскими данными (необходимо использовать strlcat или strncat).
Не то чтобы это было бы актуально в примере ...