Добавление модулей расширения (плагинов) к
программе.
Автор: Tom
Bradley
Перевод: Андрей Киселев
0. Введение
Прошли те времена, когда программы создавались как нечто
законченное, не имеющее возможности для расширения. Сегодня от программ
требуется большая универсальность и возможность расширения. Самый простой способ
увеличения гибкости и расширяемости программы заключается в добавлении поддержки
дополнительных модулей -- плагинов (от англ. plugin, прим. перев.). В качестве
примеров программ с поддержкой дополнительных модулей (плагинов) можно назвать
WEB-браузеры и медиапроигрыватели. В браузерах плагины обеспечивают поддержку
Java, Flash и QuickTime, внедренных в WEB-страницы. В медиапроигрывателях, таких
как XMMS, с помощью плагинов выполняется поддержка воспроизведения файлов
различных форматов, визуальных эффектов и т.д.. Цель этой статьи -- расказать о
том, как организовать поддержку сменных модулей -- плагинов в ваших программах.
Маленькое замечание: в пределах этой статьи я использую слова "модуль" и
"плагин" как взаимозаменяемые понятия.
1. Работа с плагинами
В распоряжении разработчика имеется библиотека dl
(Dynamic Loader -- Динамический Загрузчик), которая предоставляет всего четыре
функции. Здесь я дам лишь краткое описание этих функций. За более подробной
информацией обращайтесь к справочному руководству -- man.
- dlopen
- Производит загрузку модуля в память.
- dlclose
- Выгружает модуль из памяти.
- dlsym
- Возвращает адрес искомой функции в модуле.
- dlerror
- Возвращает сообщение об ошибке, которая могла возникнуть при вызове
dlopen и dlsym
.
2. Пример простой программы с поддержкой плагинов.
Ниже показан код
программы loader, которая принимает название плагина как аргумент командной
строки.
main.c
та же программа
в виде отдельного файла
#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
текст модуля
в виде отдельного файла
int entry()
{
printf("Я - первый модуль!\n");
return 0;
}
|
module2.c
текст модуля
в виде отдельного файла
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
Команда переводчиков:
Владимир Меренков, Александр Михайлов, Иван
Песин, Сергей Скороходов, Александр Саввин, Роман Шумихин, Александр Куприн,
Андрей Киселев
Со всеми предложениями, идеями и комментариями обращайтесь к Александру
Куприну ([email protected]). Убедительная просьба:
указывайте сразу, не возражаете ли Вы против публикации Ваших отзывов в
рассылке.
Сайт рассылки: http://gazette.linux.ru.net
Эту статью
можно взять здесь: http://gazette.linux.ru.net/lg86/bradley.html