問題:
在使用Active Accessibility SDK的過程中,我從某個窗口的句柄來獲得IHTMLDocument2指針。有沒有什麼方法可以從IHTMLDocument2指針來獲得IWebBrowser2指針?我用QueryInterface在兩個接口(IHTMLDocument2 和 IWebBrowser2)上試過,但沒有成功。我也用網景(Netscape)的 HTMLWindow2 指針試過從get_navigator獲取IOmNavigator *。也以失敗告終。請高手指點。
解答:
這個問題通常是COM編程中存在的共性問題。你有了窗口,文檔,或者浏覽器,你明明知道可以通過這些已知數據來得到其它的信息,但往往在實際環境中一運行,QueryInterface總是給你送回一個肥大的NULL。這個問題的答案實際上隱藏在神秘的IServiceProvider接口中,顧名思義,IServiceProvider的作用就是提供服務。IServiceProvider是個非常棒的接口:它只有一個方法——QueryService。如果你會用ATL智能指針,就像下面這樣。首先必須獲得IServiceProvider接口,
CComQIPtr isp = pIHTMLDocument2;
這行代碼實際上就是對文檔執行了一次 QueryInterface,以詢問IServiceProvider接口。一旦具備了isp,你便可以象下面這樣獲得浏覽器。
CComQIPtr iwb2;
isp->QueryService(IID_IWebBrowserApp,IID_IWebBrowser2, (void**)&iwb2);
如果你還是不明白上面所講東西,沒有關系,很正常。COM編程的一個根本規則是:QueryInterface必須總是要返回所查詢對象的接口。但是文檔不實現 IWebBrowser2 接口,它只知道如何獲得正在工作的對象。文檔,浏覽器和窗口都是獨立的對象。通常IServiceProvider被用於多個單獨且相關的COM對象群來實現某種類型的服務。QueryInterface詢問某個對象:“你實現這個接口嗎?”,而QueryService告訴某個服務提供者,“不管什麼對象實現這個接口都要告訴我。” 使用QueryService返回的接口指針與所查詢的對象可能相同,也可能不相同。如圖一所示。所有對象都實現它們自己不同的接口並在內部存儲指向另一個對象的指針。你必須用IServiceProvider接口來獲得特定接口的對象,不論它是哪一個對象實現的。IServiceProvider::QueryService要追隨這些內部指針來獲取實現你所想要的接口對象。
圖一 多個對象,一個IServiceProvider
從本質上講,IServiceProvider用於導航DHTML對象層次。假設你正在寫一個ActiveX控件來導航這個對象模型。你如何做呢?首先要像下面這樣查詢IOleClientSite來取得IServiceProvider:
CComQIPtr isp = pSite;
然後,一旦你具備了IServiceProvider,則可以用QueryService來從中查詢應用對象。
CComQIPtr iwba;
isp->QueryService(IID_IWebBrowserApp, IID_IWebBrowserApp, (void **)&iwba);
接下來你就可以導航這個對象層次了(應用對象在最頂層)。如果你想要得到Web浏覽器,那麼與前面類似。
CComQIPtr iwb2;
isp->QueryService(IID_IWebBrowserApp, IID_IWebBrowser2, (void **)&iwb2));
在所有這些例子中,SID_SWebBrowserApp都是服務標示,但你也能常常見到將IID_IWebBrowserApp作為服務ID的代碼。兩種用法都可以行得通,因為文件中有個宏定義:
#defines SID_SWebBrowserApp IID_IWebBrowserApp
但從編程的角度上講,SID_SWebBrowserApp在技術上更正確,並且對閱讀代碼的人來說也更清晰。
此外,如果你有足夠的勇氣去實現像DHTML對象模型這類龐大的對象系統的話,你也要用到IServiceProvider接口......
關於這個問題的解答我也只能淺嘗辄止地說明到這個地步,再往深處走,我也蒙嚓嚓。更深層次的探討請各位參見MSDN庫。
由於個人水平所限,對解答中存在的錯誤與不詳之處,請各位弟兄海涵。