代理(Proxies)
link:http://www.cppblog.com/MemoryGarden/archive/2009/12/17/103382.html
要想与某个Ice 对象联系,客户必须持有这个对象的代理1。 代理是客户
的地址空间中的一种制品(artifact);对客户而言,代理就是Ice 对象的
代表(该对象可能在远地)。一个代理充当的是一个Ice 对象的本地大使:
当客户调用代理上的操作时, Ice run time 会:
1. 定位Ice 对象
2. 如果Ice 对象的服务器没有运行,就激活它
3. 在服务器中激活Ice 对象
4. 把所有in 参数传送给Ice 对象
5. 等待操作完成
6. 把所有out 参数及返回值返回给客户(或在发生错误的情况下抛出异
常)
代理封装了完成这一系列步骤所必需的全部信息。特别地,代理包含
有:
• 寻址信息:用于让客户端run time 联系正确的服务器
• 对象标识:用于确定服务器中的哪一个对象是请求的目标
• 可选的facet 标识符:用于确定代理所引用的是对象的哪一个facet
串化代理(Stringified Proxies)
代理中的信息可以用串的形式表示。例如:
SimplePrinter:default -p 10000
这个字符串表示的是一个代理,我们可以阅读这种表示方式。Ice run
time 提供了一些API 调用,允许你把代理转换成它的串化形式,或是进行
相反的转换。例如,如果你要把代理存储在数据库表或文本文件中,这种
功能会很有用。
倘若客户知道某个Ice 对象的标识及其寻址信息,使用这些信息,它可
以“凭空”创建代理。换句话说,代理内部的所有信息都被认为是透明的
;要与某个对象联系,客户只需要知道这个对象的标识、寻址信息,以及
对象的类型(为了能调用操作),就可以了。
在客户端,接口映射到这样的类:它的成员函数与接口上的操作相对
应。考虑下面的简单接口:
2 void op();
3 };
Slice 编译器生成下面的定义,供客户使用:
2 namespace M {
3 class Simple;
4 }
5 }
6 namespace M {
7 class Simple;
8 typedef IceInternal::ProxyHandle< ::IceProxy::M::Simple> SimplePrx;
9 typedef IceInternal::Handle< ::M::Simple> SimplePtr;
10 }
11 namespace IceProxy {
12 namespace M {
13 class Simple : publicvirtual IceProxy::Ice::Object {
14 public:
15 typedef ::M::SimplePrx ProxyType;
16 typedef ::M::SimplePtr PointerType;
17 void op();
18 void op(const Ice::Context&);
19 //
20 };
21 };
22 }
(其实我生成的和这个不一样的。。。因为我不适很懂这个东西, 所以根本不敢乱贴)
在客户的地址空间中, IceProxy::M::Simple 实例是“远地的服务器
中的Simple 接口的实例”的“本地大使”,叫作代理类实例。与服务器端
对象有关的所有细节,比如其地址、所用协议、对象标识,都封装在该实
例中
注意, Simple 继承自IceProxy::Ice::Object。这反映了这样一
个事实:所有的Ice 接口都隐含地继承自Ice::Object。对于接口中的每个
操作,代理类都有两个重载的、同名的成员函数。就前面的例子而言,我
们会发现操作op 映射到了两个成员函数op。
其中一个函数的最后一个参数的类型是Ice::Context。Ice run time
用这个参数存储关于请求的递送方式的信息;你通常并不需要为此提供一
个值,可以假装这个参数不存在
客户端应用永远不会直接操纵代理类。事实上,你不允许直接实例化代
理类。下面的代码无法编译,因为IceProxy::Simple 含有纯虚的成员
函数:
IceProxy::Simple s; // Compile-time error!
代理实例总是由Ice run time 替客户实例化,所以客户代码永远都不需
要直接实例化代理。当客户从run time 那里接收代理时,它会得到指向该
代理的代理句柄,其类型是<interface-name>Prx (对于前面的例子
就是SimplePrx)。客户通过代理的句柄来访问代理;句柄负责把操作调
用转发给其底层的代理,并且会对代理进行引用计数。这意味着,你不会
遇到内存管理问题:代理的释放是自动的,会在指向代理的最后一个句柄
消失时(退出作用域时)发生。
因为应用代码总是使用代理句柄,而决不会直接使用代理类,我们通常
既会用代理这个术语来表示代理句柄,也会用它来标识代理类。这反映了
这样一个事实:在实际的使用中,代理的“观感”就像是底层的代理类实
例。如果区分它们很重要,我们会使用术语代理类、代理类实例,以及代
理句柄。
代理句柄上的方法 :
我们在前面的例子中已经看到,句柄实际上是类型为
IceInternal::ProxyHandle 的模板,其参数是代理类。这个模板有缺
省构造器、复制构造器,以及赋值构造器:
• 缺省构造器
你可以通过缺省方式构造代理句柄。缺省的构造器创建的是哪里也
不指向的代理(也就是说,根本不指向对象)。如果你调用这样的null
代理上的操作,你会收到
• 复制构造器
复制构造器负责确保你能根据另一个代理句柄构造出一个代理句
柄。在内部,这会使代理的引用计数加一;析构器会使引用计数减一,一旦计数降到零,就释放底层的代理类实例。这样就不会发生内存泄漏
了
赋值操作符
你可以随意把一个代理句柄赋给另一个句柄。句柄的实现会保证进
行适当的内存管理。自赋值(self-assignment)是安全的,你无需针对
这种情况进行保护:
宽化赋值(Widening assignments)会隐式地进行。例如,如果我们
有两个接口Base 和Derived,我们可以隐式地把一个DerivedPrx 变
宽成一个BasePrx:
• 检查转换(checked cast)
代理句柄提供了一个checkedCast 方法:
2 template<typename T>
3 class ProxyHandle : public IceUtil::HandleBase<T> {
4 public:
5 template<class Y>
6 static ProxyHandle checkedCast(const ProxyHandle<Y>& r);
7 //
8 };
9 }
对于代理来说,检查转换的作用就像是C++ dynamic_cast 相对于
指针的作用:它能够让你把基代理赋给派生代理。如果基代理的运行时
类型与派生代理的静态类型相容,赋值就能成功,而在赋值之后,基代
理代表的对象与派生代理相同。 而如果基代理的运行时类型与派生代理
的静态类型不相容,派生代理就会被设成null。下面用一个例子来加以
说明:
2 DerivedPrx derived;
3 derived = DerivedPrx::checkedCast(base);
4 if (derived) {
5 // Base has run-time type Derived,
6 // use derived
7 } else {
8 // Base has some other, unrelated type
9 }
10
表达式DerivedPrx::checkedCast(base) 测试base 指向的
是否是 Derived 类型的对象。如果是,则转换成功, derived 指向的
对象会被设成与base 指向的相同。否则,转换就会失败,而
derived 被设成null 代理。
注意, checkedCast 是一个静态方法,所以,要进行向下转换,
你总是使用这样的语法:<interface-name>Prx::checkedCast。
还要注意,你可以在布尔上下文中使用代理。例如,如果代理不为
null, if (proxy) 会返回真
在你调用checkedCast 时,通常会有一条远地消息发往服务器2。
这条消息会实际询问服务器:“这个引用所代表的对象的类型是不是
Derived?”服务器的答复会以成功(非null)或失败(null)的形式传
达给应用代码。发送远地消息是必要的,因为作为一条原则,如果没有
服务器的确认,客户无法找出代理实际的运行时类型(例如,服务器
可能会用一个派生层次更深的对象实现取代现有的某个代理的对象实
现)。这意味着,你必须准备好处理checkedCast 失败的情况。例
如,如果服务器没有运行,你就会收到ConnectFailedException
;如果服务器在运行,但代理所代表的对象已经不存在,你就会收到
ObjectNotExistException。
• 不检查转换(Unchecked cast)
在有些情况下,你知道某个对象支持一个接口,其派生层次比其代
理的静态类型的派生层次更深。对于这样的情况,你可以使用不进行检
查的向下转换:
2 template<typename T>
3 class ProxyHandle : public IceUtil::HandleBase<T> {
4 public:
5 template<class Y>
6 static ProxyHandle uncheckedCast(const ProxyHandle<Y>& r);
7 //
8 };
9 }
只有在你确知代理真的支持派生层次更深的类型时,你才应该使用
uncheckedCast:顾名思义, uncheckedCast 不会进行任何检查;
它不会联系服务器中的对象,如果失败,它不会返回null (不检查转换
在内部的实现就像是C++ static_cast,不会进行任何一种检查)。
如果你使用的代理是通过不正确的uncheckedCast 得到的,其行为
将是不确定的。你很可能会收到ObjectNotExistException 或
OperationNotExistException,但取决于具体情形, Ice run time
也可能报告一个异常,说解编失败,甚至还可能会不声不响地返回垃圾
结果。
尽管有危险, uncheckedCast 仍然是有用的,因为它不用付出向
服务器发消息的代价。而且,在初始化过程中(参见第7 章),应用常
常会收到静态类型是Ice::Object 的代理,但你知道它的具体的运行
时类型。在这样的情况下, uncheckedCast 可以节省发送远地消息
的开销。
对象标识与代理比较 :
代理句柄还支持比较操作 == !=
这两个操作符允许你比较代理是否相等和不等。为了测试代理是否
为null,你可以与直接量0 进行比较,例如:
2 // It's a nil proxy
3 else
4 // It's a non-nil proxy
5
• <
代理支持operator<。这使得你能把代理放入STL 容器,比如映
射表或有序列表。
• 布尔比较
代理有一个转换操作符,可以把自己转换成bool。如果代理不是
null,这个操作符返回真,否则就返回假。所以你可以编写这样的代
码:
BasePrx base = ...;
if (base)
// It's a non-nil proxy
else
// It's a nil proxy
注意,在通过重载的操作符==、!=,以及< 进行代理比较时,会使用
代理中的所有信息。这意味着,对象标识不仅要匹配,代理中的其他资料
也必须相同,比如协议和端点信息。换句话说,如果你用== 和!= 进行比
较,测试的是代理的同一性,而不是对象的同一性
2 Ice::ObjectPrx p2 = ; // Get another proxy
3 if (p1 != p2) {
4 // p1 and p2 denote different objects // WRONG!
5 } else {
6 // p1 and p2 denote the same object // Correct
7 }
尽管p1 和p2 是不同的,它们代表的可能是同一个Ice 对象。例如,如
果p1 和p2 包含了相同的对象标识,但各自使用了不同的协议联系目标对
象,就可能会发生上述情况。与此类似,协议可能是一样的,但使用的端
点不同(因为单个Ice 对象可以通过若干不同的传输端点联系)。换句话
说,如果两个代理用== 比较是相等的,我们就知道这两个代理代表的是同
一个对象(因为它们在所有方面都相同);但如果两个用== 比较不相等,
我们什么也不知道:这两个代理所代表的可能是、也可能不是同一个对
象。
要比较两个代理的对象同一性,你必须使用Ice 名字空间里的一个辅
助函数:
bool proxyIdentityLess(const ObjectPrx &,
const ObjectPrx &);
bool proxyIdentityEqual(const ObjectPrx &,
const ObjectPrx &);
}
只要嵌在两个代理中的对象标识是一样的, proxyIdentityEqual
函数就会返回真,代理中的其他信息会被忽略,比如facet 和传输机制信
息。proxyIdentityLess 函数建立了代理的总序(total ordering)。其
目的主要是为了让你把对象标识比较用于STL 的有序容器。
proxyIdentityEqual 能让你正确地比较代理的 对象标识:
2 Ice::ObjectPrx p2 = ; // Get another proxy
3 if (!Ice::proxyIdentityEqual(p1, p2) {
4 // p1 and p2 denote different objects // Correct
5 } else {
6 // p1 and p2 denote the same object // Correct
7 }