一切都要从接口定义语言 (IDL) 开始。当我们采用 RPC 或 COM 技术以及 CORBA 技术来编写分布式系统时都需要它。在各种情况下,接口定义语言提供了将对象的接口与其实现分离的能力。IDL 提供了抽象,它提供了将事务与其具体实现分离的概念。它还为我们提供了一套通用的数据类型使得我们可以使用它们来定义更为复杂的类型。我们将采用所有这些数据类型来定义分布式服务的功能。IDL 的另一个好处是它剥离了编程语言和硬件的依靠性。本文探讨了 OMG IDL 的内置类型和要害字。 IDL 是一种规范语言。它答应我们从实现中剥离对象的规范(如何与它交互)。这是一个约定:“客户机女士,假如您要调用这个方法,请传送这些参数,然后我,服务器先生,将把这个字符串数组返回给您。”使用这个接口的客户机程序员不知道接口背后的实现细节。 OMG IDL 看上去很像 C 语言。这就很轻易将这两种语言以及它们的要害字做比较。但是,这种相似性只流于表面。每种语言的目的是完全不同的。我们在讨论这种语言时,您已经记住 OMG IDL 的目的是定义接口和精简分布对象的过程。 IDL 基本类型 OMG 接口定义语言有一些看上去应该很熟悉的基本类型。以下就是这些内置类型的表: 表 1. IDL 基本类型 类型 范围 最小大小(以位为单位) short -215 到 215-1 16 unsigned short 0 到 216-1 16 long -231 到 231-1 32 unsigned long 0 到 232-1 32 long long -263 到 263-1 64 unsigned long long 0 到 264-1 64 float IEEE 单精度 32 double IEEE 双精度 64 long double IEEE 双字节扩展浮点数 15 位指数,64 位带符号小数 char ISO Latin-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-" "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-" "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 float Pressure, ...) 中传送气压值。假如在该方法中使用 typedef float 语句,这将使该方法更具可读性。 typedef float AtmosPressure; 在 C++ 中,typedef 要害字表示类型定义,实际上别名也许是更为精确的术语。对于 OMG IDL,也许是这样,也许不是,这取决于其所映射到的实现语言。CORBA 规范不保证 short 的两种 typedef 是兼容的和可互换的。 在文体上,应注重不要为现有类型创建别名。您应该尝试创建不同概念的类型,它将为您的 IDL 添加可读性和可扩展性。最好是明确定义一次逻辑类型,然后在整个接口中不断使用该定义。 枚举 OMG IDL 枚举是将名称附加到数字的一种方法,从而读取代码的人就可以了解到更多的含义。OMG IDL 版的枚举看上去象 C++ 版本的枚举。 enum CloudCover{cloudy, sunny}; CloudCover 现在就成为可以在 IDL 中使用的一种新类型。由于在一个枚举中可以有最多 232 个标识,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^3const float knot = 1.1508; // miles per hourconst 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);}; 异常将创建名称空间 -- 因此,异常中的成员名必须是唯一的。异常不能当作用户定义类型的数据成员使用。OMG IDL 中没有异常继续。 数组、序列和字符串 每次只传送一个元素是可以的,但我们通常有一个列表或向量或矩阵的信息要在客户机和服务器之间往返传送。数组几乎是所有编程语言所共有的类型,但一种语言的数组与另一种语言的数组实现通常是不同的。OMG IDL 开发者面临的挑战是创建一组数组类型,它可以轻易地被映射到实现语言中。这种需求产生了 IDL array 和 sequence。string 类型是一种非凡的序列,它答应语言使用它们的字符串库和优化。 数组 OMG IDL 有任意元素类型的多维固定大小的数组。所有数组都必须是有界的。数组非常适合于与拥有固定数量元素的列表一起使用,而这些元素通常都是存在的。例如: // bounded and unbounded array examplestypedef 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 examplestypedef 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 serverexception 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 sideinterface 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,那么您成功的机会会成倍增长。 IDL-to-Java的映射(1): 怎样将离散的构件接口定义转换为 Java 元素 (本文摘自IBM developerWorks) Dave Bartlett 顾问,作家,讲师 2000年10月 内容:
yuxq 回复于:2003-09-17 18:50:38 基本数据类型 基本数据类型的映射是很直接的。 下表会让你了解到映射是多么简洁: IDL 类型 Java 类型 异常 boolean Boolean char Char CORBA:ATA_CONVERSION wchar Char CORBA:ATA_CONVERSION octet Byte string java.lang.string CORBA::MARSHAL, CORBA:ATA_CONVERSION wstring java.lang.string CORBA::MARSHAL, CORBA:ATA_CONVERSION short Short unsigned short Short large number mismatch ?test long Int unsigned long Int large number mismatch ?test long long Long unsigned long long Long large number mismatch ?test float Float double Double long double **unmapped 现在还不清楚是否会增加这一类型作为新的基本类型或者是类库的补充,如java.math.BigFloat 要浏览练习所有这些基本类型的 module,请访问 motheridl.idl 的例子. 整型(Integer) IDL 符号整型(signed integer)到 Java 类型的映射不存在任何问题。IDL 的 short 类型映射到 Java 的 short,IDL 的 long 映射到 Java 的 int,IDL 的 long long 映射为 Java 的 long。这些都是直接映射,不会给你带来什么麻烦的。 问题在于 IDL 的无符号整型(unsigned)。Java 编程语言没有 unsigned 类型,并且它所有的整数类型都是有符号的。在多数情况下这不会发生问题,但是当一个 IDL 无符号整数取值正好落在最高位所限制的取值范围中时,类型转换就会发生不匹配的错误。假如不检查并改正这种错误,转换为 Java 后的结果就会是一个负数,而不是一个接近无符号整数类型取值上限的数值。 例如,假设有一个从 IDL 接口返回的 unsigned short 类型值,这一类型的取值范围是0到65535。 Java 的有符号 short 类型能够接收的取值范围是-32768到32767。那么你可以看到,对于任何在32767到65535之间的值映射为 Java 的 short 以后将成为负数。这就造成了不匹配的障碍,必须进行测试。对于 unsigned short, unsigned long, 以及 unsigned long long 来说也是这样。 这意味着什么呢?首先,我建议以后写 IDL 定义时不要再使用无符号整型。这会使事情简单的多。其次,假如你在使用或者支持现有的 IDL 接口,那你必须测试 输入的无符号整数,并确保它在 Java 程序中被作为负数正确的处理,或者被拷贝到取值范围较大的变量类型中。 布尔型(Boolean)和8位字节型(octet)