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:

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

Category

All plugins come under a given category. Currently supported categories are:

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/ that of the Core plugin in particular.

the Sample category is a special category in that it is only available when building OpenCOR with the CMake ENABLE_SAMPLE_PLUGINS option set to ON. It should therefore only be used for plugins that are aimed at helping people who want to learn how to write plugins for OpenCOR.

Interfaces

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

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

the Core interface is a special interface in that it is only, and can only be, implemented by the Core plugin. Any plugin, besides the Core 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. The ADD_PLUGIN() 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 .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 must have:

...
namespace OpenCOR {
namespace Core {
...
}   // namespace Core
}   // namespace OpenCOR
...
Basic information

Plugins must provide the following basic information about themselves:

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();

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 be able to recognise the plugin.

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

PLUGININFO_FUNC CorePluginInfo()
{
    Descriptions descriptions;

    descriptions.insert("en", QString::fromUtf8("the core plugin."));
    descriptions.insert("fr", QString::fromUtf8("l'extension de base."));

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

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, the LLVMClang 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 special needs to be done, 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 therefore defines a macro that refers either to __declspec(dllexport) or to __declspec(dllimport), depending on how the plugin's code is to be compiled. Thus, in the case of the Compiler plugin, we have:

...
#ifdef _WIN32
    #ifdef Compiler_PLUGIN
        #define COMPILER_EXPORT __declspec(dllexport)
    #else
        #define COMPILER_EXPORT __declspec(dllimport)
    #endif
#else
    #define COMPILER_EXPORT
#endif
...

_WIN32 and Compiler_PLUGIN (or, more generally, <PluginName>_PLUGIN) are automatically defined, if at all, at build time, and are 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 whether the function or class should be imported or exported:

void COMPILER_EXPORT myFunction();
class COMPILER_EXPORT myClass;