核心规范
当前最新版本是3.0,是在2002年8月整理发布的。CCM的3.0规范也已经发布。 CORBA规范3.0终于出来了,也许是不能再拖了吧。比较奇怪的是3.0规范的编号是2002年6月份的(02-06-33),但是规范是在7月份才整理出来的。在CORBA3.0规范中去掉了 MiniCORBA和实时CORBA。 OMG组织的网站上有一个页面介绍在3.0规范将要增加的内容,我将其翻译过来,大家可以点这儿查看。因为新版本规范在不断推出,所以里面的内容可能会与现在的实际情况有点儿差异。
语言映射
规定IDL语言到Ada、C、C++、JAVA、COBOL、SmallTalk、Lisp、Python和CORBA脚本语言的映射,以及JAVA到IDL的反映射等。
对象服务
共有17个对象服务:命名服务、事件服务、时间服务、对象事务服务、安全服务、收集服务、并发服务、特性服务、持久对象服务、生命周期服务、交易对象服务、外表化服务、关系服务、查询服务、通知服务、许可服务、增强时间视图。
通用设施
主要包括国际化和时间、移动代理。
行业标准
主要有商业、金融、生命科学研究、制造业、电信、交通、医疗保健等。
规则与自由
----为何选择 CORBA 和 Java 技术?
欢迎光临有关 Java 和 CORBA 技术的这一定期栏目。此第一期将概述 Java 和 CORBA 技术,并帮助您决定如何最有效地让它们为您工作。以后的栏目将提供 Java 和 CORBA 编程的内行指导和代码。
在 1855 年,时年 26 岁的 Joshua Chamberlain 是 Bowdoin 大学的修辞学教授,在一次演讲中讲述了规则与自由之间的关系,以及二者之间失衡带来的危害。没有自由的规则是专制,而没有规则的自由是混乱。
公司信息系统部门的管理可能会形成过度的规则或过度的自由。规则过度的一个例子是只适用于一个厂商的管理指令,这样您的系统在扩展时,或在与潜在的公司合作伙伴集成时就会有问题。如果自由过多,个人或者开发小组就会根据他们来选择技术,这样,如果这些技术高手遇到他们“生疏的”优先认股权这样的话题时,扩展或集成又会变得很困难。开发人员可能会受业务环境不断变化的影响。
努力实现环境平衡
多种环境因素在决定我们的系统必须遵守的的规则。存在于一个业务环境中的系统应有足够长的寿命,以为公司和客户创造价值。持续为一个组织创造长期价值的系统必须能够应付经常的变化。合并、并购、新的管理和市场力量在改变系统的环境,以及通过使用该系统完成业务过程的用户的需求。变化是一个常数,必须将它添加到评估方程式中。当然,我们无法预知未来,但我们可以分析趋势并努力设计我们的系统,使之能够接受大量潜在的变化或系统扩展,同时对组织产生的影响又可以尽可能小。
为了做出正确的决策和评估,我们需要理解支配着环境的规则,以及我们的应用程序在该环境中应具有的自由。要理解的最重要的规则是,我们的系统处在不断发展的组织中,无论是在公司、政府还是教育组织中,都是业务问题支配技术问题。许多体系结构和平台评估都将主要问题漏掉了。他们通常会将两项或多项技术并排放在一起,然后比较技术特性列表。在完成的时候,大多数参与者会达成一致,因为他们不愿与评估小组中嗓门最大、最强硬、势力最大的派别争吵。显然,这不是作出选择的最有效方法。
确定关键业务问题
将业务问题转换为对组织需要的实际评估意味着应该着重考虑以下问题:
· 组织内的技术力量
· 不抑制程序员的开发环境
· 不对应用程序构成限制的运行时环境
· 运行时环境和开发环境的许可证问题和法律问题(可以说是规则的规则)
· 可找到有经验的开发人员
· 能找到收费合理、有见地的咨询服务公司
· 对组织的客户的影响
· 对厂商或技术合作伙伴的影响
· 组织的文化和策略
· 组织的上级管理联盟
· “大亨们”是否在讨论并购或合并
· 谁是决策者
这些业务问题通常比其他问题(比如采用每个会话一个线程的并发模型还是每个请求一个线程的并发模型)更重要。您可以走进老板的办公室,向他发表足可以充斥一次软件研讨会的技术观点,但是除非您强调了组织的需要,否则您的观点将显得非常空洞。
走近 CORBA
现在让我们用 CORBA 来解决这些问题。CORBA 是一种开放的行业标准,它由参加“对象管理组织 (OMG)”的 600 多家公司支持。这些公司包括硬件或软件公司、电信公司、金融公司、大学、医药研究所和政府机构。OMG 并不实现它们自己的规范;它们依靠厂商的实现,这些实现有助于将竟争导向到功能、性能、价格和令人鼓舞的质量这些方面。OMG 本身是一个充满生机的、活跃的组织,它的许多官员在各种会议上撰写文章并发表演讲。只要留心观察,您就会发现,他们总在关注将会对他们的组织及您的组织造成影响的各种变化。
除上述优势外,CORBA 还提供了高度的交互操作性。这保证了在不同的 CORBA 产品基础之上构建的分布式对象可以相互通信。因此,大型组织不需要让所有开发工作都使用同一 CORBA 产品。OMG 一直都在辛勤工作,以期通过加强 CORBA 规范来提供更高级别的可移植性。要知道,当厂商分化它们的产品以及开发人员添加限制时,他们往往会忘记源代码的可移植性这回事。应当指出,可移植性比以前的版本中有了实质性的提高,并且随着因特网互操作性协议 (IIOP) 形成强大的用户集团和众多联盟,可移植性将越来越好。
我们很快变会转入 Java 技术的讨论,但我们首先应将语言看作一个整体。信息系统有一个确定的规则,那就是不确定性本身。Java 编程语言可能是今天的语言,但谁知道下一种更好的语言哪一天会使它暗然失色呢?应当知道,就在此时此刻,有人正在他们的车库里加紧编写一种更好的语言。因此, CORBA 的语言无关性有助于您长期遵守信息系统环境中的这一法则。CORBA 支持一种经典的、稳定的对象模型,此模型将继续步入未来。它一直在凭借最新的语言不断发展,并将继续向极好的新语言(如 Python)扩展。
Java 编程语言及其他
此环境中可确定的另一点是它的复杂性,这是我们所不愿看到的,Java 技术正好可以派上用场。它能很好地满足需要,因为它的简单性将有助于使开发免于陷入底层的复杂性中。Java 语言在许多方面隐藏了一些基本的细节,但就整体而言,Java 技术改善从分析、设计到执行的整个过程。
在分析和设计阶段要尽可能统一和明确地表示您的系统和设计模式,这一点极为重要。“统一建模语言 (UML)”事实上已成为表述系统设计的标准语言。UML 的开发是协作方面的一个极好的故事。UML 的标准化过程也相当引人注意。UML 的标准化是通过 OMG 来实现的。OMG 认识到需要一种标准建模语言,以跨语言和环境表示分布式对象模型。Java 对象模型与 CORBA 对象模型几乎完全相同,从而很容易将您的 UML 设计映射到一种实现,同时也很容易将 UML 图映射到“Interface Definition Language (IDL)-to-Java 语言”实现。
经过分析和设计阶段的对象将有一些接口,这些接口定义其他组件将如何访问您的服务。现在需要将那些接口转换为一种实现语言。Java 技术同样使这一步很容易实现。“IDL-to-Java 语言”映射相当直接。Java 语言和 IDL 分别提供一个接口关键字,这个关键字有助于说明用每种语言表示的数据类型之间的紧密关系。Java 技术提供一种从 Java 接口到 IDL 的反向映射。不管选择哪个方向,您都应该用 IDL 表示您的接口;这使您能够使用所有其他语言与 IDL 之间的映射,并为您提供了为了在以后具有更大自由而必需的语言无关性。
体系结构案例
除了设计之外,系统的体系结构也很重要。许多系统体系结构问题涉及对现有系统(数据库、旧有系统、可用对象或可能用其他语言编写的应用程序)的包含。Java 和 CORBA 语言使得可以(而不是禁止)将这些强有力的系统带给更多的用户。这是一种解放 -- 许多系统可能许多年来都在发挥着作用。实际上,当完成将这些系统带入新世纪的所有 Y2K 以前的工作以后,在今后几年它们可能还将存在。由于其编译后的字节码结构,Java 语言将使您很容易创建和分发可移植的对象,而 CORBA 允许您将这些对象与您计算环境的其余部分进行连接和集成。
我并非想用 Java 和 CORBA 技术来排斥 Java 语言的远程方法调用、Enterprise JavaBeans 技术、 Microsoft 的 DCOM 甚至 DCE。Java 和 CORBA 语言最自由的方面是,OMG 正在加紧工作,以确保 CORBA 对象能够与大量的对象交互。当然,这些连接中有些较难实现,并且最终可能会很脆弱。请记住,这些类型的连接常常是因体系结构、成本、时间或业务等原因而建立的。
Joshua Chamberlain,带着对规则与自由的见解,在他在 Bowdoin 大学的演讲过后七年加入北方军队。他功勋卓著,并证明是一位有力的领导者,所以格兰特将军选择让他接受北弗吉尼亚军队的正式投降。他向投降的士兵行行军礼以示尊敬的行动,是恢复被四年内战破坏的破碎混乱的联邦系统的新基础中的第一块基石。理解规则和自由的平衡为任何类型的组织中的参与者提供了一个清晰的图像,使他们能够清楚地理解他们所面对的系统和要避免的混乱之间的相互影响。
小结
异构系统环境中的互连和通信应该是我们的目标。我们要选择使用的规则应该允许自由地将现有的系统与新系统一起使用以满足组织的需要。在这个变化的世界中,当变化被强加给您的组织时,这些规则应该提供最大的灵活性。这些规则提供了一个限制我们的自由的操作范围,这样就不易陷入盲目开发的境况。
谢谢您耐着性子读完了本文。我不是修饰学教授,我只是一个程序员,所以在下一期我们将开始讨论一个简单的示例 -- 使用 Java 技术实现 CORBA 客户机和服务器。
探索CORBA技术的应用领域---- 一个简单的CORBA/Java示例
首
页 CORBA文章 CORBA规范 CORBA实现 CORBA应用 相关技术 CORBA论坛
(本文摘自IBM developerWorks)
Dave Bartlett
顾问、撰稿人兼讲师
2000 年 7 月
内容:
IDL 接口
IDL 编译器
服务器
客户机
结论
6 月份,我们谈过您为什么要使用 CORBA 和 Java 技术。本月,我要通过一个可用的简单示例,让您开始探索 CORBA 技术的许多领域。不过,别忘了我们的目标是,创建这样一种分布式应用程序:使驻留在一台计算机上的客户机能向运行于另一台计算机上的服务发出请求。我们不想为诸如硬件或操作系统软件等细节问题操心,而只是想让这种服务能响应客户机的请求。
IDL 接口
全部 CORBA 结构是从一个接口开始的,理解接口的最佳方法就是想像我的汽车,对,我的汽车。虽然您不熟悉它,但如果我对您说:“开上我的车,带些三明治回来当午餐”,恐怕您就不会怀疑自己能不能驾驶我的汽车。您可能想知道它停在哪里,以及开它是否安全,但是您会确信开我的车与开您的车差别不大。这是因为,在各种汽车当中,人与汽车之间的接口已高度标准化了。我的轿车和您的跑车之间可能会有一些差异,但汽车的油门踏板、刹车和方向盘的安装都是标准的,您一定能轻松快速上路。
因为 CORBA 与语言无关,所以它依靠一种接口定义语言 (IDL),来表达客户机如何向实现接口的服务发出请求。我们的接口就是一个方法:add()。这个方法将取两个数(两个 IDL 的 long 型数)并返回这两个数之和。下面是我们的接口计算程序:
清单 1. calcsimpl.idl
module corbasem { module gen { module calcsimpl { interface calculator { long add(in long x, in long y); }; }; };};
这个接口中的 IDL 关键字有:module、interface、long 和 in。IDL 使用关键字 module 来创建名称空间,并且此关键字准确地映射为 Java 关键字 package。运行 IDL-to-Java 编译器时,生成的 Java 文件将会存到名为 calcsimpl 的子目录中。IDL 关键字 interface 完美地映射为 Java 接口,并代表一种抽象类型,因为两者都只定义您与对象通讯的方式,而不涉及对象的实现。IDL 关键字 long 是一种基本的整数类型,它至少映射为一个 4 字节的类型,这种类型在 Java 代码中就是 int。
想一想执行远程方法调用的机制,您就会发现定义参数传递的方向(客户机到服务器、服务器到客户机或者双向传递)是多么的有意义。在 IDL 操作中,这些方向用 in、out 和 inout 关键字来声明,每个参数都必须声明方向,以便使对象请求代理程序 (ORB) 知道该参数的去向。这会影响到为发送而进行的参数打包、参数解包以及内存管理。ORB 对参数了解得越多,它的效率就越高。关键字 in 表明 long x 和 long y 是从客户机传递到服务器。
图 1. 参与 CORBA 请求的各个部分
IDL 编译器
需要 IDL 编译器吗?您可能已经有了 ORB 供应商和 IDL-to-Java 编译器。但如果还没有,您从哪里获取呢?这里有好多,而且有些还可以免费下载。我推荐 Object Oriented Concepts, Inc. 的 Orbacus ORB。如果不将其用于商业目的,它还可以免费下载,而且完全符合 CORBA 2.3 规范。另外一个可试用 60 天的编译器是 Inprise 的 Visibroker,也完全符合 CORBA 2.3 规范并且可下载。如想获得这两种产品,请参阅参考资料。
接口定义以后,必须在 ORB 供应商提供的 IDL-to-Java 编译器上运行。IDL 编译器是一种精巧的实用程序,它生成 IDL 的 stub 和 skeleton 以及其它支持文件。生成的这些源文件,大部分将增强 CORBA 标准中定义的特定 IDL 类型的打包功能。编译器将生成大部分网络探测 (plumbing),这在分布式系统中非常重要。在最基本的级别中,IDL-to-Java 编译器只是一个按 CORBA 2.3 规范的定义来实现从 IDL 到 Java 语言映射的程序。手动生成这些代码既枯燥又费时,还容易出错;IDL-to- Java 编译器会处理这一切,所以您就不用操心啦;同时,它会用一定的规则约束您,并强制您执行封装。IDL-to-Java 编译器将把 CORBA -land 规则强加给您的系统。
输入下面的命令,从 Orbacus 执行 IDL-to-Java 编译器,把所有生成的文件都放在 CLASSPATH 的输出目录下。
清单 2. 调用 IDL-to-Java 编译器
jidl --output-dir c:\_work\corbasem calculator.idl
生成了什么呢?这个命令生成了构建实现所需要的全部 Java 源文件。IDL-to-Java 编译器可确保所定义的接口遵守 CORBA 规范的规则。
图 2. IDL-to-Java 编译器文件生成
下面是这些文件:
· calculator.java - 这个文件叫标记接口文件。CORBA 规范指出这个文件必须扩展 IDLEntity,并且与 IDL 接口同名。这个文件提供类型标记,从而使这个接口能用于其它接口的方法声明。
· calculatorOperations.java - 这个文件内含 Java 公共接口 -- calculatorOperations。规范指出,这个文件应该与具有 Operations 后缀的 IDL 接口同名,并且这个文件内含此接口映射的操作标记。上面定义的标记接口 (calculator.java) 可扩展这个接口。
· calculatorHelper.java - 设计 helper 类的目的是,让所需要的许多内务处理功能脱离我们的接口,但又随时可用到实现过程中。帮助程序文件含有重要的静态 narrow 方法,这种方法使 org.omg.CORBA.Object 收缩为一种更具体的类型的对象引用;在这种情况下,将是一个计算程序类型。
· calculatorHolder.java - holder 类是一个专门化类,是为了需要通过引用来传递参数的任意数据类型而生成的。这个示例中将不使用 holder 类,但我们将会在以后的栏目中经常见到它。
· calculatorPOA.java - skeleton 类为 CORBA 功能提供了请求-响应探测的一大部分。生成 calculatorPOA.java,是因为缺省方式下的实现是基于继承的,如果我们选择基于委托的实现方式,输出就会不一样。这些主题将在以后的栏目中详细介绍。
· _calculatorStub.java - 顾名思义,这是一个 stub 类。您的客户机将需要这个类来进行工作。
服务器
现在生成的文件必须在服务器上开始工作,用这个服务器实现我们的接口。所幸的是,大部分探测是适合我们的要求的,但别高兴得太早 -- 还有许多工作要做;就是说,所有这些文件都必须用在正确的地方。
让我们从 add() 方法的实现开始。
清单 3. SimpleCalcSvr.java -- add() 方法
SimpleCalcServant extends calculatorPOA { public int add(int x, int y) { return x + y; }}
请注意,我们的实现类扩展了已生成的类 calculatorPOA。从客户机发来一个请求时,该请求通过 ORB 进入 skeleton, skeleton 最终将调用 SimpleCalcServant,来完成请求并启动响应。我们的接口很简单,所以我们的实现也很简单。
服务器其余部分的实现,涉及如何围绕这个接口实现来设置 CORBA 体系结构,由于可移植性和灵活性方面的原因,许多这些调用要按 CORBA 规范执行。
我们需要完成的第一项任务是,详细说明要使用哪一个 ORB,然后予以初始化。下面的代码处理此任务:
清单 4. SimpleCalcSvr.java -- 初始化 ORB
java.util.Properties props = System.getProperties(); props.put("org.omg.CORBA.ORBClass", "com.ooc.CORBA.ORG"); props.put("org.omg.CORBA.ORBSingletonClass", "com.ooc.CORBA.ORBSingleton"); org.omg.CORBA.ORB orb = null; // 初始化 ORB orb = org.omg.CORBA.ORB.init(args, props);
初始化 ORB 时,需要准确地告诉它哪一个类将用作 ORBClass,哪一个类将用作 ORBSingleton 类。我们的实现将不考虑这些,但所有相关的探测则都将考虑这些。正如我前面所说的,这种情况下,我使用的是 Object Oriented Concepts, Inc. 的 Orbacus ORB,而 OOC 类在那两个 props.put() 调用中已给出。一旦填入了属性,props 就只作为一个参数传递给 ORB.init() 方法。实际情况可能不是这样;如果我们要把这个服务器移到另一个 ORB,不希望为服务器重新编码。所以,在理想情况下,我们宁愿改变一个配置文件,使之指向另一个 ORB 类,然后直接重新启动。
现在,ORB 已经到位并已初始化,并且实现也已经到位,只是尚未创建,此时,需要为实现创建一个完善的生存地点,而这可不像听起来那么容易,在一个分布式环境中,各个实现要求的环境可能略有不同。可以赋予实现许多特征。实现既可以是单线程的,也可以是多线程的;既可以是具有高度可伸缩性的对象池,也可以是单元素。这许多不同的服务器特征已产生了可移植对象适配器 (POA)。POA 使我们可以创建完善的环境,供我们的实现在其中驻留。所有符合 2.3 规范的 ORB 都会有一个根 POA,所有其它 POA 都是从根 POA 创建的。在这个简单示例中,我已将实现专用的代码分解为它自己的方法 runcalc()。
为实现创建一个环境将是我们的第一项任务,所以必须设置一个 POA。本来,CORBA 服务器使用基本对象适配器 (BOA),但是每个供应商的 BOA 都不一样,在最新版本的 CORBA 规范中,POA 已完全取代了 BOA。
清单 5. SimpleCalcSvr.java -- 设置 POA
// 从始终存在的 rootPOA // 设置可移植对象适配器 org.omg.PortableServer.POA rootPOA = org.omg.PortableServer.POAHelper.narrow( orb.resolve_initial_references("RootPOA")); org.omg.PortableServer.POAManager manager = rootPOA.the_POAManager();
从标题和定义可以看出,这是一个简单的示例。使用根 POA 而不创建新的 POA,将使事情变得简单。POA 管理器是一种封装了 POA 处理状态的对象,所以,我们使用 POA 管理器,将发给 servant 的请求排队。
还需要实例化实现:
清单 6. SimpleCalcSvr.java -- 实例化实现
// 创建计算程序接口的 servant SimpleCalcServant calcSvt = new SimpleCalcServant(); calculator calc = calcSvt._this(orb);
按照 CORBA 2.3 规范,所有 skeleton 均提供一个 _this() 方法,该方法使 servant 能得到目标 CORBA 对象的对象引用,servant 正是用目标 CORBA 对象来与这些请求相关联的。
完成实现的实例化以后,就必须把机制放到适当的位置,以便客户机能够找到它们。有许多不同的方法和服务可用来找到满足接口请求的对象。 CORBA Service 定义 Naming Service 和 Trader Services,来专门帮助客户机查找对象,以处理请求。也可以通过方法调用来传递对象。
在这个示例中,我们将使用所有方法中最直截了当的一种 — 将对象引用写入一个文件,该文件将由客户机选取。对于所有的 ORB 来说,创建一个对象引用的字符串表示,或者反过来,创建由字符串到对象的引用,都是必备的功能。
清单 7. SimpleCalcSvr.java -- 编写对象引用
// 将对象引用写入一个文件 PrintWriter refstr = new PrintWriter ( new FileWriter("calcref.ior")); refstr.println(orb.object_to_string(calc)); refstr.close();
最后要做的一件事,就是激活 POA,使客户机请求开始排队,并强制服务器输入其事件循环,以接收这些传入的请求。
清单 8. SimpleCalcSvr.java -- 激活 POA
// 使实现成为可用 manager.activate(); System.out.println("SimpleCalcSvr is running!"); orb.run();
客户机
如果您考虑一下正在发生的事件的机制,就会明白客户机和服务器实际上正是互为映像的。客户机将所有的参数打包以创建一个请求,然后以它自己的方式来发送这个请求。服务器只是将请求中的参数解包,执行运算,将返回值和输出参数打包,然后向客户机发回响应。客户机则将返回值和输出参数解包,然后继续处理。这样,客户机打包什么,服务器就解包什么,反之亦然。
这意味着您将会看到客户机和服务器具有相似的结构。客户机还必须创建并初始化一个 ORB。它可以是我们正在使用的 ORB,也可以是另一个供应商提供的 ORB;但是,不能是任意的 ORB,而应该是支持 IIOP 的 ORB,IIOP 是由对象管理集团 (OMG) 定义的、基于 TCP/IP 的互操作性协议。如果您的 ORB 比较旧,那么请小心,它可能无法与其它 ORB 通话。
首先,我们以相同的方式创建 ORB,就像创建服务器一样。
清单 9. SimpleCalcClient.java -- 初始化 ORB
java.util.Properties props = System.getProperties(); props.put("org.omg.CORBA.ORBClass", "com.ooc.CORBA.ORG"); props.put("org.omg.CORBA.ORBSingletonClass", "com.ooc.CORBA.ORBSingleton"); org.omg.CORBA.ORB orb = null; // 初始化 ORB orb = ORB.init(args, props);
看起来眼熟?应该是这样,它看起来与服务器完全一样。现在,客户机已经连接到了一个 ORB 之上,但我们的目标是调用一个服务,而这个服务是在系统中别的地方提供的,需要找到能响应请求的对象。在这个示例中,这意味着要从创建于服务器上的文件中获取一个对象引用。为了找到计算程序服务器,需要取得存储在这个文件中的对象引用的字符串版本,然后把它转换成对象引用,通过这个对象引用就可以进行调用了。
清单 10. SimpleCalcClient.java -- 获取对象引用
System.out.println("Getting reference from string..."); BufferedReader in = new BufferedReader( new FileReader("calcref.ior") ; String ior = in.readLine(); in.close(); calculator calc = calculatorHelper.narrow( orb.string_to_object(ior));
请注意,这里使用了由 IDL-to-Java 编译器生成的 calculatorHelper 类。calcref.ior 文件含有一个对象引用,而不是含有计算程序引用。calculatorHelper 类有一个 narrow 方法,可用来将抽象类型集中到特定的计算程序类型。
仔细看一看计算程序 calc,它表示计算机空间中另外某个地方的一个服务器。最后必须做的一件事,就是调用 calc 上的方法 add()。
清单 11. SimpleCalcClient.java -- 调用 add()
System.out.println( calc.add(2,3) ;
结论
已经讨论了很多内容,不过请想一想,都学到了什么。我们的客户机与服务器是完全隔离的,客户机不知道服务器在什么样的硬件上运行,使用的是什么操作系统,它是用什么语言编写的,它是不是多线程的,还有,它位于何处 — 是在隔壁,还是距离半个地球之遥。它只知道一点,即如果它调用 calc 中的 add(),就会得到可以指望的响应。
提供服务的情形全都是这样,电话或电力公司也是如此。当您拿起电话的时候,您所期望的是听到拔号音,然后您的呼叫能畅通连接,您并不在乎电话是通过光缆传输的还是通过卫星转发的,同样的情况在信息产业中也正在成为现实。多亏有了 OMG 和这个基本结构,我们才得以加进这个既简单而又非常有说服力的例子。
接触CORBA内幕:IOR、GIOP和IIOP
(本文摘自IBM developerWorks)
Dave Bartlett
顾问、作家和讲师
2000 年 8 月
内容:
网络
IOR
存根
打包:GIOP 和 CDR
IIOP
结束语
7 月,我们创建了一个简单示例 -- SimpleCalc。这个示例不值得多说;它是单一方法 add(),接受两个 IDL long 型变量,返回一个 long 型变量。讲授与学习 CORBA 的一个难题是:已知它是基于分布的客户机和服务器,从一开始它就变得很复杂。必须立即与网络打交道。所以,让我们现在就来谈谈网络。
网络
如果将话题深入一点,将发现许多都值得探讨,但又很简单。我们简单的计算器服务器设计成被远程调用,CORBA 专门确保让我们不必担心客户机环境和服务器环境之间的差异。客户机对服务器的远程调用是根据远程过程调用 (RPC) 协议生成的,该协议自 20 世纪 80 年代就存在。RPC 是由各种通信模型经过多年测试得到的结果 -- 这是已经在产品环境中测试过的可靠且真实的技术。我们现在使用的 CORBA 模型就基于该模型。
图 1. 网络
可互操作对象引用 (IOR)
让我们跟踪方法调用。客户机必须首先获得计算器的实例。它通过使用 calculatorHelper.java narrow() 方法来达到这一目的。ior 是可互操作对象引用 (IOR) 的字符串表示,是从文件 calcref.ior 中检索到的。这个文件是由服务器写的,以便客户机可以定位并连接到它。对 orb string_to_object() 的方法调用只取得 ior 字符串,并将它转换成对象引用。以下是客户机中的代码,SimpleCalcClient.java:
calculator calc = calculatorHelper.narrow(orb.string_to_object(ior));System.out.println("Calling into the server");System.out.println( calc.add(2,3) ;
IOR 中有什么?IOR 是一个数据结构,它提供了关于类型、协议支持和可用 ORB 服务的信息。ORB 创建、使用并维护该 IOR。许多 ORB 供应商提供了一个实用程序来窥视 IOR 的内部。OOC (Object Oriented Concepts, Inc.) 的 Orbacus 附带 IORDump.exe,如果您使用 Visibroker,它为您提供了 PrintIOR.exe。也有一些网站为您分析 IOR;可在 Xerox Parc 站点上找到我使用的一个实用程序。因为正在使用 Orbacus,我将对在 SimpleCalc 示例中创建的 IOR 运行 IORDump。得到以下输出:
C:\_work\corbasem\_sources\calcsimpl>iordump -f calcref.iorIOR #1:byteorder: big endiantype_id: IDL:corbasem/gen/calcsimpl/calculator:1.0IIOP profile #1:iiop_version: 1.2host: 192.168.0.10port: 4545object_key: (36)171 172 171 49 57 54 49 48 "1/2 1/4 1/2 9610" 48 53 56 49 54 0 95 82 "05816._R"111 111 116 80 79 65 0 0 "ootPOA.."202 254 186 190 57 71 200 248 ".?.9G.." 0 0 0 0 "...."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 中嵌入的是 type_id、IIOP 版本、主机地址和端口号,以及对象键。type_id 字符串是接口类型,众所周知,它是资源库标识格式。基本上,资源库标识是接口唯一的标识。这个标识可以是 DCE UUID 格式(COM 程序员比较熟悉它)或者是您指定的本地格式。 IIOP 版本将帮助 IOR 阅读器(通常是 ORB)正确了解 IOR 是哪种格式,因为 OMG 总是改进规范,每个版本的阅读方法都与以前版本略有不同 。主机地址和端口号将让我们接触到与期望的对象通信的 ORB。对象键和许多其它资料都是按特定于服务的信息的 OMG 标准构建的。这是帮助 OTB 支持服务器的特定于服务的数据。例如,这些专用 IOR 组件可以编码 ORB 类型和版本,或者帮助支持 OMG 安全服务的 ORB 实现。以上大多数信息指定了字符代码集转换,这样客户机和服务器就能够互相理解。
如果通过 Xerox Parc IOR 语法分析器运行 IOR,将得到以下输出:
IIOP_ParseCDR: byte order BigEndian, repository id, 1 profile_IIOP_ParseCDR: profile 1 is 124 bytes, tag 0 (INTERNET), BigEndian byte order (iiop.c:parse_IIOP_Profile): bo=BigEndian, version =1.2, hostname=192.168.165.142, port =4545, object_key=<...1961005816._RootPOA......9G......>(iiop.c: parse_IIOP_Profile): encoded object key is <<?AC?9610058 16%00_RootPOA %00%00屎?G? %F8%00%00%00%00>(iiop.c:parse_IIOP_Profile)non-native cinfo is object key is <#AB#AC#AB196100 5816#00_RootPOA #00#00#CA#FE#BA #BE9G#C8#F8#00# 00#00#00>; no trustworthy most-specific-type info; unrecognized ORB; reachable with IIOP 1.2 at host "192.168.165.142", port 4545
IOR 中最主要的部分是帮助客户机连接到服务器的那些部分。可以在 Xerox Parc IOR 阅读器的输出中看到这些部分。但是,其它许多信息是 Orbacus 专有的,其它 IOR 阅读器不能解释它。这些专用部分是作为附加到 IOR 的数据序列出现的,并且只有构建 IOR 的 ORB 才懂得这些数据。
存根
现在,我们知道 IOR 带来了什么功能。IOR 的目的就是使客户机能够连接到服务器,以便它能够完成方法调用。客户机必须用 Add 方法将 IOR 转换成它可以调用的实际对象。这是通过使用从 IDL 编译器中生成的两个 Java 文件来完成的。客户机将首先使用 calculatorHelper 对象将 IOR 的范围缩小到 _calculatorStub 代理对象。
以下是 Orbacus 附带的 jidl 编译器生成的 narrow() 方法:
public static calculator narrow(org.omg.CORBA.Object _ob_v) { if(_ob_v != null) { try { return (calculator)_ob_v; } catch(ClassCastException ex) { } if(_ob_v._is_a(id())) { org.omg.CORBA.portable.ObjectImpl _ob_impl; _calculatorStub _ob_stub = new _calculatorStub(); _ob_impl = (org.omg.CORBA.portable.ObjectImpl)_ob_v; _ob_stub._set_delegate(_ob_impl._get_delegate()); return _ob_stub; } throw new org.omg.CORBA.BAD_PARAM(); } return null;}
可以看到,它最主要的任务是创建一个新的 _calculatorStub 对象。_calculatorStub 充当驻留在服务器上的实际计算器对象的代理对象。如果您不了解代理模式,我将非常乐意向您介绍“四人组”Design Patterns 一书。实际上,代理模式无非是创建一个代表或充当另一个实际对象的替身的对象,另一个对象将最终将调用或执行服务。代理模式是一种重要且常用的模式。在所有分布式设计中都会用到它。我敢打赌,您肯定用过这种模式,只不过从没有称您的设计为代理模式。
一旦创建了 _calculatorStub,它就代表客户机的计算器接口。add 方法在服务器中实现,而该服务器在 IOR 中定义的地址上的电脑空间中运行。至此,这就所调用的 add() 方法。这里,需要注意两点:首先,我们必须以 _calculatorStub 的形式调用 add 方法。其次,请注意客户机将中断直到调用返回,就像其它同步方法调用一样。这是一种请求响应协议,它模仿单进程应用程序。编程客户机,然后使用该请求响应协议执行客户机就像用库和 API 调用创建的常用编程开发环境一样普通自然。这并不表示您不能使用异步调用;您当然可以生成那种类型的调用。我将在以后的专栏文章中讨论那些话题。
打包:GIOP 和 CDR
至此,在体系结构中,我们已成功欺骗了客户机,使它相信服务与它在一起。但事实并为如此,并且在以后几步中,我们必须将数据和方法调用铸造成一种形式,它允许在网络上继续该调用,并且可以在另一端使用该调用。这并不是无关重要的,且这种模型已经问世好几年了。您也许已经多次见过 OSI 模型了,在图 2 中,您将看到 OSI 模型,旁边就是 OMG 所使用的模型。
图 2. OSI 的结构 vs. GIOP 协议堆栈
客户机调用接口操作时,它必须将操作数据(in 和 inout 参数)发送到服务器。此时的困难在于将数据转换成公共格式,这样服务器抽取操作数据时不会误解或错误对齐数据。因为服务器可以是任意数量不同的平台,我们应该预计到客户机和服务器之间的体系结构差异。CORBA 通过严格定义如何将数据转换或打包成公共格式来处理这种问题。然后在连接的另一端重新组成或解包数据。这是通过用最基本的结构表示数据来完成的,最基本的结构就是字节流,也就是八位元流。
CORBA 规范将八位元流定义成“一种抽象表示法,通常对应于要通过 IPC 机制或网络传输来发送到另一个进程或另一台机器的内存缓冲区”。IDL 八位元准确映射成 Java 字节。它们都是 8 位值,客户机或服务器都不打包这种值。将这些参数转换成八位元序列的根本目的是产生用于信息交换的基本结构。
现在,我们应当窥视 _calculatorStub 生成的代码的内部信息。请记住这不是由我编写的 -- 它是由 Orbacus 附带的 IDL-到-Java 编译器 jidl 生成的。
【发表回复】【进入论坛此帖】【关闭】
yuxq 回复于:2003-09-17 18:49:03
//// IDL:corbasem/gen/calcsimpl/calculator/add:1.0//public int add(int _ob_a0, int _ob_a1) { System.out.println("Inside _calculatorStub.add()"); while(true) { if(!this._is_local()) { org.omg.CORBA.portable.OutputStream out = null; org.omg.CORBA.portable.InputStream in = null; try { out = _request("add", true); out.write_long(_ob_a0); out.write_long(_ob_a1); in = _invoke(out); int _ob_r = in.read_long(); return _ob_r; } catch(org.omg.CORBA.portable.RemarshalException _ob_ex) { continue; } catch(org.omg.CORBA.portable.ApplicationException _ob_aex) { final String _ob_id = _ob_aex.getId(); in = _ob_aex.getInputStream(); throw new org.omg.CORBA.UNKNOWN("Unexpected User Exception: " + _ob_id); } finally { _releaseReply(in); } } else { org.omg.CORBA.portable.ServantObject _ob_so = _servant_preinvoke("add", _ob_opsClass); if(_ob_so == null) continue; calculatorOperations _ob_self = (calculatorOperations)_ob_so.servant; try { return _ob_self.add(_ob_a0, _ob_a1); } finally { _servant_postinvoke(_ob_so); } } }}
要注意的部分是包含 _request()、write_long() 调用,和 _invoke() 及随后的 read_long()。对 _request() 的调用使用要调用的方法名称,和显示是否需要响应的布尔 (boolean) 值。它返回 CORBA 规范指定的 org.omg.CORBA.portable.OutputStream 对象。对于可移植性,这是必要的,因为 Java 经常被下载,并且依赖于它运行的机器上的公共库。对于 ORB 是这样,对于 IO 也是这样。因此,CORBA 规范为 Java 语言定义了比其它语言更广泛的可移植类型集合。
通用 ORB 间协议 (GIOP)
通用 ORB 间协议 (GIOP) 用来为这个由不同计算机及其各种体系结构组成的凌乱世界中传送消息定义结构和格式。如果使用 GIOP 的结构和格式,并将它们应用于 TCP/IP,那么就得到 IIOP。GIOP 有两个版本:1.0 和 1.1。这就意味着我们的消息根据其符合的 GIOP 版本可能有不同的格式。
至此,我们必须看一下 GIOP 以了解请求在变成正确格式化的 CORBA 请求时所要经历的操作。尽管我们将仔细研究请求,响应只是请求的镜像图像。如果您知道请求的工作原理,那么您就能了解响应。
GIOP 请求消息分成三部分:GIOP 消息头、GIOP 请求头和请求主体。GIOP 消息头表示这就是一条 GIOP 消息。它包含 GIOP 版本、消息类型、消息大小,然后根据您是使用 1.0、1.1 还是 1.2,包含字节次序 (GIOP 1.0) 或一个位标志字段,该字段包括字节次序以及一些保留位标志。GIOP 1.1 添加了对消息存储碎片的支持,GIOP 1.2 添加了双向通信支持。更新的版本都是向下兼容的。
公共数据表示 (CDR)
公共数据表示 (CDR) 是 CORBA 调用中将使用的数据类型的正式映射。客户机生成请求时,它不必知道请求要发送到什么地方,或者哪一台服务器将响应该请求。CORBA(作为规范)和 GIOP(作为规范的一部分,定义消息结构和传送)被设计成允许实现一个接口的可能的多种不同服务器之一来响应请求。规范必须定义如何打包操作中的数据,这样所有可能的服务器都可以抽取参数并调用远程操作,并且数据转换不会产生多义性。这种转换问题的典型示例就是指针。客户机中的指针对于在另一台机器上运行另一个进程的服务器意味着什么?毫无意义。或者,变量如何在使用不同寻址方案(大尾数法,小尾数法)的机器间发送?这些数据类型必须转换成服务器能够理解并使用的流。显然,CORBA 规范在公共数据表示方面是十分详细的。这是我们不必涉足的细节层次,但如果您想要了解详细信息,请阅读规范或 Ruh、Herron 和 Klinkeron 合著的 IIOP Complete 一书。
一旦包装了所有数据,就将使用 IOR 中的信息来创建连接。您可以区别 IOR 的结构,通常必须使用 TCP 作为传送机制。但是,也可以使用其它传送(再次提醒,请参阅 CORBA 规范以获取详细信息)。ORB 守护程序负责查找 IOR 指定的对象实现,以及建立客户机和服务器之间的连接。一旦建立了连接,GIOP 将定义一组由客户机用于请求或服务器用于响应的消息。客户机将发送消息类型 Request、 LocateRequest、CancelRequest、Fragment 和 MessageError。服务器可以发送消息类型 Reply、 LocateReply、CloseConnection、Fragment 和 MessageError。
如果我们扯开 GIOP 消息,它看上去就像:
0x47 0x49 0x4f 0x50 -> GIOP, the key0x01 0x00 -> GIOP_version0x00 -> Byte order (big endian)0x00 -> Message type (Request message)0x00 0x00 0x00 0x2c -> Message size (44)0x00 0x00 0x00 0x00 -> Service context0x00 0x00 0x00 0x01 -> Request ID0x01 -> Response expected0x00 0x00 0x00 0x24 -> Object key length in octets (36)0xab 0xac 0xab 0x31 0x39 0x36 0x31 0x300x30 0x35 0x38 0x31 0x36 0x00 0x5f 0x520x6f 0x6f 0x74 0x50 0x4f 0x41 0x00 0x000xca 0xfe 0xba 0xbe 0x39 0x47 0xc8 0xf80x00 0x00 0x00 0x00 -> Object key defined by vendor0x00 0x00 0x00 0x04 -> Operation name length (4 octets long)0x61 0x64 0x64 0x00 -> Value of operation name ("add")0x20 -> Padding bytes to align next value
您应该了解大概情况了。这种消息流是高度结构化的。它也必须是,为了客户机可以创建服务器可以转换成实现的消息 -- 不管实现如何运行,或在哪里运行。服务器也必须为在响应客户机时使用的返回值和参数执行相同操作。此消息格式在 OMG 成就中非常重要,因为它可以实现可移植性和互操作性目标。这种可移植性将给予您我们在第一篇专栏文章中谈到的自由。您无需关心硬件、数据库或编程语言。只要关心您的信息就行了。
IIOP
我们还没有彻底结束。GIOP 是 CORBA 方法调用的核心部分。GIOP 不基于任何特别的网络协议,如 IPX 或 TCP/IP。为了确保互操作性,OMG 必须将 GIOP 定义在所有供应商都支持的特定传输之上。如果有详细和简洁的消息规范,则不会提供互操作性,因为所有供应商使用不同的传送机制来实现这个互操作性。因此,OMG 在最广泛使用的通信传输平台 -- TCP/IP 上标准化 GIOP。GIOP 加 TCP/IP 等于 IIOP!就这么简单。
需要使用已发布对象服务的客户机将使用 IOR 中的值来启动与对象的连接。我们已经绕了一个圈子,又回到了 IOR。IOR 对于 IIOP 是至关重要的,任何要对某个对象调用方法的客户机都要将“请求”消息发送到 IOR 中详细说明的主机和端口地址。在主机上,服务器进程在请求进入时会侦听端口,并将那些消息发送到对象。这就要求服务器主动侦听请求。
生活有阴阳两面,每件事都有缺点,互操作性和 IIOP 也不例外。OMG 推出和运行了 IIOP,对比 ORB 供应商它们自己的服务器上实现此功能,并且没有服务器方可移植性的年代,这是一大改进。但如果要求服务器是位置无关的,我们应该做什么?如果主机和端口值嵌入 IOR 中,每当您将对象从一个服务器移到另一个服务器,以均衡负载时,这个问题就会突然出现。可喜的是这个问题已经解决了;但又有一条坏消息,每家供应商的解决方法都不同。
结束语
现在,将负载均衡话题留到将来讨论。如果您在几年前有 CORBA“经验”(也就是说在一段时期内),而现在从事另一项研究,我相信您将感到惊喜。CORBA 规范已经获得很大进步,可以确保您为一个 ORB 编写的服务器代码可以移植到运行另一个 ORB 的另一台服务器上。解决方案非常简单,并且以经典的协议为基础。客户机和服务器间的标准交换语法基于某些 OMG 详细说明的需求。OMG 通过使用网络寻址协议 (IIOP) 独立于消息传递协议 (GIOP),为其规范创建了更多功能。这也确保随着信息工业的变动(的确发展很快),CORBA 仍能跟上它的步伐。我最喜欢的是,对于我们刚讨论的关于如何使之成功完成的 CORBA 对象调用,我不必编写代码!在 ORB 中和用 IDL 标准化接口的功能中已经概括了网络测量和打包的详细信息。
OMG 接口定义语言
----定义分布式服务的能力
(本文摘自IBM developerWorks)
Dave Bartlett
顾问、作家和讲师
2000 年 9 月
内容:
IDL 基本类型
用户定义的类型
常数定义
用户异常
数组、序列、字符串
名称和作用域
接口
结束语
一切都要从接口定义语言 (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月
内容:
前提
IDL-to-Java 库
基本数据类型
holder 类
结论
这篇文章开始阐述 IDL-to-Java 的映射。这个月的专栏介绍基本的数据类型、结构和数据传递。下个月我们将会介绍更加复杂的类型。语言映射并非无足轻重,COBRA 规范中有很大一部分阐述的就是多语言映射。
COBRA 规范详细说明了接口定义语言(IDL)并详细说明了 IDL 到几种编程语言的映射,如 C,C++,ADA,COBOL, Lisp,Smalltalk 和 Java。IDL 的优势在于它完整而详细描述了接口及操作参数。 IDL 接口提供了开发使用接口操作的 Client 和实现接口的 Server 所需要的信息。当然,Client 和 Server 并没有在接口中编写,IDL 只是一种纯粹的描述性语言。 Client 和 Server 用完全意义上的编程语言来编写。IDL 语言映射应该为接口定义提供一致的、可移植的构架。那么这些映射的接口就可以在 Server 端实现,或者像其它方法一样由 Client 端调用。对于使用的每一种编程语言来说,都需要有一种映射来方便的转换已定义的 IDL 概念。 IDL 概念到 Client 语言结构的映射取决于编程语言的结构和能力。例如,一个 IDL 异常(exception)在不提供 exception 的语言中可能被映射为结构(structure),而在提供 exception 的语言中就可以直接映射为 exception。每种语言的特性和效率必须与之相适应。
IDL 到编程语言映射的前提
所有的语言映射都有近似的结构。它们必须定义语言中的表达方法:
· 所有的 IDL 基本数据类型
· 所有的 IDL 结构数据类型
· IDL 中定义的常量
· IDL 中定义的对象引用
· 对操作的调用,包括传递参数和接收结果
· 异常,包括当操作产生异常时的处理和访问异常参数的方法
· 属性访问
· ORB 定义的操作符号,如动态调用接口,对象适配器等等。
一个完整的语言映射允许程序员以对于某种特定编程语言来说很便捷的方式来访问所有的 ORB 功能。为了支持源代码的可移植性,所有的 ORB 实现都要支持某种语言的同一映射。
MotherIDL
在本文中将使用一个叫做 MotherIDL 的文件-- motheridl.idl。以上要求这一 IDL 文件都可以做到,它的目的就是示意并检验部分映射。你最好先浏览它一下,这样我们逐步讲述的时候你就会对它比较熟悉了。
为了检验映射,你需要一个 IDL-to-Java 的编译器。每个 CORBA ORB 都至少带有一个 IDL 到某种语言的编译器。它们大多针对 C++ 或 Java 编程语言,当然也有其它的。新增加的还有针对 Python 和 Delphi 的。本栏我们要使用 Object Oriented Concepts, Inc. 的 Orbacus ORB 所带的 JIDL。(见 参考资料) 当然你也可以使用任何 IDL 到 Java 的编译器,但是要确定它兼容 CORBA 2.3,因为如果编译器的版本较早的话你的结果可能有本质的区别。
现在首先要做的就是运行 IDL-to-Java 的编译器来编译 motheridl.idl.
jidl --output-dir . . \. . \. . MotherIDL.idl
这一步很奇妙,它在提供支持和通用 CORBA 通道的同时给出了一大堆 IDL 文件所映射的 Java 文件。
IDL-to-Java 的库结构
我们要学习的第一个映射是 IDL 关键词 module 到 Java 的关键词 package 的映射。这是一个简单的完全映射。如果在 IDL 文件中有关键词 module ,你的 IDL 到 Java 编译器将会用关键词 package 生成一个 Java 类的目录结构和库结构。(如果你对于 Java 程序中如何使用关键词 package 还有什么困惑的话,那你现在应该去复习一下了。)这一映射规定了从 IDL 生成的大部分结构,对 IDL 到 Java 的映射有着重要影响。
以 motheridl.idl 为例,你将在 IDL 文件中看到如下结构:
module corbasem { module gen { module motheridl { module holderexample { ... }; module conflicts { ... }; module basictypes { ... }; module constructedtypes { ... }; module holderexample2 { ... }; module MI { ... }; module MI2 { ... }; }; };};
这转换为以下的目录结构:
E:\_work\TICORBA\Projects\corbasem\gen\motheridl>dir /AD /S Volume in drive E has no label. Volume Serial Number is B415-7161Directory of E:\_work\TICORBA\Projects\corbasem\07/15/2000 01:41p <DIR> .07/15/2000 01:41p <DIR> .. 0 File(s) 0 bytesDirectory of E:\_work\TICORBA\Projects\corbasem\gen07/15/2000 01:41p <DIR> .07/15/2000 01:41p <DIR> .. 0 File(s) 0 bytesDirectory of E:\_work\TICORBA\Projects\corbasem\gen\motheridl07/15/2000 01:41p <DIR> .07/15/2000 01:41p <DIR> ..07/15/2000 01:41p <DIR> basictypes07/15/2000 01:41p <DIR> conflicts07/15/2000 01:41p <DIR> constructedtypes07/15/2000 01:41p <DIR> holderexample07/15/2000 01:41p <DIR> holderexample207/15/2000 01:41p <DIR> MI07/15/2000 01:41p <DIR> MI2 0 File(s) 0 bytes
将 IDL 映射为 Java 元素的一个基本思想是一致性。如同其它任何类库一样,它们必须存在稳定性,也就是说在修改库中的类时不必删除现有的方法;不改变接口。CORBA 在这方面应该做的更好,因为多个 ORB 提供商将要使用这一映射来生成类库。我们希望在不同 ORB 实现之间有着一致性;这意味着可移植性。如果 ORB 提供商A的 IDL 到 Java 映射在 org.VendA.Excep 的 package 中提供了 UserException ,而提供商B在 org.VendB.UtilTypes 的 package中也提供了相同的 UserException ,Client 或 Server 的代码将不能够移植。Client 或 Server 移动到另一个 ORB 时需要改变代码并重新编译。这可不是我们选择 CORBA 的原因!我们希望并要求可移植性;因此 OMG 规定了库结构。
在 Java 编程语言中,关键词 package 将 Java 类组成类库,并控制对类库中的构件的访问。我们的 Java 类库中将包含 Java 编程语言中需要编译的所有素材,这些我们已经在 IDL 中描述过了。但是为了不同提供商的类库之间的可移植性,必须定义类库结构或者类库包,并且严格遵守这些定义。只有这样 Client 才能依赖于代码并且保证不用在不同提供商的ORB之间移植时重写代码。
在 IDL 到 Java 映射的可移植性部分中规定了 API,它提供了库结构和最小功能集,使 Java ORB中可以使用可移植的存根和骨架。因为 Java 类经常被下载,它们往往来自独立于ORB提供商的代码,因此对于 Java 编程语言的互操作性需求就超过了其它语言。出于这些原因,定义存根和骨架使用的接口是最基本的要求。如果这些结构不加以定义的话,存根(或骨架)的使用将需要一个或两个方案。一个要求由 IDL 到 Java 编译器或ORB提供商提供的类似工具(或者与ORB所使用的兼容)生成存根(或骨架),以使生成的存根能够适合ORB提供商的类库结构,另一个方案要求下载存根或骨架时同时下载整个ORB运行时环境。我们不希望采用这两种方案中的任何一个。理想的情况是将 IDL 发送到 Client 端或者由 Client 下载,利用 Client 端选择的工具生成存根,并从它们的环境连接到我们的 Server。
因此,Java 语言映射高度依赖于标准的结构,它在一套标准的 Java 包中实现 -- org.omg.*。IDL 到 Java 映射中重要的一项就是包含 PIDL,本地类型和ORB可移植接口的压缩文件。它给出了包中确切内容的定义性声明。当然,如同这一行中的其它任何事一样,在不远的将来 IDL 到 Java 映射的版本也会发生改变。但是以你对目前的映射的理解,你一定会注意到将来的版本中任何可能的二进制不兼容性
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)
这两种 IDL 类型到 Java 类型的映射也是直接映射。IDL 的布尔常量 TRUE 和 FALSE 映射到相应的布尔量 true 和 false。
IDL 的字节型 octet 长度为8位,它映射为 Java 的 byte 类型。
字符型(Character)和字符串类型(string)
字符类型的映射有一些困难。首先是所使用的字符集,其次是表示整个字符集所需要的编码位数。不同的语言有不同的字符,它们分别被国际标准化组织映射为不同的字符集。这些字符集代表了某种语言的字母或符号到数字的映射。一种语言中符号的数量决定了这种语言所需要的位宽。现在有8位和16位两种字符集。
IDL 的字符型数据用8位来表示字符集中的一个元素,而 Java 的字符型数据用16位无符号整数来表示 Unicode 字符。要正确的进行类型转换,Java CORBA 运行时环境验证了所有由 IDL 的 char 型映射来的 Java char 型的有效性范围。这一方向的映射没有什么困难,因为我们是把8位的数值映射为16位的数值,Java 程序碰到的任何问题都很容易处理,因为空间是足够的。然而,要把 Java 的 char 型映射为 IDL 的 char 型,Java 的 char 型就有可能会超出 IDL 使用的字符集所定义的范围。这种情况下就会产生 CORBA:ATA_CONVERSION 的异常。 IDL 的 wchar 仅仅是映射为16位字符集的 IDL 类型,它映射为 Java 的基本类型 char。如果 wchar 超出了字符集所定义的范围,将会产生 CORBA:ATA_CONVERSION 的异常。
IDL 的 string 类型映射为 java.lang.String。别忘了 IDL 的 string 是一个 char 的序列。这意味着 IDL 的 string 必须满足 IDL char 和 IDL sequence 的要求。因此,在编译过程中要进行字符串中的字符范围检查和字符序列的越界检查。字符范围非法会引起 CORBA:ATA_CONVERSION 的异常。越界会引起 CORBA::BAD_PARAM 的异常。对于 IDL 的 wstring 类型来说其注意事项和使用规则也是一样的。
浮点型(Floating-point)
因为OMG IDL 和 Java 的浮点型数据都遵从 IEEE 754- 1985 Standard for Binary Floating Point Arithmetic,所以浮点型数据的转换没有问题。然而,目前 Java 编程语言中还没有对 IDL 的 long double 类型的支持。现在还不清楚 java.math.* 是否会增加这一类型作为基本数据类型或新的包,或者说什么时候会增加,也许会作为 java.math.BigFloat 吧。这一问题就留待以后修订了。
映射的合法性检查
我们可以用 Orbacus ORB 带有的 IDL 到 Java 编译器来运行我们的 motheridl.idl 文件:
jidl --output-dir . . \. . \. . MotherIDL.idl
运行结果所产生的 Java 接口定义在 UseAllTypesOperations.java 文件中给出。
这一生成的文件检查映射的合法性。所有的 Java 类型都像我们所预期的那样。唯一的诀窍大概就在于 inout 和 out 参数位置上的数据类型--它们都是 holder。
holder 类
在任何语言中,参数传递都是一个有趣的话题,当要把OMG IDL 这样独立于语言的体系结构进行语言映射时,它又是一个伤脑筋的问题。 Java 程序总是采用传值的方式。这意味着要把原语传递到方法中时,会得到一个原语的本地拷贝。然而,如果方法的参数是 Java 对象的话,则不会传递对象本身而是对象的引用。因此,被传递的是引用的拷贝,但是这个引用通过值来传递。
CORBA 规定了 in 参数和返回类型采用"值调用"(call-by-value)的方式,而 CORBA 的 out 则是"结果调用 "(call-by-result)。inout 的参数类型在输入服务器时通过"值调用"的方式来传递,而在输出时则采用"结果调用"的方式。 out 和 inout 的参数传递模式不能被直接映射到 Java 的参数传递机制。要支持 out 和 inout 的参数传递模式需要另外使用 holder 类。
IDL 到 Java 的映射为所有的 IDL 基本类型定义了 holder 类,它同时也作为用户定义类型的标准格式。IDL 到 Java 编译器可以为用户定义的类型生成 holder 类,以便以后在 Java 程序中实现这些参数模式时使用。 Client 生成并传递一个适当的holder Java 类实例,这个实例的值在 Java 程序中被传递给每一个 IDL out 或者 inout 参数。Holder 实例的内容(而不是实例本身)被调用它的程序所修改,Client 使用的是调用返回后(有可能)改变了的内容。
CORBA-Java 类库中提供了 IntHolder 的例子。要注意 Java 中 public int 的值,以及 _type() 方法的返回值--它的类型是 long。记住这点是因为 Java 的 int 映射为 IDL 的 long。
记住org.omg.CORBA 包中提供了所有基本 IDL 类型的 holder 类,并为所有已命名的用户定义 IDL 类型生成 holder 类,那些用 typedef 定义的除外。
对于用户定义的 IDL 类型来说,holder 类根据映射的(Java)类型名再附加一个 Holder 来命名。对于 IDL 基本数据类型来说,holder 类的名字就是数据类型映射的 Java 类型名,开头字母大写,并附加一个 Holder(例如,IntHolder)。
每个 holder 类都有一个来自实例的构造函数,一个默认的构造函数,以及一个公有的实例成员(value)),它是一个有类型的值。默认构造函数将值域设置为 Java 语言为不同类型所定义的默认值:boolean 为 false,numeric 和 char 类型为 0, string 为 null,对象引用也是 null。为了支持可移植的存根和骨架,holder 类也实现 org.omg.CORBA.portable.Streamable 接口。
结论
这里只是 IDL 到 Java 映射的第一部分。你应该认识到,用 Java 编程语言编写基于 CORBA 的应用程序和构件要求理解 IDL 到 Java 语言映射。CORBA 规范包括很多语言映射。它们在你工作的各方面中都会有所体现。你的代码的可移植性不仅取决于OMG的库结构,而且也取决于你自己所生成的库结构。映射的目的是将接口定义转化为某种特定语言的实现,这种转化可能会以牺牲定义的完美性来换取实际的可行性。正如我们在无符号整型的例子中所看到的,你必须保持一定的警惕性以保证你的应用程序能够正常工作。
语言映射是一种翻译,因为你在使用一种语言,而要把它转换为另一种同样可以工作并且可以理解为一种实现的语言。正如一些短语和结构没法在两种自然语言之间很好的翻译一样,有些结构也不太容易映射。我们必须找到解决的方案,虽然它们可能使映射变得更复杂,但是在实现时用起来更容易。holder 类就是这样;开始理解它可能要花些功夫,但是长远来看,它提供了一种通用的、一致的解决方案。
IDL-to-Java映射(2):
使用 IDL 映射创建组件接口
(本文摘自IBM developerWorks)
Dave Bartlett
顾问、作家兼讲师
2000 年 11 月
内容:
结构
枚举
联合
序列和数组
数组
序列
异常
Any
辅助
结束语
我们就本月的 CORBA 连接中更复杂的类型和辅助类的问题,来继续研究 IDL-to-Java 映射。
上个月,在 IDL-to-Java 映射的第一部分中,我们研究了基本数据类型、结构和数据传送。本月,我们将集中精力研究映射常数和结构,讨论某些更复杂的类型,例如,序列、数组、异常和 Any 类型。最后,将研究辅助类和它们的功能。
首先,应该提醒您我们正在使用接口定义语言,任何 IDL 的目的就是创建某些组件或服务器的接口。这意味着我们正在创建新的类型。因此,让我们从 interface 关键字的映射和使用 OMG IDL 创建新类型的机制开始讨论。
接口
我们将回到所有 IDL 文件的母板 -- MotherIDL.idl。我使用这个文件来研究 IDL-to-Java 映射各个方面。在 MotherIDL 中,有一个名为 FindPerson 的接口。显示如下:
清单 1. FindPerson 接口
interface FindPerson { Person GetByName(in string sName); Person GetBySSN(in string sSSN); };
我们现在只集中讨论接口和两个方法 GetByName() 和 GetBySSN()。关键字 interface 的确切含义是什么?仔细研究 OMG CORBA 规范,就会发现 interface 关键字后带有标识(本例中是 FindPerson)。FindPerson 是接口名称,并且它定义了合法的类型名称。只要标识符合 IDL 的语法,就可以使用这种类型的标识。将 FindPerson 作为方法参数或结构成员在 IDL 中使用时,一定要记住 FindPerson 表示支持 FindPerson 接口的对象的引用。
IDL-to-Java 映射的目的是生成 Java 编程语言中的接口引用的表示,以供 Java 程序员使用,而且客户机程序员和服务器程序员都可以使用。我使用以下命令行,使用 Orbacus ORB、JIDL 附带的 IDL-to-Java 编译器来运行 MotherIDL:
jidl --output-dir ..\..\.. motheridl.idl
由于 MotherIDL 中有许多内容,因此将会得到大量生成代码的文件。而我们对于放入 corbasem\gen\motheridl\ holderexample 目录下的文件 FindPerson.java 和 FindPersonOperations.java 感兴趣。
FindPerson.java 称为签名接口。它很简单:
Listing 2. FindPerson.java
public interface FindPerson extends FindPersonOperations, org.omg.CORBA.Object, org.omg.CORBA.portable.IDLEntity { }
接下来如何?毫无结果!而事实上有很多,只不过是隐藏的。首先注意两个 Java 关键字:public interface。是的,Java 编程语言有自己的 interface 关键字。它很重要,因为它使映射既快速又顺畅。
Java 编程语言的 interf