Plugins

To help you get started, you might want to have a look at the following sample plugins. They illustrate the basic concepts needed to develop plugins for OpenCOR:

Note: these plugins only get built if the ENABLE_SAMPLE_PLUGINS option is set to ON in CMake.

Categories

All plugins come under one of the following categories:

A category is used by OpenCOR to group plugins together to improve user experience. From a developer’s perspective, a category determines where a plugin’s code should be located. Thus, the different folders under [OpenCOR]/src/plugins/ are for our different categories. For example, [OpenCOR]/src/plugins/miscellaneous/ contains the code of our different Miscellaneous plugins and [OpenCOR]/src/plugins/miscellaneous/Core/ the code of our Core plugin in particular.

Note: the Sample and Test categories are special categories that are only available when building OpenCOR with the ENABLE_SAMPLE_PLUGINS and ENABLE_TEST_PLUGINS options set to ON, respectively. They should only be used for plugins that are aimed at helping people who want to learn how to write plugins for OpenCOR and at testing things, respectively.

Interfaces

Plugins can implement different interfaces. Those interfaces allow a plugin, through the implementation of various methods, to interact with OpenCOR. Current interfaces can be found under [OpenCOR]/src/plugins/. They are:

Some plugins do not implement any interface (e.g., the LLVM+Clang plugin) while others may implement one or several interfaces (e.g., the Core plugin implements the Core, File Handling, GUI, Internationalisation, and Plugin interfaces).

Note: the Core interface is only, and can only be, implemented by the Core plugin. Any other plugin that tries to implement the Core interface will be reported by OpenCOR as being invalid.

CMake project

OpenCOR is built and packaged using CMake. When it comes to plugins, this requires creating a CMakeLists.txt file in the plugin’s root folder and calling the add_plugin() macro, which is defined in [OpenCOR]/cmake/common.cmake. That macro uses information passed to it to build and package the plugin. That information comes in the form of a series of parameters, some of which are keywords:

Following those keywords are the parameters themselves, as can be seen in [OpenCOR]/src/plugins/miscellaneous/Core/CMakeLists.txt for the Core plugin.

Plugin information

For a plugin to be recognisable by OpenCOR, it must provide some basic information about itself, as well as define a plugin class. For this, we need a .cpp, a .h, and a .json file, such as [OpenCOR]/src/plugins/miscellaneous/Core/src/coreplugin.cpp, [OpenCOR]/src/plugins/miscellaneous/Core/src/coreplugin.h, and [OpenCOR]/src/plugins/miscellaneous/Core/src/coreplugin.json for the Core plugin.

.json file

The .json file is a simple JSON file, which sole purpose is to reference the name of the plugin class. In the case of the Core plugin, the contents of that file is:

{
    "Keys": [ "CorePlugin" ]
}

Namespace

The code for the basic information and plugin class must be in the plugin’s own namespace within the OpenCOR namespace. More generally, any plugin-related code should be within those two namespaces, this to ensure the integrity of the plugin’s code. Thus, in the case of the Core plugin, we have:

...
namespace OpenCOR {
namespace Core {
...
} // namespace Core
} // namespace OpenCOR
...

Basic information

Plugins must provide the following basic information:

  • Category: category under which the plugin is to be listed.

  • Selectable: whether the plugin can be selected by the user (for loading upon starting OpenCOR).

  • CLI support: whether the plugin works from the command line.

  • Dependencies: plugins on which the plugin depends directly.

  • Descriptions: description of the plugin in various languages.

  • Load before: plugins before which the plugin should be loaded.

This information is made available to OpenCOR through a function, which in the case of the Core plugin has the following declaration:

PLUGININFO_FUNC CorePluginInfo();

Note: to ensure the uniqueness of a plugin, OpenCOR uses the name of a plugin to determine the name of its function. In other words, the name of the function is expected to be <PluginName>PluginInfo(). If it is not, OpenCOR will not recognise the plugin.

In the case of the Core plugin, the body of that function is:

PLUGININFO_FUNC CorePluginInfo()
{
    static const Descriptions descriptions = {
                                                 { "en", QString::fromUtf8("the core plugin.") },
                                                 { "fr", QString::fromUtf8("l'extension de base.") }
                                              };

    return new PluginInfo(PluginInfo::Category::Miscellaneous, false, false,
                          {},
                          descriptions);
}

Note: support for the internationalisation of a plugin’s description would normally be done using Qt’s tr() function, but the C nature of the function means that it cannot be done. So, instead, we use a QMap-based approach.

Plugin class

We rely on Qt’s support for plugins, which means that plugins must define a specific class. The class must inherit from QObject, as well as from any interface the plugin implements. For example, the Core plugin implements the Core, File Handling, GUI, Internationalisation, and Plugin interfaces, so its class definition is:

...
class CorePlugin : public QObject, public CoreInterface,
                   public FileHandlingInterface, public GuiInterface,
                   public I18nInterface, public PluginInterface
{
    Q_OBJECT

    Q_PLUGIN_METADATA(IID "OpenCOR.CorePlugin" FILE "coreplugin.json")

    Q_INTERFACES(OpenCOR::CoreInterface)
    Q_INTERFACES(OpenCOR::FileHandlingInterface)
    Q_INTERFACES(OpenCOR::GuiInterface)
    Q_INTERFACES(OpenCOR::I18nInterface)
    Q_INTERFACES(OpenCOR::PluginInterface)

public:
#include "coreinterface.inl"
#include "filehandlinginterface.inl"
#include "guiinterface.inl"
#include "i18ninterface.inl"
#include "plugininterface.inl"
...
};
...

On the other hand, our LLVM+Clang plugin does not need to implement any interface since its sole purpose is to provide other plugins with access to LLVM and Clang. Hence, its much simpler class definition:

...
class LLVMClangPlugin : public QObject
{
    Q_OBJECT

    Q_PLUGIN_METADATA(IID "OpenCOR.LLVMClangPlugin" FILE "llvmclangplugin.json")
};
...

Global header file

There may be cases where a plugin declares a function or defines a class that we want to be able to use from another plugin. On Linux and macOS, nothing needs to be done about it, but on Windows, the function or class needs to be exported by the original plugin:

void __declspec(dllexport) myFunction();
class __declspec(dllexport) myClass;

and imported by the plugin that wants to use it:

void __declspec(dllimport) myFunction();
class __declspec(dllimport) myClass;

Each plugin that exports functions and/or classes must therefore define a macro that refers either to __declspec(dllexport) or to __declspec(dllimport), depending on how the plugin’s code is to be compiled. In the case of the Compiler plugin, we have:

...
#ifdef Compiler_PLUGIN
    #define COMPILER_EXPORT Q_DECL_EXPORT
#else
    #define COMPILER_EXPORT Q_DECL_IMPORT
#endif
...

Compiler_PLUGIN (or, more generally, <PluginName>_PLUGIN) is automatically defined, if at all, at build time, and is used to determine the value of COMPILER_EXPORT (or, more generally, the value of <PLUGINNAME>_EXPORT), which can then be used as follows without having to worry about whether the function or class should be imported or exported:

void COMPILER_EXPORT myFunction();
class COMPILER_EXPORT myClass;