日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

COM 组件设计与应用(十一)—— IDispatch 及双接口的调用

發布時間:2025/3/20 编程问答 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 COM 组件设计与应用(十一)—— IDispatch 及双接口的调用 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
原文:http://www.vckbase.com/index.php/wv/1236

一、前言

前段時間,由于工作比較忙,沒有能及時地寫作。其間收到了很多網友的來信詢問和鼓勵,在此一并表示感謝。咳......我也需要工作來養家糊口呀......

上回書介紹了兩種方法來寫自動化(IDispatch)接口的組件程序,一是用 MFC 方式編寫“純粹”的IDispatch?接口;二是用 ATL 方式編寫“雙接口”的組件。

二、IDispatch?接口和雙接口

使用者要想調用普通的 COM 組件功能,必須要加載這個組件的類型庫(Type library)文件?tlb(比如在 VC 中使用?#import)。然而,在腳本程序中,由于腳本是被解釋執行的,所以無法使用加載類型庫的方式進行預編譯。那么腳本解釋器如何使用 COM 組件那?這就是自動化(IDispatch)組件大顯身手的地方了。IDispatch?接口需要實現4個函數,調用者只通過這4個函數,就能實現調用自動化組件中所有的函數。這4個函數功能如下:

HRESULT GetTypeInfoCount(
????[out] UINT * pctinfo)
組件中提供幾個類型庫?當然一般都是一個啦。
但如果你在一個組件中實現了多個?
IDispatch?接口,那就不一定啦(注1)
HRESULT GetTypeInfo(
????[in] UINT iTInfo,
????[in] LCID lcid,
????[out] ITypeInfo ** ppTInfo)
調用者通過該函數取得他想要的類型庫。
幸好,在 99% 的情況下,我們都不用關心這兩個函數的實現,因為 MFC/ATL 都幫我們完成了默認的一個實現,如果是自己完成函數代碼,甚至可以直接返回 E_NOTIMPL 表示沒有實現。(注2)
HRESULT GetIDsOfNames(
????[in] REFIID riid,
????[in,size_is(cNames)] LPOLESTR * rgszNames,
??? [
in] UINT cNames,
????[in] LCID lcid,
????[out,size_is(cNames)] DISPID * rgDispId)
根據函數名稱取得函數序號,為調用?Invoke()?做準備。
所謂函數序號,大家去觀察雙接口 IDL 文件和 MFC 的 ODL 文件,每一個函數和屬性都會有
?[id(序號)....]?這樣的描述。
HRESULT Invoke(
????[in] DISPID dispIdMember,
??? [in] REFIID riid,
??? [in] LCID lcid,
??? [in] WORD wFlags,
??? [in,out] DISPPARAMS * pDispParams,
??? [out] VARIANT * pVarResult,
??? [out] EXCEPINFO * pExcepInfo,
??? [out] UINT * puArgErr)
根據序號,執行函數。
使用 MFC/ATL 寫的組件程序,我們也不必關心這個函數的實現。如果是自己寫代碼,則該函數類似如下實現:
switch(dispIdMember)
{
??? case 1: .....; break;
??? case 2: .....; break;
??? ....
}
其實,就是根據序號進行分支調用啦。(注3)

從?Invoke()?函數的實現就可以看出,使用?IDispatch?接口的程序,其執行效率是比較低的。ATL 從效率出發,實現了一種叫“雙接口(dual)”的接口模式。下面我們來看看,到底什么是雙接口:

圖一、雙接口(dual)?結構示意圖

從上圖中可以看出,所謂雙接口,其實是在一個 VTAB 的虛函數表中容納了三個接口(因為任何接口都是從IUnknown?派生的,所以就不強調?IUnknown?了,叫做雙接口)。我們如果從任意一個接口中調用QueryInterface()得到另外的接口指針的話,其實,得到的指針地址都是同一個。雙接口有什么好處那?答:好呀,多好呀,特別好呀......

使用方式因為所以
腳本語言使用組件解釋器只認識?IDispatch?接口可以調用,但執行效率最低
編譯型語言使用組件它認識?IDispatch?接口可以調用,執行效率比較低
編譯型語言使用組件它裝載類型庫后,就認識了?Ixxx?接口可以直接調用?Ixxx?函數,效率最高啦

結論

雙接口,既滿足腳本語言的使用方便性,又滿足編譯型語言的使用高效性。
于是,我們寫的所有的 COM 組件接口,都用雙接口實現嗎?
錯!否!NO!
如果不是明確非要支持腳本的調用,則最好不要使用雙接口,因為:

如果所有函數都放在一個雙接口中,那么層次、結構、分類不清
如果使用多個雙接口,則會產生其它問題(注4)
雙接口、IDispatch接口只支持自動化的參數類型,使用受到限制,某些情況下很不方便嘍
還有很多弊病呦,不過現在我想不起來嘍......

三、使用方法

如果你的開發環境是?vc6.0,那么我們使用第九回中的Simple6組件為例,快去下載呀......

如果你的開發環境是?vc.net 2003,那么用第十回中的Simple8組件為例,快去下載呀......

嘿嘿,其實不下載也沒有關系,因為你只要下載本回的示例程序,里面已經包含了所需的組件。但使用前不要忘了去注冊呀:regsvr32.exe simple6.dll?或?regsvr32.exe simple8.dll?(注意別忘了輸入組件的安裝目錄)。注冊成功后,就可以使用了,使用方法有:

示例程序自動化組件的使用方式簡要說明
示例0在腳本中調用在第九回/第十回中,已經做了介紹
示例1使用 API 方式調用揭示?IDispatch?的調用原理,但傻子才去這么使用那,會累死了
示例2使用?CComDispatchDriver的智能指針包裝類比直接使用 API 方式要簡單多啦,這個不錯!
示例3使用 MFC 裝載類型庫的包裝方式簡單!好用!常用!但它本質上是使用?IDispatch?接口,所以執行效率稍差
示例4使用?#import?方式加載類型庫方式#import?方式使用組件,咱們在第七回中講過啦。常用!對雙接口組件,直接調用自定義接口函數,不再經過?IDispatch,因此執行效率最高啦
示例xvb、java、c#bcb、delphi.......反正我不會,自己去請教高人去吧?:-(

示例一、IDispatch 調用原理篇

01.void?demo() 02.{ 03.::CoInitialize( NULL );?????// COM 初始化 04.? 05.CLSID clsid;????????????????// 通過 ProgID 得到 CLSID 06.HRESULT?hr = ::CLSIDFromProgID( L"Simple8.DispSimple.1", &clsid ); 07.ASSERT( SUCCEEDED( hr ) );??// 如果失敗,說明沒有注冊組件 08.? 09.IDispatch * pDisp = NULL;???// 由 CLSID 啟動組件,并得到 IDispatch 指針 10.hr = ::CoCreateInstance( clsid, NULL, CLSCTX_ALL, IID_IDispatch, (LPVOID*)&pDisp ); 11.ASSERT( SUCCEEDED( hr ) );??// 如果失敗,說明沒有初始化 COM 12.? 13.LPOLESTR pwFunName = L"Add";????// 準備取得 Add 函數的序號 DispID 14.DISPID dispID;??????????????????// 取得的序號,準備保存到這里 15.hr = pDisp->GetIDsOfNames(???????// 根據函數名,取得序號的函數 16.IID_NULL, 17.&pwFunName,?????????????????// 函數名稱的數組 18.1,??????????????????????????// 函數名稱數組中的元素個數 19.LOCALE_SYSTEM_DEFAULT,??????// 使用系統默認的語言環境 20.&dispID );??????????????????// 返回值 21.ASSERT( SUCCEEDED( hr ) );??????// 如果失敗,說明組件根本就沒有 ADD 函數 22.? 23.VARIANTARG v[2];????????????????????// 調用 Add(1,2) 函數所需要的參數 24.v[0].vt = VT_I4;??? v[0].lVal = 2;??// 第二個參數,整數2 25.v[1].vt = VT_I4;??? v[1].lVal = 1;??// 第一個參數,整數1 26.? 27.DISPPARAMS dispParams = { v, NULL, 2, 0 };??// 把參數包裝在這個結構中 28.VARIANT vResult;????????????// 函數返回的計算結果 29.? 30.hr = pDisp->Invoke(??????????// 調用函數 31.dispID,?????????????????// 函數由 dispID 指定 32.IID_NULL, 33.LOCALE_SYSTEM_DEFAULT,??// 使用系統默認的語言環境 34.DISPATCH_METHOD,????????// 調用的是方法,不是屬性 35.&dispParams,????????????// 參數 36.&vResult,???????????????// 返回值 37.NULL,???????????????????// 不考慮異常處理 38.NULL);??????????????????// 不考慮錯誤處理 39.ASSERT( SUCCEEDED( hr ) );??// 如果失敗,說明參數傳遞錯誤 40.? 41.CString str;????????????// 顯示一下結果 42.str.Format("1 + 2 = %d", vResult.lVal ); 43.AfxMessageBox( str ); 44.? 45.pDisp->Release();????????// 釋放接口指針 46.::CoUninitialize();?????// 釋放 COM 47.}

示例二、CComDispatchDriver 智能指針包裝類的使用方法

01.void?demo() 02.{ 03.// 已經進行過了 COM 初始化 04.? 05.CLSID clsid;????????????????// 通過 ProgID 取得組件的 CLSID 06.HRESULT?hr = ::CLSIDFromProgID( L"Simple8.DispSimple.1", &clsid ); 07.ASSERT( SUCCEEDED( hr ) );??// 如果失敗,說明沒有注冊組件 08.? 09.CComPtr < IUnknown > spUnk;???// 由 CLSID 啟動組件,并取得 IUnknown 指針 10.hr = ::CoCreateInstance( clsid, NULL, CLSCTX_ALL, IID_IUnknown, (LPVOID*)&spUnk ); 11.ASSERT( SUCCEEDED( hr ) ); 12.? 13.CComDispatchDriver spDisp( spUnk );?// 構造只能指針 14.CComVariant v1(1), v2(2), vResult;??// 參數 15.hr = spDisp.Invoke2(????// 調用2個參數的函數 16.L"Add",?????????????// 函數名是 Add 17.&v1,????????????????// 第一個參數,值為整數1 18.&v2,????????????????// 第二個參數,值為整數2 19.&vResult);??????????// 返回值 20.ASSERT( SUCCEEDED( hr ) );??// 如果失敗,說明或者沒有 ADD 函數,或者參數錯誤 21.? 22.CString str;????????????// 顯示一下結果 23.str.Format("1 + 2 = %d", vResult.lVal ); 24.AfxMessageBox( str ); 25.}

示例程序中使用了?Invoke2()函數,其實你根據不同的函數,還可以使用Invoke0()Invoke1()、InvokeN()PutProperty()、GetProperty()......等等等,的確很方便。

示例三、加載類型庫,產生包裝類來使用

這個方法使用更簡單一些,如果你觀察 MFC 幫你產生的包裝類的實現,你就會發現,其實它調用的是IDispatch?接口函數。使用?vc6.0?的朋友,步驟如下:

1、建立一個 MFC 的應用程序

2、開啟?ClassWizard,執行?Add Class,選擇?From a type library

圖二、加載類型庫

3、然后找到你要使用的組件文件?simple6.dll(tlb?文件也可以),選擇接口后確認

圖三、選擇類型庫中需要包裝的接口

4、在適當的地方輸入調用代碼

01.#include "simple6.h"??? // 包裝類的頭文件 02.? 03.void?demo() 04.{ 05.// 已經進行過了 COM 初始化 06.? 07.IDispSimple spDisp;?????// 包裝類的對象 08.? 09.spDisp.CreateDispatch( _T("Simple6.DispSimple.1") )?//啟動組件 10.spDisp.xxx(...);????// 調用函數 11.? 12.spDisp.ReleaseDispatch();???// 釋放接口 13.}

使用?vc.net?的朋友,步驟如下:

1、建立一個 MFC 的應用程序

2、執行菜單“添加\添加類”,選擇 MFC 分類中的“類型庫中的MFC類”

圖四、添加類型庫中的MFC類

3、選擇組件文件?simple8.dll(或?tlb?文件),并選擇需要包裝的接口

圖五、選擇文件和接口

4、在適當的位置輸入調用代碼

01.#include "CDispSimple.h"??? // 包裝類的頭文件 02.? 03.void?demo() 04.{ 05.// 已經進行過了 COM 初始化 06.? 07.CDispSimple spDisp;?// 包裝類的對象 08.spDisp.CreateDispatch( _T("Simple8.DispSimple.1") )?// 啟動組件 09.spDisp.xxx(...);????// 調用函數 10.? 11.spDisp.ReleaseDispatch();???// 釋放接口 12.}

示例四、使用?#import?方式調用組件

#import?方式在第七回中已經作過介紹,這里就不多羅嗦了。大家下載本回的示例程序后,自己去看吧。并且一定要掌握這個方法,因為它的運行效率是最快的呀。

四、小結

留作業啦。在我們以前所實現的所有組件程序中,只添加了接口方法(函數),而沒有添加接口屬性(變量),你自己練習一下吧,很簡單的,然后寫個程序調用看看。其實對于 VC 來說,調用屬性和調用方法沒有太大的區別(vc?把屬性包裝為?GetXXX()/PutXXX()getXXX()/putXXX()的函數方式),但在另外一些語言中(比如腳本語言)則更方便,設置屬性值是:對象.屬性 = 變量或常量,獲取屬性值是:變量?=?對象.屬性。

本回書至此做一了斷,更多組件設計和使用的知識,且聽下回分解......

注1:多個自動化接口的實現方法,我們以后再說。

注2:將來介紹 ITypeLib::GetTypeInfo()?的時候,大家再回味?IDispatch::GetTypeInfo()吧。

注3:在后面介紹“事件”的時候,我們會自己真正去實現一個?IDispatch::Invoke()?函數。

注4:介紹多個雙接口實現的時候,會談到這個問題。

總結

以上是生活随笔為你收集整理的COM 组件设计与应用(十一)—— IDispatch 及双接口的调用的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。