Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

C/C++

Building Your Own Plugin Framework: Part 5


Gigi Sayfan specializes in cross-platform object-oriented programming in C/C++/ C#/Python/Java with emphasis on large-scale distributed systems. He is currently trying to build intelligent machines inspired by the brain at Numenta (www.numenta.com).


This is the final article in a series about developing cross-platform plugins in C++. In previous articles -- Part 1, Part 2, Part 3, and Part 4 -- I examined the difficulties of working with C++ plugins in portable way.

In this installment, I cover the missing pieces of the sample game introduced in Part 4 and give a demonstration. I also take a quick tour of the source code that accompanies this series, tell a few good stories (it's about time), and finally compare the plugin framework to the NuPIC (Numenta's Platform for Intelligent Computing) plugin framework, which is its conceptual ancestor. But first, let's take a look at some monster plugins that will be loaded into the game.

Monster Plugins

I created four different plugins to demonstrate the scope and diversity of the plugin framework.

  • A pure C++ plugin
  • A pure C plugin
  • A hybrid plugin deployed as dynamic/shared libraries
  • One static C++ plugin that should be linked directly to the executable.

All these plugins register their monsters with the PluginManager as actors. In addition, the game itself implements the Hero as an object that implements the IActor interface and large parts of the code that work at the IActor interface don't (and can't distinguish) between the game-supplied Hero and any monster. The game could also provide some built-in monsters.

Dynamic C++ Plugins

The dynamic C++ plugin registers the KillerBunny and StationarySatan monsters. Listing One is the KillerBunny.h header where the KillerBunny class is defined. KillerBunny is derived directly from the C++ IActor interface (which makes it a pure C++ plugin). It implements the create() and destroy() static functions to support creation and destruction via the PluginManager. The StationarySatan and any other pure C++ plugin object should look exactly the same (except for private members if any).

#ifndef KILLER_BUNNY_H
#define KILLER_BUNNY_H
#include <object_model/object_model.h>
struct PF_ObjectParams;
class KillerBunny : public IActor
{
public:
  // static plugin interface
  static void * create(PF_ObjectParams *); 
  static apr_int32_t destroy(void *);
  ~KillerBunny();
  // IActor methods
  virtual void getInitialInfo(ActorInfo * info);
  virtual void play(ITurn * turnInfo);
private:
  KillerBunny();
};
#endif
Listing One

Example 1 contains the implementation of create() and destroy(). They are almost trivial. The create() function simply instantiates a new KillerBunny object and returns it (as opaque void pointer). The destroy() function accepts a void pointer, which is actually a pointer to an instance created earlier using the create() function. It casts the void pointer to a KillerBunny pointer and deletes it. The important part here is that these functions let the PluginManager create KillerBunny objects without "knowing" anything about the KillerBunny class. The returned instance is usable via the IActor interface later (even though it is returned as a void pointer).

void * KillerBunny::create(PF_ObjectParams *)
{
  return new KillerBunny();
}
apr_int32_t KillerBunny::destroy(void * p)
{
  if (!p)
    return -1;
  delete (KillerBunny *)p;
  return 0;
}
Example 1

Example 2 contains the implementation of the IActor interface methods. These methods are trivial too. The getInitialInfo() method simply populates the ActorInfo struct with some data. The play() method is where a real KillerBunny will do the actual work, run around, avoid or attack enemies, and generally justify its name. Here, it just gets the list of friends from the ITurn interface to verify it works. This is just laziness on my part, and in fact all the monsters don't do anything. The Hero is the only one actually fighting. The monsters do defend themselves when attacked and even retaliate.

void KillerBunny::getInitialInfo(ActorInfo * info)
{
  ::strcpy((char *)info->name, "KillerBunny");
  info->attack = 10;
  info->damage = 3;
  info->defense = 8;
  info->health = 20;
  info->movement = 2;

  // Irrelevant. Will be assigned by system later
  info->id = 0;
  info->location_x = 0;
  info->location_y = 0;
}
void KillerBunny::play(ITurn * turnInfo)
{
  IActorInfoIterator * friends = turnInfo->getFriends();
}
Example 2

The main point here is that writing pure C++ plugin objects is pretty easy. Other than the boilerplate create() and destroy() static methods, you just implement a standard C++ class. No arcane incantations are required.

Listing Two contains the plugin initialization code. It's not too bad, but it's boring and error prone: Define an exit function, in the PF_initPlugin function, define a PF_RegisterParams struct, populate it, and register every plugin object. Make sure you return NULL if initialization failed. Less than exhilarating. That's all it takes to write a pure C++ monster plugin (with two monsters).

#include "cpp_plugin.h"
#include "plugin_framework/plugin.h"
#include "KillerBunny.h"
#include "StationarySatan.h"

extern "C" PLUGIN_API apr_int32_t ExitFunc()
{
  return 0;
}
extern "C" PLUGIN_API PF_ExitFunc PF_initPlugin(const PF_PlatformServices * params)
{
  int res = 0;
    
  PF_RegisterParams rp;
  rp.version.major = 1;
  rp.version.minor = 0;
  rp.programmingLanguage = PF_ProgrammingLanguage_CPP;
  
  // Register KillerBunny
  rp.createFunc = KillerBunny::create;
  rp.destroyFunc = KillerBunny::destroy;
  res = params->registerObject((const apr_byte_t *)"KillerBunny", &rp);
  if (res < 0)
    return NULL;

  // Regiater StationarySatan
  rp.createFunc = StationarySatan::create;
  rp.destroyFunc = StationarySatan::destroy;
  res = params->registerObject((const apr_byte_t *)"StationarySatan", &rp);
  if (res < 0)
    return NULL;

  return ExitFunc;
}
Listing Two


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.