在這篇文章里面,我將向大家介紹如何在.Net中訪問Office所公開的編程接口。其實,不管是使用哪種具體的技術來針對Office進行開發(比如VSTO,或者用C#編寫一個Office Add-in,或者在一個WinForms程序中調用Office的功能,甚至在一個ASP.NET應用的服務器端啟動一個Excel進程),只要是基於.Net平台,這篇文章所描述的內容都是有價值的。
在這篇文章以及后續的文章中,所有的演示都將基於Office 2003 Professional和Visual Studio 2005。使用Visual Studio 2005並不代表我們不能在Visual Studio 2003中使用這些方法來訪問Office編程接口,相反,這篇文章以及后續文章中的幾乎所有演示,都能在Visual Studio 2003中完成。筆者使用Visual Studio 2005的原因只是因為喜歡它更好的IDE特性。:)
一、Office PIA
在第一篇文章中曾經說過,Office的編程接口都是通過COM組件公開的,任何訪問Office編程接口的程序,實際上都必須要最終調用Office COM組件。如果你是一個剛從事Windows平台開發不太久,一開始就是學習和使用.Net的程序員,也許你又要感到郁悶了。所幸的是,.Net能夠讓開發人員非常方便的訪問COM組件。我會用盡量簡潔明了的描述,讓大家理解我們在.Net中是如何訪問Office COM組件的。
(一)Interop Assembly
在.Net Framework中,提供了一項叫做COM Interop的技術,這項技術就是專門用於讓我們能夠在.Net代碼中直接訪問COM組件的。它的基本原理是,.Net Framework能夠自動針對某一個COM組件,幫助開發人員生成一個Interop Assembly(互調用程序集,后面簡稱IA),IA是一個完全的托管程序集。IA中的名稱空間、類、方法等等,都是針對那個COM組件對應的。在我們的.Net程序中,我們可以直接引用這個IA,並且調用里面相關的方法,這時IA就會幫我們再去訪問真正的COM組件里面的相應方法。最后的效果就是,在我們自己的應用程序中,只需要調用一個純粹.Net的IA即可。
下面的圖片說明了IA是如何幫我們的程序訪問到COM組件的。
生成一個IA的方法非常簡單,當我們在Visual Studio開發環境中添加一個新的COM組件引用時,Visual Studio就會自動幫我們生成一個相應的IA。如下圖,我們示范在一個項目中引用Microsoft XML 5.0組件。
當在上圖中點擊OK按鈕,Visual Studio就已經自動為我們生成了一個針對Microsoft XML 5.0組件的IA。打開項目目錄的obj\debug目錄,就能夠看到一個名稱為Interop.MSXML2.dll的程序集文件,這個文件就是自動生成的IA,並且在項目中,Visual Studio也自動引用了這個IA程序集。如下圖。
(二)Primary Interop Assembly
我們在自己的應用程序中,訪問Office COM組件的方法的基本原理,就如同下面所述,都是通過COM Interop,透過IA間接的訪問到Office中的COM組件。但是針對Office這個軟件,則有一點點特殊的區別,那就是我們不應該自己在Visual Studio中生成一個“自己的”訪問Office COM的IA,而需要使用微軟提供的“官方的”PIA。
PIA的意思可以理解為“官方互操作程序集”,它和IA最主要的區別如下:
1、IA是由開發人員在開發機器上通過向導自動生成的,PIA是由軟件廠商(針對Office這個軟件而言,就是指微軟)提供的;
2、PIA經過了廠商的優化處理,使之更容易被.Net調用;
(三)Office Primary Interop Assembly
所以,我們都應該使用PIA來訪問Office COM組件,而不應該使用IA。那么如何把Office PIA安裝到我們的電腦上呢?
如果我們的機器上已經安裝了.Net Framework,那么在安裝Office時,在安裝向導的高級自定義選項中,我們在每個組件(Word、Excel、PowperPoint等)的子選項中,都能看到一個“.NET可編程性支持”,選擇安裝它,Office2003的安裝程序就會自動把PIA安裝到我們的計算機上。
另外,對於Office PIA的客戶端分發(就是說,給我們軟件的用戶都統一裝上PIA),微軟專門提供了一個安裝包。可以在http://www.microsoft.com/downloads/details.aspx?FamilyID=3c9a983a-ac14-4125-8ba0-d36d67e0f4ad&DisplayLang=en下載到這個分發安裝包。
Office PIA按照Office的各個組件(Word、Excel、PowerPoint、Outlook等),分成多個單獨的程序集。比如Word對應的PIA程序集是Microsoft.Office.Interop.Word.dll(程序集里面的類都放在命名空間Microsoft.Office.Interop.Word中),Excel對應的程序集是Microsoft.Office.Interop.Excel.dll(程序集里面的類都放在命名空間Microsoft.Office.Interop.Excel中)。另外,Office公用的一些組件(比如菜單欄)放在一個單獨的程序集中:Office.dll(對應的命名空間是Microsoft.Office.Interop.Core)。
如果我們的開發機器上已經安裝好了Office PIA,那么當我們通過上面所述的方法,在Visual Studio中引用Office COM組件時,Visual Studio會檢測到本機已經安裝了Office PIA,然后,它會直接引用安裝好了的PIA,而不會再自動生成一個新的IA。
如下圖,我們在Visual Studio中添加一個對Word COM組件的引用(Word在COM組件列表中是“Microsoft Word 11.0 Object Library”,相似的,Excel、Outlook、PowerPoint的COM組件名稱都遵循這個規律)。
在上圖中點擊OK按鈕后,在項目管理器中就可以看到,Visual Studio已經幫我們引用了需要引用的組件。實際上,除了我們選擇要引用的Word組件外,其他額外但是必需的諸如Microsoft.Office.Core、stdole、VBIDE等組件也已經被自動引用進來了。
在上圖的Word組件引用上點擊鼠標右鍵,查看它的屬性,在它的路徑屬性中,我們可以看到這個PIA文件其實是在“C:\Windows\assembly\...”目錄中,這個目錄也就是我們機器上的全局程序集緩存(GAC,Global Assembly Cache)所在的目錄。這是因為Office PIA是被安裝到機器上的GAC中,所以對Office PIA的引用會直接指向GAC中的相應文件。
二、深入瀏覽Office PIA
如果讀者曾經使用過VBA進行過開發(或者使用其他的開發工具諸如VB/VC/Delphi直接調用過Office),那么其實你已經對Office COM接口有了一定的了解,因為在VBA編輯器中所編寫的操作諸如Application、Document、Range的代碼,其實正是在操作Office COM組件中的Application、Document、Range這些類。
我們已經知道,在Office PIA中,已經把Office COM組件進行了封裝,所以我們可以預見,對於每一個Office COM組件中的類或者接口,在Office PIA的程序集中,我們應該都能找到一個對應的類或者接口。接下來,我們就用對象瀏覽器直接打開Office COM組件,然后再打開Office PIA,這樣我們就可以對照它們,更清楚的理解它們。
在Visual Studio中,打開視圖菜單中的對象瀏覽器,然后點擊對象瀏覽器中的添加其他組件按鈕,在出現的選擇窗口中,選擇COM組件中的“Microsoft Word 11 Object Library”,這時對象瀏覽器就直接打開了Word 2003的COM組件,如下圖。
在上圖所示的Word COM組件成員列表中,可以看到我選中了Word中的Application類的Quit()方法。Application類可以說是各個Office組件的核心類,不管是Word、Excel、PowerPoint,都存在一個對應的Application類,對應Word、Excel、PowerPoint主程序。如果要在我們的程序中直接打開Word,就需要創建這個Application類的一個實例,如果要關閉掉這個新打開的Word程序,就調用這個新創建的Application對象的Quit()方法。
接下來,我們再用對象瀏覽器打開Office PIA中的Word所對應的程序集。在前面的操作步驟中,我們已經在項目中引用了Word的PIA,在項目管理器的引用列表中選中Word,點擊鼠標右鍵,選擇在對象瀏覽器中查看,就可以在對象瀏覽器中打開Word的PIA了。如下圖。
如果在上圖所示的Microsoft.Office.Interop.Word命名空間所包含的類中做一些瀏覽,相信讀者會發現一個很有意思的事情。那就是其實Word的PIA中的類、接口,並不是和Word的COM組件中的類、接口一一對應的。比如,我們在Word COM組件中能夠看到一個叫做Application的類,但是在Word的PIA中,我們只能找到一個叫做Application的接口,和一個叫做ApplicationClass的類。
出現這個情況的原因,在於.Net的COM Interop(具體說就是.Net SDK中的TlbImp.exe這個命令行工具)幫我們根據COM組件生成Interop Assembly時,其實是不會一一對照COM組件來生成.Net類和接口的。相反,它會根據一定的規則,來生成對應的.Net類和接口。
由於Application是Word編程接口中最重要的部分,所以我具體針對Word中的Application這個接口,把它的轉換規則簡要的說明一下(實際生成的接口和類比下面描述的要更多,相關的關系更復雜)。首先,Word PIA中會生成一個_Application接口,這個_Application接口基本描述了Word COM組件中的Application類中的所有操作和屬性,然后,Word PIA中還會生成多個ApplicationEvents_Event系列接口(ApplicationEvents2_Event、ApplicationEvents3_Event、ApplicationEvents4_Event接口…我們可以不用管這些具體的細節),這個接口基本描述了Word COM組件中的Application類中的所有事件。然后,Word PIA中會生成一個Applicatin接口,它實現了_Application接口和ApplicationEvents_Even接口,這樣,Application接口就基本描述了Word COM組件中的Application類中的所有操作、屬性、事件等等。最后,Word PIA中生成了一個具體的ApplicationClass類,這個類實現了Applicatin接口。
如果你已經被上面那一段描述搞得頭昏腦脹,那么只需要記住:在Word PIA中,我們有一個Application接口和一個ApplicationClass類,Application接口描述了對應的Word COM組件中的Application類的所有成員,而ApplicationClass類是具體的實現類。
三、Code WalkThrough:一個.Net WinForms程序
終於,在你忍受了N久,勉強看完了上面那些羅嗦的文字之后,總算可以看到一個具體的示范了。我們要用C#寫一個Windows應用程序,在這個程序中,啟動Word,用代碼操作它做一些操作,然后再關閉掉它。
首先,我們創建一個新的C# Windows應用程序,然后通過上面介紹過的方法,在項目中引用Word的PIA(在添加引用的界面中,選擇COM組件列表中的Microsoft Word 11 Object Library)。
在自動創建的啟動窗體上,放兩個Button控件,一個叫做btnStartWord,另外一個叫做btnStopWord。我們希望當用戶點擊btnStartWord時,我們的程序自動啟動Word,然后創建一個新的Word文檔,然后將其自動保存在磁盤上,當用戶點擊btnStopWord時,就關閉掉Word。窗體設計視圖如下。
在這個主窗體類的源代碼中,我們引用Word PIA的名稱空間,我們使用MSWord來替代Microsoft.Office.Interop.Word這個完整的命名空間名稱:
using MSWord = Microsoft.Office.Interop.Word;
在主窗體類的源碼中,添加一個類級別的成員,_wordApp是一個Application類型的對象(記住:MSWord.Application是一個接口!):
private MSWord.Application _wordApp = null;
然后在btnStartWord按鈕的點擊事件代碼中,添加如下代碼,代碼創建一個新的Word實例,然后顯示它:
_wordApp = new MSWord.Application();
_wordApp.Visible = true;
看到這里,很多人有一個非常大的疑惑,那就是MSWord.Application實際上是一個接口,那么我們怎么可能通過“new MSWord.Application()”來創建一個Word實例呢?難道我們不應該使用“new MSWord.ApplicationClass()”來做嗎?畢竟ApplicationClass才是實現Application接口的具體類啊。
在這里,Office PIA為我們提供了一個小小的“cookie”,我們實際上的確可以使用“new MSWord.Application()”來創建一個Word程序實例的,我們只需要知道,Office PIA會在底下自動幫我們創建一個真正的Word程序實例。
接下來,我們在btnStartWord按鈕的事件代碼中,再添加如下的代碼。
Object missing = Type.Missing;
Object sFileName = "C:\\Sample.doc";
MSWord.Documents docs = _wordApp.Documents;
MSWord.Document doc = docs.Add(ref missing, ref missing, ref missing, ref missing);
doc.SaveAs(ref sFileName, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing);
doc.Close(ref missing, ref missing, ref missing);
在上面的代碼中,我們通過訪問Application的Documents屬性,得到一個Documents類,然后通過Documents類的Add()方法,創建一個新文檔,並得到對應這個新文檔的類型為Document的對象,然后通過Document類的SaveAs()方法將文檔保存在磁盤上,最后通過Document類的Close()方法關閉這個新文檔。
嗯,我知道我其實解釋得不夠徹底,上面代碼中的那些ref、missing之類的東東,到底是什么意思,為什么要用它們,相信不少人都非常迷惑(特別是曾經用VBA或其他語言訪問過Office COM組件接口的程序員而言)。這其實牽涉到用C#語言調用Office編程接口的一個“語法兼容”問題。就是說,Office的產品開發組在對編程接口進行設計時,實際上是專門設計為被VBA調用的,所以接口都非常配合VBA的語法,使VBA程序員盡量感到方便。但是由於C#語法和VB語法有很多不同,所以在用C#訪問Office編程接口時,就會感到非常的“別扭”。
這這里,我只想對上面的代碼做如下簡要的額外解釋:
(1)很多的Office編程接口中的方法,都帶了非常多的參數(比如Document.SaveAs()方法有16個參數!!!),而實際上我們調用它們的時候,並不是每一個參數都需要明確給一個特定的值的(比如Document.SaveAs()方法只需要明確給定第一個參數,即保存到哪里),那么對於不需要給定明確值的參數,我們可以直接傳一個.Net類庫中自帶的靜態對象:Type.Missing就可以了。
(2)很多的Office編程接口中的方法,其參數都必需傳引用,而不能傳值,所以,調用這些方法的時候,對於參數都需要加上C#中的ref關鍵字。比如上面代碼中的Document.Add()、Document.SaveAs()、Document.Close()方法,它們的參數都必須傳引用,所以每個參數前面都加上了ref關鍵字。
對於使用C#語言調用Office編程接口時,對“語法兼容”問題的更全面的描述,請參看《Office with .Net(二)之外傳―――C#訪問Office編程接口時的“語法兼容”問題》。
繼續為我們的項目添加代碼。在btnStopWord按鈕的事件代碼中,填充下面的代碼:
Object missing = Type.Missing;
_wordApp.Quit(ref missing, ref missing, ref missing);
_wordApp = null;
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
GC.WaitForPendingFinalizers();
上面的代碼通過調用Application.Quit()方法,退出Word程序。更值得關注的部分是如何釋放掉Word程序實例。上面的代碼用了一個簡單但是很有效的方法,讓Word程序進程被關閉回收,首先將對象_wordApp重新賦值為null,這樣底下的那個Word Application對象將不再被任何變量引用,最后通過強行調用.Net Framework中的垃圾收集方法,使Application對象能夠被垃圾回收器回收掉。(實際上,.Net Framework的垃圾回收器回收的只是Office PIA的一個包裝類對象而已,但是這個包裝類對象被回收后,對應Word程序的COM組件對象會發現自己不再被其他任何對象引用,引用計數變為了0,於是,那個COM組件就會被真正釋放掉了。)
如何保證在自己的應用程序中關閉Office程序其實是一個不小的問題,上面描述的方法並不是100%有效的,對這個話題更完整的描述請參考《Office with .Net(二)之外傳―――“徹底干凈的”關閉Office程序》一文。
我們的第一個示范程序到這里就已經寫完了,現在我們可以運行一下這個程序,然后先點擊第一個按鈕啟動Word,並操作Word創建一個新文檔后再保存到磁盤上,接着點擊第二個按鈕關閉掉Word。
(四)總結
這篇文檔簡要描述了如何在.Net中訪問Office的編程接口,講解了Office PIA的概念和使用方法。從這篇文章可以看出,在.Net中操作Office是非常簡單而直接的,微軟通過提供Office PIA,大大簡化了.Net程序員的工作。
轉載自:http://hi.baidu.com/sammyhxm/item/fd16be28dcdd6fc0ee10f1a6