Dynamic C Plugins
C plugins register objects that implement the C_Actor interface. The plugin itself -- and even the C_Actor implementation -- may be C++ classes (using static methods). In this case, I implemented everything in C, just to ensure the system supports pure C plugins (there were a few compilation gotchas before it worked). The C plugin registers a single monster called MellowMonster. The header file of this quaint monster is presented in Example 4. This is a C object, so there is no class definition only the global functions MellowMonster_create() and MellowMonster_destroy(), which correspond to PF_CreateFunc and PF_DestroyFunc. The names are qualified with the monster type because, in general, a single plugin may register monster types with different pairs of create()/destroy() functions and in C we can't hide them in a namespace or as static methods of a class.
#ifndef MELLOW_MONSTER_H #define MELLOW_MONSTER_H #include <plugin_framework/plugin.h> // static plugin interface void * MellowMonster_create(PF_ObjectParams *); apr_int32_t MellowMonster_destroy(void *); #endif
Example 5 presents the actual monster. It's just a struct that contains a C_Actor member and optionally more monster-specific data. Not much of a monster so far.
typedef struct MellowMonster_ { C_Actor actor; /* additional monster-specific data */ apr_uint32_t dummy; } MellowMonster;
Example 6 is the implementation of the C_Actor interface and consists of two static functions (not visible outside of this compilation unit) -- MellowMonster_getInitialInfo() and MellowMonster_play() -- that correspond to the IActor methods. The big difference is that the C++ methods get the object instance as the implicit 'this' pointer. In C, you must pass a C_ActorHandle explicitly (well, not you, but the PluginManager) and the C functions laboriously cast the handle to a MellowMonster pointer. When the C_Turn object is used in the play() function, you must pass it its own handle too.
void MellowMonster_getInitialInfo(C_ActorHandle handle, C_ActorInfo * info) { MellowMonster * mm = (MellowMonster *)handle; strcpy((char *)info->name, "MellowMonster"); 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 MellowMonster_play(C_ActorHandle handle, C_Turn * turn) { MellowMonster * mm = (MellowMonster *)handle; C_ActorInfoIterator * friends = turn->getFriends(turn->handle); }
Example 7 contains the MellowMonster_create() and MellowMonster_destroy() functions and ties up loose ends. The MellowMonster_create() function allocates a MellowMonster struct (using malloc, of course), assigns the pointer to the handle member of the actor field (without checking if the memory allocation failed, boo :-), and goes one to assign the MellowMonster_getInitialInfo() and MellowMonster_play() functions to proper function pointers. Finally it returns the MellowMonster pointer as an opaque void pointer. It is important that the C_Actor interface be the first member of the MellowMonster struct, because the PluginManager (via the adapter) casts the returned void pointer to a C_Actor pointer and treats it as such from then on.
The MellowMonster_destroy() frees the memory. If there is any need for destructor-like cleanup, it can do it too.
Let's check the initialization code of the C plugin in Listing Four. It looks just like the C++ plugin. This isn't surprising because it is a C function that needs to prepare a C struct and call yet another C function. The only real difference is that the registered programming language for MellowMonster is PF_ProgrammingLanguage_C. That tells the PluginManager that it's dealing with a C object and it should adapt it.
#ifdef WIN32 #include "stdafx.h" #endif #include "c_plugin.h" #include "c_plugin.h" #include "plugin_framework/plugin.h" #include "MellowMonster.h" PLUGIN_API apr_int32_t ExitFunc() { return 0; } PLUGIN_API PF_ExitFunc PF_initPlugin(const PF_PlatformServices * params) { int res = 0; PF_RegisterParams rp; rp.version.major = 1; rp.version.minor = 0; // Regiater MellowMonster rp.createFunc = MellowMonster_create; rp.destroyFunc = MellowMonster_destroy; rp.programmingLanguage = PF_ProgrammingLanguage_C; res = params->registerObject((const apr_byte_t *)"MellowMonster", &rp); if (res < 0) return NULL; return ExitFunc; }
As you can see, working at the C API level is much thornier. You need pass explicit handles around, cast a lot, pay attention to function names, and hook up free functions to your monster C_Actor interfaces. It's not fun, but it's survivable if you must work with C.