作 者: 品雪
如果你已在使用OOP,那CORBA使你编写分布式应用程序变得容易,因为它让你象使用本地对象一样使用远程对象。这是因为CORBA应用程序的设计与任何其它OO应用程序非常相似,除了它多出一层,该层当一个对象驻留在另外一台机器上时管理网络通信。这个多出来的层由两个特殊的对象管理:存根和框架(stub&skeletons),如下图所示。
在CORBA客户端,存根充当某个对象的代理,该对象可能实现在同一进程、另一进程甚至另一台计算机(服务器)中。客户与存根打交道,好象存根就是那个对象一样。
然而,不象其它大多数对象,存根通过调用安装在客户机上的ORB软件来管理界面调用。VisiBroker ORB使用在局域网某处中运行着的智能代理(osagent),智能代理是一个动态分布目录服务,目录服务能够定位提供对象实现的服务器。
在CORBA服务器端,ORB软件将界面调用传给一个自动生成的骨架。这个骨架对过基本对象配适器(Basic
Object Adaptor)与ORB软件通信。通过使用BOA,骨架将对象注册到智能代理,说明对象的范围(即它对远程计算机是否可用)及对象是何时实例化并准备好响应客户的。
以下的论题说明如何用这个基本模型去匹配你的分布应用程序需要。
理解存根与骨架
使用智能代理
激活服务器应用程序
动态绑定界面调用
理解存根与骨架
CORBA中分布对象的基础是它的界面。界面很象类定义,除了它不包含实现信息。用CORBA的界面定义语言(IDL)定义界面。然后就可以把界面定义作为一个IDL文件加进项目中。关于IDL、STUB、SKELETON的进一步信息见VisiBroker编程指南。对于Cbuilder中使用IDL文件的信息见
定义对象界面。
当你编译IDL文件时,CBUILDER为你建立两个.cpp文件。一个是客户文件,包含存根的实现;另一个是服务器文件,包含骨架类的实现。存根和框架提供了允许CORBA应用程序套接解析(Marshaling)界面调用的机制。Marshaling:
取服务器进程中的一个对象实例使之对客户进程代码可用。
传递界面调用的参数,即将客户传来的参数放进远程对象进程空间里。
当客户应用调用CORBA对象的方法时,它将参数压进栈中然后调用存根对象。存根将参数写进marshaling缓冲区,然后用一个结构(structure)将调用改送给远程对象。服务器框架解开这个结构,将参数压进栈,然后调用对象实现。简单点说,框架在自己的地址空间里重建客户调用。
-----------------------------------------------------------------
使用智能代理
智能代理(osagent)是一个动态的分布式目录服务,该服务定位实现特定对象的有效服务器。如果有多个服务器供选,智能代理提供载入平衡。它还提供服务器失败保护,方式是在链接失败时尝试重起服务或必要时定位到其它主机上的服务器。
智能代理必须在局域网中的至少一台主机上启动,这里的局域网是指一个广播消息可以被送达范围的网络。ORB用广播消息定位智能代理。如果网络中有多个智能代理,ORB使用第一个响应的。一旦定位到一个智能代理,ORB使用点对点UDP协议与之通信。UDP协议占用的资源比TCP连接少。
当一个网络包括多个智能代理时,每个智能代理识别有效对象的一个部分,并与其它代理通信让它们定位其接直识别的对象。如果一个智能代理意外结束,其它代理会自动重新登记它所保持跟踪的对象。
在局域网中使用和配置智能代理的细节,见VisiBroker安装与管理指南。
-----------------------------------------------------------------
激活服务器应用程序
当服务器启动时,它会告知可接受用户调用的对象的ORB(通过基本对象配适器)。初始化ORB并告诉它服务器已经起动就绪的代码会被向导(你用来生成CORBA服务器程序的那个)自动加进你的应用程序里。
通常,CORBA服务器程序要自动启动。然而你可以用对象激活守护进程(Object
Activation Daemon)在客户需要使用时启动你的服务器或只实例化它们的对象。
要使用OAD,必要在它上面注册你的对象。当对象已经注册到OAD之后,它将对象及实现这些对象的服务的对应关系保存在数据库中,这个库称为Implemnetation
Repository(实现仓库)。
一旦实现仓库中有对象的入口,OAD将为ORB模拟你的程序(服务器)。当客户请求这个对象时,ORB与OAD联系就好象它就是服务器程序一样。OAD接着将客户请求转给真正的服务器,必要时启动这个应用程序(服务器)。
在OAD中注册对象的细节见VisiBroker Programmers Guide。
-----------------------------------------------------------------
动态绑定界面调用
通常CORBA客户在调用服务器上的对象界面时使用静态绑定。这种方法有许多好处,包括更快速的性能、编译时类型检查。然而,有时会直到运行时才知道想用什么界面。这时,Cbuilder允许你运行时动态绑定界面。
当使用动态绑定时,将界面注册到界面仓库(Interface Repository)中是有帮助的。
在客户程序中如何使用动态绑定,参见VisiBroker Programmers Guide的Dynamic
Invocation Interface (DII)部分。
----------------------------------------------------------------
定义对象界面
CORBA对象界面是用IDL(界面定义语言)定义的。IDL的语法类似于C++,因此IDL文件看起来类似于C++头文件。IDL文件的作用也十分类似于C++头文件,说明可以共享的界面,正如C++头文件说明可以共享的类。然而CORBA中的界面(类)是在不同应用程序间共享的,这与C++类在同一应用程序的不同模块间共享是有区别的。
注意:术语IDL在不同的上下文中所指的都是但彼此不并是同一种的interface definition
languages。实际上有CORBA IDL(OMG定义) Microsoft IDL(用于COM)和DCE IDL。CORBA IDL语言的详情见
href=""#IDL语法"">CORBA IDL 语法。
要想在Cbuilder中定义一个新的IDL文件,选择File|New菜单,再从对话框的Multitier
page页中选择CORBA IDL。这将导致用编辑器打开一个空白IDL文件,并且这个文件自动被加到当前工程项目中(project)。
如果你已经有一个定义对象的IDL文件,只要简单的将它加到项目中来:Project|Add to Project,文件类型选为IDL,选取已经存在的那个IDL文件。
--------------------------------------------------------------------------------
IDL语法
CORBA IDL是由对象管理组织(Object Management Group)为定义所有的CORBA界面而制定的。这里提供一个IDL的浏览,让你能够建立IDL文件定义大多数的对象。最新的IDL完整定义见OMG的Web站点(
href=""mailto:
[email protected]"">
[email protected])。
你可以用IDL定义types(类型),constans(常量)和
href=""#界面"">interfaces(界面)。这与C++中定义类型、常量和类相似。IDL是定义界面和类型的语言,它没有供你编写实现部分的元素。界面定义在IDL文件中的对象必须实现在分离的实现单元中。
IDL文件的标识符包括英文字母、数字和下划线,但不可以用下划线开头。IDL是大小写敏感的。然而,避免使用下划线通常是个好主意,因为将IDL翻译到不支持下划线的语言时会出问题。类似的,仅用大小写区标识符也不好,这在大小写不敏感的语言中会引起问题。
IDL文件支持以下预编译指令:
#define
#elif
#else
#endif
#ifdef
#ifndef
#include
例如,只有在IDL文件包含以下预编译指令时:
#include
才可以用CORBA伪类型NamedValue, Principal和TypeCode定义界面名。
预编译指令细节见VisiBroker文档。
--------------------------------------------------------------------------------
类型:与C++类似IDL中各种成员都要指定其类型。
--------------------------------------------------------------------------------
Typedefs:可以用来给任何类型指定名字,如
typedef string MemoVal;
它对于基本类型或结构类型来说不是必需的(它们在定义中已经命名),但对数组和模板类型是必要的。
--------------------------------------------------------------------------------
基本类型
下表为IDL支持的基本类型。注意没有int类型并且char类型等价于unsigned(字母是无所谓正负的)
类型 说明
float IEEE单精度浮点数
double IEEE双精度浮点数
long 32位有符号整数
short 16位有符号整数
unsigned long 32位无符号整数
unsigned short 16位无符号整数
char 8位字符(ASCII的ISO-Latin子集)
boolean 布尔型(真或假)
octet 8位8位数保证传递过程中不变(char不是)
any 任意IDL类型(类似Variant型)
NamedValue 成对的一个名字(string)和一个值(any型)
--------------------------------------------------------------------------------
数组(Arrays)
IDL提供多维定长数组以存放同类成员。每一维的长度都必须在定义中说明。例如:
typedef long CellValues [10][20];
注意:必须加一个typedef,如果数组用作参数、属性或返回值
--------------------------------------------------------------------------------
模板类型(Template types)
IDL提供两个模板类型:sequence(序列)和string(字符串)。象数组一样,要用typedef指定sequence和string类型的名字
一个序列是一个可变长度成员清单,成员可以为任何IDL类型。它象一维数组,但长度是不定的。它可以是有界的也可以是无界的,要看序列类型定义时指定的最大长度。例如,下面定义一个有界序列类型:
typedef sequence UpToTenByte;
这个类型的实例是一个长度小于等10的字节序列。
下一行定义了一个无界限的字节序列类型:
typedef sequence SomeBytes;
这个类型的实例可以是任意长度的字节序列。
string是一个字符序列。象序列一样,它可以是有界的:
typedef string<15> Moniker;
或无界的:
typedef string Description;
当定义一个有界字符串类型时,记住任何null结束符不算在串长度之内。使用string面不要用字符数组。字符数组中未初始化的成员在翻译过程中可能引起问题。
--------------------------------------------------------------------------------
结构类型
IDL文件也可以定义结构类型,用关键字struct, union和enum。
下边有一个结构类型定义的例子:
struct StructName {
char charMember;
unsigned short AnotherMember;
};
注意:这里没有typedef。对struct用typedef是不好的习惯,因为它会定义两个类型名字。
enum(枚举)类型定义示例:
enum Pet {cat, dog, fish, bird, rat, horse, gerbil};
IDL中的union必须是可区分(discriminated)的。即,用一个标记字段(tag field)说明联合的哪些成员是当前被赋值的:
union Reference switch (short) {
case 1 : { Title: string; Author: string; }
case 2: URL: string;
case 3: TopicID: long;
};
--------------------------------------------------------------------------------
常量
IDL文件可以定义常量,这些常量可用于界面和类型的定义。用关键字const定义常量:
const unsigned long LengthOfNameString = 15;
IDL文件可以定义类型为long, unsigned long, unsigned short,char boolean,float,double,string的常量。注意,IDL不支持octet型的常量。
整数常量可以用10、8、16进制形式说明。字符常量定义可以使用标准的C++转义序列,如
等。
--------------------------------------------------------------------------------
界面
IDL文件中的界面描述了CORBA对象提供的功能。它封装客户使用界面所需的全部信息。每个界面对应CORBA服务器上的一个类,其定义近似于类的定义:
interface Example1 {
readonly attribute string Name;
attribute long Value;
long AddToValue(in long Summand, out long Result);
};
界面用关键字interface定义。在界面定义内部是一个属性和
href=""#方法"">方法的清单。所有的属性和方法都是公开的(public)。这里没有私有(private)或保护(protected)的概念,因为这些应该在实现部分处理而不应在对象的公开界面里。
--------------------------------------------------------------------------------
属性
界面没有成员变量。属性(Attributes)象Cbuilder的property(这种说法并不暗示值的存储方法相同)而不象成员变量。用关键字attribute定义。
如果不允许客户写属性,定义时要加readonly前缀。
Readonly attribute float Balance;
注意:尽管attribute的行为表现得象properties,但它们并不是用Cbuilder的properties(不可移植)实现的。相反,属性(attributes)被编译成与属性同名的getter(及可选的setter)方法。
--------------------------------------------------------------------------------
方法
方法定义必须包括返回值类型。
另外,它们必须在每个参数中用关键字in,out,inout提供用法说明,每个参数都必须命名。
通常,方法调用在服务器对象处理调用时是阻塞的(blocked)。然而,IDL语法允许一个方法申明为oneway,如果调用者确实不需要等待响应的话。
Oneway void SendInformation(long Value);
重复的方法名是非法的,因为CORBA是可用于支持方法重载的编程语言的。
--------------------------------------------------------------------------------
注释:
界面中可以加注释,用和C++相同的//。
// this is a comment
类型定义
界面可以包括类型定义:
interface Example2 {
struct Example2Struct {
string Name;
long Value;
};
// …其它定义部分
};
在同一界面中的定义可以用名字(Example2Struct)引用这些类型,但该界面外的定义必须使用范围限定符(Example2::Example2Struct)。
--------------------------------------------------------------------------------
异常
除了attributes, methods, types, 界面还可以包括用户定义异常处理:
interface ExceptionExample {
exception ValueOutOfBounds {
long value;
void SetValue(in long Value)
raises (ValueOutOfBounds);
}
}
SetValue方法指出它可能抛出ValueOutOfBounds异常。这个例子显示一个成员的一个异常,它可以帮助调试。然而,界面也可以包括无成员包括的花括号{}里的异常。
上下文(Contexts)
方法可以附带context子句。客户可以维护一个或多个CORBA上下文对象,它提供标识符(indentifier)到字符串(string)值的映射。一个IDL方法可以用关键字context申明对特定标识符的映射必须提供给它的客户:
interface ContextExample {
long SomeMethod(in long Value, in char Letter)
raises (/* */)
context(“value1”, “value2”);
};
};
ContextExample的调用者必须传送一个context对象作为参数。
--------------------------------------------------------------------------------
模块(Modules)
IDL文件将一些定义结合成模块。模块的概念和名字空间(namespace)是一样的:它允许定义组合成逻辑单元并预防命名空间冲突。一个模块定义一个名字空间,就象这样:
module SyntaxExamples {
interface Example1 {
// definitions here
};
interface Example2 {
// definitions here
};
};
模块外的定义访问模块内的定义必须加范围界定符(SyntaxExamples::Example1)。
--------------------------------------------------------------------------------
继承(Inheritance)
正如C++类,IDL界面可以作为其它类的后代而建立。子代界面继承祖先界面的属性和方法:
interface Ancestor{
//definitions here
};
interface Desendant:Ancestor {
// more definitions here
};
注意:因为没有界面相关的实现部分,衍生界面重载相应类方法时不得再在成员函数中列出。
界面支持多继承,但所有祖先界面都不能有包含相同名字的定义。
所有的IDL界面都是隐含继承了CORBA界面Object。这意味着一个类型为Object的参数可以接受任何CORBA对象。
--------------------------------------------------------------------------------
提前引用(Forward references)
如果两个界面互相引用,IDL文件必须一个包含对其中一个的提前引用,即一个界面可以引用IDL文件中其后部分的定义。如下所示:
interface Example2; // forwar reference
interface Example1 {
//definitions
readonly attribute Example2 TheOtherOne;
};
interface Example2 {
//definitions
Example1 ReturnTheOtherOne();
};
提前引用也允许一个界面包含对自己的引用。