From: Андрей Киселев <kis_an@mail.ru>
Newsgroups: gazette.linux.ru.net
Subject: Добавление модулей расширения (плагинов) к программе.
Оригинал: http://gazette.linux.ru.net/lg84/bradley.html
Автор: Tom Bradley <http://gazette.linux.ru.net/authors/bradley.html>
Перевод: Андрей Киселев <kis_an@mail.ru>
_________________________________________________________________
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)
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;
}
Несколько замечаний по компиляции. Во-первых, флаг `-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
779 Прочтений • [Добавление модулей расширения (плагинов) к программе. (lib gcc)] [08.05.2012] [Комментариев: 0]