集成 COM 和 Java 组件

很长时间以来,互操作性问题使得微软的组件对象模型®(COM)和 Java ™ 组件之间的集成成为一项令人畏惧的工作。IBM alphaWorks 提供的 Java-COM 桥开发工具简化了集成工作,而且还为应用程序从 COM 迁移到 Java 平台提供了改良方法。IBM Rational 的 Cheng-Yee Lin、Thomas Houser 和 Peter Parapounsky 是桥接技术的三位缔造者,他们将解释桥接的一些基础知识,并将展示一个表现桥接功能的示例应用程序。

随着企业的需求和复杂性的增长,可能需要把拥有完全不同底层实现的应用程序和组件组合成一个集成的解决方案。Java 技术和 COM 技术在 Windows 平台上的大型应用程序和组件开发方面,都占据着主要的位置,但在集成世界中,这两项技术之间的互操作性(或者说 桥接)方面仍然存在着大量没有解决的问题。最近几年,出现了几个工具,它们启用了 Java 组件与 COM 组件之间的轻量级集成。但是,其中有些工具的可应用性非常有限,例如它们只支持从 Java 组件到 COM 的桥接(即从 Java 代码调用 COM 服务器的方法)。而面向通用性设计的桥接工具则会因为密集的交互而造成很高的性能开销。

本文将介绍桥接技术,它平衡了性能与实用性。IBM Rational Java-COM 桥(RJCB)不仅支持从 Java 组件到 COM 以及从 COM 到 Java 组件的桥接,而且在组件通过桥进行密集的互动时,还提供了合理的性能。建立 RJCB 桥使用的工具(Java-COM 桥开发工具,DTJCB)与开放源代码的 Eclipse IDE 集成(请参阅侧栏 DTJCB 作为 Eclipse 的扩展 ),使得在单一 Java 环境中建立和使用 RJCB 桥变得很容易。您可以通过使用 DTJCB 建立的桥,用 Microsoft 的 工具建立从 COM 到 Java 组件的桥接。

DTJCB 还为您提供了一条渐进的迁移路线,可以把大型的 COM 应用程序转换成 Java 技术。您不必一夜之间改变整个应用程序,使用 RTJCB,可您可以一个组件一个组件地执行迁移,从而保证对应用程序使用过程造成的破坏最小。

了解 RJCB 技术

RJCB 技术采用 Java 本机接口(JNI)框架在桥 Java 代码和 COM 之间实现桥接。JNI 让您可以在 Java 语言中调用本机代码,反之亦然。您可以在 Java 语言中声明一个方法,并用 C 或 C++ 来定义方法主体。反过来,您也可以在 C 或 C++ 代码中调用 Java 的方法。

图 1 表示了 RJCB 桥的结构。

DTJCB 作为 Eclipse 扩展

DTJCB 是一个重要的 Eclipse 插件(目前 Eclipse 的版本是 3.0),Eclipse 是一个开放源代码的 Java 开发工具(请参阅 参考资料)。Eclipse 被广泛地用作多个开放源代码工具和商业产品的基础开发环境。DTJCB 可以“安装”并集成到这些工具和产品中。这样,您既可以在自己喜欢的基于 Eclipse 的工具中使用 DTJCB,您也可以下载 Eclipse IDE,把它安装为基础的 Eclipse 环境,把 DTJCB 添加到它的插件列表中,然后就可以进行桥接了。为了简便起见,我们把任何安装了 DTJCB 插件的基于 Eclipse 的工具都简称为“DTJCB”。


图 1. RJCB 桥的结构
图 1. RJCB 桥的结构

图 1 中绿色的盒子代表随 RJCB 一起安装的 RJCBRT.jar 和 RJCBRT.dll 文件。它们提供了支持类和服务,生成的桥代码将使用这些类和服务(您的 Java 代码在一定程度内也使用它们)。红色的盒子代表 RJCB 桥生成器生成的特定于具体 COM API 的代码。

每个 COM API 都由一个称为 类型库 的特殊文件进行描述。独立类型库通常以 .tlb 为扩展名。类型库也可以嵌在可执行(.exe、.dll、以及 .ocx)文件内。RJCB 桥生成器读取类型库,并根据类型库所描述的 API 生成 Java 代码和 C++ 桥代码。

COM 接口通常提供两个不同的方法调用机制: 后期绑定(late-bound)前期绑定(early-bound)。使用后期绑定调用,需要在运行时解析方法名称,并把所有方法参数打包成特殊的 variant 数组,或者从这个特殊的 variant 数组解包出方法参数。而前期绑定调用则可以利用能够确切知道要调用的方法以及方法参数类型的优势。对于进程内 COM 服务器(在相同的 COM 进程中),前期绑定调用等价于 C++ 的 vtable 调用(C++ 调用 虚拟 方法的标准机制)。

后期绑定机制则是通过实现一个称为 IDispatch 的特殊的超级接口来提供的。 IDispatch 接口提供了一种方法,该方法可以根据名称查找 COM 接口的方法(或者属性),然后返回方法的 dispidIDispatch 接口还提供了另外一个方法,可以通过 dispid 和包含调用参数的 variant 数组调用 COM 接口的方法。比起简单的 C++ vtable 调用,通过 IDispatch 接口调用 COM 方法的效率非常低(既使您不用 IDispatch 接口的后期绑定特性,只是用硬编码的 dispid 调用方法,也是如此)。

开发 RJCB 桥接技术的主要目的是建立可能的、最迅速的 Java/COM 桥。我们把 Java 和 COM 之间的一些高流量 API 桥接起来,这样需要我们的桥能提供最大可能的性能。出于这个原因,RJCB 桥只支持那些提供前期绑定 vtable 接口的 COM API。RJCB 生成的桥代码可以进行直接的、针对特定接口的 vtable 调用。它不使用 IDispatch 超级接口。

RJCB 代码生成器为 COM 类型库中定义的每个 vtable 接口辅助类 生成代理。它还生成 Java 单元,里面包含类型库中定义的 模块 常量和 枚举 标量。





回页首


研究桥代码示例

为了展示这个过程,让我们来看一看为一个简单的 COM API 示例生成的桥代码。这个示例 COM API 声明了一个有几个常量的模块;一个有几个枚举标量的枚举类型;一个只有一个 Name 属性的接口;一个它实现了仅有的那个接口的辅助类。(更典型一些的 COM API 还应当包含更多的接口,每个接口还要包含更多的方法和属性。)

清单 1 显示了这个简单的 COM API 的接口定义语言(IDL)规范


清单 1. SimpleTestModule 的IDL 规范
module SimpleTestModule
{
    static const int SIMPLETEST_INT_CONST = 99;
    static const LPCOLESTR SIMPLETEST_STRING_CONST = L"This is a test.";
};
typedef [public] enum SimpleTestEnum {
    STE_VALUE1 =  0,
    STE_VALUE2 =  1,
    STE_VALUE3 =  2
} SimpleTestEnum;
[
    object,
    uuid(1C551D4C-B3D8-4BCA-BDC0-6D870D84CA7F),
    helpstring("ISimpleTest Interface"),
    dual,
    pointer_default(unique)
]
interface ISimpleTest : IDispatch
{
    [propget, helpstring("property Name"), id(1)]
    HRESULT Name([out, retval] BSTR* theName);
    [propput, helpstring("property Name"), id(1)]
    HRESULT Name([in] BSTR theName);
};
[
    uuid(14CED841-ED27-4450-9255-FE384C6C3B0D),
    helpstring("SimpleTest Class")
]
coclass SimpleTest
{
    [default] interface ISimpleTest;
};
    

RJCB 代码生成器生成的对应的 Java 源文件,如清单 2 和 3 所示:


清单 2. SimpleTestModule.java
package com.ibm.simpletest;
public interface SimpleTestModule {
    public static final int SIMPLETEST_INT_CONST = 99;
    public static final String SIMPLETEST_STRING_CONST = "This is a test.";
}
    


清单 3. SimpleTestEnum.java

package com.ibm.simpletest;
public interface SimpleTestEnum {
    public static final int STE_VALUE1 = 0;
    public static final int STE_VALUE2 = 1;
    public static final int STE_VALUE3 = 2;
}

针对接口生成了三个不同的 Java 文件。其中一个文件包含 Java 语言中与接口对应的等价物,如清单 4 所示:


清单 4. ISimpleTest.java
package com.ibm.simpletest;
public interface ISimpleTest {
    public static final String IID = "1C551D4C-B3D8-4BCA-BDC0-6D870D84CA7F";
    public static final Class BRIDGECLASS = SimpleTestBridgeObjectProxy.class;
    public static final String CLSID = "7B422507-1B5B-49F7-BCEF-0FDE519C621A";
    /** 
     * getName. property Name
     */
    public String getName() throws java.io.IOException;
    /** 
     * setName. property Name
     */
    public void setName(String theName) throws java.io.IOException;
}

第二个文件包含接口中每个方法的 JNI 本机声明,如清单 5 所示:


清单 5. ISimpleTestJNI.java
package com.ibm.simpletest;
public class ISimpleTestJNI {
    public static native String getName(long native_this) throws java.io.IOException;
    public static native void setName(long native_this, String theName) throws java.io.IOException;
}

第三个文件包含 Java 代理类,它调用 JNI 方法实现接口。代理类还有一个引用,指向它所代表的实际的本机 COM 对象,引用由叫作 native_object 的成员变量表示(该变量是在一个支持性的超类中定义的,所有代理类都将扩展这个超类)。清单 6 显示了 Java 代理类文件:


清单 6. ISimpleTestProxy.java
package com.ibm.simpletest;
public class ISimpleTestProxy extends SimpleTestBridgeObjectProxy implements ISimpleTest {
    protected ISimpleTestProxy(String clsid, String iid) throws java.io.IOException
    {
        super(clsid, iid);
    }
    public ISimpleTestProxy(String clsid, String dumb1, Object dumb2) throws java.io.IOException
    {
        super(clsid, ISimpleTest.IID);
    }
    public ISimpleTestProxy(long native_object)
    {
        super(native_object);
    }
    public ISimpleTestProxy(Object com_proxy_object) throws java.io.IOException
    {
        super(com_proxy_object, ISimpleTest.IID);
    }
    protected ISimpleTestProxy(Object com_proxy_object, String iid) throws java.io.IOException
    {
        super(com_proxy_object, iid);
    }
    // ISimpleTest methods
    public String getName() throws java.io.IOException
    {
        String theName = ISimpleTestJNI.getName(native_object);
        return theName;
    }
    public void setName(String theName) throws java.io.IOException
    {
        ISimpleTestJNI.setName(native_object, theName);
    }
}

还要为辅助类也生成一个辅助类的代理类,名为 SimpleTest。清单 7 显示了 SimpleTest.java 文件:


清单 7. SimpleTest.java
package com.ibm.simpletest;
public class SimpleTest extends ISimpleTestProxy {
    public static final String CLSID = "14CED841-ED27-4450-9255-FE384C6C3B0D";
    public SimpleTest(long native_object)
    {
        super(native_object);
    }
    public SimpleTest(Object com_proxy_object) throws java.io.IOException
    {
        super(com_proxy_object, ISimpleTest.IID);
    }
    public SimpleTest() throws java.io.IOException
    {
        super(CLSID, ISimpleTest.IID);
    }
}

Java 辅助类的代理类扩展了其 默认接口的代理类。如果还实现了额外的接口,那么它还要包含对额外接口的实现(就象是接口代理一样)。

辅助类代理也包含一个默认的无参数构造器,用来建立它所代表的 COM 对象的实例。构造器用类型库辅助类的声明中定义的 CLSID 来实例化 COM 对象。

ISimpleTestJNI.java 中声明的本机方法的主体,在 ISimpleTestJNI.cpp 中定义,如清单 8 所示:


清单 8. ISimpleTestJNI.cpp
JNIEXPORT jstring JNICALL Java_com_ibm_simpletest_ISimpleTestJNI_getName(
    JNIEnv* env, jclass,
    jlong native_this)
{
    SimpleTestLib::ISimpleTest* this_intf = (SimpleTestLib::ISimpleTest*)native_this;
    CComBSTR nativeTheName;
    CHRT(this_intf->get_Name(&nativeTheName));
    return JSTRING_FROM_CCOMBSTR(env, nativeTheName);
}
JNIEXPORT void JNICALL Java_com_ibm_simpletest_ISimpleTestJNI_setName(
    JNIEnv* env, jclass,
    jlong native_this, jstring theName)
{
    SimpleTestLib::ISimpleTest* this_intf = (SimpleTestLib::ISimpleTest*)native_this;
    BSTR_FROM_JSTRING nativeTheName(env, theName);
    CHRTV(this_intf->put_Name(nativeTheName));
}

从清单 8 中您可以看到,方法主体只是通过 COM 接口指针调用对应的 COM 方法,进行必要的参数转换(例如在 Java 语言和 COM 所表示的字符串之间转换)。代码还负责把 COM 的错误返回值转换成 Java 语言异常(通过 CHRT* 宏)。

使用双向桥时,就是告诉 RJCB 桥生成器,COM API 中的某些接口是用 Java 语言实现的,在 C++ 端还生成了额外的两个文件,它们为 COM 接口的 Java 实现提供 COM 代理。COM 实际上与 Java 代理一样,只是方向不同。COM 代理以成员变量的形式持有一个引用,指向其所代表的 Java 对象。

在我们简单的示例中, ISimpleTest 接口的代理文件叫作 ISimpleTestProxy.h 和 ISimpleTestProxy.cpp。由于在 C++ 中进行 COM 编程很复杂,所以这些程序读起来有点难。清单 9 显示了从 ISimpleTestProxy.cpp 文件摘录的一段代码:


清单 9. ISimpleTestProxy.cpp 的代码段
STDMETHODIMP ISimpleTestProxy::get_Name(BSTR* theName)
{
    JNIEnv* env = 0;
    jobject java_object;
    CHRR(_RJCBService->GetJavaObject(this, &env, &java_object));
    JNILocalFrame _JNILocalFrame(env);
    if (theName == 0) {
        return E_INVALIDARG;
    }
    jstring jniTheName = (jstring)env->CallObjectMethod(java_object, _ISimpleTestProxyInfo->
      m_getName_method_id);
    *theName = BSTR_FROM_JSTRING(env, jniTheName).Detach();
    return _RJCBService->CatchException(env);
}
STDMETHODIMP ISimpleTestProxy::put_Name(BSTR theName)
{
    JNIEnv* env = 0;
    jobject java_object;
    CHRR(_RJCBService->GetJavaObject(this, &env, &java_object));
    JNILocalFrame _JNILocalFrame(env);
    env->CallVoidMethod(java_object, _ISimpleTestProxyInfo->
      m_setName_method_id, JSTRING_FROM_BSTR(env, theName));
    return _RJCBService->CatchException(env);
}

请注意,在清单 9 中,方法主体只是通过代理所代表的 Java 对象调用对应的 Java 方法,从而进行必要的参数转换(例如在 COM 和 Java 语言所表示的字符串之间转换)。代码还负责把 Java 语言的异常转换成 COM 的错误返回值。

DTJCB 和 Java 接口工具的区别

一个供 Java 使用的,与 DTJCB 接口类似的工具(又叫作 Bridge2Java)—— 也可以从 IBM alphaWorks 站点得到(请参阅 参考资料)。下表总结了两项技术之间的差异。该表只是针对比较提供一些一般性的信息。如果您想在实际工作中使用这些工具,那么请您在选择之前进行更详细的分析。

特性 DTJCB 接口工具
与 Eclipse 开发环境集成 Yes No
支持 Java 到 COM 的调用 Yes Yes
通过 IDispatch 接口进行 Java 到 COM 的调用 No Yes
Java 到 COM 的 vtable 调用直接发到特定接口 Yes No
支持 COM 到 Java 的调用(也就是说,COM 接口的 Java 实现) Yes No
可以从 COM 客户机初始化 Java 服务器Yes No
Java 服务器可以动态地注册到 COM 的运行时对象表中 Yes No
可以由独立的类加载器(也就是说,各有各的类路径)加载多个 Java 服务器 Yes No
可以通过 VBScript 以编程方式访问运行中的 Java 应用程序的 API。(不用生成任何桥) Yes No
支持类型安全的转换(也就是说,通过 QueryInterface 进行类型转换) Yes No
可伸缩。生成桥的类型库可以从其他类型库中导入(也就是说,Java 代码可以引用导入类型库生成的桥中定义的类型) Yes No
可以通过 java.util.Enumeration 接口访问 COM _NewEnum 属性 Yes No
支持用 ProgId 而不是 CLSID 生成 COM 对象 Yes No
支持多线程 Yes ?
运行时跟踪选项 Yes ?





回页首


使用 DTJCB:概述

要通过 DTJCB 使用 RJCB 技术,需要为“服务器”组件建立、生成桥,然后把代码添加到将通过桥访问“服务器”的“客户机”组件中。DTJCB 可以让您在单一的环境中实现这个过程。

您要从服务器组件开始,用 COM 或 Java 语言建立这些组件,然后用 Eclipse 环境在 RJCB 桥项目中生成桥。完成的项目中会包含桥的代理代码,用 Java 语言和 Visual C++ 生成,还有代理代码使用的运行时库。然后,您要用 Eclipse 中的标准 build 命令生成桥的各个部分。

当您从 Java 客户机访问 COM 服务器时,可以继续在 Eclipse 中工作,建立 Java 项目。还可以添加与桥项目中的代理交互的 Java 客户机代码,它会通过桥运行时库与 COM 服务器进行对话。在 Eclipse 中使用 DTJCB,提供了一种端对端的体验(从建立桥到进行实际的桥接调用)。

当您从 COM 客户机访问 Java 服务器时,您首先要在微软的 Windows 环境中注册 Java 服务器,这样才可以将它“暴露”给 COM 环境。然后就可以使用基于 COM 的开发工具(例如微软 Visual Studio 的 Visual C++ 或 Visual Basic)编写通过桥访问 Java 服务器的客户机代码。在后面的章节中,我们会用一些示例来演示这个过程。





回页首


用 DTJCB 开发 Java COM 客户机

让我们来看看如何为示例 DLL 文件中的 COM 服务器建立桥,把它的接口暴露到 Java 技术这端,并从 Java 客户机调用这个接口。我们以一个办公家具店为例,这家家具店运行着一个 COM 服务器,负责跟踪公司的库存。它的 COM 接口暴露在 OfficeFurniture.dll 文件中。而您想把库存信息从 COM 服务器传递到 Java 客户机应用程序。

创建 RJCB 桥项目

要开始桥的创建过程,请在 Eclipse 新项目向导中选择 Java-COM 桥项目,如图 2 所示。


图 2. 新项目的向导
图 2. 新项目的向导

单击 Next 按钮,打开新建 Java-COM 桥项目页,在这里指定项目名称和位置,如图 3 所示。


图 3. 新建 Java-COM 桥项目页
图 3. 新建 Java-COM 桥项目页

下一页显示目前在项目中的桥(如果有的话)。对于我们的新项目来说,其中为空,如图 4 所示。


图 4. Java-COM 桥项目的内容页
图 4. Java-COM 桥项目的内容页

单击 Add... 按钮,打开 Java-COM 桥设置页,在这里提供所要建立的 COM 服务器和桥的信息。图 5 显示了我们示例的页面。您要指定桥的名称,把 COM 服务器作为源类型库,输入 Java 代码访问服务器要使用的 Java 包的名称。(您可以在开发工具中找到更多选项和信息。)


图 5. Java-COM 桥设置页
图 5. Java-COM 桥设置页

如果对创建的桥的设置感到满意,请单击 OK 按钮。桥的名称就会出面在桥项目的内容页中,如图 6 所示。


图 6. Java-COM 桥项目内容页
图 6. Java-COM 桥项目内容页

单击向导的 Finish 按钮,可以看到 Eclipse 的 Java Perspective 中的 Package Explorer 被桥代理代码所填充,该代码是用 Java 语言和 Visual C++ 写成的,如图 7 所示。


图 7. Package Explorer 中生成的文件
图 7. Package Explorer 中生成的文件

建立 RJCB 桥项目

现在您已经生成了桥的代理代码,就可以用 Eclipse 中标准的 build 菜单创建桥了。请注意,桥的代码依赖于JDK(不仅仅是 JRE)和微软 Visual C++ 6.0(SP 5),所以要确保它们已经安装在系统上。

Eclipse 既支持自动 build 模式,也支持手动 build 模式。两种模式都能创建桥所必需的二进制文件。在我们的示例中,build 命令生成了 OfficeFurnitureBridge.dll 和 OfficeFurnitureBridge.jar 文件。

现在就可以使用桥了。您可以展开代理的 Java 文件,查看可以通过桥使用哪个接口。例如,图 8 显示了在 Java 语言端可以使用的 OfficeFurniture.dll 中的接口方法。


图 8. 通过桥看到的接口方法
图 8. 通过桥看到的接口方法

创建作为 COM 客户机的 Java 项目

按照在 Eclipse 中建立普通 Java 代码开发的过程,您可以很容易地创建 Java 项目,并编写访问接口的代码。从使用新建项目向导建立 Java 项目开始,如图 9 所示。


图 9. 新建项目向导
图 9. 新建项目向导

在下一页中,指定 Java 项目的名称,如图 10 所示。


图 10. 新建 Java 项目页面
图 10. 新建 Java 项目页面

然后,在接下来的 Java 设置页面中,可以在项目和库各自的附签中提供它们之间的依存关系,如图 11 和图 12 所示。


图 11. 项目依赖性的设置
图 11. 项目依赖性的设置

图 12. 库依赖性的设置
图 12. 库依赖性的设置

在单击 Finish 按钮时,Eclipse 将建立 Java 项目。您在 Package Explorer 中可以看到桥和 Java 项目,如图 13 所示。


图 13. Package Explorer 中的桥和 Java 项目
图 13. Package Explorer 中的桥和 Java 项目

使用客户机项目中的 RJCB 桥项目

现在要做的,是添加调用 COM 服务器接口的 Java 代码。要使用 Eclipse 中标准的新建 Java 类命令,首先要向刚才创建的 Java 项目中添加一个新的类,如图 14 所示。


图 14. 添加新的 Java 类
图 14. 添加新的 Java 类

现在添加 Java 代码,引用通过桥暴露的 COM 服务器接口。首先,把清单 10 中的代码段添加到 Test 类的 main() 方法的主体中,如下所示:


清单 10. main() 方法的新代码
int count = 0;
try {
// Load RJCB Runtime library
com.ibm.rjcb.RJCBUtilities.loadRJCB
  ("C://eclipse//plugins//com.ibm.rjcb.dtk.common_1.0.0//RJCBRT.dll");
// Load Bridge dll
string dll_location = "C://eclipse//workspace//"
+ "SampleBridgeProject//MyJ2CBridge//"
+ "c++//OfficeFurnitureBridge//Release//OfficeFurnitureBridge.dll";
System.load(dll_location);
// Access COM object through the bridge
IOfficeCatalog catalog = new OfficeCatalog();
count = catalog.Count();
System.out.println("count = " + count);
for (int index = 0; index < count; index++) {
System.out.println("item=" + index
+ " ID=" + catalog.GetID(index)
+ " Name=" + catalog.GetName(index));
}
} catch (java.io.IOException e) {
e.printStackTrace();
}

在清单 10 中, catalog 是实际的 COM 对象的代理对象,您可以调用它的方法。然后,可以使用类上的上下文菜单 Organize Imports,Eclipse 会自动把需要的导入项目添加到这个类中,如清单 11 所示:


清单 11. 通过 Eclipse 自动添加的导入项
import com.officefurniture.IOfficeCatalog;
import com.officefurniture.OfficeCatalog;

然后,还是使用 build 命令生成 Java 项目,并设置 Run 配置,如图 15 所示。


图 15. Run 配置的对话框
图 15. Run 配置的对话框

Java 客户机执行的结果将在 Output 视图中显示,如图 16 所示。


图 16. Output 视图中的结果
图 16. Output 视图中的结果

很漂亮,是不是?您已经建立了能够与基于 COM 的服务器进行交互的 Java 代码。





回页首


开发 Java COM 服务器

现在让我们看看如何定义一个在 Java 端实现的接口,同时通过桥把该接口暴露给 COM 端,并从 COM 客户机调用这个接口。我们将用 ICookie 接口来演示完成这项任务的步骤。清单 12 显示了这个接口的 IDL 规范:


清单 12. ICookie 的 IDL 规范
import "oaidl.idl";
import "ocidl.idl";
[
object,
uuid(364DC55D-9C03-4dc6-AD82-5CAC8F1077FF),
dual,
helpstring("ICookie Interface"),
pointer_default(unique)
]
interface ICookie : IDispatch
{
[id(1), helpstring("method GetModel")] 
HRESULT GetName([retval, out] BSTR *name);
[id(2), helpstring("method GetModel")] 
HRESULT GetKind([retval, out] BSTR *name);
};
[
uuid(658EEF6A-D9C9-4a06-870D-2FA8A31A3026),
version(1.0),
helpstring("COM to Java test 1.0 Type Library")
]
library CookieLib
{
importlib("stdole32.tlb");
importlib("stdole2.tlb");
[
uuid(F0E00502-16E2-43e2-B12C-02EE037C402B),
helpstring("Cookie Class")
]
coclass Cookie
{
[default] interface ICookie;
};
};

建立 RJCB 桥项目

DTJCB 支持从类型库建立桥。所以,这里的秘诀就是先用微软的 MID 编译器处理清单 12 中的 IDL 规范,建立一个名为 cookie.tlb 的类型库。

从类型库建立 RJCB 桥项目的步骤与我们在前面一节( 用 DTJCB 开发 Java COM 客户机)介绍的步骤类似,只是桥设置的规范有些差别。我们把这个桥项目称作 SampleBridgeProject2。您需要输入如图 17 所示的桥设置。


图 17. MyC2JBridge 的桥设置
图 17. 桥设置

注意图 17 中的设置与 图 5 中设置的差异。源库是一个扩展名为 .tlb 的文件,图 5 中的扩展名则是 .dll。这个设置告诉 TJCB 是在为 Java 组件而不是在为 COM 组件创建桥。而且, ICookie 在 “Java implemented Interfaces” 下面有一个复选标记。

其余步骤实际上与前面的例子的相同。在桥生成过程的最后,您可以看到桥项目和接口的 Java 代理,如图 18 所示。


图 18. SampleBridgeProject2 的桥项目
图 18. SampleBridgeProject2 的桥项目

现在您可以编写 Java 类来实现 ICookie 接口,用它调用服务器的代码。为了简化这个示例,我们添加了一个新类,它的方法返回一些简单数据。可以使用标准的 Eclipse 机制,用新建类向导添加类,如图 19 所示。


图 19. 新建 MyCookie
图 19. 新建类 MyCookie

现在提供两个方法的简单实现,如清单 13 所示:


清单 13. GetNameGetKind 的实现
public class MyCookie implements ICookie {
/* (non-Javadoc)
* @see com.Cookie.ICookie#GetName()
*/
public String GetName() throws IOException {
return "Butter-n-Sweet";
}
/* (non-Javadoc)
* @see com.Cookie.ICookie#GetKind()
*/
public String GetKind() throws IOException {
return "Buttermilk";
}
}

构建 RJCB 桥项目

现在就可以构建项目了。同样,因为 DTJCB 与 Eclipse 集成在一起,所以您可以使用 Eclipse 标准的 build 命令。

注册 Java COM 服务器

COM 技术严重依赖于 Windows 的注册表。所以需要确保正确地注册了正确的组件,其中包括:

  • DTJCB 生成的桥 DLL 文件。
  • 将于其中运行桥的 JVM,包括正确的类路径。
  • 实现接口的 Java 类以及其定制程序 ID。
  • RJCB 的运行时库 DLL。

可以用 Windows 的 regsvr32.exe 命令以及 DTJCB 支持的 registerJavaVM.exeregisterJavaClass.exe 命令注册所有这些组件。一旦全部注册完这些组件,就可以让它们与 COM 客户机一起工作了。

在 COM 客户机项目中使用 RJCB

可以用几种方式来创建 COM 客户机。在这个练习中,假设您正在微软的 Visual Studio 中使用 Visual Basic 6.0。您首先要创建一个标准的 EXE 项目,并用 Projects/Reference 命令添加对 cookie.tlb 文件的引用。图 20 展示了该命令打开的 References 对话框,以及您应当如何添加这个引用。


图 20. 将引用添加到项目中
图 20. 将引用添加到项目中

现在把 Command 按钮添加到窗体中,如图 21 所示。


图 21. 窗体上的 Command 按钮
图 21. 窗体上的 Command 按钮
然后,添加通过桥进行调用、并从 Java 类检索信息的代码。图 22 显示了用户单击 Command1 按钮时执行的代码。
图 22. Command 按钮执行的代码
图 22. Command 按钮执行的代码

请注意,代码会实例化 Cookie 对象,并通过桥透明地调用其方法。执行的结果就是在用户单击 Command1 按钮时,出现一个消息框,显示从 Java 类检索到的信息,如图 23 所示。


图 23. 生成的消息框
图 23. 生成的消息框
使用独立 RJCB

不必一定用 Eclipse 建立 RJCB 桥并。RJCB 也有自己独立的工具,您可以用它们生成桥。这样,就可以把生成的桥用于任何 Java 开发环境。

RJCB 产品二进制的完整集可以在 DTJCB 安装的 com.ibm.rjcb.dtk.ui_1.0.0/generator 文件夹中找到。

要在 Eclipse 之外建立 RJCB 桥,请用图形用户界面接口(GUI)模式启动 bin/generateBridge.exe 工具(也就是说,没有命令行参数)。它显示的窗体与 DTJCB 的窗体类似,在窗体中,您可以指定输入类型库、目标文件夹、Java 包名称等设置。然后单击 GenerateBuild 按钮就可以生成或生成/构建桥。

您也可以用批处理模式,用命令行参数运行 generateBridge 工具。(DTJCB Eclipse 集成就是用这种方式调用该工具的。)执行工具时使用 /? 选项可以查看命令行参数的说明。决定需要在批处理模式中用哪个命令行参数生成桥的最好方法,就是先用 GUI 模式运行 generateBridge 工具。这样会生成一个叫作 bridgename.bat 的文件,其中包含在批处理模式下调用 generateBridge 工具的命令,而且包含所有必需的参数,同您在 GUI 的字段和选项中指定的值一一对应。每次在 GUI 中单击 GenerateBuild 按钮,都会生成该文件。

因为 Eclipse 是行业中的首选 Java 开发环境,而且可以免费获得(请参阅 参考资料),所以我们极力推荐您用它和 DTJCB (请参阅侧栏 DTJCB 作为 Eclipse 扩展)来开发您的桥。与 Eclipse 的集成提供了更好的 all-in-one 体验。





回页首


部署 RJCB 桥

可以用几种不同的方式来部署 RJCB 桥,具体的操作取决于使用桥的客户机的特征。客户机可以是单独的应用程序,也可以是 Eclipse 插件,或者是基于 COM 的客户机。我们将分别针对这些场景,讨论在每种场景中您需要部署什么。我们还会介绍如何在团队环境中开发、共享已经部署的部分。

把“部分”部署到客户机环境

对于应用程序的最终用户来说,不会在 Eclipse 内部或是基于 COM 的开发工具内部运行应用程序。所以要把桥以文件集合的形式部署到最终用户的系统上,包括可以部署的桥的各个部分和 RJCB 桥运行时。

在另外一些情况下,在应用程序开发的时候,桥是共享的。在这些情况下,可以用 Eclipse 插件的形式部署桥,把它和容纳 RJCB 运行时的公共插件放在一起。这样可以使得正在进行中的 Eclipse 中的代码开发更容易有些。

哪些是要部署的“部分”?

通过前面看过的示例场景,您对桥本身和 RJCB 运行时需要部署的文件可能有了一些基本的了解:

  • 桥的可执行部分在两个文件中:一个 JAR 文件和一个 DLL 文件。这两个文件的文件名的格式是类型库文件名加上后缀 “Bridge”。例如,为 MSO.DLL 文件构建的桥的可执行部分是 MSOBridge.DLL 和 MSOBridge.JAR。

  • 当您将桥部署为 Eclipse 插件时,必须部署插件的 JAR 和 XML 文件,这样 Eclipse 才能正确地标识和加载加载插件。

  • RJCB 运行时文件是所谓公共插件的一部分,它负责加载运行时 DLL,为 Eclipse 中的所有桥插件导出 RJCB.jar。当您将桥部署为 Eclipse 插件时,还必须部署公共插件。对于独立 Java 程序来说,桥的客户机则必须自行处理这些操作,首先要把这些文件复制到合适的位置,然后在第一次使用桥之前动态地加载运行时 DLL,或者设置正确的 PATH 变量指向 DLL 的位置。独立 Java 程序客户机还必须把 RJCB 的 Java 库 RJCBRT.jar 文件添加到它的类路径中。请注意,我们推荐用运行时 DLL 的绝对路径显式地加载该文件。这样可以确保即使在机器上安装了 RJCB.DLL 的多个版本,也能加载正确的 RJCB.DLL 版本。

部署场景的更多细节

以下部署场景基于 RJCB 桥的类型(单向或双向)、客户机的类型(Eclipse 插件或独立 Java 程序),以及部署的桥是否要求额外的 COM 注册(与仅仅复制部署的文件相对应):

  • 把单向 RJCB 桥部署到 Eclipse 插件端(不需要注册任何 DLL):

    1. 把公共 RJCB 插件(com.ibm.xtools.rjcb.common)复制到目标 Eclipse 平台文件夹下的插件文件夹(如果在目标文件夹中还没有的话)。公共插件会包含并加载 RJCB 运行时。

    2. 把 RJCB 桥插件复制到目标 Eclipse 平台文件夹的插件文件夹中。每个桥插件在其 <requires> 段中都有一个对公共插件的引用:

      <requires>
      <import plugin="com.ibm.xtools.rjcb.common" export="true"/>
      </requires>
      



    3. 把桥插件的插件 ID(例如 xxx.yyy.zzz)添加到客户机插件的 plugin.xml 文件的 <requires> 段中,您需要通过客户机插件访问桥插件:

      <requires>
      <import plugin="xxx.yyy.zzz"/>
      </requires>
      



  • 把桥部署为独立 Java 客户机,不注册任何 DLL:

    1. 把桥的 JAR 文件放在客户机机器上的任意位置。

    2. 把桥的 DLL 文件放在客户机机器上的任意位置。

    3. 把 RJCBRT.DLL 运行时和 RJCBRT.jar 文件放在任意位置。

    4. 在第一次引用桥之前,用绝对路径加载 RJCBRT.DLL:

       RJCBUtilities.load(<absolute path to RJCBRT.DLL>)
      



    5. 把代码添加到 Java 客户机程序中,在第一次引用桥之前用 System.load 加载桥的 DLL:

       System.load(<absolute path to DLL>);
      



    6. 把桥 JAR 文件和运行时 RJCBRT.jar 添加到 Java 程序的类路径中。


  • 把桥插件部署到独立 Java 客户机,并注册必需的 DLL:

    1. 把桥 JAR 放在客户机计算机上的任意位置。

    2. 把桥 DLL 放在客户机计算机上的任意位置。

    3. regsvr32 命令注册桥 DLL。

    4. 把 RJCBRT.DLL 和 RJCBRT.jar 放在任意位置。

    5. regsvr32 命令注册 RJCBRT.DLL。

    6. 把桥 JAR 文件和 RJCBRT.jar 添加到 Java 程序的类路径中。

  • 把桥(Java 语言实现的 COM 服务器)部署到 COM 客户机:

    1. 把桥 JAR 文件放在任意位置。

    2. 把桥 DLL 放在任意位置。

    3. regsvr32 命令注册桥 DLL。

    4. 用 RJCB registerJavaVM.exe 工具注册用来实例化 Java 服务器的 JVM。

    5. 用 RJCB registerJavaClass.exe 工具注册 Java 服务器类。

    6. regsvr32 命令注册运行时 RJCBRT.DLL

  • 部署桥插件,把它从 Eclipse 导出为可部署的插件:

    1. 在 Eclipse 的 Package Explorer 中选中桥项目。

    2. 打开“Export”对话框。

    3. 选择 “Deployable Plugins and Fragments”。

    4. 在正在部署对话框中,选择目标 Eclipse 安装的 “eclipse” 文件夹作为“Destination Directory”,并提交对话框。

    5. 如果桥不是单向的(只从 Java 语言到 COM),那么必须用 regsvr32 命令注册桥 DLL。




回页首


在团队中用 RJCB 进行开发

通常,由开发团队中的某一个人创建桥,并把它放在源代码控制当中,这样就可以在团队成员之间共享它。RJCB 桥项目的以下部分应当放在源代码控制之下:

  • 桥项目中的以下文件:
    • .project
    • .classpath
    • plugin.xml
    • build.properties
    • src (subfolder)
    这使参与项目的开发人员可以用 Eclipse 的 “Import”向导容易地把 RJCB 桥项目导入 Eclipse 环境,使它指向项目的位置,并可以在 Eclipse 内部构建和生成项目。

  • 所有 bridge.xml 文件(每个桥一个这样的文件)都位于桥的根文件夹中。

  • 用来生成桥的类型库源文件。DTJCB 把该文件从其原始位置复制到 RJCB 桥项目目录下的桥的位置上。您也需要这样做,这样,就可以在团队成员间共享源类型库。(其他团队成员可能没有该文件,或者有的只是其他位置上的该文件。)

  • 如何并不是所有团队成员都拥有桥的可执行文件,那么可以随意共享这些文件,例如,安装了 Visual Studio,但无法自己构建桥的安歇团队成员。

共享 RJCB 运行时

RJCB 桥客户机可以共享 RJCB 运行时的一个副本,也可以使用自己私有、独立的副本。要想共享该副本,必须在开发人员的机器上进行全局地配置运行库:

  • PATH 中有包含 RJCBRT.DLL 的目录。

  • regsvr32.exe 命令注册 RJCBRT.DLL。

  • 在类路径中有相应的 RJCBRT.JAR 文件。

如果只有一个单向的 Java-COM(反过来是 COM 到 Java 语言)桥,还可以使用 RJCB 的独立副本,它甚至可以是不同的版本。这是有可能的,因为不一定要用 regsvr32 命令注册 RJCB 运行时并把它放在 PATH 中。





回页首


从 COM 到 Java 技术的迁移

如果您很耐心地阅读本文,一直看到了现在,那么您可能会有一组基于 Java 和 COM 的应用程序或组件需要桥接。虽然 RJCB 桥提供了一个在两类应用程序之间进行互操作的方法,但在创建大型企业解决方案所有必需的桥时,仍然不是一项轻而易举的任务。

管理这些应用程序或组件更好的策略,应当是把所有相互联接的应用程序或组件的实现统一到单一技术中。这会为您节约大量花费在处理底层组件互操作性问题上的精力,从而让您有更多的时间和资源解决真正需要解决的业务问题。把这作为我们的目标,让我们看一看怎样才能把所有组件迁移成基于 Java 的。

策略:改革与发展

让我们假设一个企业现有的应用程序中存在着 Java 和 COM 组件的混合,并希望把它们都转换成 Java 组件。如果这家企业有许多时间和资料,那么有可能用 Java 语言从头开始对整个应用程序进行重新设计并实现它。这样做有可能会形成一个整洁的架构,需要较少的开发工作和维持工作。而不利的一面是,风险会非常高,从一个应用程序切换到另外一个的冲击可能会非常巨大。如果应用程序规模较小,则可以采用这种方式。

现实通常更具挑战性。企业可能没有时间和精力做这么大的投入,所以渐进的迁移可能是更现实的方案。一个一个地用 Java 组件替换 COM 组件,最终把应用程序的所有组件转换成 Java 技术会比较好。最直接的问题就是,您怎样才能有效地管理这些细微步骤,同时不会对应用程序的可用性造成重大破坏。

用 RJCB 进行迁移

RJCB 桥有助于把所有组件维持在一起。当您决定把 COM 组件转换成 Java 技术时,组件的新 Java 实现可以直接与它依赖的 Java 组件交互。对于仍然存在于 COM 中的依赖组件,您可以建立 RJCB 桥,使这些组件可以与新的 Java 实现交互。通过这个方法,一旦新实现完成,就可以与应用程序中其余的组件进行互操作。

通过进行这种小型、递增的处理,应用程序可以一直保持可用,几乎没有什么中断,而且消除了完成迁移时的应用程序切换。该方法消除了与这种革命性迁移方法有关的巨大风险,赋予迁移计划更好的成功机会。





回页首


结束语

Java 技术和 COM 是当今基于组件架构世界中的两大骨干技术。只要它们继续在组件互操作性上发挥重大作用,就需要弥补两项技术之间的间隙。RJCB 桥是一个有效的解决方案,而 DTJCB 则为您提供了一个多用途的实现 RJCB 桥的工具。



参考资料



作者简介

 

Cheng-Yee Lin 是 IBM Rational 建模产品的开发经理。他曾经参与过多项与编译器、UML 和基于 API 集成有关的项目。Lin 先生拥有耶鲁大学计算机科学硕士和博士学位。


 

Tom Houser 是 Rational Java/COM 桥技术的缔造者。早在加入 IBM 之前,他就已经在软件工具开发行业工作了 24 年,处理过编译器、调试器和建模工具。Houser 先生拥有麦迪逊威斯康星大学的计算机科学学士学位。


 

Peter Parapounsky 是 IBM 软件集团下属的 Rational Software 的软件工程师。过去的 5 年中,他一直忙于 Rational XDE 产品的某些方面的研究。Parapounsky 先生拥有布拉格索非亚理工大学的计算机科学硕士学位。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值