by Jesper K. Pedersen <blackie@kde.org>

Translations of this tutorial

Spanish translation by Duncan Mac-Vicar Prett.

Building a Plugin Structure for a KDE application

Plugins give the capability of loading extra functionality into an application at run time. Besides it being a cool thing to say that your application has a plugin structure it also has certain real values: This document will take you through all the steps required to build a plugin structure for a KDE application. It might be worth reading even if you are not writing a plugin structure for an application, but only wants to write a plugin.

There exist two kinds of plugins:

The main focus of this document is on the second kind, but whenever applicable comments will be added regarding difference between the two kinds.

The demo application

Along with this document is an example application with a plugin structure. The application can be downloaded here, and throughout the document, I will refer to this application as a running example.

The files are separated in to three directories:

The Steps

The steps required to build a plugin structure for an application are roughly these: The order things are described in this document is slightly different from the order above, simply to make it more comprehensive.

We will start by looking at how all of the above is accomplished from a C++ point of view, and then we will look at what kind of support files is needed. I.e. Makefile.am's and desktop files.

Base class for plugins

Below the hood of KDE, the following is roughly going on when a plugin is loaded: The application finds the dynamic library which is the plugin, the library is dlopen'd, and a special C function is searched for, and when found executed. The C function must return something that is valuable to the program loading the plugin. In KDE it must return a pointer to a base class defined by the calling function.

In other words, the result of loading the plugin is a pointer to a class, which the loading application knows about. Therefore our very first task when developing a plugin structure is to create a virtual base class for the plugins to implement.

In our sample application, this class is interfaces/plugindemo/plugin.h. The class is, as all the files in the template subdirectory, in the namespace PluginDemo. This is not a requirement but merely to avoid name clashes, and make it more obvious where things comes from. Likewise the file is in the subdirectory plugindemo, which means that including the file is done by an include statement like #include <plugindemo/plugin>, this is also done only to avoid name clashes.

The important thing to notice in this class is that it inherits KXMLGUIClient. This has the consequence that the host application may merge its tools bar and menu bar actions with those from the plugin. We will discuss how that is done later.

Two other methods are defined in this class, namely editorInterface(), and selectionInterface(). For the plugin to be useful at all, it must be able to access the internals of the applications it is plugged into. These two methods return an interface for this. The interface is implemented, as we will later see, by the host application. For now just accept that the plugins may access the host application though the two classes which can be found in interfaces/plugindemo/editorinterface.h and interfaces/plugindemo/selectioninterface.h.

Had this been a loadable module then you would have seen these differences:

Developing plugins

Lets jump to the plugins, and see how they are implemented. A sample plugin can be found in plugindemo-capitalize/capitalizeplugin.h and plugindemo-capitalize/capitalizeplugin.cpp. This plugin uses the selection interface to capitalize the frist letter of every word in the selection

The first thing to notice is that the class inherits from PluginDemo::Plugin, this is a requirement for it to be a plugin at all. From a C++ point of view this class is just a normal C++ class, which of course can implement slots, have instance variables, connect to signals etc. The only tricky thing about this class is how it is constructed. It is namely constructed by the host application dynamically loading the library defining it, and by a set of indirections, creating an instance of it.

Understanding the loading mechanism.

To understand how all this works, please look at the implementation (plugindemo-capitalize/capitalizeplugin.cpp). The crucial point is to be found at the top of the file, where you will see these lines of code:
  K_EXPORT_COMPONENT_FACTORY( plugindemo_capitalize,
                              KGenericFactory<CapitalizePlugin>( "plugindemo_capitalize" ) )
K_EXPORT_COMPONENT_FACTORY is a macro defined by KDE, but to really understand what it does it's useful to look at its definition, which is:
  #define K_EXPORT_COMPONENT_FACTORY( libname, factory ) \
    extern "C" { void *init_##libname() { return new factory; } }
This reveals that the macro expands to a definition of a C function[2], and that function returns an instance of the class defined by the second argument to the macro.

The second argument is itself a bit of magic, it says:

  KGenericFactory<CapitalizePlugin>( "plugindemo_capitalize" )
KGenericFactory is a template, which takes another class for template instantiation. The KGenericFactory has a method needed for all this to fit together, namely a create() method. The create method will when called from the KDE subsystem, create an instance of the class that was given to it during template instantiation.

If all this is black magic to you, or it seems overly complex, then don't bother, its not that important for developing plugins, but was merely included here for those wanting to know all the gory details. All you need to remember are these steps:

Merging GUI with the host application

Looking at the body of the constructor you will see these two lines:
    setInstance(KGenericFactory<CapitalizePlugin>::instance());
    setXMLFile("plugindemo_capitalizeui.rc");
What they basically do are to load the user interface of the plugin, and merge it with the host application. That means that the plugin may add items to the menu bar and tool bars of the host application.

The name specified as an argument to the setXMLFile() method is a resource file, which must be installed by the Makefile.am file for the plugin. The details about Makefile.am will be explained later. The content of this file is described in this howto.

The resource file specifies one action to be put into the Edit menu bar item. This actions must be set up in the constructor. For that an instance of KApplication is being created.

Communicating with the host application

The plugin communication with the host application happens through the interfaces available from the super class (interfaces/plugindemo/plugin.h). An example of this can be seen in the slotCapitalize() method, which is invoked when the user selects our action in the menu bar. In this method, we ask the selection interface for the text of the selection, capitalize the words in that text, and set back the text.

Loading plugins

Its now time to look at the host application which can be found in application/mainwindow.cpp, and reveal how easy it is to actually load the plugins.

First thing to notice about the host application is that its main window inherits KMainWindow. This is needed for the merging of menu bar and tool bars.

Loading the plugins are done in the function loadPlugins(). The first line of that function is:

    KTrader::OfferList offers = KTrader::self()->query("PluginDemo/Plugin");
What this basically does is to ask the KDE system to return a list of plugins which matches the type PluginDemo/Plugin. Each plugin must come with a desktop file which specify the plugins type. We will later discuss the desktop file in details.

Next we iterate through the list of plugins, and load each of them in turn. This is done with this code:

    for(iter = offers.begin(); iter != offers.end(); ++iter) {
        KService::Ptr service = *iter;
        int errCode = 0;
        PluginDemo::Plugin* plugin =
            KParts::ComponentFactory::createInstanceFromService
            ( service, _pluginInterface, 0, QStringList(), &errCode);
All the hardcore stuff regarding dlopening the file and calling the init function is taken care of by the createInstanceFromService funciton.

Many things can happen when loading a plugin, for one thing it might not even be a plugin for our application, the user might have mistyped something in the desktop file etc. Therefore it is very important to check that we actually got a pointer back from the createInstanceFromService() method. We should of course notify the user if we did not manage to load a plugin, but for simplicity we skip that part here.

You might notice that the second parameter to the createInstanceFromService() method is _pluginInterface, this is the parent pointer, but in this framework it needs to be special in ways described in the next section.

Once the plugin has been loaded, it is time to add it to the KXML factory, so that the GUI of the plugin is merged into the host application. This is done by the method call:

  guiFactory()->addClient(plugin);

Communication between the plugins and the host applications.

Until now we have accepted that communication between the plugin and the host application has been through the interface methods which is returned by the Plugin class from interfaces/plugindemo/plugin.h.

These functions do, however, need to get their information out of the host application one way or the other, which is what we will discuss now.

Looking at the implementation of the selectionInterface() function will show us how this happens:

PluginDemo::SelectionInterface* PluginDemo::Plugin::selectionInterface()
{
    return static_cast<SelectionInterface*>
           ( parent()->child( 0, "PluginDemo::SelectionInterface" ) );
}
The plugin simply asks its parent for the child inheriting the class selectionInterface. In other words the parent need to set up an instance of a class which inherits SelectionInterface. How the actual setup is done, can be seen from the application/mainwindow.cpp file:
    _pluginInterface = new QObject( this, "_pluginInterface" );
    new MySelectionInterface( _editor, _pluginInterface, "selection interface" );
In the above we create the _pluginInterface object, and give it a pointer to the class MySelectionInterface. The _pluginInterface pointer is later given as a parent when constructing the plugins (as we saw above):
   KParts::ComponentFactory::createInstanceFromService
     ( service, _pluginInterface, 0, QStringList(), &errCode);
The actual implementation of the SelectionInterface interface is in application/myselectioninterface.h and application/myselectioninterface.cpp.

In the above code you might have noticed that no interface was implemented for the EditorInterface class. This is to illustrate how one kind of host application might implement only some of the interfaces. Imagine that the plugins was shared among a number of applications. Not all application may be able to offer the full interface. Plugins requiring a given non-implemented interface will of course not work when that interface is not implemented. Thus, when a plugin needs access to a certain interface, it should first check if the interface is available, and if not disable itself.

At this point, I'm pretty sure you ask yourself, why on earth was the communication between plugins and host application implemented that way? To be honest with you, this was indeed not my initial idea either. My initial suggestion was something along the line of a virtual PluginInterface class, which the plugins were given a pointer to upon creation, and which the base application had to implement.

This was indeed much clearer, and a much better design object oriented wise, but it had a fatal drawback, namely it made it impossible to add new interfaces in a binary compatible way. Thus binary compatibility was broken when a new interface was added, and all plugins had to be recompiled.

With the current solution, a new interface means a new non-virtual method needs to be added to the plugin.h file. An example would be a paragraph interface, which would result in a method returning a pointer to such a ParapgrahInterface class. Adding a non-virtual method to a class can be done without breaking binary compatibility.

Auxiliary files needed to make everything work.

Now you know how all the pieces of code fits together to develop a plugin structure for your application. That's not enough, however, the KDE frame work must be told that the code you compile as a plugin actually is a plugin, and you need to tell KDE about your new plugin type, which was used by KTrader above. This is what this section will talk about.

The interface directory

The Makefile.am in interfaces/plugindemo needs a single addition compared to a normal Makefile.am for a library, namely the following line:
  kde_servicetypes_DATA = plugindemoplugin.desktop
What this line says is that the file plugindemoplugin.desktop must be installed on the system as a service type.

Remember, when we loaded the plugins, we did not specify any directory for KDE to search for plugins, nor did we specify any files to look in. All we specified was that we wanted to load a plugin of the service type PluginDemo/Plugin. The file interfaces/plugindemo/plugindemoplugin.desktop describes the service type:

[Desktop Entry]
Type=ServiceType
X-KDE-ServiceType=PluginDemo/Plugin
Comment=A Demo Plugin
The important issue in the above is the line X-KDE-ServiceType=PluginDemo/Plugin, where you will recognize the type we asked for when loading the plugins.

This file may also contain some attributes, which may be queried when loading plugins, that is, however, out of the scope for this document.

The plugins directory

The Makefile.am in plugindemo-capitalize directory do also need a few special lines.
kde_module_LTLIBRARIES = plugindemo_capitalize.la
For the plugin to be installed in the right place, we need to specify it using kde_module_LTLIBRARIES.
plugindemo_capitalize_la_LDFLAGS = -module $(KDE_PLUGIN) $(all_libraries)
Plugins are different from libraries in the sense that they do not contain version information, just like you don't specify that you want to load konqueror version x.y, but only that you want to load konqueror. Likewise you do not specify that you want to load a specific version of a plugin[3]. For that reason you need to add the -module parameter to the LDFLAGS.
pluginsdir = $(kde_datadir)/plugindemo_capitalize
plugins_DATA = plugindemo_capitalizeui.rc
As we saw above, the plugin has a resource file for the GUI that comes with the plugin, that resource file do of course need to be installed on the system. which is what the above lines ensures.

Note, the file name you specify for the argument to setXMLFile() must match the filename in the plugins_DATA line, and the name you specify as an argument to KGenericFactory as part of the K_EXPORT_COMPONENT_FACTORY macro, must match the name in the plugins_DATA section.

kde_services_DATA = plugindemo_capitalize.desktop
Finally, the plugin needs a desktop file, which we specify in the above line. The desktop file, looks like this:
[Desktop Entry]
Name=Capitalize
Comment=Capitalize word plugin for PluginDemo
ServiceTypes=PluginDemo/Plugin
Type=Service
X-KDE-Library=plugindemo_capitalize
There are three important things to note about this file. First the ServiceTypes line must match the service type specified in the desktop file in the interfaces directory. Second, the X-KDE-Library must specify the name of the library that the plugin is compiled into, without the postfix .la. Finally, it's no coincidence that the filename has plugindemo_ as prefix. Bad things are bound to happen if two applications installs a desktop file with the same name, therefore you should always namespace them by prefixing them as done above.

Conclusion

You have now seen an example of how to build a plugin system for your KDE application, We have discussed the advantages of plugins, but we haven't discussed how to design your interface classes. This is pretty similar to designing the interface of a library. You should be careful what you make available to the plugin. Making too little available to the plugin will render the plugin interface useless. Making too much available to the plugins will make it impossible to change anything in the host application without breaking plugins.

Notes

Credits


Jesper Kjær Pedersen <blackie@blackie.dk>
Last modified: Mon May 31 16:56:30 2004