Friday, 8 January 2016

Injeqt 1.1 is coming

After some hiatus (summer is time for riding a bike, not for spending evenings at home writing open-source code) I've moved Kadu few steps forward. New version - 4.0 - will require some more functionalities from Injeqt dependency injection library. So a few new had been added. Most of them for plugin support. I'll give a short description of each of them and then show you how these works together.

Subinjectors

I've promised subinjectors when Injeqt 1.0 was released. And now these are available. In short - you can create new injector with new modules and classes that also gains all knowledge and instance of another injector (or injectors).

In Kadu this means that each plugin can new have its own injector and use all of Kadu classes and services in a nice way (using INJEQT_SET setters).

INJEQT_INIT and INJEQT_DONE methods

It is quite frequent to run some code after all required objects were injected into newly created one. Think of that as of two-step initialization (in that case it is not as bad as most people describe it, do not be scared). Slots marked with INJEQT_INIT tag will do just that - be called after all INJEQT_SET/INJEQT_SETTER slots.

INJEQT_DONE marked slots that will be called before any object created by injector is destructed. It allows for cleanup, writing files do disc and do other stuff without worrying about availability of other objects (such code can not be run from destructors, because Injeqt does not guarantee anything about order of object destruction).

In Kadu INJEQT_INIT methods are used to connects slots and signals of services and to do loading/storing data.

INJEQT_SET tag

It is a new name for INJEQT_SETTER. Just to match grammar of INJEQT_INIT and INJEQT_DONE.

INJEQT_INSTANCE_ON_DEMAND and INJEQT_INSTANCE_IMMEDIATE

Now Injeqt allows you to select when given object will be instantiated. If it is tagged with INJEQT_INSTANCE_ON_DEMAND (default) the behavior will be the same as with Injeqt 1.0 - object will be first instantiated when it is needed. If class is tagged with INJEQT_INSTANCE_IMMEDIATE - object will be instantiated as soon as possible (before injeqt::injector constructor finishes). Useful for services that works on their own using signals from other services. In Kadu all main plugin objects are marked with INJEQT_INSTANCE_IMMEDIATE, so there is no need to manually create them after plugin is loaded and plugin's injector is created.

inject_into method

Sometimes you create object manually, but still want to put injectable objects into it. Just call injeqt::injector::injeqt_into(QObjec *) on it and you are done.

In Kadu this is mainly used to allow access to Kadu services by dynamically created widgets.

Usage in Kadu

Now I'll show you real-life example of how it all works together.

Lets get a quick overview on how Kadu plugins were loaded in the past (like 3.x releases).

class PluginRootComponent
{
public:
  virtual bool init() = 0;
  virtual void done() = 0;
};

Q_DECLARE_INTERFACE(PluginRootComponent, "im.kadu.PluginRootComponent")
This is the class that each plugin must implement and export. You can see example implementation for OTR encryption plugin in otr-plugin.cpp file. As you can see it uses init() method to initialize its own injector, connect some of its signals and slots (unfortunately, Injeqt can not do it for you yet). Some objects from Kadu core are retrieved using Core::instance() calls (this is one thing that I'm fixing currently). And the done() method is used to remove OTR objects from Kadu services. Fortunately the Injeqt takes care of objects created with OTR injector. The last important part are PluginRootComponentHandler class:
PluginRootComponentHandler::PluginRootComponentHandler(
  PluginRootComponent *pluginRootComponent) :
  m_pluginRootComponent(pluginRootComponent)
{
  m_pluginRootComponent->init(firstLoad));
}

PluginRootComponentHandler::~PluginRootComponentHandler() noexcept
{
  m_pluginRootComponent->done();
}
And the ActivePlugin class:
ActivePlugin::ActivePlugin(const QString &pluginName) :
  m_pluginLoader{make_unique<PluginLoader>pluginName)},
  m_pluginRootComponentHandler{make_unique(m_pluginLoader->instance())}
{
}
So, finally, the steps are:
  1. load .dll or .so file
  2. create PluginRootComponent instance from it using Qt QPluginLoader
  3. call init() method on the instance, it creates its own objects and connects to Kadu core classes with awkward ::instance() calls (either Core::instance()->className() or className::instance()
  4. before unloading call done() method
In comparison, Kadu 4.x uses different method for that. PluginRootComponent is replaced with PluginModulesFactory:
class KADUAPI PluginModulesFactory : public QObject
{
  Q_OBJECT

public:
  explicit PluginModulesFactory(QObject *parent = nullptr);
  virtual ~PluginModulesFactory();

  virtual std::vector<std::unique_ptr<injeqt::module>>
    createPluginModules() const = 0;
  virtual QString parentInjectorName() const;

};

Q_DECLARE_INTERFACE(PluginModulesFactory, "im.kadu.PluginModulesFactory")
Its only job is to create list of Injeqt modules. Implementation of this class is usually fairly simple:
std::vector<std::unique_ptr<injeqt::module>>
OtrPluginModulesFactory::createPluginModules() const
{
  auto modules = std::vector<std::unique_ptr<injeqt::module>>{};
  modules.emplace_back(make_unique<OtrModule>());

  return modules;
}
Then the injector is created and stored in PluginLoader class using following code:
injeqt::injector{std::vector<injeqt::injector *>{parentInjector},
  pluginModulesFactory->createPluginModules()};
And that is all that is needed from Kadu core. Now please look at new otr-plugin-object.h and otr-plugin-object.cpp files. There are few things to be seen in OtrPluginObject class:
  1. it is marked with INJEQT_INSTANCE_IMMEDIATE - it means it will be instantiated as soon as plugin is loaded and injector is created
  2. it has INJEQT_SET slots with Otr* objects (that come from OtrModule) and with other objects that comes from Kadu core - it is possible because of adding parentInjector into injeqt::injector constructor - we got rid of some of ::instance() calls
  3. it has INJEQT_INIT and INJEQT_DONE methods to set up and shut down the plugin - these two are called by Injeqt and replaces manually called init() and done() methods of PluginRootComponent


Summary

I think that Injeqt is going into good direction. It makes working with such a big project as Kadu a bit easier each time it is itself improved. In future I hope for two more things to be implemented in it: INJEQT_SIGNAL and INJEQT_SLOT tags that will allow to automatically connects slots and signals of Injeqt-created objects and something like INJEQT_ADD and INJEQT_REMOVE to simplify creating repository/registry-like classes. Maybe one of these feature will be added to Injeqt 1.2. After adding both of them, I could remove most of INJEQT_INIT and INJEQT_DONE methods from plugins.

No comments:

Post a Comment