Plugin Tutorial

From K-3D

Revision as of 15:50, 1 May 2010 by Tshead (Talk | contribs)
Jump to: navigation, search

Overview

This tutorial will guide you through the creation of a new plugin module and plugin. In the following examples we will create a plugin module named "sample_module" that contains a plugin named "sample_plugin". You can use 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 built K-3D 0.7 or later from source, see Getting Started for details. You should also read Plugin Design for an overview of how K-3D plugins work.

Note: copies of the source code for this tutorial are available in the K-3D source tree under docs/sample_module.

Creating a Module

First, you will create a new plugin module (shared library) that can be loaded into K-3D at runtime. To begin, you should create a new directory where you will create the plugin module sources. Then, create a file module.cpp, and add the following code:

#include <k3dsdk/module.h>

K3D_MODULE_START(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 function that all K-3D plugin modules must implement. The module doesn't contain any plugins yet, but we can still build it and see that K-3D loads it.

Building a Module

Although you could create your own private makefiles or IDE project files to build your new plugin module, K-3D provides an innovative way to integrate plugins into the K-3D build, instead. By incorporating your module into K-3D's build you save time and effort, and your binary module can be automatically packaged and installed along with the rest of K-3D, creating your own personal K-3D distribution!

To make it happen, add a new file to your module directory, CMakeLists.txt:

K3D_BUILD_MODULE(sample-module)
K3D_CREATE_MODULE_PROXY(sample-module)

Now you're ready to include your module in the K-3D build. To do so, cd into your K-3D build directory and run cmake:

$ cd ~/k3d-build
$ make edit_cache

Using the cmake UI, add sample_module to the K3D_EXTERNAL_MODULES variable, and configure. CMake will display a harmless error that we will correct in the next step:

CMake Error: The "sample_module" module source directory "sample_module_EXTERNAL_SOURCE_DIR-NOTFOUND" does not exist.
Specify a different directory using sample_module_EXTERNAL_SOURCE_DIR

You will see that two new cmake variables have been added:

  • K3D_BUILD_sample_module - Enables / disables compilation of your module.
  • sample_module_EXTERNAL_SOURCE_DIR - Points to the directory containing your module source code and CMakeLists.txt file.

K3D_BUILD_sample_module will already be "ON", but you must alter sample_module_EXTERNAL_SOURCE_DIR to contain the absolute path to your new plugin directory. Once you've done this, configure; generate; and you'll be ready to build K-3D, including your new module:

$ make

You will see your module built along with the rest of K-3D:

...
[ 99%] Built target k3d-yafray-proxy
Scanning dependencies of target sample-plugin
[ 99%] Building CXX object modules/external/CMakeFiles/sample-plugin.dir/__/__/__/k3d/docs/sample_plugin/module.o
Linking CXX shared library ../../lib/k3d/plugins/sample-plugin.module
[ 99%] Built target sample-module
[ 99%] Generating ../../lib/k3d/plugins/sample-module.module.proxy
Loading plugin module /home/tshead/k3d-build/lib/k3d/plugins/sample-module.module
[100%] Built target sample-module-proxy

Note the location of your module binary, along with the rest of the standard K-3D plugins:

$ ls lib/k3d/plugins/
...
sample-module.module
sample-module.module.proxy

Now, run K-3D and see that the module is successfully loaded:

$ make run

In the console output, you will see:

INFO: Proxying plugin module /home/user/k3d-build/lib/k3d/plugins/sample-module.module

which indicates that your new module was successfully loaded!

Creating a Plugin

Now that we've verified that the new module can be loaded by K-3D we're ready to create a plugin. Before writing any code, however, we need to generate a unique plugin identifier. A plugin identifier is a universally-unique 128-bit number that is used by K-3D to identify a specific type of plugin. It's important that all K-3D plugin types everywhere have unique identifiers to prevent confusion when they're loaded, so you'll need to generate a new plugin identifier every time you create a new plugin. K-3D includes a small program - k3d-uuidgen - that generates random identifiers. You can run it from your K-3D build directory:

$ cd ~/k3d-build
$ bin/k3d-uuidgen
k3d-uuidgen version 0.7.0.0
You can paste the following unique ID into your new K-3D plugin:
0x35b34163, 0xa1ee432a, 0x8f8503d2, 0xf09e7ac7

Of course, the ID produced by k3d-uuidgen will be different when you run it. Copy the new plugin identifier, and add it to module.cpp, along with the rest of the code for your new plugin class:

#include <k3dsdk/document_plugin_factory.h>
#include <k3dsdk/log.h>
#include <k3dsdk/module.h>
#include <k3dsdk/node.h>

#include <iostream>

namespace module
{

namespace sample
{

class plugin :
        public k3d::node
{
public:
        plugin(k3d::iplugin_factory& Factory, k3d::idocument& Document) :
                k3d::node(Factory, Document)
        {
                k3d::log() << debug << "Howdy, World!" << std::endl;
        }

        ~sample_plugin()
        {
                k3d::log() << debug << "Goodbye, Cruel World!" << std::endl;
        }

        static k3d::iplugin_factory& get_factory()
        {
                static k3d::document_plugin_factory<plugin> factory(
                        k3d::uuid(0x35b34163, 0xa1ee432a, 0x8f8503d2, 0xf09e7ac7),
                        "SamplePlugin",
                        "Sample K-3D document plugin",
                        "Tutorials",
                        k3d::iplugin_factory::EXPERIMENTAL);

                return factory;
        }
};

} // namespace sample

} // namespace module

K3D_MODULE_START(Registry)
        Registry.register_factory(module::sample::plugin::get_factory());
K3D_MODULE_END

Recompile and start K-3D:

$ make
$ make run

Once the program opens, a new document is created. From the Create menu, select Create > Tutorials > SamplePlugin. 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 sample_plugin class was created. You can also see "SamplePlugin" 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 module.cpp:

#include <k3dsdk/document_plugin_factory.h>
#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 module
{

namespace sample
{

...
} // namespace sample

} // namespace module

Because K-3D 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 from someone else that also contained a plugin class named "plugin"). 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 "namespace module { namespace foo { ... } }" as the namespace for a module named "foo".

class plugin :
       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.

 sample_plugin(k3d::iplugin_factory& Factory, k3d::idocument& Document) :
   k3d::node(Factory, 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. Document plugins must provide methods that return references to the plugin factory that created the plugin, and the document that it is a member of. As a result, our plugin class must take references to the factory and document in its constructor, and pass those references along to its base class.

Finally, we provide a singleton instance of a plugin factory for our plugin type:

 static k3d::iplugin_factory& get_factory()
 {
   static k3d::plugin_factory<k3d::document_plugin<plugin> > factory(
     k3d::uuid(0x35b34163, 0xa1ee432a, 0x8f8503d2, 0xf09e7ac7),
     "SamplePlugin",
     "Sample K-3D document 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::document_plugin_factory template provides all this functionality, and all we have to do is specify our plugin class - "plugin" - 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.

K3D_MODULE_START(Registry)
 Registry.register_factory(module::sample::plugin::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 module.cpp called sample_plugin_2:

K3D_MODULE_START(Registry)
 Registry.register_factory(libk3dmymodule::sample_plugin::get_factory());
 Registry.register_factory(libk3dmymodule::sample_plugin_2::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 put each plugin into its own separate 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::application_plugin_factory<my_app_plugin> factory(
     k3d::uuid(0xb8302f22, 0x948d48b4, 0x97743db9, 0xced7cfb5),
     "sample_application_plugin",
     "Sample K-3D application plugin",
     "Tutorials",
     k3d::iplugin_factory::EXPERIMENTAL);

   return factory; 
 }
Personal tools