在工作中我们要实现一个功能,需要创建MS Office 和 WPS 兼容插件,也就是创建一个DLL,可以同时兼容office和wps。这样带来的好处就是只需要维护同一份代码,大大降低维护的工作!
1. 我们先看看要创建office插件都有哪些技术可以用
- VSTO
VSTO = Visual Studo Tools for Office,基于.net framework框架的Office开发技术。相对于传统的VBA(Visual Basic Application)开发,VSTO为中高级开发人员提供了更加强大的开发平台和语言,并部分解决了传统Office开发中的诸多问题(难于更新、可扩展性差、难以维护、安全性低等),开发人员可以使用熟悉的技术来构建更加灵活的、强大的、跨平台的企业级解决方案。
下图是我的机器上VS2013的创建项目:
主要采用C#语言开发,功能强大,感兴趣的同学可以去google更多相关知识。
我的需求是要创建可兼容两大办公软件平台的插件,很显然这种技术在WPS下不大可能支持,而且对于XP系统的用户,我们不可能让用户再去安装一个几百M的.net框架,毕竟国内使用XP的量还比较大。因此这个方案不属于我们的要求,继续寻找中。。。
- Shared Add-in
这是VS2010的项目创建截图:
在扩展插件项目下,有两种类型的插件可以创建。
1) Visual Studio Add-in 顾名思义,这个项目类型是用于创建 Visual Studio IDE插件的项目,不是我们的菜。
2) Shared Add-in 字面意思是共享插件 项目,这个正是我们所需要的插件类型。
Shared Add-in 的官方解释:Conversely, a Shared add-in can be loaded only into Microsoft Office applications such as Microsoft Word, Microsoft Publisher, Microsoft Visio, and Microsoft Excel. 大意是,Shared add-in可以被MS Office系列软件调用。
进一步研究后得知,Shared Add-in 也就是com插件技术,在wps的最新版本上支持这种com插件,这样就初步满足了我们要的全平台兼容插件。
2. 开始创建我们的插件
创建项目
点击OK后,会出来一个创建向导,第一步是可以选择你要使用的语言,如果用C#语言可能会导致引入.net的依赖,这不是我们所希望,我们希望创建的插件尽可能是本地代码,所以我们选择了使用C++/ATL。
选择要支持的哪些软件,可选项很多。这里选择软件的意义,就是增加一些接口和注册表项,这里的选择对WPS系列软件的支持没有影响,推荐这里选一个就好了,后面我们会使用手工自定义的方式来做。
填写你的插件名字和描述。
如果你希望在应用程序启动时通知你的插件,你就勾选那个选项。
最后确认你的选择没有问题后,点击 Finish就能创建你的插件了。
3. 认识插件项目
下图是创建项目后文件分布,rpc文件夹是我自己创建的,请忽略。
我们主要会对以下文件进行修改:
Addin.rgs文件 - 注册脚本(Register Script, 简 称RGS),该文件会主要用于将插件注册到相应注册表中。在ATL中,COM服务程序的注册是在工程编译连接的最后阶段,由ATL辅助完成的。在手工的COM编程中,服务程序的注册是比较麻烦的工作。在ATL中,系统通过读取在建立工程过程中形成的注册脚本文件来完成注册工作。
Connect.h\cpp 文件 - 插件的事件通知接口均在该文件中定义。
其它文件几乎不用动,都是一些自动生成的代码。
4. 连接插件事件
Office系列软件的版本很多,从Office2003 到 Office2013 都有,好消息是,com插件是向下兼容的,不同版本间的不同点在于高版本一搬会增加更多的事件通知,根据你需要的事件通知来选择你要从哪个版本的office系列开始支持。
我需要监控office打开某个文件的事件通知,选择了从Office10版本开始进行支持,该事件可以被全部版本兼容。
1) 添加com库类型文件
安装office07后,在安装目录下office10目录中,其中com库对应关系如下:
word - MSWORD.OLB
PPT - MSPPT.OLB
EXCEL – EXCEL.exe
其中EXCEL比较特殊,com库存在于其exe之中,其它office软件也有相应的com库,这里就不一一列出了。
把上述文件copy出来到你的目录中。
2)引入com库文件到项目
有了上述com类型库文件后,我们就可以引入需要的com了。在Connect.h增加好下代码:
#import "..\3rdparty\Office12\MSO.DLL" rename_namespace("Office2010") rename("RGB","RGB2"), rename("DocumentProperties","DocumentProperties2")
MSO.DLL是我们要用到的office系列com库的公共库文件,必须要先引入该库。
引入VBA,主要是为了防止编译不过去:
#import "..\3rdparty\VBA6\VBE6EXT.OLB"
同样方法,引入实际com:
#import "..\3rdparty\Office12\MSWORD.OLB" rename_namespace("MSWord"), rename("ExitWindows","WordExitWindows"),rename("FindText","WordFindText"), named_guids
#import "..\3rdparty\Office12\excel.tlb" rename_namespace("MSExcel"), rename("DialogBox","ExcelDialogBox"), rename("RGB", "ignorethis"), rename("DialogBox", "ignorethis"), rename("ReplaceText", "EReplaceText"), rename("CopyFile","ECopyFile"), rename("FindText", "EFindText"), rename("NoPrompt", "ENoPrompt") exclude("IFont","IPicture")
#import "..\3rdparty\Office12\MSPPT.OLB" rename_namespace("MSPowerPoint"), rename("RGB", "ignorethis")
编译代码,会在项目目录下生成众多相关文件。tlh、tli文件:他们是vc++编译器解析tlb文件生成的标准c++文件。因为odl和tlb并不是C++标准的东东,有必要把它们翻译成标准的 C++类型,使得C++开发者可以使用。相信vb和j++也会把tlb翻译成自己语言兼容的类型描述信息。
tlh相当于类型申明(头文件)
tli相当于定义实现(CPP文件)编译上面的com时,你的本机必须要安装了相应的office版本,否则很有可能会出错。由于我们的代码是在单独的构建机上编译,为了避免在纯净的构建机上安装office10软件,我做了些处理,直接使用解析后的文件。类似于如下代码:
#include "..\3rdparty\Office12\include\msword.tlh"
#include "..\3rdparty\Office12\include\excel.tlh"
#include "..\3rdparty\Office12\include\msppt.tlh"wps相关:
#include "..\3rdparty\wps-office6\include\ksoapiv8.tlh"
#include "..\3rdparty\wps-office6\include\wpsapiv8.tlh"
#include "..\3rdparty\wps-office6\include\etapiv8.tlh"
#include "..\3rdparty\wps-office6\include\wppapiv8.tlh"tlh文件中,有相应tli文件的绝对位置,这个可能在其它机器上编译不通过,因此需要手动修改为引用相对地址,根据编译错误,很好修改。
3)连接com事件
通过上述步骤后,已经可以使用com中的事件了。首先实现一个模板类:
typedef IDispEventSimpleImpl</*nID =*/ MSWord_ID, CConnect, &__uuidof(MSWord::ApplicationEvents2)> MSWordDispEventImpl;
MSWord_ID : 随意定义一个ID即可,用于下面区分不同事件。
CConnect增加一个继承类MSWordDispEventImpl,增加如下一个消息循环:
BEGIN_SINK_MAP(CConnect)
// msword events
SINK_ENTRY_INFO(/*nID =*/ MSWord_ID, __uuidof(MSWord::ApplicationEvents2), /*dispid =*/ 0x4, OnDocumentOpen, &DocumentOpenInfo)END_SINK_MAP()
其中:
dispid - 事件ID号,查询MSDN官方文档,或者tlh中会有相关ID
OnDocumentOpen - 事件响应函数,函数类型:void __stdcall OnDocumentOpen(LPDISPATCH ptr); 这里的参数类型要根据这个事件实际的参数类型来创建
DocumentOpenInfo – 参数类型信息,_ATL_FUNC_INFO DocumentOpenInfo = {CC_STDCALL,VT_EMPTY,1,{VT_DISPATCH|VT_BYREF}};,具体参数信息,可以查询其它相关文档
上面操作完成后,CConnect已经可以收到Word打开文档的事件通知,关于该事件的详细触发时间点,可以查询相关MSDN文档。在OnDocumentOpen函数体中,你已经可以写下你想要的功能代码了。
其它各种事件采用相同方式完成即可。
4)注册插件
在AddIn.rgs文件中加入如下代码,完成注册过程:
HKLM
{
Software
{
Microsoft
{
Office
{
Word
{
Addins
{
ForceRemove \'YourAddin.Connect\'
{
val Description = s \'Yourdesc\'
val FriendlyName = s \'YourName\'
val LoadBehavior = d \'3\'
}
}
}Excel
{
Addins
{
ForceRemove \'YourAddin.Connect\'
{
val Description = s \'\'Yourdesc\'\'
val FriendlyName = s \'YourName\'
val LoadBehavior = d \'3\'
}
}
}
}
}
}}
有关rgs文件语法说明,需要参考其它相关文件。
5)调试插件
插件写好,我们得要调试插件。首先你运行的vs必须是要以“管理员”方式启动的,把插件库设置为启动项,在启动参数里写入world.exe的绝对目录,启动调试后就可以调试插件中的事件响应了。
6. 总结
本篇是是对Office的插件技术实现的描述,特点是实现了兼容wps的插件事件。优点在于使用C++语言实现,生成的插件dll体积小,不依赖于.net ,方便安装使用;缺点是c++语言,对ATL com的知识也有一定要求,开发难道较高。