[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Written by Jorrit Tyberghein, jorrit.tyberghein@gmail.com and Eric Sunshine, sunshine@sunshineco.com.
Making a plugin in Crystal Space is not very hard but nevertheless there are still a few issues that are often forgotten. Here in this article we show you how you can write a simple plugin and use it in your application.
The first thing that you need to do when making a plugin is to define the API for it. The API is what your application is going to use to talk to the plugin. It is the interface to the plugin so it is very important to get this right. In the Crystal Space framework the Shared Class Facility (see section Shared Class Facility (SCF)) is used to define the API. With this facility you create an abstract interface containing only the methods from the API. An abstract class in C++ means that all methods are pure virtual. This means that no implementation is given; only method declarations. The implementation will come later in the code of the plugin.
This concept is completely analogous to the Java interface mechanism. The advantage of using this paradigm is that you have a clear separation between the API and the implementation. This allows one to easily replace an implementation of some API or even provide multiple implementations (for example, the software and OpenGL renderers are two implementations of the same 3D rendering API).
Here is the API definition for our sample plugin:
#ifndef __GAME_MYAPI_H__ #define __GAME_MYAPI_H__ #include <csutil/scf.h> class csVector3; SCF_VERSION (iMyApi, 0, 0, 1); /** * This is the API for our plugin. It is recommended * that you use better comments than this one in a * real situation. */ struct iMyApi : public iBase { /// Do something. virtual void DoSomething (int param, const csVector3&) = 0; /// Get something. virtual int GetSomething () const = 0; }; #endif // __GAME_MYAPI_H__ |
The above text should be put in a header file. Let's put it in `myapi.h'.
First we include `csutil/scf.h'. This is a Crystal Space header
for SCF which we need to get the definition of `iBase' and the
definition of the SCF_VERSION()
macro.
Then we declare `csVector3' as a class. We do this so that we can later use `csVector3' as a parameter in one of the API methods. We do not need the complete definition of `csVector3' since we are going to define the method so that it passes the vector by reference.
After this we use the SCF_VERSION()
macro to define the version
of this interface. This versioning can be used to query for
specific versions of an interface. This can be useful later when you
want to extend the API without breaking existing apps. The version
has three parts: major, minor, and micro.
Finally we define the API by making a structure that inherits from `iBase'. We use `struct' instead of `class' simply because, for structures, the default visibility is `public' instead of `private' as for classes. This is just a convenience. There is no other difference between a `struct' or a `class' in C++.
The name `iMyApi' is not random. Crystal Space uses this naming convention (starting a name with `i') for SCF interfaces so that it is easy to see that they refer to SCF interfaces.
We inherit from `iBase' because it is the basis of all SCF interfaces. All SCF interfaces must inherit from `iBase' either directly or indirectly. This will ensure that we have reference counting (more on that later) and also takes care of the other internal SCF issues.
In that structure we define two methods: DoSomething()
and
GetSomething()
. Note that every method is defined as follows:
virtual ... = 0; |
The `= 0' means that we will not give an implementation here. The implementation will be provided by the plugin (see later).
Note that it is good practice to use `const' wherever applicable. In the
declaration of GetSomething()
we added `const' at the end to
indicate that this method will not change the object. This is useful
for a number of reasons:
After you defined the API for your plugin it is now time to actually make the plugin implementation. First you define a header called `myplug.h' with the following contents:
#ifndef __GAME_MYPLUG_H__ #define __GAME_MYPLUG_H__ #include <iutil/comp.h> #include <csgeom/vector3.h> #include <myapi.h> struct iObjectRegistry; /** * This is the implementation for our API and * also the implementation of the plugin. */ class MyPlugin : public iMyApi { private: iObjectRegistry* object_reg; csVector3 store_v; public: SCF_DECLARE_IBASE; MyPlugin (iBase* parent); virtual ~MyPlugin (); bool Initialize (iObjectRegistry*); virtual void DoSomething (int param, const csVector3&); virtual int GetSomething () const; struct Component : public iComponent { SCF_DECLARE_EMBEDDED_IBASE (MyPlugin); virtual bool Initialize (iObjectRegistry* r) { return scfParent->Initialize (r); } } scfiComponent; }; #endif // __GAME_MYPLUG_H__ |
This requires a little explanation. The Crystal Space plugin framework
requires that every named SCF class which will be requested by name
from a plugin module via the Crystal Space plugin manager/loader must
implement the `iComponent' interface. This interface has a single method,
Initialize()
, with which the class will be initialized after it is
instantiated. This gives the instance a chance to perform various
initialization operations and it also provides the instance with a pointer to
the global object registry.
But, our plugin also needs to implement its own native `iMyApi' interface. So here is a situation where the same class needs to implement two interfaces at the same time. There are basically two ways to do this: multiple inheritance, or via use of an embedded SCF class. We use the second technique here since it tends to be more portable and works better with older compilers which may have trouble supporting multiple inheritance.
In the example above the class `MyPlugin' inherits from
`iMyApi'. The methods from `iMyApi' are implemented directly in
`MyPlugin'. To do that, the method declarations from `iMyApi' are
copied to `MyPlugin' except that the `= 0' is removed. To indicate
that this class represents an implementation of an SCF interface,
we additionally need the SCF_DECLARE_IBASE()
macro. This macro
will take care of declaring the DecRef()
and IncRef()
functions
which handle reference counting. In addition the macro also
declares QueryInterface()
so that it is possible to request
other interfaces (like `iComponent)' from this class. You don't need
to worry much about this.
Note that `MyPlugin' needs a constructor that accepts an `iBase*' parameter. Otherwise SCF will not be able to intantiate this class.
To implement `iComponent' we add an inner class called `Component'.
This new class will inherit directly from `iComponent' which allows
`MyPlugin' to implement `iComponent' indirectly. Since this is an
embedded interface we now need the SCF_DECLARE_EMBEDDED_IBASE()
macro.
This macro takes care of the fact that this interface actually belongs to the
parent class (which is given as a parameter). One thing it does is declare a
variable of type `MyPlugin' which is called `scfParent'. Through
that variable the method implementations of the embedded interface can access
the main class. After declaring the class `Component' we immediately
create an instance of this called `scfiComponent'. This name is not
chosen at random. It is composed of the token `scf' followed by the name
of the SCF interface which this embedded object implements. The
SCF_IMPLEMENTS_EMBEDDED_INTERFACE()
macro (see below) expects the name
to be composed in this fashion.
Sometimes embedded classes are also made friend of the main
class so that they can access the private information. In this case
that is not needed. The only thing that `Component' has is an
Initialize()
method which immediately transfers control to
the parent's public Initialize()
method.
Now we create the main source file containing the implementation of our plugin. Let's call this `myplug.cpp':
#include <cssysdef.h> #include <myplug.h> #include <iutil/objreg.h> #include <iutil/plugin.h> CS_IMPLEMENT_PLUGIN SCF_IMPLEMENT_IBASE (MyPlugin) SCF_IMPLEMENTS_INTERFACE (iMyApi) SCF_IMPLEMENTS_EMBEDDED_INTERFACE (iComponent) SCF_IMPLEMENT_IBASE_END SCF_IMPLEMENT_EMBEDDED_IBASE (MyPlugin::Component) SCF_IMPLEMENTS_INTERFACE (iComponent) SCF_IMPLEMENT_EMBEDDED_IBASE_END SCF_IMPLEMENT_FACTORY (MyPlugin) MyPlugin::MyPlugin (iBase* parent) : object_reg(0) { SCF_CONSTRUCT_IBASE (parent); SCF_CONSTRUCT_EMBEDDED_IBASE (scfiComponent); } MyPlugin::~MyPlugin () { SCF_DESTRUCT_EMBEDDED_IBASE (scfiComponent); SCF_DESTRUCT_IBASE (); } bool MyPlugin::Initialize (iObjectRegistry* r) { object_reg = r; return true; } void MyPlugin::DoSomething (int param, const csVector3& v) { // Just some behavior. if (param == 1) store_v = v; else store_v = -v; } int MyPlugin::GetSomething () const { return (int)store_v.x + (int)store_v.y + (int)store_v.z; } |
The first macro is CS_IMPLEMENT_PLUGIN()
. This indicates to the
Crystal Space framework that this module will end up as a plugin
(as opposed to an application or library). On some platforms this
actually makes a difference; on others it does not. For best portability,
you should use this macro in exactly one C++ file within each plugin
module.
The SCF_IMPLEMENT_IBASE()
describes what interfaces the class
`MyPlugin' implements. This section says that `MyPlugin' implements
`iMyApi' directly and `iComponent' through embedding.
The SCF_IMPLEMENT_EMBEDDED_IBASE()
lists the interfaces
implemented by the class `MyPlugin::Component' (the embedded class).
In this case, `MyPlugin::Component' implements only `iComponent'.
It is important to correctly use the above macros. These macros ensure
that the implementation for IncRef()
, DecRef()
, and
QueryInterface()
are provided by your custom class.
The SCF_IMPLEMENT_FACTORY()
says that C++ class `MyPlugin'
represents an SCF factory which allows SCF to instantiate objects of
this class. In addition to some other administrative tasks, this macro defines
a function capable of instantiating an object of class `MyPlugin'. Note
that one plugin module can in fact define several distinct named SCF
classes. In that case you need multiple SCF_IMPLEMENT_FACTORY()
lines;
one for each exported SCF class.
SCF_IMPLEMENT_..._IBASE()
and SCF_IMPLEMENT_FACTORY()
) are
general purpose SCF macros. As such they are not specifically related to
plugin modules; but rather help to define SCF classes.
In the constructor of `MyPlugin' you must call
SCF_CONSTRUCT_IBASE()
for the main interface and
SCF_CONSTRUCT_EMBEDDED_IBASE()
for each embedded interface. These
macros will ensure that the object is initialized correctly (i.e. reference
count set to 1, and so on). Likewise, in the destructor, you must call the
corresponding SCF_DESTRUCT_IBASE()
and
SCF_DESTRUCT_EMBEDDED_IBASE()
macros.
The rest of the plugin is very straightforward. It is important
to realize that you should do most initialization of the plugin
in the Initialize()
function and not in the constructor. The reason
for this is that, at construction time, you cannot depend on the entire
Crystal Space framework being ready. Also when Initialize()
is
called you get a pointer to the object registry which is essential
for locating other modules and plugins loaded by the Crystal Space
framework.
SCF discovers plugins automatically and dynamically. It determines which plugin modules implement which SCF classes by consulting meta-information associated with each plugin. The meta-information file for your plugin must have the same basename as your built plugin module, but with extension `.csplugin'. For instance, if the example plugin is built with the name `myplugin.dll' (Windows) or `myplugin.so' (Unix), then the associated meta-information file should be named `myplugin.csplugin'. At build-time, the meta-information may be embedded directly into the plugin module if supported by the platform and if embedding is enabled. If not, then then the `.csplugin' file will be laid down alongside the built plugin module.
The meta-information file is a structured XML-format document, and can
contain any information relevant to the plugin module; it is not limited only
to SCF information. SCF itself expects to find a node named
<scf>
, which contains SCF-related information about the plugin
module.
The `myplugin.csplugin' meta-information file for our example plugin module might look like this:
<?xml version="1.0"?> <!-- myplugin.csplugin --> <plugin> <scf> <classes> <class> <name>crystalspace.mygame.myplugin</name> <implementation>MyPlugin</implementation> <description>My Special Game Plugin</description> <requires> <class>crystalspace.graphics3d.</class> </requires> </class> </classes> </scf> </plugin> |
Each named SCF class exported by the plugin should be presented in a
<class>
node within the <classes>
group. Each class has a
<name>
, which is the SCF name of the class; an
<implementation>
, which is the name of the C++ class implementing the
SCF class; a <description>
; and optionally a <requires>
node, which lists the other SCF classes upon which this class depends.
Any number of classes may appear in the <requires>
group. If your
plugin depends only upon a certain type of class, rather than a specific
SCF class, then you list only the prefix portion of the desired class
type, as shown in this example (where we desire any 3D renderer).
Depending on the development tools that you use, you should refer to one of the HOWTO's on the subject of building an external Crystal Space module.
First, include the header defining the API of the plugin:
#include <myapi.h> |
Do not include the `myplug.h' header file since it is implementation specific and you should not use the implementation of the plugin directly. Doing this invalidates the entire reason to use plugins in the first place.
To load the plugin there are a few possibilities. First, you
can load the plugin manually using CS_LOAD_PLUGIN()
like this:
csRef<iPluginManager> plugin_mgr = CS_QUERY_REGISTRY (object_reg, iPluginManager); csRef<iMyApi> myapi = CS_LOAD_PLUGIN (plugin_mgr, "crystalspace.mygame.myplugin", iMyApi); if (myapi.IsValid()) { ... } |
This will get the plugin manager from the object registry. This is the module that is responsible for loading and unloading plugins. The code then uses the plugin manager to load your plugin. Note that this can fail. You should always check the returned value to see if it is valid.
Another way to load the plugin is through RequestPlugins()
, which
is called at initialization time:
if (!csInitializer::RequestPlugins (object_reg, CS_REQUEST_VFS, CS_REQUEST_SOFTWARE3D, CS_REQUEST_ENGINE, ... CS_REQUEST_PLUGIN("crystalspace.mygame.myplugin", iMyApi), CS_REQUEST_END)) { ... } ... csRef<iMyApi> myapi = CS_QUERY_REGISTRY (object_reg, iMyApi); |
This way has several advantages. First, it allows the user to override your plugin at the command line or in the configuration file (if your program has one). In cases where there are multiple implementations for the same API this can be an important consideration. It is by doing this, for example, that it is possible to switch between software and OpenGL renderers with the command-line `--video=' option, or via configuration file.
Secondly it registers the plugin with the object registry so that it is easier to find your module later. This also allows other plugins to find your plugin by doing a query on the object registry.
After loading the plugin you can use the plugin simply by calling the methods defined in the API:
myapi->DoSomething (1, csVector3 (2, 3, 4)); printf ("%d\n", myapi->GetSomething ()); |
This should print out 9.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] |
This document was generated using texi2html 1.76.