ActorInfoContainer
用于管理ActorInfo
对象集合。它实现了 C++ 接口IActorInfoIterator
,在其内部保存集合索引。当调用 next() 的时候,返回该索引位置的对象;如果索引大于集合元素数目,则返回 NULL。当调用reset()
时,则将索引置 0。当然,索引的初始化为 0。
struct ActorInfoContainer : IActorInfoIterator, C_ActorInfoIterator { ... ActorInfoContainer() : index(0) { ... } void reset() { index = 0; } ActorInfo * next() { if (index %gt;= vec.size()) return NULL; return vec[index++]; } apr_uint32_t index; std::vector<ActorInfo *> vec; };
C_ActorInfoIterator
实现了 C 接口。在构造函数中,将reset_()
和next_()
static 函数赋给父结构的reset
和next
函数指针。它也要将this
赋值为该句柄:
static void reset_(C_ActorInfoIteratorHandle handle) { ActorInfoContainer * aic = reinterpret_cast<ActorInfoContainer *>(handle); aic->reset(); } static C_ActorInfo * next_(C_ActorInfoIteratorHandle handle) { ActorInfoContainer * aic = reinterpret_cast<ActorInfoContainer *>(handle); return aic->next(); } ActorInfoContainer() : index(0) { C_ActorInfoIterator::handle = (C_ActorInfoIteratorHandle)this; C_ActorInfoIterator::reset = reset_; C_ActorInfoIterator::next = next_; }
现在,让我们揭开支持双对象的内部秘密!双对象模型的真实实现的关键都是在双对象的 C++ 部分。 C 函数指针始终指向 C++ 对象的 static 函数,这些函数实际是将真正的操作委托给的 C++ 接口。这正是最有技巧的一步。虽然 C 和 C++ 接口都是ActorInfoContainer
的父接口,但是在 C++ 中,并没有好的办法能够从一个基类转移至另一个基类。为了实现这一点,static 的 C 函数需要访问ActorInfoContainer
接口(也就是子类)。这就是技巧所在。每一个 static 的 C 函数都将句柄转换成ActorInfoContainer
指针(使用reinterpret_cast
),然后调用相应的 C++ 函数。reset()
函数没有参数,没有返回值。next()
函数没有参数,返回ActorInfo
指针,该指针对于 C 和 C++ 接口是相同的。当需要在两种对象之间转换时,我们会遇到一点儿困难。该对象需要实现 ITurn
C++ 接口和C_Turn
C 接口:
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;
Turn
对象同ActorInfoContainer
类似,有一个 static C 函数,同构造函数中C_Turn
接口的函数指针相关联,并将底层实现委托给 C++ 函数。下面再来看看getFriends()
函数。该函数在 C++ 的ITurn
接口中返回IActorInfoIterator
,在C_Turn
返回C_ActorInfoIterator
。也就是返回值不同。static 的getFriends_()
函数不能直接返回getFriends()
的结果,因为IActorInfoIterator
指针不能通过reinterpret_cast
或者 C 转换为C_ActorInfoIterator
。这是由于C_Turn
基类的偏移不同。解决方法是使用一些内部信息。ITurn::getFriends()
的结果确实是IActorInfoIterator
,但它返回的是ActorInfoContainer
,这个类即实现了IActorInfoIterator
又实现了C_ActorInfoIterator
。
为了将IActorInfoIterator
转换成C_ActorInfoIterator
,getFriends_()
将其向上转型为ActorInfoContainer
(通过 static_cast<ActorInfoContainer>
)。一旦有了ActorInfoContainer
实例,我们就可以得到C_ActorInfoIterator
。这是现阶段最好的实现方式。
我们的核心思想史,全部对象模型都同时实现 C/C++ 接口,因此就可以即使用 C 接口,又使用 C++ 接口(利用某些转换技巧)。当然,我们也可以使用基本类型和 类似ActorInfo
的 C 结构。注意,这些实际在应用程序对象模型的实现内部都是安全的。应用程序剩余的代码以及插件代码都不需要关心多种语言实现的多继承、复杂的类型转换以及利用子类切换接口等。这种设计够复杂的,但是一旦你设计实现完成,那么在以后的编码过程中就不需要太关心这一点了。
不过,现在我们还没完成,只是快要完成了。我是说,IActor
和C_Actor
还没有涉及到。这些接口用于表示真实的插件对象,这些对象是由PF_CreateFunc
创建的。我们并没有一个 Actor
对象同时实现了IActor
和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;
同时实现了IActor
和C_Actor
的对象由插件提供。它们不是应用程序对象模型的一部分。它们是用户定义的业务对象,其接口在应用程序的业务对象模型头文件中进行定义(object_model.h 和 c_object_model.h)。每个插件对象要么实现 C++IActor
接口,要么实现 CC_Actor
接口(它们都由PluginManager
进行注册)。PluginManager
会把实现了C_Actor
接口的 C 对象适配成基于IActor
的 C++ 对象。
本章到此为止。在下面的内容中,我们将实际开始代码的编写,开始我们的简单的程序及其插件。