在工作中我們要實現一個功能,需要創建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的知識也有一定要求,開發難道較高。