UML软件工程组织

IDL-to-Java 的映射:第一部分 怎样将离散的构件接口定义转换为 Java 元素
Dave Bartlett
顾问,作家,讲师
2000年10月

这篇文章开始阐述 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-7161

Directory of E:\_work\TICORBA\Projects\corbasem\

07/15/2000 01:41p <DIR> .
07/15/2000 01:41p <DIR> ..
0 File(s) 0 bytes

Directory of E:\_work\TICORBA\Projects\corbasem\gen

07/15/2000 01:41p <DIR> .
07/15/2000 01:41p <DIR> ..
0 File(s) 0 bytes

Directory of E:\_work\TICORBA\Projects\corbasem\gen\motheridl

07/15/2000 01:41p <DIR> .
07/15/2000 01:41p <DIR> ..
07/15/2000 01:41p <DIR> basictypes
07/15/2000 01:41p <DIR> conflicts
07/15/2000 01:41p <DIR> constructedtypes
07/15/2000 01:41p <DIR> holderexample
07/15/2000 01:41p <DIR> holderexample2
07/15/2000 01:41p <DIR> MI
07/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 映射的版本也会发生改变。但是以你对目前的映射的理解,你一定会注意到将来的版本中任何可能的二进制不兼容性。

基本数据类型
基本数据类型的映射是很直接的。 下表会让你了解到映射是多么简洁:

IDL 类型
Java 类型
异常

boolean
Boolean

char
Char
CORBA::DATA_CONVERSION

wchar
Char
CORBA::DATA_CONVERSION

octet
Byte

string
java.lang.string
CORBA::MARSHAL, CORBA::DATA_CONVERSION

wstring
java.lang.string
CORBA::MARSHAL, CORBA::DATA_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::DATA_CONVERSION 的异常。 IDL 的 wchar 仅仅是映射为16位字符集的 IDL 类型,它映射为 Java 的基本类型 char。 如果 wchar 超出了字符集所定义的范围,将会产生 CORBA::DATA_CONVERSION 的异常。

IDL 的 string 类型映射为 java.lang.String。别忘了 IDL 的 string 是一个 char 的序列。这意味着 IDL 的 string 必须满足 IDL char 和 IDL sequence 的要求。因此,在编译过程中要进行字符串中的字符范围检查和字符序列的越界检查。字符范围非法会引起 CORBA::DATA_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 到 Java 的映射。我们要来研究一些更有用的类,更加复杂的映射如和 enum 和 union,以及用户定义的类型。

参考资料

见上个月的 CORBA 综合栏目 "OMG 接口定义语言"。
关于OMG IDL 和 CORBA 的更多资料见 OMG主页。
IDL 到 Java 的映射规范见 OMG主页。
Michi Henning 和 Steve Vinoski 的优秀作品 Advanced CORBA Programming with C++。
从 Object Oriented Concepts, Inc. 下载 Orbacus ORB 可以得到 jidl 编译器。
从 Inprise Corp 下载60天评估期的 Visibroker。这个包带有 idl2java 编译器。
学习 Java 编程语言,见 Bruce Eckel 的 Mindview.net 和 Thinking in Java 第二版。
作者介绍
Dave Bartlett 住在 Pennsylvania 州的 Berwyn,兼任顾问、作家和教师。他是 Hands-On CORBA with Java 的作者,这是一本通过公开会议或内部组织赠送的5天教程。目前,Dave 正致力于将原始讲义改编为 Thinking in CORBA with Java 一书。Dave 具有宾夕法尼亚州工程和商学双硕士学位。如果你有什么问题或者对某一主题感兴趣的话,可以和 Dave 联系,他的邮件地址是 dbartlett@pobox.com。



版权所有:UML软件工程组织