FirebirdSQL logo

Написание плагинов

Чтобы написать плагин, нужно реализовать некоторые интерфейсы ипоместить вашу реализацию в динамическую библиотеку (.dll в Windowsили .so в Linux), которую называют модулем плагина или просто модулем.В большинстве случаев одиночный плагин размещается в динамическойбиблиотеке, но не обязательно. Один из этих интерфейсов —IPluginModule — является модульным(как более или менее ясно из его имени), другие отвечают за плагин.Также каждый модуль плагина должен содержать специальнуюэкспортированную точку входа firebird_plugin(), имя которой указано вфайле include/firebird/Interfaces.h как FB_PLUGIN_ENTRY_POINT.

В предыдущей части мы в основном описывали, как использоватьсуществующие интерфейсы, здесь основное внимание будет уделеносамостоятельной реализации интерфейсов. Разумеется, для этого можно инужно использовать уже существующие интерфейсы, общие для доступа кбазам данных Firebird (уже описанные) и некоторые дополнительныеинтерфейсы, специально предназначенные для плагинов.

Далее активно используется пример плагина шифрования базы данныхexamples/dbcrypt/DbCrypt.cpp. Будет хорошей идеей собрать этот примерсамостоятельно и изучить его при чтении позже.

Реализация модуля плагина

Плагины активно взаимодействуют со специальным компонентом Firebird,называемым диспетчером плагинов. В частности, менеджер плагинов должензнать, какие модули плагина были загружены и должен быть уведомлен, еслиоперационная система пытается выгрузить один из этих модулей без явнойкоманды диспетчера плагина (это может произойти прежде всего прииспользовании встроенного сервера (embedded) — когда в программевызывается exit() или основная библиотека Firebird fbclientвыгружается). Основная задача интерфейса IPluginModule — этоуведомление. Прежде всего, нужно решить — как определить, что модульбудет выгружен? Когда динамическая библиотека выгружается по какой-либопричине, выполняется множество зависимых от ОС действий, и некоторые изэтих действий могут использоваться для обнаружения этого факта впрограмме. При написании плагинов, распространяемых вместе с firebird,мы всегда используем вызов деструктора глобальной переменной. Большой«плюс» этого метода заключается в том, что он независим от ОС (хотячто-то вроде функции exit(), возможно, также успешно используется). Ноиспользование деструктора позволяет легко сконцентрировать почти все,что связано с обнаружением выгрузки в одном классе, реализуя в то жевремя интерфейс IPluginModule.

Минимальная реализация выглядит следующим образом:

class PluginModule : public IPluginModuleImpl<PluginModule, CheckStatusWrapper>
{

private:
  IPluginManager* pluginManager;

public:
  PluginModule()
    : pluginManager(NULL)
  { }


  ~PluginModule()
  {
    if (pluginManager)
    {
      pluginManager->unregisterModule(this);
      doClean();
    }
  }

  void registerMe(IPluginManager* m)
  {
    pluginManager = m;
    pluginManager->registerModule(this);
  }

  void doClean()
  {
    pluginManager = NULL;
  }

};

Единственным членом данных является интерфейс диспетчера плагиновIPluginManager. Он передаетсяфункции registerModule() и сохраняется в приватной переменной, в то жевремя модуль регистрируется в диспетчере плагинов методом callModule() ссобственным адресом в качестве единственного параметра. ПеременнаяpluginManager не только сохраняет указатель на интерфейс, ноодновременно служит в качестве флага, что модуль зарегистрирован. Когдавызывается деструктор зарегистрированного модуля, он уведомляетдиспетчер плагинов о неожиданной выгрузке с помощью вызоваunregisterModule(), передающим указатель на себя. Когда диспетчерплагинов будет регулярно выгружать модуль, то в первую очередь вызовметода doClean() меняет состояние модуля на незарегистрированное, и этопозволяет избежать вызова unregisterModule(), когда ОС выполняетфактическую выгрузку.

Реализовав интерфейс плагина IPluginModule, мы встретились с первыминтерфейсом, необходимым для реализации плагинов — IPluginManager. Онбудет активно использоваться позже, остальные члены этого класса вряд липотребуются вам после копирования в вашу программу. Просто не забудьтеобъявить глобальную переменную этого типа и вызвать функцию registerMe()из FB_PLUGIN_ENTRY_POINT.

Основной интерфейс любого плагина

Приступим к реализации самого плагина. Тип основного интерфейса зависитот типа плагина, что очевидно, но все они основаны на общем интерфейсеIPluginBase с подсчётом ссылок, который выполняет общие для всехплагинов (и очень простые) задачи. Каждый плагин имеет некоторый (тоже сподсчётом ссылок) объект, которому принадлежит этот плагин. Чтобывыполнять интеллектуальное управление жизненным циклом плагинов, любойплагин должен иметь возможность хранить информацию о владельце исообщать её диспетчеру плагинов по запросу. Это означает, что каждыйплагин должен реализовывать два тривиальных метода setOwner() иgetOwner(), содержащиеся в интерфейсе IPluginBase. Зависимые от типаплагина методы, безусловно, более интересны — они обсуждаются в частиописания интерфейсов.

Давайте рассмотрим типичную часть реализации любого плагина (здесь яспециально использую несуществующий тип SomePlugin):

class MyPlugin : public ISomePluginImpl<MyPlugin, CheckStatusWrapper>
{
public:
  explicit MyPlugin(IPluginConfig* cnf) throw()
     : config(cnf), refCounter(0), owner(NULL)
  {
    config->addRef();
  }
  ...

Конструктор получает в качестве параметра интерфейс конфигурацииплагина. Если вы собираетесь конфигурировать плагин каким-то образом, торекомендуется сохранить этот интерфейс в вашем плагине и использоватьего позже. Это позволит вам использовать общий стиль конфигурацииFirebird, позволяя пользователям иметь привычную конфигурацию и свести кминимуму написание кода. Конечно, при сохранении какого-либо ссылочногоинтерфейса лучше не забывать добавлять ссылку на него. Также не забудьтеустановить счетчик ссылок в 0 и владельца плагина в NULL.

  ~MyPlugin()
  {
    config->release();
  }

Деструктор освобождает конфигурационный интерфейс. Обратите внимание: мыне меняем счетчик ссылок нашего владельца, потому что он принадлежитнам, а не мы принадлежим ему.

  // IRefCounted implementation
  int release()
  {
    if (--refCounter == 0)
    {
      delete this;
      return 0;
    }
    return 1;
  }


  void addRef()
  {
    ++refCounter;
  }

Абсолютно типичная реализация объекта с подсчётом ссылок.

  // IPluginBase implementation
  void setOwner(IReferenceCounted* o)
  {
    owner = o;
  }

  IReferenceCounted* getOwner()
  {
    return owner;
  }

Как и было обещано, реализация IPluginBase тривиальна.

  // ISomePlugin implementation
  // … here go various methods required for particular plugin type
private:
  IPluginConfig* config;
  FbSampleAtomic refCounter;
  IReferenceCounted* owner;
};

В этом примере формальная часть реализации основного интерфейса плагиназавершена. После добавления специфичных для типа методов (и, возможно,написания кода, чтобы сделать их полезным), интерфейс готов.

Фабрика плагинов

Еще один интерфейс, необходимый для работы плагина —IPluginFactory. Фабрика создаетэкземпляры плагина и возвращает их в диспетчер плагинов. Фабрика обычновыглядит так:

class Factory : public IPluginFactoryImpl<Factory, CheckStatusWrapper>
{
public:
  IPluginBase* createPlugin(CheckStatusWrapper* status,
                            IPluginConfig* factoryParameter)
  {
    MyPlugin* p = new MyPlugin(factoryParameter);
    p->addRef();
    return p;
  }
};

Здесь внимание следует уделить тому факту, что даже в случае, когда кодв функции может генерировать исключения (оператор new может бросать вслучае, когда память исчерпана), то не обязательно всегда вручнуюопределять блок try/catch — реализация интерфейсов Firebird делает этуработу за вас, в реализации IPluginFactory эта обработка происходит вшаблоне IPluginFactoryImpl. Обратите внимание, что обертки статуса поумолчанию выполняют полноценную обработку только для FbException. Ноесли вы работаете над каким-то крупным проектом, то определите своюсобственную оболочку, в этом случае вы можете обрабатывать любой типисключения C++ и передавать полезную информацию об этом из своегоплагина.