Legacy Plugin Tutorial
From K-3D
Overview
This tutorial will guide you through the creation of a new plugin object. In the following examples we will create a plugin called "MyPlugin" in a module called "mymodule". You can substitute better names once you're ready to create your own real plugins.
We assume you have a better-than-basic understanding of C++, your compiler, and your system / shell. We further assume that you have already compiled and installed K-3D 0.5 or later on your system, and setup your environment so that the K-3D executables, including k3d-uuidgen and k3d-config, are on your PATH.
Create a Module
First, you will create a new plugin module (shared library) than can be loaded into K-3D at runtime. Although plugin modules may contain any number of plugins, we recommend when creating a new plugin that you always create a new module for it. It's easy to move it back to an existing module if necessary. Note that if you are introducing a new third-party-library dependency with a plugin, it should stay in a separate module anyway - this gives people building from source the option to not compile that module if they don't have / don't want / can't build the external library. For binary distros, this allows that one module to gracefully fail to load at runtime, without taking-down other critical functionality.
Without further ado, go to your home directory and create a subdirectory to contain the new module:
$ cd $ mkdir mymodule $ cd mymodule
Before we continue, we need to generate a unique module identifier. A module identifier is a universally-unique 128-bit number that is used by K-3D to identify a specific module. It's important that all K-3D modules everywhere have unique identifiers to prevent confusion when they're loaded, so you'll need to generate a new module identifier every time you create a new module. A small utility called k3d-uuidgen is installed with the rest of the K-3D package which generates unique identifiers. For your reference, k3d-uuidgen uses the uuidgen program which is part of the e2fsprogs package. Run it now:
$ k3d-uuidgen k3d-uuidgen version 0.5.0.20 You can paste the following unique ID into your new K-3D plugin or module: 0x3edefa05, 0xd5ed4f35, 0x89c1f0f6, 0x2b1e0648
- Tip: If you installed k3d from the tar or you have two versions of k3d, you will probably want to use the newer version. So for the following commands replace: (where /usr/local/k3d may change depending on the --prefix option when installing)
- k3d -> /usr/local/k3d/bin/k3d
- k3d-uuidgen -> /usr/local/k3d/bin/k3d-uuidgen
- k3d-config -> /usr/local/k3d/bin/k3d-config
- Replace where needed
Of course, you will have a different ID when you run k3d-uuidgen on your machine. Now, create the file mymodule.cpp, and add the following code, using your new module identifier:
#include <k3dsdk/module.h> K3D_MODULE_START(libk3dmymodule, k3d::uuid(0x3edefa05, 0xd5ed4f35, 0x89c1f0f6, 0x2b1e0648), Registry) K3D_MODULE_END
Believe-it-or-not, these three lines are all it takes to create a (minimal) K-3D plugin module. The K3D_MODULE_START and K3D_MODULE_END macros are used to declare the entry-point functions that all K-3D plugin modules must implement. More on that later, but first - let's build the module and see that K-3D will load it:
$ gcc -shared -o mymodule.so mymodule.cpp `k3d-config --cflags --libs` -lstdc++
- Tip:If you have problems with the headers (i.e. you dont have them on /usr/include/ or you have two versions of k3d) you can tell the compiler where your headers are with the -I option.
- For example, if you extracted the .tar on /home/user/.
- $ gcc -shared -o mymodule.so mymodule.cpp `k3d-config --cflags --libs` -lstdc++ -I/home/user/k3d-0.6.3.1/
- Where /home/user/k3d-0.6.3.1/ has the k3dsdk directory (also could be /usr/local/k3d/include/k3d/ if installed)
You should see a new shared library named mymodule.so. Now, run K-3D and see that the module is successfully loaded:
$ k3d --plugins `pwd` --log-level debug --ui none --exit
The --plugins `pwd` option tells K-3D to look in the current directory for plugins, so it will find your new plugin module. --ui none disables the graphical user interface, --log-level debug ensures that all log messages are printed to the console, and --exit instructs the program to exit immediately after loading. In the console output, you should see:
INFO: Loading plugin module mymodule.so
which indicates that your new module was successfully loaded!
Add a Plugin
Now that we've verified that the new module can be loaded by K-3D we're ready to add a plugin to the module. Before writing any code, however, we need to generate a unique class identifier. Just as module identifiers help K-3D keep track of different modules, class identifiers help K-3D keep track of different plugin types. Run k3d-uuidgen again, to create a new identifier for your plugin:
$ k3d-uuidgen k3d-uuidgen version 0.5.0.20 You can paste the following unique ID into your new K-3D plugin or module: 0x35b34163, 0xa1ee432a, 0x8f8503d2, 0xf09e7ac7
Once again, you will see a different set of numbers when you run k3d-uuidgen on your machine. Copy the new class identifier, and then add it to mymodule.cpp, along with the code for your new plugin class:
#include <k3dsdk/log.h>
#include <k3dsdk/module.h>
#include <k3dsdk/node.h>
#include <iostream>
namespace libk3dmymodule
{
class myplugin :
public k3d::node
{
public:
myplugin(k3d::idocument& Document) :
k3d::node(Document)
{
std::cerr << debug << "Howdy, World!" << std::endl;
}
~myplugin()
{
std::cerr << debug << "Goodbye, Cruel World!" << std::endl;
}
k3d::iplugin_factory& factory()
{
return get_factory();
}
static k3d::iplugin_factory& get_factory()
{
static k3d::plugin_factory<k3d::document_plugin<myplugin> > factory(
k3d::uuid(0x35b34163, 0xa1ee432a, 0x8f8503d2, 0xf09e7ac7),
"MyPlugin",
"My first K-3D plugin",
"Tutorials",
k3d::iplugin_factory::EXPERIMENTAL);
return factory;
}
};
} // namespace libk3dmymodule
K3D_MODULE_START(libk3dmymodule, k3d::uuid(0x3edefa05, 0xd5ed4f35, 0x89c1f0f6, 0x2b1e0648), Registry)
Registry.register_factory(libk3dmymodule::myplugin::get_factory());
K3D_MODULE_END
Recompile the module as before:
$ gcc -shared -o mymodule.so mymodule.cpp `k3d-config --cflags --libs` -lstdc++
Now, run K-3D:
k3d --plugins "&:`pwd`" --log-level debug
Note that this time around we've specified that K-3D should load plugins from the default location &, along with the directory containing our new module, `pwd` (the colon is the standard directory separator and the double-quotes prevent the shell from misinterpreting the ampersand).
Once the program opens, a new document is created. From the Create menu, select Create > PluginTutorial > MyPlugin. A new instance of your plugin will be created, and its properties (empty, except for the name of the plugin instance) will appear in the Object Properties Panel ... check the console and you should see
DEBUG: Howdy, World!
... confirming that an instance of the myplugin class was created. You can also see "MyPlugin" in the Node List Panel. Finally, when you close the open document, you will see
DEBUG: Goodbye, Cruel World!
... confirming that the plugin instance was destroyed when the document was closed.
Plugin Details
Let's look at the sample plugin code in detail, starting at the top of mymodule.cpp:
#include <k3dsdk/log.h> #include <k3dsdk/module.h> #include <k3dsdk/node.h>
... all of the functionality available to plugin authors is part of the K3DSDK library. K3DSDK includes a large collection of classes and functions for use in plugin development, and is installed as a shared library and header files.
namespace libk3dmymodule
{
...
} // namespace libk3dmymodule
Because K-3D potentially loads a large number of plugin modules at runtime, and because those modules may be written by many people at different times from around the globe, there is a very real potential for linking problems when two modules contain similarly-named objects (e.g. if you downloaded a plugin module that also contained a plugin named myplugin). To minimize the risk, plugin module authors are strongly urged to use the C++ namespace mechanism wherever possible to limit symbol names to a specific namespace scope (of course, the module entry point must be visible to K-3D so it can load the module - which is why K3D_MODULE_START and K3D_MODULE_END are at global scope). By convention, the modules that ship with K-3D use "libk3dfoo" as the namespace for a module named "foo".
class myplugin :
public k3d::node
All K-3D plugins must implement one-or-more mandatory C++ class interfaces (based on their purpose) so that they can be integrated with the rest of the application. Much of the functionality in K3DSDK is provided as base classes which provide default implementations of those interfaces - by deriving your plugin from the correct base class, you can then focus on implementing the functionality that makes your plugin unique. k3d::node is the simplest base class for a plugin, providing the absolute-minimum functionality required for a plugin that can become part of a document. As you move on to writing more useful plugins, you will derive your plugin classes from more specific base classes, overriding their methods to implement your plugin's behavior.
myplugin(k3d::idocument& Document) : k3d::node(Document)
In K-3D, most plugins are document plugins ... which is to say that each plugin becomes part of a specific K-3D document when instantiated. As a result, our plugin class must take a reference to the document in its constructor, and pass that reference along to its base class.
k3d::iplugin_factory& factory()
{
return get_factory();
}
One mandatory method that all document plugins must implement is a factory() method that returns a reference to the plugin factory that created the plugin instance. In this example we defer to a second, static member method that actually returns the factory:
static k3d::iplugin_factory& get_factory()
{
static k3d::plugin_factory<k3d::document_plugin<myplugin> > factory(
k3d::uuid(0x35b34163, 0xa1ee432a, 0x8f8503d2, 0xf09e7ac7),
"MyPlugin",
"My first K-3D plugin",
"Tutorials",
k3d::iplugin_factory::EXPERIMENTAL);
return factory;
}
Plugin factories perform two tasks: they return information that describes the type of plugin that they can create, such as its class identifier, name, description, category, and stable/experimental status; and they create plugin instances on demand. The k3d::plugin_factory template provides all this functionality, and all we have to do is specify the type of plugin, k3d::document_plugin, our plugin class, myplugin, and remaining descriptive data, starting with the class identifier that we generated with k3d-uuidgen. We make the factory for our plugin available as a singleton through a static class method, so that one factory instance is shared among all myplugin instances.
K3D_MODULE_START(k3d::uuid(0x3edefa05, 0xd5ed4f35, 0x89c1f0f6, 0x2b1e0648), Registry) Registry.register_factory(libk3dmymodule::myplugin::get_factory()); K3D_MODULE_END
The main entry point to a K-3D module is called once when the module is loaded. At this time the module must "register" all of the plugin factories that it contains, as you see here. K-3D uses the list of available plugin factories to generate the list of available plugins in the user interface.
Advanced
Multiple Plugins per Module
As alluded to earlier, it's not uncommon for a K-3D module to provide more than one plugin type. To do this, you simply code the other plugins, generating a unique class identifier for each, and register their factories within the module entry point. For example, if we added a second (unshown) plugin to mymodule.cpp called my_other_plugin:
K3D_MODULE_START(k3d::uuid(0x3edefa05, 0xd5ed4f35, 0x89c1f0f6, 0x2b1e0648), Registry) Registry.register_factory(libk3dmymodule::myplugin::get_factory()); Registry.register_factory(libk3dmymodule::my_other_plugin::get_factory()); K3D_MODULE_END
In practice it quickly becomes unwieldy to cram multiple plugins into a single file. As you browse through the K-3D sources, you will see that we generally code one plugin per file. When you do this, however, it's important to remember that your module can only have one entry point - i.e. K3D_MODULE_START/END must appear in one-and-only-one file in your module (you will have link problems, otherwise). Most of the modules that ship with K-3D put the entry point in its own file: module.cpp.
Application Plugins
As mentioned earlier, most plugins are document plugins - they immediately become part of a specific document when created. There is a second class of plugins called application plugins. Application plugins are "global" objects that aren't associated with any particular document. Because of this, application plugins have considerably less required functionality than document plugins - they do not receive a document reference in their constructor, and they need only implement the k3d::iunknown interface. Current examples of application plugins that ship with K-3D include user interface plugins, script engines, and file readers / writers. Other possible uses for application plugins on the horizon include RPC servers, specialized user interface components, and specialized desktop-integration libraries.
Note that the factory for an application plugin is declared differently:
static k3d::iplugin_factory& get_factory()
{
static k3d::plugin_factory<k3d::application_plugin<my_app_plugin> > factory(
k3d::uuid(0xb8302f22, 0x948d48b4, 0x97743db9, 0xced7cfb5),
"MyAppPlugin",
"An application plugin",
"Tutorials",
k3d::iplugin_factory::EXPERIMENTAL);
return factory;
}
Interface Lists
As you will see later, K-3D plugins communicate with each other via interfaces, which are C++ classes containing nothing but pure-virtual methods. One plugin instance can "query" another for a specific interface using the dynamic_cast operator. However it's often necessary to find out whether a plugin will support a specific interface before creating it. As an example, script engines are plugin instances that implement the k3d::iscript_engine interface. Before K-3D can run a script, it has to have a script engine to run it; before it can create a script engine, it has to figure out which plugin factories will create plugins that are script engines. To do this, plugin factories provide a mechanism called Interface Lists that allow them to tell K-3D in advance whether the plugin instances they create will support a specific interface.
As an example, assume you are creating a script engine, so your plugin must implement the k3d::iscript_engine interface, and your plugin factory must advertise this fact. In this case, you would provide an interface list containing k3d::iscript_engine in your plugin factory:
static k3d::iplugin_factory& get_factory()
{
static k3d::plugin_factory<k3d::application_plugin<my_script_engine>,
k3d::interface_list<k3d::iscript_engine> > factory(
k3d::uuid(0xb8302f22, 0x948d48b4, 0x97743db9, 0xced7cfb5),
"MyScriptEngine",
"A script engine plugin",
"Tutorials",
k3d::iplugin_factory::EXPERIMENTAL);
return factory;
}

