訪問智能文檔內容 為了能起到作用,你的智能文檔操作DLL一般需要訪問(並且可能修改)下層的Office文檔。在例子中,它是Excel項目進程表電子表格。為了這個目的,ISmartDocument的幾個方法為你下層文檔提供了一個IDispatch COM接口指針。在Office上編寫COM的人知道,Idispatch提供了進入Office對象模型的通道。在Visual Basic中,使用Idispatch和類型庫是相當自動化的,但是在C++中稍微復雜一些。
起先我准備使用#import指令,它允許Visual C++為類型庫中的所有接口生成ATL智能指針包裝。但是要讓它正確的編譯需要做大量的工作,它常常提示有東西出錯了。果真,我找到了知識庫文章“Office Application Remains in Memory After Program Finishes”,它描述了在Office類型庫中使用#import指令所遇到的知名的問題以及相應的建議。
最後,我決定建立自己的類CexcelWorkbook來包裝需要的Excel方法。這個類繼承自ATL模版CcomDispatchDriver,這使它相對容易通過IDispatch調用Excel對象模型上的方法。使用CcomDispatchDriver的方法GetIDOfName,你可以得到一個給定的屬性或方法(例如,Excel對象模型中的ActiveSheet的范圍)的DISPID。你一旦有了DISPID(為了效率更高,我在類中對它進行了緩沖處理),就可以使用某個其它的CcomDispatch方法(例如GetProperty或InvokeN,此處的N是參數的數量)訪問對象模型中的屬性和方法。注意Exce Range值是作為VBA屬性(而不是方法)暴露的,但是它也需要一個參數(范圍地址)。因為CcomDispatchDriver沒有提供為GetProperty調用傳遞的參數的途徑,我在自定義類(CExcelWorkbook)中實現了一個新的方法(GetProperty1),它用於處理這種情況。
調試智能文檔DLL 一切都在預料之中,在我嘗試建立智能文檔操作DLL的時候也沒有出現異常。我試圖在Excel中附加一個XML大綱的時候,收到了一個錯誤信息“XML擴展包邏輯丟失或無效”。為了調試這個錯誤,我首先使用ListDLLs(http://www.sysinternals.com上的一個方便的工具)確定Excel是否載入了我的庫,這樣清單才能看起來足夠好,它可以讓Excel找到該DLL的。我退出Excel,接著修改了項目的Debugging屬性(右鍵點擊解決方案並選擇“屬性”)告訴Visual Studio使用Excel作為該DLL的EXE容器。我浏覽Excel.exe並選擇它作為Command值。
接著,我按下Ctrl-B打開“New Breakpoints”對話框,在Function字段中輸入DllMain,當出現“disambiguate symbol”窗口(顯然有兩個重載的DllMain函數)的時候選擇了它們兩個,通過這些操作在DllMain中設置了一個斷點。我的目的是當Excel第一次調用該DLL的時候得到控制權。接著我按下F5,Excel啟動了。Visual Studio警告沒有Excel符號,但是我早就知道了。我打開智能文檔,使用Data | XML | XML Expansion Packs試圖再次附加XML大綱。Visual Studio同ATL生成的DllMain中的斷點一起出現了。
這個時候我的目標是確定DLL中是否有方法、哪些方法被調用了。我在自己的IsmartDocument接口實現中的所有方法上設置了斷點,以確定它們其中的哪些被調用了。實際上,有幾個方法被調用了,並且通過逐步運行我找到了一個普通的索引問題,傳遞到get_SmartDocXMLTypeName的控件索引是從1開始的,但是C++代碼把它處理為從0開始的,因此對最後一個元素的調用返回了E_INVALIDARG。後來我給所有的接口方法的入口點添加了ATLTRACE2宏,使自己更容易知道正在調用什麼、什麼時候調用。
改變通知 對於示例解決方案,我需要知道用戶什麼時候選擇了電子表格的數據項區域中的某行,這意味著他希望編輯該事務的相關信息。接著我從當前行中抓取信息並填充事務面板,允許他輸入本周的工作和下一周的計劃工作的相關信息。當他改變了事務面板中的某些東西的時候,我將使用Excel對象模型把新的信息復制回原工作表行。但是我如何知道什麼時候選擇了新的行?有一種比較復雜的解決方案,即使用Excel事件(在Visual Basic中容易,但是在C++中不是太容易),但是我發現這是沒有必要的。在Excel中無論選擇什麼時候發生了改變,都會調用IsmartDocument接口方法,因此無論什麼事情,你僅僅需要改變通知。通過更新每次改變後的進度表事務內部視圖,我能夠忽略行選擇的變化。
安全性 因為宏病毒和其它的腳本技術的惡意使用變得很普遍,Office在兩個途徑做了修改:通過提高默認的安全性設置,防止運行大多數沒有簽名的、潛在的惡意代碼;通過增加安全性設置的數量,為即使沒有數字簽名的解決方案的運行提供了更多的管理權限。這考慮了現實情況:很多Office解決方案(嵌入Word或Excel文檔中的宏)都已經廣泛的布署在大型組織中,要讓它們完全安全將花費一定時間。如果新版本Office突然要求所有的解決方案必須有數組簽名(理想的解決方案),這將給很多組織帶來布署方面的障礙。但是,Office安全性設置的增加也使安全性更加復雜,在本文中我沒有談到這個主題。
應用於宏和插件程序的相同的Office安全性設置也可應用於智能文檔。這些設置包括:
·用戶宏的安全性設置(高、中或低,在Tools | Macro | Security中設置;默認的是高)。
·智能文檔是否從可信(trusted)位置載入:可信的文件系統目錄(例如每個用戶的或工作組模版目錄)、公司局域網上的Web服務器或可信的Internet站點。
·智能文檔組件是否是數字簽名的,如果是,發行人是否在Tools | Macro | Security | Trusted Publishers設置為可信發行人。
·是否在Tools | Macro | Security | Trusted Publishers中選擇了“相信所有安裝了的插件程序和模版”。
默認情況下,Tools | Macro | Security中的安全性層次被設置高,Office阻止任何沒有數字簽名的插件程序DLL(包含智能文檔操作DLL)的載入。上面列舉的安全性設置的其它組合可以允許智能文檔被載入(可能給用戶顯示一個安全性提示),但是保證組織中的安全性的最好途徑仍然是使用從VeriSign或GTE CyberTrust得到的數字證書對所有的已布署的解決方案(包括智能文檔組件)進行數字簽名。微軟2003 智能文檔SDK中的XMLSign.exe工具可以用於對XML智能文檔清單文件進行數字簽名。
此外,如果某個Office智能文檔解決方案是從Web服務器上運行的,微軟Internet Explorer和Office安全性設置都會影響解決方案是否能運行。如果服務器上的XML擴展包清單文件既不在Internet Explorer可信站點中,也不在局域網區域,就不會試圖檢索它,也不會給用戶提示把該站點添加到Internet Explorer可信列表站點列表中。如果XML擴展包清單文件位於可信的服務器或局域網上,清單是否被載入依賴於它是否簽名了。如果它是簽名了的,並允許運行,它仍然受到用戶Office安全性設置的約束。
在智能文檔解決方案開發過程中,你可以通過編輯注冊表的下述鍵下面的REG_DWord值“DisableManifestSecurityCheck”激活或禁止XML擴展包清單文件安全性檢查:
HKEY_LOCAL_MacHINE\Software\Microsoft\Office\Common\Smart Tag
如果它的值為1將禁止XML擴展包清單文件安全性檢查,如果值為0就激活了它。當你試圖引用某個XML擴展包清單文件的時候,如果這個注冊表設置是1(禁止安全性檢查),將出現一個對話框警告用戶禁止安全性是危險的。它同時提供一個“確定”按鈕,這個按鈕允許你立即重新激活XML擴展包安全性檢查。你應該僅僅在開發解決方案的時候把開發計算機的注冊表的這個值設置為1(這個時候每次編譯每個組件後進行數字簽名可能不方便),但是要確保在簽名後的組件的最後測試中激活它。在用戶計算機上禁止XML擴展包清單文件安全性檢查是非常不可取的。
盡管本文討論了在C++中智能文檔操作DLL的建立,但是用Visual Basic .NET或C#建立受控操作DLL也是可行的。在那種情況下,將應用.Net框架組件安全性模型,但是這超出了本文討論的范圍。
生成狀態報告 我的示例依據Excel對象模型使用Idispatch訪問來自進度表智能文檔工作表的信息以生成狀態報告。我也可以選擇開發一些C++代碼來驅動Word對象模型建立狀態文檔,但是我選擇了另一種途徑,它也利用了Word 2003中的新XML特性的優點。我把狀態報告生成為XML文件,並使用XSLT在Word中打開它以提供良好的格式化。這種方法利用了使用XSLT很容易把幾個XML文件(可能是整個小組的狀態報告)合並成一個大的報告的優點,如果它們作為Word文檔保存就很難合並了。
在花費一段時間研究如何實現XML保存後,我決定定義一個類來管理將顯示在狀態報告上的數據項列表。當開發者更新進度表中的信息的時候,智能文檔操作DLL中的代碼就會建立並更新這個事務列表,這與文檔操作工作面板中放置的控件對應。最後,開發者點擊工作面板中的控件生成XML狀態報告。每個類負責使用MSXML DOM正確地保持存儲在XML中的信息。處理這種情行的處理方法是CScheduleTaskList和CScheduleTask。我用於格式化它的Word XSL樣式表是ScheduleReport.xsl,它在示例代碼中提供了。當你安裝智能文檔解決方案的時候,這個XLST文件就會被安裝好,並讓Word知道它,這樣當你在Word中打開XML狀態報告的時候,它將自動應用這個樣式表。你也可以使用Word中的XML數據視圖事務面板來選擇這個樣式表。
在我生成的XML中有一個指令值得提起:
<?mso-application progid="Word.Document"?>
這條指令告訴Windows資源管理器這個XML文件的默認的應用程序是Word。你也可能注意到結果是這個文件的圖標改變了。
讓這個Word樣式表精確地顯示狀態報告需要花一段時間,因此此處我沒有提供詳細信息。但是,我要警告你,如果你查看我提供的樣式表,你會對顯示的這個Word XML大綱的復雜程度感到驚奇。其中的很多是樣板代碼,很多都是在Word中建立示例狀態文檔(我希望通過這種方式設置樣式和字體)並把該文檔保存為XML格式的時候獲得的。我把生成的XML中的大部分復制到自己的XSLT樣式表中。但是,我不能確定這種辦法生成的XML是在Word中生成良好格式化的狀態報告所需要的最小的XML.
我可以根據自己的經驗為開發XSLT提供一些建議。要記住XML是一種非常精確的、大小寫敏感的語言。在這方面它與C++沒有不同,但是如果出錯了,你接收到的診斷信息可能是某種類型的“不能應用樣式表”信息,而不是編譯器提供的詳細描述。如果你對某些已經在運行的東西做大范圍的修改,應該逐步增加並做好版本控制,這樣才能回滾到以前的修改去。我遇到了一個問題,即樣式表引用了ScheduleSmartDocument名字空間,但是由於操作DLL中出了錯誤,在生成的XML狀態報告中該名字空間的名稱變成了ScheduleSmartDoc。結果當我在XML狀態報告上應用該XSLT的時候,Word簡單地顯示了一個空文檔,因為樣式表中使用的XPATH與XML中的不匹配。
讓Word自動地應用正確的樣式表需要花點力氣。最後,我讓安裝智能文檔操作DLL的清單也安裝該樣式表。為了讓Word知道這個樣式表,清單中的解決方案條目必須含有context屬性,它引用該Word XML名字空間(http://schemas.microsoft.com/Office/word/2003/Wordml)。
最後一步是把智能文檔URI(示例中的是ScheduleSmartDocument)作為清單、樣式表和生成的XML狀態報告文檔的XML名字空間。在例子中,我把ScheduleSmartDocument作為名字空間的名稱。你應該根據組織中的唯一的URI來使用名字空間。
反向操作,從Word文檔生成XML看起來是個好的辦法。但是,幅面和切割結果的Word輸出的時候要非常小心。例如,我試圖減小列表部分的復雜性(它描述了文檔中使用的格式列表),因為我的狀態報告只需要一個簡單的、一層的符號列表。在一個小時以後,我放棄了,留下大量的未改動的部分,雖然我確信其中的很多是我不需要的。
如果你在Visual Studio中打開一個生成好的Word XML文件,你會發現它對於Visual Studio .Net大綱查看器來說太復雜了。因此,我建議當XML打開的時候,首先點擊“數據視圖”,強制Visual Studio分解該XML,接著返回“XML視圖”查看良好格式化的版本(它在Notepad中打開的時候,實際上是不可閱讀的)。
我的最後一條建議是在調整樣式表的時候可以使用一個非常強大的工具。MSXSL.EXE命令行工具允許你給XML文件應用樣式表並查看生成的XML輸出。你可以從鏈接http://msdn.microsoft.com/library/en-us/dnXML/Html/msxsl.ASP處下載它。
結論 我首先是在C++中開始開發操作DLL的,這是因為目前Office編碼的太多示例都是用Visual Basic編寫的。但是,使用C++執行一些甚至於很簡單的事務(例如引用Excel中的一個單元值)也比使用Visual Basic明顯復雜多了。圖11顯示了與Visual Basic中ActiveSheet.Range("A1").Value語句等同的C++代碼(注意這個方法的示例代碼有不同的版本,因為我用設置值方法把它重新編寫為共享代碼)。我提供了智能文檔處理程序希望對Excel對象模型執行的少量操作的C++代碼,但是我編寫的示例根本沒有因為使用C++而受益。我鼓勵讀者認真的考慮使用C++與Visual Basic之間的代價。
C++中的ActiveSheet.Range("A1").Value
HRESULT CExcelWorkbook::HrCellValue(
int iRow,
int iColumn,
CComVariant *pValue)
{
USES_CONVERSION;
HRESULT hr;
ATLASSERT(p); // IDispatch必須在調用方法前設置
if (pValue == NULL)
return E_INVALIDARG;
pValue->Clear();
AssureDispidRange();
TCHAR tzRangeReference[20];
ATLASSERT(iColumn<26); // 支持的不能多於26個,
// 為"CurrentRow"處理特定的值
if (iRow == iCurrentRow && FAILED(hr=GetSelectedRow(iRow)))
return hr;
wsprintf(tzRangeReference, "%c%d", 'A'+(iColumn-1), iRow);
CComVariant varRangeName(T2COLE(tzRangeReference));
CComVariant varRange;
if (SUCCEEDED(hr=GetProperty1(m_dispidRange, &varRangeName,
&varRange)))
{
ATLASSERT(varRange.vt == VT_DISPATCH);
ATLASSERT(varRange.pdispVal);
LPDISPATCH lpRange = varRange.pdispVal;
CComDispatchDriver dispRange(lpRange);
AssureDispidRangeValue(dispRange);
hr = dispRange.GetProperty(m_dispidRangeValue, pValue);
}
return hr;
}
Office 2003中的另一個選擇是C#,它是有吸引力的,但是請你確保自己清楚了解非受控代碼(Office)調用受控代碼(用C#或Visual Basic .Net編寫的操作處理程序部件)的性能問題。有可能使用Visual Basic是最好的辦法。
智能文檔為開發者提供了非常強大的用戶界面范例。你可以在利用微軟Office應用程序的所有功能的時候,同時提供無縫地集成到Office事務面板用戶界面中的自定義UI和行為。我認為智能文檔將很快在大型組織中廣泛使用,以簡化業務過程,如同目前使用的帶有宏的電子表格一樣。【完】