通过 JACOB 实现 Java 与 COM 组件的互操作

1 JACOB

1.1 概述

jacobjava com bridgejava com桥)分为两个部分,jacob.jar,jacob.dll,使用时两个东西的版本要一致,而且还分32位64位,它的位数和jdk的位数有关,与操作系统的位数无关。它的原理是通过javajni功能,调用系统组件dll,通过这个com桥来操作com组件(windows的一种软件编程技术)

如果可以在 Java 中调用 COM 组件,就可以充分利用 Java 技能和现有的成熟 COM 工具包,大大简化应用开发的过程。
COM 组件提供了一种与其他语言的互操作方式,叫做自动化(Automation)。
现有的 Java COM 互操作的解决方案有很多种,由于设计目的的不同,在性能、易用性等方面都有很大的区别。本文介绍的 JACOB 开源项目,致力于简化 Java 操作 COM 组件,提供了一个虚拟机独立的自动化服务器实现,由于其通用性设计,您可以非常简单地完成对现有应用的集成。

1.2 JACOB 项目的由来

首先,我们将了解 JACOB 项目的基本情况,探讨在什么样的情况下选择它来完成任务调用 COM 中暴露出来的方法,主要有两种机制:早期绑定晚期绑定

  • 早期绑定
    早期绑定显式的声明对象、数据类型等,编译器获取了足够的信息在编译期进行链接和优化,这样通常可以获得更好的性能,通过这种机制实现 Bridge 调用可以参考 IBMRJCB 项目,它提供了一套高性能的解决方案。当然需要了解更多 COM 组件的细节,虽然框架完成了大部分的生成 Bridge 代码的工作,但是总体来说,编码工作量还是偏大,编码难度也比较高,而且 RJCB 仅支持那些提供早期绑定的 vtable 接口的 COM API
  • 晚期绑定
    晚期绑定方式是通过 IDispatch接口来实现,类似 Java 的反射机制,可以按照名称或者 ID 进行方法调用,这种设计主要目的是支持脚本语言操作 COM,因为脚本是解释执行的,通常都不支持指针也就没有 C++ 中的 vtable 机制,无法实现早期绑定。这种方式的缺点是在一定程度上性能较差,由于是在运行时按照名字或者 ID 进行对象的调用,只有运行时才确切知道调用的是哪个对象,哪个方法,这样必然带来一定的开销和延迟。但是这种方式的优点也是非常明显的,简单、灵活,可以不必关注动态链接库的细节,可以非常快地完成代码开发工作。

JACOB 开源项目提供的是一个 JVM独立的自动化服务器实现,其核心是基于 JNI 技术实现的 Variant, Dispatch 等接口,设计参考了 Microsoft VJ++内置的通用自动化服务器,但是 Microsoft 的实现仅仅支持自身的 JVM。通过 JACOB,可以方便地在 Java 语言中进行晚期绑定方式的调用。
下图是一个对 JACOB 结构的简单说明

在这里插入图片描述

可以综合考虑时间、技术难度、性能要求等各方面来选择适合您的解决方案,下面结合在实际的订单处理系统的部分功能的开发,展示使用 JACOB 技术进行开发的成本优势。

1.3 JACOB操作邮箱

1.3.1 使用背景

某开发团队要利用一个订单处理的工作流系统,对现有的业务方式进行集成、优化。原有的业务处理方式是基于邮件系统的半人工方式,业务人员将订单以邮件方式发送到特定邮箱,然后后续人员处理订单。
现在设计的集成方式是,处理引擎定期轮询订单邮箱,以特定命名规则区分订单,获取存储在邮件正文和命名属性中的订单信息,将订单放入处理队列进行进一步处理,最后将源邮件标记状态并移出订单目录。
开发工作中遇到的难点是,应用的核心部分是基于 Java 实现的工作流应用,而客户使用 Outlook/Exchange 邮件系统,开发团队并没有足够的人力和时间去为其开发纯 Java 实现的客户端,Java 程序也难以操作邮件命名属性的信息。
下面我们就尝试利用 JACOB 和一些成熟的 COM 组件来解决这个问题。

1.3.2 配置您的开发和运行环境

我们的例子是一个基于 EclipseJava 工程。首先,使用 JACOB搭桥,下载最新的 JACOB 相关类库,包括Jacob.jarJacob.dll,将jar 文件添加到Eclipse工程的构建路径。

在这里插入图片描述

由于我们要通过 JNI调用 DLL 文件,所以您需要把 DLL 文件添加到系统的 PATH 环境变量中,或者您也可以在程序中设置运行时的 PATH 变量。
在这里插入图片描述

一个简单的已经搭起来了,就可以轻松的调用熟悉的 COM 类库了,我们这里以 Redemption 工具包为例来完成 Outlook 相关操作,它提供了对 Outlook 对象的比较完整的操作,可以方便的绕过 Outlook 对象模型所隐藏的一些细节部分。同时,Redemption 提供了对任何 IDispatch 友好的语言的支持。
需要在操作系统中注册 COM 组件,可以使用 Redemption 工具包本身提供的安装程序,也可以采用 Windows 系统自带的 REGSVR32 工具来注册 DLL 文件

1.3.3 实现业务逻辑

完成上面的准备工作后,就开始实现具体的业务逻辑了。
首先,要获取邮件您需要登录到指定邮箱,Outlook profile 中存储了您的邮箱的基本信息,下面的例子是如何登录特定 profile:

public void logon(String profile, String password) { 
    // 登录到指定 Profile,并获取一个会话
    ActiveXComponent session = new ActiveXComponent("Redemption.RDOSession"); 
    Dispatch.call(session, "Logon", profile, password, false, true); 
}

现在已经能够访问用户邮箱,我们要做的是获取收件箱中的标题以ORDER NO:开始的邮件,把信息存储到订单管理系统之中

Dispatch items = Dispatch.call(folder, "Items").toDispatch(); 
int count = Dispatch.call(items, "Count").getInt(); 
// 轮询所有邮件,并检查符合标准的邮件
for (int x = 1; x <= count; x++) { 
    Dispatch item = Dispatch.call(items, "_Item", new Integer(x)).toDispatch(); 
    ActiveXComponent sMail = new ActiveXComponent("Redemption.SafeMailItem"); 
    sMail.setProperty("Item", item); 
    Variant vSubj = Dispatch.get(sMail, "Subject"); 
    String subj = vSubj.getString(); 
    if (subj.startWith(“ORDER NO”)){ 
        // 将获取的定单信息存储到订单处理队列之中
        OrderMail mail = new OrderMail(item); 
        mail.persistToOrderQueue(); 
    } 
}

获取订单之后,将订单标记为已处理,这里我们通过操作命名属性的方式来标记状态,然后移动邮件到放置已处理邮件的目录:

// 获取命名属性的 ID 
ActiveXComponent utils = new ActiveXComponent("Redemption.MAPIUtils"); 
Variant namedPropID = Dispatch.call(utils, "GetIDsFromNames", orderMail, 
    GUID_OF_USERPROP, ORDER_STATE_DONE, true ); 
// 将邮件标记处理状态
ActiveXComponent utils = new ActiveXComponent("Redemption.MAPIUtils"); 
Dispatch.call(utils, "HrSetOneProp", orderMail, namedPropID, "Done", true ); 
// 将处理过的邮件移出现在的目录
Dispatch.call(orderMail, "Move",  folderForProcessedMsg);

可以看到,无需关注太多类库的细节,只需要知道要使用的对象名称、方法和输入变量,就可以轻松的调用 COM 组件。JACOB 的准备工作非常简单,并且只需要非常有限的编码,就完成了现有业务系统和新应用的无缝集成。

1.4 JACOB操作word

现在的项目中操作word文件比较多,word文件的加密解密,转换为各种格式,插入图片,添加水印、htmlword等等各种东西,大家也都知道,java语言是不能直接操作word或者excel的,不像C#,可以调用VBA的类来直接操作office

目前java也有一些操作office的开源框架,比如poi,这个没怎么研究过,估摸着它是通过解析officexml文档模型来操作office的,因为office03office07以及以上的文档模型结构不太一样,导致了poi处理不同版本office时要用不同的类,用的最多的是jacob,它对office03office07的操作基本一致,高版本的还没有试过

1.4.1 环境配置

1、在工程中引入jar
2、将dll文件放在jdkpath目录下面,通过System.getProperty("java.library.path");可以看到path路径,或者直接放到jdk/bin、jre/bin、system32/system64下面都放一份,总能找得到,版本要急着对应好。
3、附件为使用的组件,配合jdk使用。

1.4.2 常用类以及方法

常用类以及方法:

  • ComThread:com组件管理,用来初始化com线程,释放线程,所以会在操作office之前使用,操作完成再使用。
  • ActiveXComponent:创建office的一个应用,比如操作的是word还是excel
  • Dispatch:调度处理类,封装了一些操作来操作office,里面所有的可操作对象基本都是这种类型,所以jacob是一种链式操作模式,就像StringBuilder对象,调用append()方法之后返回的还是StringBuilder对象
  • Variant:封装参数数据类型,因为操作office是的一些方法参数,可能是字符串类型,可能是数字类型,虽然都是1,但是不能通过,可以通过Variant来进行转换通用的参数类型,new Variant(1),new Variant("1"),

Dispatch的几种静态方法:(这些方法就是要用来操作office的)

  • call( )方法:调用COM对象的方法,返回Variant类型值。
  • invoke( )方法:和call方法作用相同,但是不返回值。
  • get( )方法:获取COM对象属性,返回variant类型值。
  • put( )方法:设置COM对象属性。

以上方法中有的有很多重载方法,调用不同的方法时需要放置不同的参数,至于哪些参数代表什么意思,具体放什么值,就需要参考vba代码了,仅靠jacob是无法进行变成的。

Variant对象的toDispatch()方法:将以上方法返回的Variant类型转换为Dispatch,进行下一次链式操作。

1.4.3 初始化com线程

使用jacob之前使用下面的语句来初始化com线程,大概意思是打开冰箱门,准备放大象。。。

ComThread.InitSTA();  

使用完成后使用下面的语句来关闭现场,大概意思是关上冰箱门。。。

ComThread.Release();  

1.4.4 创建应用程序对象

创建应用程序对象,设置参数,得到文档集合
操作一个文档之前,我们必须要创建一个应用对应,比如是word还是excel,设置一些文档应用的参数,得到文档集合对象,(大家应该知道wordDocumentsexcelWorkBooks

ActiveXComponent wordApp = new ActiveXComponent("Word.Application");//word  
ActiveXComponent wordApp = new ActiveXComponent("Excel.Application");//excel  
wordApp.setProperty("Visible", new Variant(false));//设置应用操作是文档不在明面上显示,只在后台静默处理。  

这个操作就是模仿vba的写法,Application.Visible = False;vba代码中的对application的设置这里都可以用

Dispatch document = wordApp.getProperty("Documents").toDispatch();  

1.4.5 打开文档

有了文档对象集合,我们就可以来操作文档了,链式操作就此开始:
call方法,调用open方法,传递一个参数,返回一个我们的word文档对象,

Dispatch doc = Dispatch.call(document, "Open",new Variant("D:\\my.doc")).toDispatch();  

1.4.6 保存文档

Dispatch.call(doc, "Save");  

doc是我们打开的文档的对象

1.4.7 退出wordapplication

参数有很多个,我们一个都不传,执行完后winword进程关闭

wordApp.invoke("Quit", new Variant[] {});  

1.4.8 释放com线程

ComThread.Release();  
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容