[LG 84]:Добавление модулей расширения (плагинов) к
программе.
Linux Gazette на русском | Выпуск #100 |
Тираж 7830 экз.
"Используй ...плагины..., Люк!"
Всем привет! Вашему вниманию предлагается статья, описывающая
процесс создания и использования плагинов. Сразу скажу, что
примеры очень просты, но у них есть одно преимущество -- они
работают. :) А это главное.
Спасибо за перевод Андрею Киселёву!
Присылайте свои вопросы и замечания о материалах,
публикуемых на сайте по адресу lgrus@lrn.ru и, если не трудно,
сразу указывайте, можно ли использовать Ваши письма в
рассылке.
Александр Куприн
Добавление модулей расширения (плагинов) к
программе.
Прошли те времена, когда программы создавались как нечто
законченное, не имеющее возможности для расширения. Сегодня от
программ требуется большая универсальность и возможность
расширения. Самый простой способ увеличения гибкости и
расширяемости программы заключается в добавлении поддержки
дополнительных модулей -- плагинов (от англ. plugin, прим. перев.).
В качестве примеров программ с поддержкой дополнительных модулей
(плагинов) можно назвать WEB-браузеры и медиапроигрыватели. В
браузерах плагины обеспечивают поддержку Java, Flash и QuickTime,
внедренных в WEB-страницы. В медиапроигрывателях, таких как XMMS, с
помощью плагинов выполняется поддержка воспроизведения файлов
различных форматов, визуальных эффектов и т.д.. Цель этой статьи --
расказать о том, как организовать поддержку сменных модулей --
плагинов в ваших программах. Маленькое замечание: в пределах этой
статьи я использую слова "модуль" и "плагин"
как взаимозаменяемые понятия.
1. Работа с плагинами
В распоряжении разработчика имеется библиотека dl (Dynamic Loader
-- Динамический Загрузчик), которая предоставляет всего четыре
функции. Здесь я дам лишь краткое описание этих функций. За более
подробной информацией обращайтесь к справочному руководству -- man.
dlopen
Производит загрузку модуля в память.
dlclose
Выгружает модуль из памяти.
dlsym
Возвращает адрес искомой функции в модуле.
dlerror
Возвращает сообщение об ошибке, которая могла возникнуть при
вызове dlopen и
dlsym.
2. Пример простой программы с поддержкой плагинов.
Ниже показан код программы loader, которая принимает название
плагина как аргумент командной строки.
#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":
Несколько замечаний по компиляции. Во-первых, флаг -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 делает поддержку сменных модулей - плагинов в
программе достаточно простой задачей. Приведенный здесь пример
демонстрирует возможность импорта единственной функции из плагина,
но он может быть легко распространен на случай значительно большего
количества функций и обращения к ним так, как будто они являются
частью превоначальной программы.
Команда переводчиков:
Владимир Меренков, Александр Михайлов, Иван Песин, Сергей
Скороходов, Александр Саввин, Роман Шумихин, Александр Куприн,
Андрей Киселев
Со всеми предложениями, идеями и комментариями обращайтесь к
Александру Куприну (lgrus@lrn.ru). Убедительная
просьба: указывайте сразу, не возражаете ли Вы против публикации
Ваших отзывов в рассылке.