内容


集成 COM 和 Java 组件

用 Java-COM 桥的开发工具实现互操作性

Comments

随着企业的需求和复杂性的增长,可能需要把拥有完全不同底层实现的应用程序和组件组合成一个集成的解决方案。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 桥的结构。

图 1. RJCB 桥的结构
图 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:概述

要通过 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. 新项目的向导
图 2. 新项目的向导

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

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

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

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

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

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

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

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

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

图 7. Package Explorer 中生成的文件
图 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. 通过桥看到的接口方法
图 8. 通过桥看到的接口方法

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

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

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

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

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

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

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

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

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

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

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

图 14. 添加新的 Java 类
图 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 配置的对话框
图 15. Run 配置的对话框

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

图 16. Output 视图中的结果
图 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. 桥设置

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

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

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

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

图 19. 新建 MyCookie
图 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. 将引用添加到项目中
图 20. 将引用添加到项目中

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

图 21. 窗体上的 Command 按钮
图 21. 窗体上的 Command 按钮
图 21. 窗体上的 Command 按钮

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

图 22. Command 按钮执行的代码
图 22. Command 按钮执行的代码
图 22. Command 按钮执行的代码

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

图 23. 生成的消息框
图 23. 生成的消息框

部署 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 桥的工具。


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Java technology
ArticleID=49161
ArticleTitle=集成 COM 和 Java 组件
publish-date=11162004