charon-core  0.3.1
Working with Plug-ins

This page describes how to develop and use Plug-ins based on the ParameteredObject base class.

The PluginManager will be introduced to handle the Plug-ins. Please refer to the Using charon-core page for more details on creating a class derived from ParameteredObject.

This example will do mainly the same as the example of the Using charon-core page. But I will use plugins and the PluginManager class to realize is.

Developing non-templated plug-ins

Of course, we need to develop the plugins first. But, fortunately, there is not much developing to do. In the example on the Using charon-core page, there were two classes: Sample and Outputgen. Out of these two classes we will make two plugins. First, we will need to create two separate header files, because later on, we want to have two separare binaries for the plugins, each one containing only the code of the plugin itself.

Sample.h

#ifndef SAMPLE_H_
#define SAMPLE_H_
Of course, we need to use include guards.

#if defined(MSVC) && defined(HANDLE_DLL)
#ifdef sample_EXPORTS
#define sample_DECLDIR __declspec(dllexport)
#else
#define sample_DECLDIR __declspec(dllimport)
#endif /*Export or import*/
#else /* No DLL handling or GCC */
#define sample_DECLDIR
#endif
This code is needed to compile the plugin using the Visual C++ compiler. It handles the export and import of symbols in/from DLLs. If you either use the GNU compiler or don't specify the HANDLE_DLL compiler flag, no symbols will be exported/imported. You may want to include the file directly into your application, then you must not specify the HANDLE_DLL flag. Only if you want create a shared library (as we will do in this example) and use the Visual C++ compiler (ensure the MSVC compiler flag is set), you have to handle symbol exporting/importing. It then depends on the sample_EXPORTS flag whether symbols will be exported (__declspec(dllexport)) or imported (__declspec(dllimport)). Generally, the compiler flag is named "classname_EXPORTS", the class name in lowercase letters. We have to store the information in the sample_DECLDIR compiler directive.

#include <iostream>
#include <cstdlib>
class sample_DECLDIR Sample : public ParameteredObject {
public:
Sample(const std::string& name = "") :
ParameteredObject("sample", name, "some sample class"),
par1(20), // initial values ...
par2(1.5), // ... for parameters ...
out1(8), // ... and for output ...
out2(7.3f), // ... slots
in2(true, true) // make in2 optional and multislot
{
// parameters
_addParameter (par1, "par1", "sample integer parameter");
_addParameter (par2, "par2", "sample float parameter");
// slots
_addInputSlot (in1, "in1", "sample integer input slot");
_addInputSlot (in2, "in2", "sample float input slot");
_addOutputSlot(out1, "out1", "sample integer output slot");
_addOutputSlot(out2, "out2", "sample float output slot");
}
};
#endif /* SAMPLE_H_ */
The rest of the code hasn't changed except for the sample_DECLDIR directive which stands before the class name in the declaration.

To use this class as a plugin, we will need to create a source file which contains some special code.

Sample.cpp

#include "Sample.h"
Of course, we have to include our plugin code.

#if defined(MSVC) && defined (sample_EXPORTS)
#define DECLDIR __declspec(dllexport)
#else
#define DECLDIR
#endif
Same thing as in the file Sample.h. Only we will never import symbols out of this file, so we don't have to handle this.

extern "C" DECLDIR ParameteredObject* create(const std::string& name, template_type /*t*/) {
return new Sample(name);
}
This function just calls the cunstructor of the plugin class. The function takes a template_type argument which we can ignore in this case. I will discribe templated plugins later.

extern "C" DECLDIR void destroy(ParameteredObject * b) {
delete b;
}
This function just calls the destructor of the plugin.

If you are developing an own non-templated plugin, you can simply copy this code (Sample.cpp). Of course, you have to replace the include directive, the $classname$_EXPORTS compiler directive and the class name inside the create function. Everything else stays the same.

I won't explain how to procede with the Outputgen class, because it's all the same.

Compiling a Plug-in

If you want to compile your plugin using the GNU Compiler Collection, you can do this manually.

  • Set the compiler flag UNIX (-DUNIX)
  • If you are using Mac OS X, you may also specify the APPLE flag (-DAPPLE)
  • Make the binaries position independent (-fPIC)
  • Specify charon-utils include dir path as include path (-I "$charon-core$/include")
  • Specify the charon-core installation path as library path (-L "$install$")
  • On Linux systems, set the -shared compiler flag, on Mac OS X the -dynamiclib flag
  • Link the plugin to the charon-core library (-lcharon-core)

That's all.

If you want to compile your plugin with the VC++ compiler, I recommend to use Visual Studio. Please refer to this page for more details. You have to set the MSVC, the HANDLE_DLL and the $classname$_EXPORT flags manually. $classname$ stands for the class name in lowercase letters.

Using the Plug-ins

Now, we want to use these plugins in a main function, like it was done in the Using charon-core page.

Main.cpp

#include "PluginManager.h"
We will use the PluginManager class to handle the plugins.

int main() {
Create an instance of the PluginManager.

//Create sample instance
ParameteredObject* sample = man.createInstance("sample", "sample");
Instances are created by calling the PluginManager::createInstance() method. You have to specify the name of the plugin (first argument) and you can specify the name of the instance (second argument). If you don't specify the instance name, a unique name will be generated automatically.

// sample->par1 is 20
sample->setParameter<int>("par1", 5);
// now, sample->par1 == 5
std::cout << "par1: " << sample->getParameter("par1") << std::endl;
Since we can no longer access the parameters and slots directly, we have to use the get- and set-methods offered by the ParameteredObject class.

// parametered object connections
ParameteredObject* outgen = man.createInstance("outputgen", "outputgen");
ParameteredObject* outgen2 = man.createInstance("outputgen", "outgen2");
man.connect(outgen->getSlot("out1"), sample->getSlot("in1"));
// slot in2 needs not to be connected because it's optional
// try to connect multiple sources to in2
man.connect(outgen->getSlot("out2"), sample->getSlot("in2"));
man.connect(outgen2->getSlot("out2"), sample->getSlot("in2"));
// outgen and sample are now in a connected component of 3 objects
// outgen only has one neighbour, sample2
// sample has two neighbours, outputgen and outgen2
Slot connections can be established using the PluginManager::connect() method. You can of course sill use the Slot::connect method directly.

// save object and connections
man.saveParameterFile("example.wrp");
The current configuration can be saved into a parameter file by using the PluginManager::saveParameterFile() method.

// delete connected object
// connection should be removed (also from outgen)
man.destroyInstance(sample);
assert(!outgen->getSlot("out1")->connected());
assert(!outgen->getSlot("out2")->connected());
// PluginManager does the cleanup for you.
return EXIT_SUCCESS;
}
We do not have to delete the instances manually, because the PluginManager does this for us. But if you at any time want to do this, do NOT simply call delete on the instance, use the PluginManager::destroyInstance() method instead.

Templated Plug-ins

Creating a templated Plug-in is very similar to creating a non-templated Plugin.

TemplatedSample.h

#ifndef TEMPLATEDSAMPLE_H_
#define TEMPLATEDSAMPLE_H_
#if defined(MSVC) && defined(HANDLE_DLL)
#ifdef templatedsample_EXPORTS
#define templatedsample_DECLDIR __declspec(dllexport)
#else
#define templatedsample_DECLDIR __declspec(dllimport)
#endif /*Export or import*/
#else /* No DLL handling or GCC */
#define templatedsample_DECLDIR
#endif
Until here, nothing has changed compared to the non-templated plugin code.

template<typename T>
class templatedsample_DECLDIR TemplatedSample: public TemplatedParameteredObject<T> {
Templated Plug-ins have to be derived from the TemplatedParameteredObject class instead of ParameteredObject.

public:
Parameter<double> sampleParameter;
OutputSlot<T> sampleOutput;
TemplatedSample(const std::string& name = "") :
TemplatedParameteredObject<T> ("sample", name, "some sample class"),
sampleParameter(1.5) {
Of course, you have to call the inherited constructor of the TemplatedParameteredObject instead of the ParameteredObject constructor.

//Add the output slot. Use the this-pointer to call the method.
this->_addOutputSlot(sampleOutput, "sampleoutput",
"a sample output slot", "T");
You have to use the this-pointer to call methods of ParameteredObject. If an input slot or an output slot depends on yout template type T, you have to specify this in the _addInputSlot() or _addOutputSlot()-call. For example, if your Plugin has an imput slot of type CImg<T>, the last parameter of the _addInputSlot()-call would be "CImg<T>". This is important because when connecting two slots, these types will be compared. If you have, like in this example, an input or output slot which just contains T, just specify "T" in the function call.

//Add the parameter. And specify a default value (here: 1.5).
//Notice that I do not specify the template type.
this->_addParameter(sampleParameter, "sampleparameter",
"a sample parameter", 1.5);
Same thing, use the this-pointer to call _addParameter(). But there is one more thing. The method we call here is a member template of ParameteredObject. Unlike the non-templated addParameter() method it adds a default value to the parameter (here: 1.5). For any reason, the GNU compiler won't let us pass the template type (in our case, double) to the function call. So just leave it out.

}
};
#endif /* TEMPLATEDSAMPLE_H_ */
That's all the difference in the plugin class.

Now, let's see what's different in the "plugin-special" code:

TemplatedSample.cpp

#define TYPE TemplatedSample
Here stands the name of the plugin class. This is mainly for your comfort, for you only have to type it once.

#include "TemplatedSample.h"
#if defined(MSVC) && defined (templatedsample_EXPORTS)
#define DECLDIR __declspec(dllexport)
#else
#define DECLDIR
#endif
Nothing changed here.

extern "C" DECLDIR ParameteredObject * create(const std::string& name, template_type t) {
switch(t) {
return new TYPE<double>(name);
break;
return new TYPE<float>(name);
break;
return new TYPE<int>(name);
break;
default:
return new TYPE<int>(name);
break;
}
}
Now, the create function considers the template_type argument. Since template classes normally aren't compiled, this function forces the compiler to build the class as double, float and int type. Other types aren't supported.

extern "C" DECLDIR void destroy(ParameteredObject * b) {
delete b;
}
Nothing changed here.

If you are developing an own templated plugin, you can simply copy this code (TemplatedSample.cpp) and change the class name, the include directive and the $classname$_EXPORTS compiler directive. But beware: Always use the this-pointer or ParameteredObject:: prefix (recommended) when calling a method of ParameteredObject and when using the templated ParameteredObject::_addParameter() method, don't specify the template argument in the function call. This won't compile using the GCC.