Channels ▼


Building Your Own Plugin Framework: Part 4


ActorBaseTemplate is the heart of the hybrid approach. The plugin developer just has to derive from it and implement the C++ IActor interface and automatically the plugin will communicate with the plugin manager via the C interface and provide full binary compatibility. The pluguin developer should never see the C interface or even be aware of it.

This template provides many services to its sub-classes so let's take it slowly. Example 1 contains the declaration of the template.

template >typename T, typename Interface=C_Actor<
class ActorBaseTemplate : 
  public C_Actor,
  public IActor
Example 1

There are two template parameters: T and Interface. T is type of the subclass and when you derive from ActorBaseTemplate you must provide the type of the derived class to the base class (template). This is an instance of CRTP, the "Curiously Recurring Template Pattern". Interface is the interface that the plugin object will use to communicate with the plugin manager. It can be the C++ IActor or the C C_Actor. By default it is C_Actor. You may wonder why is not always C_Actor. After all if the plugin object wishes to communicate with the plugin manager using C++ it can just register itself as a C++ object and directly derive from IActor. This is good thinking. The reason AutoBaseTemplate supports IActor too, is to let you switch effortlessly from C to C++ interfaces. This is useful during debugging when you want to skip the whole C wrapper code and also if you want to deploy in a controlled environment and you don't need the full C compatibility. In this case, with a flip of a template parameter and you change the underlying communication channel.

ActorBaseTemplate derives from both C_Actor and IActor. It even provides a trivial implementation of IActor in case you want to implement only part of the interface. That saves you from declaring empty methods yourself. The C_Actor is the critical interface because this is the interface used to communicate with the plugin manager when Interface=C_Actor.

Example 2 is the constructor.

ActorBaseTemplate() : invokeService_(NULL)
    // Initialize the function pointers of the C_Actor base class
    C_Actor::getInitialInfo = staticGetInitialInfo;
    C_Actor::play = staticPlay;
    C_Actor * handle = this;
    C_Actor::handle = (C_ActorHandle)handle;
Example 2

It accepts no arguments initializes the invokeService_ function pointer to NULL and goes on to initialize the members of its C_Actor interface to point to static functions and the assigns the this pointer to the handle. This is similar to the C/C++ dual object model and indeed it is a dual object, except that the actual C++ implementation that does the real work is in the derived class.

Example 3 is the mandatory PF_CreateFunc and PF_DestroyFunc that are registered with the plugin manager and are invoked to create and destroy instances.

// PF_CreateFunc from plugin.h
static void * create(PF_ObjectParams * params)
T * actor = new T(params);
// Set the error reporting function pointer
actor->invokeService_ = params->platformServices->invokeService;

// return the actor with the correct inerface
return static_cast<Interface *>(actor);

// PF_DestroyFunc from plugin.h
static apr_int32_t destroy(void * actor)
if (!actor)
  return -1;
delete ActorBaseTemplate<T, Interface>::getSelf(reinterpret_cast<Interface *>(actor));
return 0;
Example 3

They are named create() and destroy() but the names are irrelevant because they are registered and invoked as function pointer and not by name. The fact that ActorBaseTemplate defines them saves a lot of headaches to aspiring plugin developers. The create() function simply creates a new instance of T (the derived class) and initalizes assigns the invokeService function pointer to the invokeService_ data member. The destroy() function casts the void pointer it gets to the Interface template arguments and then use the getSelf() method (discussed shortly) to get a properly typed pointer to the T derived class. It subsequently calls delete to destroy the instance for good. This is really nice. The plugin developer creates a simple C++ class with a standard constructor (that accepts PF_ObjectParams, but it can ignore it) and destructor and the ActorBaseTemplate does its magic under the covers and make sure that all the weird static functions will be routed properly to derived class.

Example 4 contains the thrice-overloaded getSelf() static method.

// Helper method to convert the C_Actor * argument 
// in every method to an ActorBaseTemplate<T, Interface> instance pointer
static ActorBaseTemplate<T, Interface> * getSelf(C_Actor * actor)
return static_cast<ActorBaseTemplate<T, Interface> *>(actor);
static ActorBaseTemplate<T, Interface> * getSelf(IActor * actor)
return static_cast<ActorBaseTemplate<T, Interface> *>(actor);
static ActorBaseTemplate<T, Interface> * getSelf(C_ActorHandle handle)
return static_cast<ActorBaseTemplate<T, Interface> *>((C_Actor *)handle);
Example 4

There are three overloads for IActor, C_Actor, and C_ActorHandle. The getSelf() method just performs a static_cast to get from the interface to full dual object as you have seen before. In the case of the handle it just performs a C cast to make it a C_Actor. As you saw in the constructor and later again the ActorBaseTemplate often gets an Interface or handle when it really needs itself to keep going.

Example 5 contains the static reportError method.

// Helper method to report errors from a static function
  static void reportError(C_ActorHandle handle, 
                          const apr_byte_t * filename, 
                          apr_uint32_t line, 
                          const apr_byte_t * message)
    ActorBaseTemplate<T, Interface> * self = ActorBaseTemplate<T, Interface>::getSelf(handle);
    ReportErrorParams rep;
    rep.filename = filename;
    rep.line = line;
    rep.message = message;
    self->invokeService_((const apr_byte_t *)"reportError", &rep);
Example 5

This is a convenience function that forwards the call to the invokeService function pointer. It saves the caller from packing its arguments into the ReportErrorParams defined by the application's services.h header and from invoking the service with the "reportError" string. These error-reporting conventions are defined by the application service layer and are immaterial to the plugin developer who just wants to churn out plugin objects as fast and easy as possible.

Example 6 contains the implementation of the C_Actor interface.

// C_Actor functions
  static void staticGetInitialInfo(C_ActorHandle handle, C_ActorInfo * info)
    ActorBaseTemplate<T, Interface> * self = ActorBaseTemplate<T, Interface>::getSelf(handle);
    catch (const StreamingException & e)
      ActorBaseTemplate<T, Interface>::reportError(handle, (const apr_byte_t *)e.filename_.c_str(), e.line_, (const apr_byte_t *)e.what());
    catch (const std::runtime_error & e)
      ActorBaseTemplate<T, Interface>::reportError(handle, (const apr_byte_t *)__FILE__, __LINE__, (const apr_byte_t *)e.what());
    catch (...)
      ActorBaseTemplate<T, Interface>::reportError(handle, (const apr_byte_t *)__FILE__, __LINE__, (const apr_byte_t *)"ActorBaseTemplate<T, Interface>::staticGetInitialInfo() failed");
  static void staticPlay(C_ActorHandle handle, C_Turn * turn)
      TurnWrapper tw(turn);
      getSelf((C_Actor *)handle)->play(&tw);
    catch (const StreamingException & e)
      ActorBaseTemplate<T, Interface>::reportError(handle, (const apr_byte_t *)e.filename_.c_str(), e.line_, (const apr_byte_t *)e.what());
    catch (const std::runtime_error & e)
      ActorBaseTemplate<T, Interface>::reportError(handle, (const apr_byte_t *)__FILE__, __LINE__, (const apr_byte_t *)e.what());
    catch (...)
      ActorBaseTemplate<T, Interface>::reportError(handle, (const apr_byte_t *)__FILE__, __LINE__, (const apr_byte_t *)"ActorBaseTemplate<T, Interface>::staticPlay() failed");
Example 6

The implementation of both interface functions is almost identical: getSelf(), calls the C++ IActor implementation in the derived class via the wonders of polymorphism and employs robust error handling. Before I discuss the error handling, pay attention to staticPlay() function. It accepts a C_Turn interface, wraps it in a TurnWrapper, and then passes it to the IActor::play() method where it will arrive as a C++ ITurn. This is what the wrappers are for.

The error handling is another nice feature of ActorBaseTemplate. It lets plugin developers forget that they are writing a plugin object that must adhere to strict rules (such as not throwing exceptions across the binary compatibility boundary) and just throw exceptions on error. Every call to the derived class (except for the constructor and destructor) is wrapped in these try-except clauses. There is here a chain of exception handler from the most informative to the least informative. The plugin developer may elect to throw the StreamingException class defined by plugin framework. This is a nice standalone exception class that contains the location (filename and line number) of thrown exception in addition to an error message. If you want to learn more about StreamingException, see Practical C++ Error Handling in Hybrid Environments.

Listing Two contains a few convenient macros for checking and asserting that throw StreamingException on failure.

#ifndef PF_BASE
#define PF_BASE
#include "StreamingException.h"
#define THROW throw StreamingException(__FILE__, __LINE__) \
#define CHECK(condition) if (!(condition)) \
  THROW << "CHECK FAILED: '" << #condition <<"'"
#ifdef _DEBUG
  #define ASSERT(condition) if (!(condition)) \
     THROW <<"ASSERT FAILED: '" << #condition << "'"
  #define ASSERT(condition) {}
#endif // DEBUG
namespace base
  std::string getErrorMessage();
#endif // BASE_H
Listing Two

This is nice for debugging purposes because you the end result is all this information will propagate to the application invokeService() implementation via the reportError() method. If the plugin developer chooses to throw a standard std::runtime_error, then the error-handling code extracts the error message from the what() method, but no meaningful filename and line number will be provided. The __FILE__ and __LINE__ macros will report the file and line number of the error-handling code in ActorBaseTemplate and not the actual location of the error. Finally, the fallback is catching any exception with the elipsis except handler. Here, there isn't even an error message to extract and a generic message that at least records the name of the failed function is provided.

The bottom line is that ActorBaseTemplate frees the plugin developer from all the vagaries of implementing a plugin object and allows the developer concentrate on implementing the object interface in standard C++ (IActor in this case) without getting tangled up with strange requirements like defining special static methods for creation and destruction, reporting error through funny function pointers, or dealing with any shred of C.

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.