Использование плагинов в ваших программах (Добавление модулей расширения (плагинов))

Полезные советы и программы от пользователей нашего форума.

Модератор: Модераторы разделов

Ответить
Аватара пользователя
svyatogor
Сообщения: 33
Контактная информация:

Использование плагинов в ваших программах

Сообщение svyatogor »

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


Оригинал: http://gazette.linux.ru.net/lg84/bradley.html
Автор: Tom Bradley <http://gazette.linux.ru.net/authors/bradley.html>
Перевод: Андрей Киселев <kis_an@mail.ru <mailto:kis_an@mail.ru>>
_________________________________________________________________


0. Введение

Прошли те времена, когда программы создавались как нечто законченное,
не имеющее возможности для расширения. Сегодня от программ требуется
большая универсальность и возможность расширения. Самый простой способ
увеличения гибкости и расширяемости программы заключается в добавлении
поддержки дополнительных модулей -- плагинов (от англ. plugin, прим.
перев.). В качестве примеров программ с поддержкой дополнительных
модулей (плагинов) можно назвать WEB-браузеры и медиапроигрыватели. В
браузерах плагины обеспечивают поддержку Java, Flash и QuickTime,
внедренных в WEB-страницы. В медиапроигрывателях, таких как XMMS, с
помощью плагинов выполняется поддержка воспроизведения файлов
различных форматов, визуальных эффектов и т.д.. Цель этой статьи --
расказать о том, как организовать поддержку сменных модулей --
плагинов в ваших программах. Маленькое замечание: в пределах этой
статьи я использую слова "модуль" и "плагин" как взаимозаменяемые
понятия.

1. Работа с плагинами

В распоряжении разработчика имеется библиотека dl (Dynamic Loader --
Динамический Загрузчик), которая предоставляет всего четыре функции.
Здесь я дам лишь краткое описание этих функций. За более подробной
информацией обращайтесь к справочному руководству -- man.

dlopen
Производит загрузку модуля в память.

dlclose
Выгружает модуль из памяти.

dlsym
Возвращает адрес искомой функции в модуле.

dlerror
Возвращает сообщение об ошибке, которая могла возникнуть при
вызове dlopen и dlsym

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 © 2002, Tom Bradley. Copying license http://www.linuxgazette.com/copying.html
Published in Issue 84 of Linux Gazette, November 2002
Fire within me!
Спасибо сказали:
sdk
Бывший модератор
Сообщения: 210

Re: Использование плагинов в ваших программах

Сообщение sdk »

Спасибо!
Весьма поучительно :). Про dl я знал, но никогда не пользовался, а вот __atribute__ это нечто новое.
Серьезность - это способ сделать простые вещи сложными.
Если много знать - устанут глаза. Если много спать - то нет.
Нас никому не сбить с пути - нам пофигу куда идти.
:-)
Спасибо сказали:
Ответить