级别: 初级 Dave Bartlett (dbartlett@pobox.com), 顾问、作家和讲师
2000 年 9 月 01 日 一切都要从接口定义语言 (IDL) 开始。当我们采用 RPC 或 COM 技术以及 CORBA 技术来编写分布式系统时都需要它。在各种情况下,接口定义语言提供了将对象的接口与其实现分离的能力。IDL 提供了抽象,它提供了将事务与其具体实现分离的概念。它还为我们提供了一套通用的数据类型使得我们可以使用它们来定义更为复杂的类型。我们将采用所有这些数据类型来定义分布式服务的功能。IDL 的另一个好处是它剥离了编程语言和硬件的依赖性。本文探讨了 OMG IDL 的内置类型和关键字。
IDL是一种规范语言。它允许我们从实现中剥离对象的规范(如何与它交互)。这是一个约定:“客户机女士,如果您要调用这个方法,请传送这些参数,然后我,服务器先生,将把这个字符串数组返回给您。”使用这个接口的客户机程序员不知道接口背后的实现细节。
OMG IDL 看上去很像 C语言。这就很容易将这两种语言以及它们的关键字做比较。但是,这种相似性只流于表面。每种语言的目的是完全不同的。我们在讨论这种语言时,您已经记住OMG IDL 的目的是定义接口和精简分布对象的过程。
IDL 基本类型
OMG接口定义语言有一些看上去应该很熟悉的基本类型。以下就是这些内置类型的表:
表 1. IDL基本类型
|
类型
|
范围
|
最小大小(以位为单位)
| | short | -2
15到2
15-1
| 16 | | unsignedshort | 0 到2
16-1
| 16 | | long | -2
31到2
31-1
| 32 | | unsignedlong | 0 到2
32-1
| 32 | | long long | -2
63到2
63-1
| 64 | | unsigned longlong | 0 到2
64-1
| 64 | | float | IEEE单精度 | 32 | | double | IEEE双精度 | 64 | | longdouble | IEEE双字节扩展浮点数 | 15 位指数,64位带符号小数 | | char | ISOLatin-1 | 8 | | wchar | 从任何宽字符集编码宽字符,如 Unicode | 依赖于实现 | | string | ISO Latin-1,除了ASCII NUL 以外 | 可变化 | | Boolean | TRUE 或FALSE | 未指定 | | octet | 0 到 255 | 8 | | any | 自己描述的数据类型,可表示任何 IDL 类型 | 可变化 |
整数类型
OMG IDL 的整数类型非常简单。虽然它没有提供
int 类型,但它不会受到
int 在不同平台上的取值范围不同所带来的多义性的困扰。然而,IDL确实提供几种整数类型,2 字节 (
short )、4 字节(
long ) 和 8 字节 (
long long )的整数类型。
所有这些整数类型都有相应的无符号数类型。这对于 Java程序又产生了问题,因为 Java 编程语言不支持无符号数类型。尽管这不是OMG IDL 的特性,它还是在 Java-to-IDL的映射中创建了一种独有的局面,我们将在下个月的专栏文章中讨论Java-to-IDL 的映射。但在此之前,您已经考虑如何将 IDL 中的
unsigned short 映射成一种 Java 类型。使用 Java
short 还是 Java
int ?它们各自的利弊是什么?这些是语言映射的作者必须努力解决的问题,并且这是一个很好的练习,可以帮助您为阅读下一篇专栏文章做好准备。
浮点类型
OMG IDL 浮点数类型
float 、
double 和
long double 遵循 IEEE 754-1985二进制浮点数算术的标准。目前,
long double 用于巨大数字,您也许会发现您的特殊语言映射还不支持这种类型。
char 和 wchar
我们都使用相同的术语,字符集就是字母或其它构成单词的字符以及其它本机语言或计算机语言的基本单元的集合。编码字符集(或代码集)是一组明确的规则,它建立了字符集和集合的每个字符与其位表示法之间的一一对应关系。
处理
char 时,必须记住 OMG IDL必须分两个层次处理字符集。首先必须明确规定从哪个字符集生成 IDL定义。词法约定(表示 IDL 文件的关键字、注释和文字的字符记号)规定ISO 8859.1 字符集表示 IDL 文件中的字符。是的,连 IDL都必须有一个标准字符集,它将构建在这个字符集上。ISO 464定义了空字符(null)和其它图形字符。
接着,OMG必须处理从一个计算机系统到另一个计算机系统之间的字符传输。这意味着可能涉及到从一个字符代码集到另一个字符代码集的转换,这取决于语言绑定。在
上个月的专栏文章中,我们对Orbacus Object Reference 执行了 IORDump操作,并且发现了以下信息:
Native char codeset:
"ISO 8859-1:1987; Latin Alphabet No. 1"
Char conversion codesets:
"X/Open UTF-8; UCS Transformation Format 8 (UTF-8)"
"ISO 646:1991 IRV (International Reference Version)"
Native wchar codeset:
"ISO/IEC 10646-1:1993; UTF-16,
UCS Transformation Format 16-bit form"
Wchar conversion codesets:
"ISO/IEC 10646-1:1993; UCS-2, Level 1"
"ISO 8859-1:1987; Latin Alphabet No. 1"
"X/Open UTF-8; UCS Transformation Format 8 (UTF-8)"
"ISO 646:1991 IRV (International Reference Version)"
|
可以看到,IOR可以包含代码集信息,以在转换时协调首选代码集和可用代码集。
解决了所有问题后,您应该知道 OMG IDL
char 是一个 8位变量,可以用两种方法表示一个字符。首先,它可以从面向字节的代码集编码单字节字符,其次,当在数组中使用时,它可以从多字节字符集(如Unicode),编码任何多字节字符。
Wchar 只允许大于 8个字节的代码集。规范不支持特殊的代码集。它允许每个客户机和服务器使用本机的代码集,然后指定如何转换字符和字符串,以便在使用不同代码集的环境之间进行传输。
Boolean
这里没有什么可以多说的 -- Boolean 值只能是 TRUE 或 FALSE。
Octet
octet 是 8 位类型。因为保证了
octet 在地址空间之间传送时不会有任何表示更改,因此这就使它变成了一种非常重要的类型。这就表示您可以发送二进制数据,并且知道当将它打包时,它的形式仍然相同。其它每种IDL 类型在传输时都有表示变化。例如,根据 IOR代码集信息的指示,
char 数组会经历代码集转换。而
octet 数组却不会。
any 类型
IDL
any 是一种包含任何数据类型的结构。该类型可以是
char 或
long long 或
string 或另一种
any ,或者是已经创建的一种类型,如
Address 。
any 容器由类型码和值组成。类型码描述
any 的值部分中的内容是什么。
如果您拥有 C++ 经验,则可以将
any 看作是自我描述的数据类型,它类似于
void* ,但更安全。如果有 Visual Basic 经验,可以将
any 看作类似于
variant 。当我们讨论IDL-to-Java 映射时,
any 类型的结构和它如何对用户定义的类型产生作用将变得一目了然。
用户定义的类型
基本类型是必不可少的;它们为接口定义提供了构件块。OMG IDL为您提供了定义您自己的类型的能力,这可以帮助减少复杂程度并且让您可以根据基本类型组成更精巧的数据类型。这些复杂的类型可以是枚举、结构和联合,或者您可以使用
typedef 来创建类型的新名称。
命名的类型
应该使用
typedef 创建新的类型名称,这将帮助解释接口或保存输入。
例如,您也许想在方法
PresentWeather(..., in floatPressure, ...) 中传送气压值。如果在该方法中使用
typedef
float 语句,这将使该方法更具可读性。
typedef float AtmosPressure;
|
在 C++ 中,
typedef 关键字表示
类型定义,实际上
别名也许是更为精确的术语。对于OMG IDL,也许是这样,也许不是,这取决于其所映射到的实现语言。CORBA规范不保证
short 的两种
typedef 是兼容的和可互换的。
在文体上,应注意不要为现有类型创建别名。您应该尝试创建不同概念的类型,它将为您的IDL添加可读性和可扩展性。最好是明确定义一次逻辑类型,然后在整个接口中不断使用该定义。
枚举
OMG IDL枚举是将名称附加到数字的一种方法,从而读取代码的人就可以了解到更多的含义。OMGIDL 版的枚举看上去象 C++ 版本的枚举。
enum CloudCover{cloudy, sunny};
|
CloudCover 现在就成为可以在 IDL中使用的一种新类型。由于在一个枚举中可以有最多 2
32个标识,OMG IDL 保证枚举被映射到至少 32位的类型。规范中没有规定标识的有序数值,但它规定了将保持顺序。因此,不能假设
cloudy 永远拥有序数值 0 -- 某些语言映射可能将 1赋值给它。但可以确保
cloudy 小于
sunny 。
如果认为 IDL的目的是定义跨各种系统的接口,那么不指定序数值是明智的。您只将值发送到服务器。即,"cloudy"。在服务器空间中,
cloudy 可以由 0、1或如何实现语言规定的值表示。某些实现语言不允许您控制序数值,而 C++允许。OMG IDL 不允许空的枚举。
结构
struct 关键字提供了将一组变量集中到一个结构的方法。一旦创建了,
struct 表示可以在整个接口定义中被使用的
新类型。
struct Date {
short month;
short day;
long year;
};
|
定义
struct 时,要确保所创建的类型是可读的。不要在不同的名称空间中创建几个不同的同名结构,这只会使IDL 的用户搞糊涂。
识别联合
OMG CORBA 规范将 IDL 联合描述成 C 联合类型和 switch语句的混合物。IDL识别联合必须有一个类型标记字段用于确定在当前实例中使用哪个联合成员。像C++一样,一次只能有一个联合成员是活动的,并且可以从其识别名称来确定该成员。
enum PressureScale{customary,metric};
union BarometricPressure switch (PressureScale) {
case customary :
float Inches;
case metric :
default:
short CCs;
};
|
在以上示例中,如果识别名称是metric,或者使用了不能识别的识别名称值,那么
short CCs 就是活动的。如果识别名称是 customary,那么
float 成员Inches是活动的。联合成员可以是任何类型,包括用户定义的复杂类型。识别名称类型必须是整数类型(
short 、
long 、
long long 等,以及
char 、
boolean 或
enumeraton )。
常数定义
在 IDL 中定义常数的语法和语意与 C++一样。常数可以是整数、字符、浮点数、字符串、Boolean、octet或枚举型,但不能是
any 类型或用户定义的类型。这里有一些例子:
const float MeanDensityEarth = 5.522; // g/cm^3
const float knot = 1.1508; // miles per hour
const char NUL = '\0';
|
可以用十进制、十六进制或八进制记数法定义整数常数:
const long ARRAY_MAX_SIZE = 10000;
const long HEX_NUM = 0xff;
|
对于指数和小数,浮点字符使用常用的 C++ 约定:
const double SPEED_OF_LIGHT = 2.997925E8;
const double AVOGADRO = 6.0222E26;
|
字符和字符串常数支持标准换码序列:
const char TAB = '\t';
const char NEWLINE = '\n';
|
只要没有混合的类型表达式,就可以在常数说明中使用算术运算符。
用户异常
IDL 允许创建异常来指出错误条件。IDL用户异常类似于一个结构,在这个结构中,异常可以包含所选类型的任意多错误信息。最终是从方法中使用异常。这里有一个例子:
exception DIVIDE_BY_ZERO {
string err;
};
interface someIface {
long div(in long x, in long y) raises(DIVIDE_BY_ZERO);
};
|
异常将创建名称空间 --因此,异常中的成员名必须是唯一的。异常不能当作用户定义类型的数据成员使用。OMGIDL 中没有异常继承。
数组、序列和字符串
每次只传送一个元素是可以的,但我们通常有一个列表或向量或矩阵的信息要在客户机和服务器之间来回传送。数组几乎是所有编程语言所共有的类型,但一种语言的数组与另一种语言的数组实现通常是不同的。OMGIDL开发者面临的挑战是创建一组数组类型,它可以轻易地被映射到实现语言中。这种需求产生了IDL
array 和
sequence 。
string 类型是一种特殊的序列,它允许语言使用它们的字符串库和优化。
数组
OMG IDL有任意元素类型的多维固定大小的数组。所有数组都必须是有界的。数组非常适合于与拥有固定数量元素的列表一起使用,而这些元素通常都是存在的。例如:
// bounded and unbounded array examples
typedef long shares[1000];
typedef string spreadsheet[100][100];
struct ofArrays {
long anArray[1000];};
// unbounded arrays NOT ALLOWED
// typedef long orders[];
|
必须指定数组维数,并且它们必须为正的整型常量来表示。IDL 不支持在C 和 C++ 中的开放数组,这是因为没有指针支持。必须出现
typedef 关键字,除非指定的数组是结构的一部分。
在许多实例中,CORBA规范所没有提及的内容与它提及的内容是一样重要的。规范
不以任何方式、形态或形式指定数组下标编排方法。这表示从一种实现语言到另一种实现语言的数组下标可以是不同的,这样您不能假定将数组下标从客户机发送到服务器时,服务器会调整并指向正确的数组元素。某些语言的数组下标从0 开始,而其它的则是从 1 开始。
序列
在开发接口定义时,会大量使用序列。如果正在处理数据数组,其中许多值相同,那么序列就可以提供灵活性。
序列是变长向量,它有两个特征:元素的最大大小,在编译时确定,可以是无限的;长度,在运行时确定。序列可以包含所有类型的元素,不管是基本类型还是用户定义的类型。
序列可以是有界的,也可以是无界的。例如:
// bounded and unbounded sequence examples
typedef sequence<long> Unbounded;
typedef sequence<long, 31> Bounded;
|
一个无限序列可以拥有任意多个元素,只会受到平台内存大小的限制。有限序列则有边界限制。这两种序列都可以不包含元素、用户定义的类型,但可以包含其它序列。
string 和 wstring
string 等价于
char 的序列,而
wstring 表示
wchar 的序列。作为 C 和 C++的折衷,OMG IDL
string 和
wstring 可以包含任何字符,除空字符以外。
char 或
wchar 约定确定了类型为
string 的元素大小由 8 个字节表示,
wstring 类型的元素大小是 16个字节或更多。
名称和作用域
IDL中的字符串很特殊,然而在大多数语言中字符串都很特殊。许多语言都用库和特殊优化来处理字符串处理。通过将字符串归到它自己的类型,OMG允许语言映射使用特殊优化,这些优化不会与通用序列一起处理。
所有 OMG IDL标识都是区分大小写的。这意味着将把两个只有字符大小写不同的标识看作是彼此的重新定义。应该注意根据区分大小写的语言,所有的定义引用必须与定义的大小写相同。
IDL 作用域规则非常易于掌握。整个 OMG IDL内容和通过预处理器伪指令传入的所有文件共同组成了命名作用域。任何未出现在某个作用域中的定义都是全局作用域的一部分--只有一个全局作用域。在全局作用域中,以下定义组成了作用域:
module 、
interface 、
struct 、
union 、
operation 和
exception 。
module 关键字用于创建名称空间;这是其唯一目的。您定义的模块将创建一个逻辑组,模块的自由使用防止了全局名称空间的污染。根或全局空间被认为是空的,文件扫描中每次遇到模块关键字时,字符串"
:: "和其标识都会附加到当前根的名称后面。这就可以通过包括其它名称作用域来引用其它模块中的类型,如以下示例中的
Pennsylvania::river 。
一个标识可以在一个作用域中定义一次,但可以在嵌套作用域中重新定义。下例将解释这些要点:
module States {
// error: redefinition
// typedef sequence<string> states;
module Pennsylvania {
typedef string river;
interface Capital {
void visitGovernor();
};
};
module NewYork {
interface Capital {
void visitGovernor();
};
interface Pennsylvania {
void visit();
};
};
module NewJersey {
typedef Pennsylvania::river NJRiver;
// Error
// typedef string Pennsylvania;
interface Capital {
void visitGovernor();
};
};
};
|
每个内部模块(
Pennsylvania 、
New York 和
New Jersey )都有一个接口
Capital 和一个操作
visitGovernor() 。但它们并不相互牵连,因为它们在各自的模块中。当我们尝试在模块
States 中创建一个同名序列时,遇到了一个重新定义'State' 的问题。重新定义
Pennsylvania 发生在已经将它介绍为
New Jersey 中 'NJRiver'的作用域解析标识之后。请注意,我们在带有接口
Pennsylvania 的
New York 模块中没有发生错误,因为通过某些作用域解析标识介绍外部
Pennsylvania 模块。
接口
现在该定义接口了,它是我们学习 OMG接口定义语言的首要原因。有一个好方法来理解 IDL接口:它指定了服务实现和使用它的客户机之间的软件约定。让我们开始定义接口吧,这将运用我们所学到的IDL 知识。由于这一切都与通信有关,就让我们看一些定义
Listener 和
Speaker 的IDL。
Listener 必须连接到
Speaker ,然后
Speaker 将消息传送给
Listener 。这是一个回调的例子。
// Thrown by server when the client passes
// an invalid connection id to the server
exception InvalidConnectionIdException
{
long invalidId;
};
// This is the callback interface that
// the client has to implement in order
// to listen to a talker.
interface Listener
{
// Called by the server to dispatch messages on the client
void listen(in string message);
// Called by the server when the connection
// with the client is successfully opened
void engage(in string person);
// Called by the server when the connection with the client is closed
void disengage(in string person);
};
// interface on the server side
interface Speaker
{
// Called by the client to open a new connection
// Returned long is the connection ID
long register(in Listener client, in string listenerName);
// Makes the server broadcast the message to all clients
void speak(in long connectionId, in string message)
raises(InvalidConnectionIdException);
// Called by the client to sever the communication
void unregister(in long connectionId)
raises(InvalidConnectionIdException);
};
|
使用这个定义,我们定义了两个新的 CORBA接口类型:
Listener 和
Speaker 。每个接口都有一些方法,它们将由连接的另一端使用。客户机将通过获取对实现
Speaker 接口的服务器对象的初始对象引用来启动连接。这个对象引用可以传送给客户机,或者可以从命名服务中检索到这个引用。最重要的是,客户机首先联系
Speaker 。接着,客户机(即
Listener ,因为它实现
Listener 接口)必须注册到
Speaker ,并将引用传给
Listener 接口。这就使它们可以从
Speaker 处接收到消息。
要注意的一点是在 register 方法中
Listener 接口被当作一个类型使用。接口名称变成了类型,可以当作参数传送。看上去就像在传送
Listener 对象,但实际是一个对象引用。这是提供位置透明性的 CORBA模型的另一个示例。
有一点值得注意,每个对象引用 (IOR)仅指向一个接口。每个接口都披露一个或多个分布式对象的详细信息。我说“一个或多个”是因为可以有几千个对象实现分布式系统中的同一个接口。在这个示例中,
Speaker 可以将消息发送到几千个
Listeners 。因而在某种程度上,IDL接口对应于类定义,CORBA 对象对应于类实例。
结束语
对于 OMG IDL,我只是介绍了其的皮毛。显而易见,OMG IDL提供了一组内容丰富的内置类型和关键字,它们可以用来为与分布式系统中的对象的交互创建严密的描述。由于这种语言类似于C 语言,您应该了解所有使 C 语言变得如此成功的描述功能。所有 OMG服务定义都是用 IDL 编写的,证明 OMG IDL的强大功能。所有垂直市场标准化努力(Financial、CORBAMed 等)都是用IDL 编写的,这证明了其灵活性。
学习正确和有效地使用 OMG IDL 是您开始学习 CORBA和编写优秀的分布式系统的良好起点。每件事都从 IDL开始,如果您在项目开始时正确运用了IDL,那么您成功的机会会成倍增长。
参考资料
关于作者  | 
|  | Dave Bartlett 居住在美国宾夕法尼亚州的
Berwyn,他是顾问、作家和讲师。他是
Hands-On CORBA with Java
的作者,这是适用于公共课程或企业内部培训的 5 日教程。目前,Dave
正将课程资料编成书
Thinking in CORBA with Java。Dave
拥有宾州大学的工程和商业硕士学位。如果您对某个主题还有疑问或感兴趣,可通过
dbartlett@pobox.com 与
Dave 联系。
|
对本文的评价
|