Writing a Plugin
What does it mean to write a plugin? The plugin framework is very generic and doesn't provide any tangible objects your application can interact with. You must build your application object model on top of the plugin framework. This means that your application (that loads the plugins) and the plugins themselves will have to agree about and coordinate their interaction model. Usually it means that the application expect the plugin to provide certain types of objects that expose some specific API. The plugin framework will provide all the infrastructure necessary to register, enumerate and load those objects. Example 1 is a definition of a C++ interface called IActor
. It has two operations -- getInitialInfo()
and play()
. Note that this interface is not sufficient because getInitialInfo()
expects a pointer to a struct called ActorInfo
and play()
expects a pointer to yet another interface called ITurn
. This is usually the case and you must design and specify a whole object model.
struct IActor { virtual ~IActor() {} virtual void getInitialInfo(ActorInfo * info) = 0; virtual void play( ITurn * turnInfo) = 0; };
Each plugin can register multiple types that implement the IActor
interface. When the application decides to instantiate an object registered by a plugin, it invokes the registered PF_CreateFunc
implemented by the plugin. The plugin is responsible to create a corresponding object and return it to the application. The return type is void *
because the object creation operation is part of the generic plugin framework that knows nothing about the specific IActor
interface. The application then casts the void *
to the an IActor *
and can work with it through the interface as if it was a regular object. When the application is done with the IActor
object it invokes the registered PF_DestroyFunc
implemented by the plugin and the plugin destroys the actor object. Pay no attention to the virtual destructor behind the curtain. I'll discuss it in the next installment.
Programming Language Support
In the binary compatibility section I explained that you can have C++ vtable-level compatibility if you use compilers with matching vtable layouts for the application and the plugins or you can use C-level compatibility and then you can use different compilers to build the application and the plugins, but you are limited to C interaction. Your application object model must be C-based. you can't use a nice C++ interface like IActor
in Example 1, but you must devise a similar C interface.
Pure C
In pure C programming model you simply develop your plugin in C. When you implement the PF_CreateFunc
function you return a C object that interacts with further C object in your application C object model. What is all this talk about C objects and C object models. Everybody knows C is a procedural language and has no concept of objects. This is correct and still C has enough abstraction mechanism to implement objects including polymorphism (which is necessary in this case) and support object-oriented programming style. In fact, the original C++ compiler was actually a front-end to a C compiler. It produced C code from the C++ code that was later compiled using a plain C compiler. It's name Cfront is more than telling.
The ticket is to use structs that contain function pointers. The signature of each function should accept its own struct as first argument. The struct may also contain other data members. This simple idiom corresponds to a C++ class and provides encapsulation (state and behavior in one place), inheritance (by using the first data member for a base struct), and polymorphism (by setting different function pointers).
C doesn't support destructors, function, and operators overloading and namespaces so you have fewer options when defining interfaces. That may be a blessing in disguise because interfaces are supposed to be used by other people who may master a different subset of the C++ language. Reducing the scope of language construct in interfaces may improve the simplicity and usability of your interfaces.
I will explore object-oriented C in the context of the plugin framework in the follow up articles. Listing Two contains the C object model of the sample game that accompanies this article series (just to whet your appetite). If you take a quick look you can see that it even supports a form of collections and iterators beyond plain objects.
#ifndef C_OBJECT_MODEL #define C_OBJECT_MODEL #include <apr-1/apr.h> #define MAX_STR 64 /* max string length of string fields */ typedef struct C_ActorInfo_ { apr_uint32_t id; apr_byte_t name[MAX_STR]; apr_uint32_t location_x; apr_uint32_t location_y; apr_uint32_t health; apr_uint32_t attack; apr_uint32_t defense; apr_uint32_t damage; apr_uint32_t movement; } C_ActorInfo; typedef struct C_ActorInfoIteratorHandle_ { char c; } * C_ActorInfoIteratorHandle; typedef struct C_ActorInfoIterator_ { void (*reset)(C_ActorInfoIteratorHandle handle); C_ActorInfo * (*next)(C_ActorInfoIteratorHandle handle); C_ActorInfoIteratorHandle handle; } C_ActorInfoIterator; typedef struct C_TurnHandle_ { char c; } * C_TurnHandle; typedef struct C_Turn_ { C_ActorInfo * (*getSelfInfo)(C_TurnHandle handle); C_ActorInfoIterator * (*getFriends)(C_TurnHandle handle); C_ActorInfoIterator * (*getFoes)(C_TurnHandle handle); void (*move)(C_TurnHandle handle, apr_uint32_t x, apr_uint32_t y); void (*attack)(C_TurnHandle handle, apr_uint32_t id); C_TurnHandle handle; } C_Turn; typedef struct C_ActorHandle_ { char c; } * C_ActorHandle; typedef struct C_Actor_ { void (*getInitialInfo)(C_ActorHandle handle, C_ActorInfo * info); void (*play)(C_ActorHandle handle, C_Turn * turn); C_ActorHandle handle; } C_Actor; #endif
Pure C++
In pure C++ programming model you simply develop your plugin in C++. The plugin programming interface functions can be implemented as static member functions or as plain static/global functions (C++ is mostly a superset of C after all). The object model can be your garden variety C++ object model. Listing Three contains the C++ object model of the sample game. It is almost identical to the C object model of Listing Two.
#ifndef OBJECT_MODEL #define OBJECT_MODEL #include "c_object_model.h" typedef C_ActorInfo ActorInfo; struct IActorInfoIterator { virtual void reset() = 0; virtual ActorInfo * next() = 0; }; struct ITurn { virtual ActorInfo * getSelfInfo() = 0; virtual IActorInfoIterator * getFriends() = 0; virtual IActorInfoIterator * getFoes() = 0; virtual void move(apr_uint32_t x, apr_uint32_t y) = 0; virtual void attack(apr_uint32_t id) = 0; }; struct IActor { virtual ~IActor() {} virtual void getInitialInfo(ActorInfo * info) = 0; virtual void play( ITurn * turnInfo) = 0; }; #endif
Dual C/C++
In the dual C/C++ programming model you can develop your plugin in either C or C++. When you register your objects you specify if they are C or C++ object. This is useful if you create a platform and you want to provide third-party developers ultimate freedom to choose their programming language and programming model and mix and match C and C++ plugins.
The plugin framework supports it, but the real work is in devising a dual C/C++ object model to your application. Each object type needs to implement both C interface and the C++ interface. This means that you will have a C++ class with a standard vtable and also a bunch of function pointers that correspond to the methods of the virtual table. The mechanics are not trivial and I'll demonstrate it in the context of the sample game.
Note that from the point of view of a plugin developer the dual C/C++ model doesn't introduce any additional complexity. The plugin developer always develop either a C or a C++ plugin using C interfaces or the C++ interfaces.
Hybrid C/C++
In hybrid C/C++ programming models, you develop your plugin in C++ but under the covers the C object model is used. This involves creating C++ wrapper classes that implement the C++ object model and wrap corresponding C objects. The plugin developers programs against this layer that translates every call, parameter and return value back and forth between C and C++. This requires additional work when implementing your application object model, but is very straight forward usually. The benefit is a nice C++ programming model for the plugin developer with a full C-level compatibility. I don't demonstrate it in the context of the sample game.
Language-Linkage Matrix
Figure 1 shows the various pros and cons of different combinations of deployment models (static vs. dynamic libraries) and programming language choice (C vs. C++).
For the sake of this discussion the dual C/C++ model has the prerequisites and limitations of C++ if using C++ plugins and the prerequisites and limitations of C if using C plugins. Also, the hybrid C/C++ model is just a C model because the C++ layer is hidden behind the plugin implementation. This can all be confusing, but the bottom line is that you have options and the plugin framework allows you to make choices and pick the tradeoffs you feel appropriate to your situation. It doesn't force you to use a specific model and it doesn't aim to lowest common denominator.
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).