FirebirdSQL logo
 Accès à la base de donnéesInterfaces de A à Z 

Mise en œuvre d’un module de plugin

Les plugins interagissent activement avec un composant spécial de Firebird appelé le gestionnaire de plugins. En particulier, le gestionnaire de plugins doit savoir quels modules de plugins ont été chargés et doit être averti si le système d’exploitation tente de décharger l’un de ces modules sans une commande explicite du gestionnaire de plugins (cela peut se produire principalement lors de l’utilisation du serveur embarqué — lorsque le programme appelle exit() ou que la bibliothèque principale de Firebird fbclient est déchargée). L’objectif principal de l’interface IPluginModule est la notification. Tout d’abord, vous devez décider comment informer Firebird que le module sera déchargé. Lorsqu’une bibliothèque dynamique est déchargée pour une raison quelconque, de nombreuses actions spécifiques au système d’exploitation sont effectuées, et certaines de ces actions peuvent être utilisées pour détecter ce fait dans le programme. Lors de l’écriture de plugins distribués avec firebird, nous utilisons toujours l’appel global de destructeur de variables. Le gros « plus » de cette méthode est qu’elle est indépendante du système d’exploitation (bien que la fonction exit() puisse également être utilisée avec succès). Mais l’utilisation d’un destructeur permet de concentrer facilement presque tout ce qui concerne la détection de déchargement dans une seule classe, tout en implémentant l’interface IPluginModule.

La mise en œuvre minimale est la suivante :

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;
  }

};

Le seul membre des données est l’interface du gestionnaire de plugins IPluginManager. Il est passé à la fonction registerModule() et stocké dans une variable privée, tandis que le module est enregistré dans le gestionnaire de plugins en utilisant la méthode callModule() avec sa propre adresse comme seul paramètre. La variable pluginManager stocke non seulement un pointeur vers l’interface, mais sert également d’indicateur indiquant que le module est enregistré. Lorsque le destructeur d’un module enregistré est appelé, il avertit le gestionnaire de plugin d’un déchargement inattendu avec un appel à unregisterModule() qui passe le pointeur à lui-même. Lorsque le gestionnaire de plugins décharge le module sur une base régulière, la première chose que l’appel de la méthode doClean() est de changer l’état du module en unregistered, ce qui évite l’appel à unregisterModule().

Après avoir implémenté l’interface du plugin IPluginModule, nous sommes tombés sur la première interface nécessaire pour implémenter les plugins – IPluginManager. Il sera activement utilisé plus tard, il est peu probable que vous ayez besoin du reste des membres de cette classe après l’avoir copié dans votre programme. N’oubliez pas de déclarer une variable globale de ce type et d’appeler la fonction registerMe() à partir de FB_PLUGIN_ENTRY_POINT.

L’interface principale de n’importe quel plugin

Commençons à implémenter le plugin lui-même. Le type de l’interface principale dépend évidemment du type de plugin, mais ils sont tous basés sur l’interface générale IPluginBase avec comptage de références, qui effectue des tâches communes (et très simples) pour tous les plugins. Chaque plugin a un objet (également avec comptage de liens) auquel il appartient. Afin d’effectuer une gestion intelligente du cycle de vie des plugins, tout plugin doit être en mesure de stocker des informations sur le propriétaire et de les transmettre au gestionnaire de plugins sur demande. Cela signifie que chaque plugin doit implémenter les deux méthodes triviales setOwner() et getOwner() contenues dans l’interface IPluginBase. Les méthodes dépendantes du type de plugin sont certainement plus intéressantes — elles sont discutées dans la partie description de l’interface.

Jetons un coup d’œil à la partie implémentation typique de n’importe quel plugin (j’utilise spécifiquement le type SomePlugin inexistant ici) :

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

Le constructeur reçoit l’interface de configuration du plugin en tant que paramètre. Si vous comptez configurer le plugin d’une manière ou d’une autre, il est recommandé d’enregistrer cette interface dans votre plugin et de l’utiliser plus tard. Cela vous permettra d’utiliser le style de configuration global de Firebird, ce qui permettra aux utilisateurs d’avoir une configuration familière et de minimiser le codage. Bien sûr, lors de l’enregistrement d’une interface de lien, il est préférable de ne pas oublier d’y ajouter un lien. N’oubliez pas non plus de définir le nombre de liens sur 0 et le propriétaire du plugin sur NULL.

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

Le destructeur libère l’interface de configuration. Notez que nous ne modifions pas le nombre de liens de notre propriété car elle ne nous appartient pas.

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


  void addRef()
  {
    ++refCounter;
  }

Il s’agit d’une implémentation tout à fait typique d’un objet avec comptage de références.

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

  IReferenceCounted* getOwner()
  {
    return owner;
  }

Comme promis, l’implémentation d’IPluginBase est triviale.

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

Dans cet exemple, la partie formelle de l’implémentation de l’interface principale du plugin est terminée. Après avoir ajouté des méthodes spécifiques au type (et peut-être écrit du code pour les rendre utiles), l’interface est prête.

Constructeur de plugins

Une autre interface requise pour que le plugin fonctionne est IPluginFactory. La class instancie le plugin et le renvoie au gestionnaire de plugins. La class ressemble généralement à ceci :

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

Il faut faire attention au fait que même dans le cas où le code de la fonction peut lever des exceptions (le nouvel opérateur peut lever une exception lorsque la mémoire est épuisée), vous n’avez pas toujours besoin de définir manuellement le bloc try/catch - l’implémentation des interfaces Firebird fait le travail pour vous, dans l’implémentation IPluginFactory ce traitement a lieu dans le modèle IPluginFactoryImpl. Notez que les shims d’état par défaut ne gèrent que l’exception FbException. Mais si vous travaillez sur un grand projet, définissez votre propre wrapper, auquel cas vous pouvez gérer n’importe quel type d’exception C++ et transmettre des informations utiles à ce sujet à partir de votre plugin.

Point d’initialisation du module Plugin

Lorsque le gestionnaire de plugins charge un module de plugin, il appelle la routine d’initialisation du module, qui est la seule fonction FB_PLUGIN_ENTRY_POINT du plugin exportée. Pour écrire le code, il aura besoin de deux variables globales : le module de plugin et la constructeur de plugins. Dans notre cas, il s’agit de :

PluginModule module;

Factory factory;

Si un module contient plus d’un plugin, vous aurez besoin d’un constructeur pour chaque plugin.

Pour FB_PLUGIN_ENTRY_POINT il ne faut pas oublier qu’il doit être exporté depuis le module plugin, cela nécessite de prendre en compte certaines caractéristiques du système d’exploitation. Pour ce faire, nous utilisons la macro FB_DLL_EXPORT définie dans examples/interfaces/ifaceExamples.h. Si vous êtes sûr que vous n’utilisez le plugin que pour un système d’exploitation spécifique, vous pouvez rendre cet endroit un peu plus facile. Au minimum, la fonction doit enregistrer le module et toutes les constructeurs dans le gestionnaire de plugins :

extern "C" void FB_DLL_EXPORT FB_PLUGIN_ENTRY_POINT(IMaster* master)
{
  IPluginManager* pluginManager = master->getPluginManager();
  module.registerMe(pluginManager);
  pluginManager->registerPluginFactory(IPluginManager::TYPE_DB_CRYPT,
                                       "fbSampleDbCrypt",
                                       &factory);
}

Tout d’abord, nous appelons la fonction récemment écrite PluginModule::registerMe(), qui enregistre le IPluginManager pour une utilisation ultérieure et enregistre notre module de plugin. Enregistrez ensuite la class (ou les class s’il y aura plusieurs plugins dans un module). Nous devons passer le bon type de plugin (les types valides sont listés dans l’interface IPluginManager) et le nom sous lequel le plugin sera enregistré. Dans le cas le plus simple, il devrait être le même que le nom de la bibliothèque dynamique du plugin. Cette règle vous aidera à ne pas configurer le plugin manuellement dans plugins.conf.

Notez que contrairement aux applications, les plugins n’ont pas besoin d’utiliser fb_get_master_interface() pour obtenir iMaster. Au lieu de cela, vous devez utiliser l’instance passée à FB_PLUGIN_ENTRY_POINT. Si vous avez besoin d’une interface iMaster dans votre plugin, assurez-vous de la conserver dans cette fonctionnalité.