ZeroC ICE中的对象
在ZeroC Ice中定义了三种基本对象类型。
它们分别是IceProxy::Ice::Object(于Ice/Proxy.h),Ice::Object(于Ice/Object.h)和Ice::LocalObject(于Ice/LocalObject.h)。
这三种基本对象类型的基类都是Shared。Shared类的作用就是一个引用计数,利用引用计数管理内存,在c++11之前,相当于boost::shared_ptr的作用。在整个Ice项目中一切需要使用引用计数的类都继承自Shared。而上面定义的三个基本对象就是与Ice Object概念相关。
我们先来看一下关于Handle,指针管理的类。
Handlebase对指针进行简单的包装,包装了指针的最基本的操作,引用操作以及指针空判断。Handle和ProxyHandle继承自HandleBase,充当智能指针管理器,并要求被管理的指针的指向的对象的类型继承自Shared,即指针指向的对象是利用引用计数进行内存管理。Handle和ProxyHandle重载了构造以及赋值拷贝函数,完成指针传递过程中的自动引用计数。项目中使用指针管理器代替直接使用底层指针。指针的传递通过指针管理器完成,传递过程自动完成对指针指向的对象的引用计数。
现在就来看Handle和ProxyHandle的区别。Handle提供dynamicCast方法,而ProxyHandle提供checkedCast和uncheckedCast,以及upCast方法。
Handle提供的dynamicCast就如名字一样,只是简单地进行c++的dynamic_cast类型转换,因为它管理的指针的对象,就是native的c++继承类。
ProxyHandle的upCast直接向上转换成Shared。checkedCast和uncheckedCast是用来向下转换的。ProxyHandle用于管理Ice Object的代理指针,代理不是一种native的c++继承关系的类,因为一个Ice Object可能有多个接口(facets),而这个Ice Object可能有不只一种类型的服务实体提供服务,甚至实体之间没有接口的继承。这个Ice Object的代理注定不是一个由继承完成的类,而是一系列的接口代理端的类。所以ProxyHandle在进行向下类型转换的时候不能直接使用c++native的类型转换。实际上Ice也并对代理进行类型转换,那它做了什么?
看下面的实现:
// // checkedCast and uncheckedCast functions without facet: // template<typename P> P checkedCastImpl(const ::Ice::ObjectPrx& b, const ::Ice::Context& context) { P d = 0; if(b.get()) { typedef typename P::element_type T; if(b->ice_isA(T::ice_staticId(), context)) { d = new T; d->_copyFrom(b); } } return d; } template<typename P> P uncheckedCastImpl(const ::Ice::ObjectPrx& b) { P d = 0; if(b) { typedef typename P::element_type T; d = dynamic_cast<T*>(b.get()); if(!d) { d = new T; d->_copyFrom(b); } } return d; }
checkedCastImpl和uncheckedCastImpl最后采取的行动是对向下的代理类型进行实例。不同于checkedCastImpl,uncheckedCastImpl允许你对代理进行简单的c++native向下(上)类型转换,因为你当前使用的接口(facet)代理可能与你想要转换的目标接口(facet)代理,这两个接口间存在继承关系。但是uncheckedCastImpl不会像checkedCastImpl那样马上发送一个ice_isA调用,去验证在远端的"Ice Object"是否支持你的代理转换后的接口。如果你不手动去调用ice_isA验证这个代理的接口的话,就不能知道远端的"Ice Object"是否支持你想要的接口,除非直到你调用一个接口方法后返回了异常,你才能知道远端的"Ice Object"不支持你的接口。
uncheckedCast只会从继承树的角度进行转换,同一继承树就直到转换,不同一继承树就创建新的代理实例。
checkedCast是从"Ice Object"的角度进行转换,只要远端"Ice Object"应答ice_isA成功,一律创建新的代理实例。
最典型的就是,我们要是使用Ice Object都必须通过代理。当我们通过Ice环境创建出一个代理,Ice环境总是返回一个最基本的接口代理ObjectPrx(就是ProxyHandle<::IceProxy::Ice::Object>)。这个最基本接口代理包含了几个最基本的接口方法,如ice_isA,ice_ids和ice_ping等。我们可以通过上面任意方法激活一个Ice Object。但我们需要使用我们自定义的接口时,我们就需要使用对应的接口代理,我们就需要转换接口代理的类型,尽管我们的接口代理继承自::IceProxy::Ice::Object,但是我们不要求ProxyHandle进行向下类型转换时,ProxyHandle就只能按我们希望的接口代理类型,新实例一个代理给我们。因为这个接口代理并没有实例,ProxyHandle根本不可能通过对::IceProxy::Ice::Object进行简单的向下类型转换而得到。
现在已经提到了::IceProxy::Ice::Object,那么它与::Ice::Object的关系就清楚了。::IceProxy::Ice::Object与::Ice::Object作为一个Ice Object的最小功能的两方面,一方面是我们使用的代理,另一方面是为我们提供的服务。::IceProxy::Ice::Object与::Ice::Object实现最小接口方法集包含如下:
const string object_all[] = { "ice_id", "ice_ids", "ice_isA", "ice_ping" };
左边为Ice Object使用的客户端(代理端),右边是服务端(servant)。作为最基本的对象的两面,它们已经实现了4个最基本的接口方法,ice_id,ice_ids,ice_isA以及ice_ping。服务端以_iceD_前缀对应接口方法的函数,是在_iceDispatch函数分派调用的分支入口。通过接口方法在两端的原型比较,有这些的规律。生成的代理端接口方法函数有一个依赖参数,类型是Context(,实质是一个字符串map或字典);生成的服务端接口方法函数也有一个依赖参数,类型是Current。Current包含了以下内容,Identity,facet,operation,mode,context,requestId以及EncodingVersion,这些就包含在协议的请求头中,详细请参看上一篇《ZeroC ICE的协议》。服务端解释出请求头后,分派到具体servant实体后,由servant实体的_iceDispatch进行分派,_iceD_X函数再从Incoming的inputStream反序列化出调用参数,最后调用接口方法的函数实现体进行处理。
所以有如下图
slice程序根据接口描述文件Module.ice分别在名字空间::IceProxy::Module和::Module下生成同名的接口类。它们分别继承名字空间::IceProxy::Ice和::Ice下的Object类。这两个同名的接口类为我们完成Ice远程调用的框架,通过实现体ConcreteIntfImpl覆盖多态方法,实现接口方法。