The OpenNET Project / Index page

[ новости/++ | форум | wiki | теги ]

Добавление модулей расширения (плагинов) к программе. (lib gcc)


<< Предыдущая ИНДЕКС Поиск в статьях src Установить закладку Перейти на закладку Следующая >>
Ключевые слова: lib, gcc,  (найти похожие документы)
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) #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

<< Предыдущая ИНДЕКС Поиск в статьях src Установить закладку Перейти на закладку Следующая >>

Обсуждение [ RSS ]
 
  • 1, Илья, 09:33, 05/07/2011 [ответить] [смотреть все]
  • +/
    Процесс сборки тропы (path) до библиотеки подвержен переполнению буфера пользовательскими данными (необходимо использовать strlcat или strncat).
    Не то чтобы это было бы актуально в примере ...
     

    Ваш комментарий
    Имя:         
    E-Mail:      
    Заголовок:
    Текст:





      Закладки на сайте
      Проследить за страницей
    Created 1996-2017 by Maxim Chirkov  
    ДобавитьРекламаВебмастеруГИД  
    Hosting by Ihor