This is the time to unveil the inner workings of the dual object. The actual implementation of each dual object is always in the C++ part of each dual object. The C function pointers always point to static methods of the C++ object that delegate the work to the corresponding methods of the C++ interface. This is the tricky part. Although the C and the C++ interfaces are the "parents" of ActorInfoContainer there is no portable way in C++ to get from one base class to another base class. To do that the static C functions need an access to the ActorInfoContainer instance (the "child"). This is where the handle comes in handy (pun intended). Each static C method casts the handle to ActorInfoContainer pointer (using reinterpret_cast) and calls the corresponding C++ method. The reset() method accepts no arguments and return nothing. The next() method accepts no arguments and returns ActorInfo pointer, which is the same return type for the C and C++ interfaces.
The situation is a little more complicated when it comes to the Turn dual object. This object implements the ITurn C++ interface (see Example 9) and the C_Turn C interface (see Example 10).
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; };
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;
The Turn object follows in the footsteps of ActorInfoContainer and has static C methods that are hooked up to the function pointers of the C_Turn interface in the constructor and delegate the work to the C++ methods. Let's focus on the getFriends() method. This method is supposed to return IActorInfoIterator from the C++ ITurn interface and C_ActorInfoIterator from the C_Turn interface. A different return value type. What a conundrum! The static getFriends_() can't just return the result of calling getFriends(), which is IActorInfoIterator pointer and it can't just do reinterpret_cast or C cast to C_ActorInfoIterator because the offset of the C_Turn base struct is different. The solution is to use a little inside information. The result of ITurn::getFriends() is indeed IActorInfoIterator, but actually it returns ActorInfoContainer dual object, which implements both IActorInfoIterator and C_ActorInfoIterator.
In order to get from IActorInfoIterator to C_ActorInfoIterator, getFriends_() performs up-casting to the ActorInfoContainer dual C/C++ object (using static_cast<ActorInfoContainer>). Once, it has an ActorInfoContainer instance it can serve as C_ActorInfoIterator. It's okay to take a sip of water or something stronger now. You earned it.
The core idea is that the entire object model is implemented in terms of dual C/C++ objects that can be used through either the C or C++ interfaces (with some nudging and casting). It's also okay to use basic types and C structs like ActorInfo that can be used trivially in C and C++. Note that all this mind-boggling stuff is safely entombed inside the application object model implementation. The rest of the application code and the plugin code don't have to deal with multi-language multiple inheritance, nasty casts and switching from one interface to another through the derived class. This design pattern/idiom is admittedly convoluted, but once you the get a grip on it you can see it simply repeats all over the object model.
We are not done yet. I lied. Just two sentences ago I said that this weird dual C/C++ pattern repeats all over the place. Well, almost. When it comes to the IActor (see Example 11) and C_Actor (see Example 12) interfaces this is not the case. These interfaces represent the actual plugin objects that are created using the PF_CreateFunc. There is no dual Actor object that implements both IActor and C_Actor.
struct IActor { virtual ~IActor() {} virtual void getInitialInfo(ActorInfo * info) = 0; virtual void play(ITurn * turnInfo) = 0; };
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;
The objects that implement IActor and C_Actor come from the plugins. They are not part of the application object model. they are users of the application object model. Their interfaces are just defined in the application's object model header files (object_model.h and c_object_model.h). Each plugin object implements either the C++ IActor interface or the C C_Actor interface (and they were registered accordingly with the PluginManager). The PluginManager will adapt C objects that implement the C_Actor interface to IActor-based adapted C++ object and the application will remain ignorant.
In the next installment I will discuss writing plugins. I'll go over the sample application and its plugins. I'll also give a quick tour of source code (there's a lot of it).