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

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

COM组件设计与应用(十三)(转载)

發(fā)布時間:2023/12/18 编程问答 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 COM组件设计与应用(十三)(转载) 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

COM組件設(shè)計與應(yīng)用(十三)
事件和通知(VC6.0)

作者:楊老師

下載源代碼

一、前言
  我的 COM 組件運行時產(chǎn)生一個窗口,當(dāng)用戶雙擊該窗口的時候,我需要通知調(diào)用者;
  我的 COM 組件用線程方式下載網(wǎng)絡(luò)上的一個文件,當(dāng)我完成任務(wù)后,需要通知調(diào)用者;
  我的 COM 組件完成一個鐘表的功能,當(dāng)預(yù)定時間到達(dá)的時候,我需要通知調(diào)用者;
  ... ... ... ...
  本回書開始話說 COM 的事件、通知、連接點......這些內(nèi)容比較多,我分兩次(共四回)來介紹。

二、通知的方法
  當(dāng)程序甲方內(nèi)部發(fā)生了某個事件的時候,需要通知乙方,無非使用幾個方法:
 

通知方式簡單說明評論
直接消息PostMessage()
PostThreadMessage()
向窗口或線程發(fā)個消息你什么時候執(zhí)行我就不管啦
SendMessage()馬上執(zhí)行消息響應(yīng)函數(shù)不執(zhí)行完消息處理函數(shù)不會返回
SendMessage(WM_COPYDATA...)發(fā)消息的同時,還可以帶過去一些自定義的數(shù)據(jù)比較常用,所以單獨列了出來
間接消息InvalidateRect()
SetTimer()
......
被調(diào)用的函數(shù)會發(fā)送相關(guān)的一些消息這樣的函數(shù)太多了
回調(diào)函數(shù)GetOpenFileName()......當(dāng)用戶改變文件選擇的時候,執(zhí)行回調(diào)函數(shù)嗨!哥們,這是我的電話,有事就言語一聲。

  在 COM 的時代,以上這些方法就基本上不能玩轉(zhuǎn)了,因為...您想呀 COM 組件是運行在分布式環(huán)境中的,地球另一邊計算機(jī)上運行的組件,怎么可能給你的窗口發(fā)消息那?當(dāng)然不能!(但話又說回來,對于 ActiveX 這樣只能在本地運行的組件,當(dāng)然也可以發(fā)送窗口消息的啦。)
  回調(diào)函數(shù)的方式,是設(shè)計 COM 通知方法的基礎(chǔ)?;卣{(diào)函數(shù),本質(zhì)上是預(yù)先把某一函數(shù)的指針告訴我,當(dāng)我有必要的時候,就直接呼叫該函數(shù)了,而這個回調(diào)函數(shù)做了什么,怎么做的,我是根本不關(guān)心的。好了,問你個問題:啥是 COM 的接口?接口其實就是一組相關(guān)函數(shù)的集合(這個定義不嚴(yán)謹(jǐn),但你可以這么理解哈)。因此,在COM中不使用“回調(diào)函數(shù)”而是使用“回調(diào)接口”(說的再清楚一些,就是使用一大堆包裝好的“回調(diào)函數(shù)”集) ,回調(diào)接口,我們也叫“接收器接口”。


圖一、客戶端傳遞接收器接口指針給COM。當(dāng)發(fā)生事件時,COM調(diào)用接收器接口函數(shù)完成通知

本回示例程序完成的功能是:
  客戶端啟動組件(Simple11.IEvent1.1)并得到接口指針 IEvent1 *;
  調(diào)用接口方法 IEvent1::Advise() 把客戶端內(nèi)部的一個接收器(sink)接口指針(ICallBack *)傳遞到組件服務(wù)器中;
  調(diào)用 IEvent1::Add() 去計算兩個整數(shù)的和;
  但是計算結(jié)果并不通過該函數(shù)返回,而是通過 ICallBack::Fire_Result() 返回給客戶端;
  當(dāng)客戶端不再需要接受事件的時候,調(diào)用 IEvent1::Unadvise() 斷開和組件的聯(lián)系。

三、組件實現(xiàn)步驟
1、建立一個工作區(qū)(WorkSpace)
2、在工作區(qū)中,建立一個 ATL 工程(Project)。示例程序中工程名稱叫 Simple11,接受全部默認(rèn)選項。
3、ClassView 中,執(zhí)行鼠標(biāo)右鍵菜單命令 New Atl Object...,添加 ALT 類。
?? 3-1、左側(cè)分類 Category 選擇 Objects,右側(cè) Objects 選擇 SimpleObject(其實就是默認(rèn)項目)
?? 3-2、名稱 Name 卡片中,輸入組件名稱。示例程序中是 Event1(注1)
?? 3-3、屬性 Attributes 卡片中,修改接口類型 Interface 為定制的 Custom(注2)
4、ClassView 中,選擇接口(IEvent1),鼠標(biāo)右鍵菜單添加函數(shù) Add Method...


圖二、增加接口函數(shù) Add([in] long n1,[in] long n2)


圖三、增加接口函數(shù) Advise([in] ICallBack *pCallBack,[out] long *pdwCookie)


圖四、增加接口函數(shù) Unadvise([in] long dwCookie)

  你應(yīng)該注意到了,在Add()函數(shù)中,并沒有[out]、[retval] 這樣的 IDL 屬性,嘿嘿,因為我們本來就不打算通過 Add() 函數(shù)直接得到計算結(jié)果。不然怎么演示回調(diào)接口呀:-) 另外,在函數(shù) Advise()中,需要返回一個整數(shù) dwCookie,這是干什么?道理很簡單,因為我們的組件想同時支持多個對象的回調(diào)連接。因此當(dāng)客戶端傳遞一個接口給我們組件的時候,我返回給它唯一的一個 cookie 號碼來表示身份,將來斷開連接的時候 Unadvise(),它需要把這個 cookie 身份號再給我,這樣我就知道是誰想斷開了。
5、增加回調(diào)接口 ICallBack 的 IDL 定義。打開 IDL 文件并手工輸入(黑體字部分為手工輸入的) ,然后保存:

import "oaidl.idl"; import "ocidl.idl"; [object,uuid(7E659BB1-FB79-4188-9661-65CA22B6A3E6), // 這個 IID 可以用 GUDIGEN.EXE 產(chǎn)生helpstring("ICallBack Interface"),pointer_default(unique) ] interface ICallBack : IUnknown {};[object, // 以下內(nèi)容同示例程序,當(dāng)然如果是你自己生成的程序就肯定有差別的啦uuid(7E659BB0-FB79-4188-9661-65CA22B6A3E6),helpstring("IEvent1 Interface"),pointer_default(unique) ] interface IEvent1 : IUnknown {[helpstring("method Add")] HRESULT Add([in] long n1, [in] long n2);[helpstring("method Advise")] HRESULT Advise([in] ICallBack * pCallBack, [out] long * pdwCookie);[helpstring("method Unadvise")] HRESULT Unadvise([in] long dwCookie); };[uuid(695C9BB2-2AE9-4232-8225-17AB8BD3BABC),version(1.0),helpstring("Simple11 1.0 Type Library") ] library SIMPLE11Lib {importlib("stdole32.tlb");importlib("stdole2.tlb");[uuid(6FCF997C-C811-49DB-9D16-46FAF8D24822),helpstring("Event1 Class")]coclass Event1{[default] interface IEvent1;// 需要手工輸入,據(jù)說 VB 使用的話,不能有 [source,default] 屬性[source, default] interface ICallBack; }; };

6、增加回調(diào)接口函數(shù)


圖五、增加回調(diào)接口函數(shù)

其實和以前的方法一樣,只要注意別選錯了接口就好。


圖六、增加接口函數(shù) Fire_Result([in] long nResult)

我們計算整數(shù)和,得到結(jié)果后,就是要靠這個回調(diào)接口函數(shù)去反饋給客戶端呀。

7、添加組件內(nèi)部保存回調(diào)接口指針的數(shù)組
  剛才已經(jīng)說過,我們這個組件打算支持多個對象的回調(diào)連接,因此我們要使用一個數(shù)組來保存。在 ClassView 中,選擇 CEvent1 類,增加成員變量 Add Member Variable...


圖七、增加保存 ICallBack * 的數(shù)組

  當(dāng)然,保存一個數(shù)組可以有多種方式。示例程序比較簡單,定義了一個10個元素空間的成員數(shù)組變量。如果你已經(jīng)學(xué)會了使用 STL,那么你也可以用 vector 等容器來實現(xiàn)。注意!注意!注意!在構(gòu)造函數(shù)中別忘了初始化數(shù)組元素為 NULL。

8、好了,下面開始完成所有代碼

STDMETHODIMP CEvent1::Add(long n1, long n2) {long nResult = n1 + n2;for( int i=0; i<10; i++){if( m_pCallBack[i] )  // 如果回調(diào)接口有效m_pCallBack[i]->Fire_Result( nResult ); // 則發(fā)出事件/通知}return S_OK; }STDMETHODIMP CEvent1::Advise(ICallBack *pCallBack, long *pdwCookie) {if( NULL == pCallBack ) // 居然給我一個空指針?!return E_INVALIDARG;for( int i=0; i<10; i++) // 尋找一個保存該接口指針的位置{if( NULL == m_pCallBack[i] ) // 找到了{(lán)m_pCallBack[i] = pCallBack; // 保存到數(shù)組中m_pCallBack[i]->AddRef(); // 指針計數(shù)器 +1*pdwCookie = i + 1; // cookie 就是數(shù)組下標(biāo)// +1 的目的是避免使用0,因為0表示無效return S_OK;}}return E_OUTOFMEMORY; // 超過10個連接,內(nèi)存不夠用啦 }STDMETHODIMP CEvent1::Unadvise(long dwCookie) {if( dwCookie<1 || dwCookie>10 ) // 這是誰干的呀?亂給參數(shù)return E_INVALIDARG;if( NULL == m_pCallBack[ dwCookie - 1 ] ) // 參數(shù)錯誤,或該接口指針已經(jīng)無效了return E_INVALIDARG;m_pCallBack[ dwCookie -1 ]->Release(); // 指針計數(shù)器 -1m_pCallBack[ dwCookie -1 ] = NULL; // 空出該下標(biāo)的數(shù)組元素return S_OK; }

四、客戶端實現(xiàn)步驟
  大家下載示例程序后,去瀏覽客戶端的實現(xiàn)程序吧。這里我只說明一下關(guān)于接收器是如何構(gòu)造的:


圖八、從 ICallBack 派生接收器類 CSink

  從 ICallBack 派生一個類 CSink。確認(rèn)后 IDE 會有一個警告,說它找不到 ICallBack 的頭文件,不用理它,因為只有當(dāng)編譯的時候,#import 才會為我們生成 xxxx.tlh、xxxx.tli 文件,這些文件就有 ICallBack 的聲明啦。
  這里 ICallBack 是 COM 接口,因此 CSink 是不能事例化的,如果你去編譯,會得到一坨一坨(注3)的錯誤,報告說你沒有實現(xiàn) virtual 函數(shù)。然后,我們可以按照錯誤報告,去實現(xiàn)所有的虛函數(shù):

// STDMETHODIMP 是宏,等價于 long __stdcall STDMETHODIMP CSink::QueryInterface(const struct _GUID &iid,void ** ppv) {*ppv=this; // 不管想得到什么接口,其實都是對象本身return S_OK; }ULONG __stdcall CSink::AddRef(void) { return 1; }// 做個假的就可以,因為反正這個對象在程序結(jié)束前是不會退出的ULONG __stdcall CSink::Release(void) { return 0; }// 做個假的就可以,因為反正這個對象在程序結(jié)束前是不會退出的STDMETHODIMP CSink::raw_Fire_Result(long nResult) {... ... // 把計算結(jié)果顯示在窗口中return S_OK; }

五、小結(jié)
  COM 組件實現(xiàn)事件、通知這樣的功能有兩個基本方法。今天介紹的回調(diào)接口方式非常好,速度快、結(jié)構(gòu)清晰、實現(xiàn)也不復(fù)雜;下回書介紹連接點方式(Support Connection Points),連接點方法其實并不太好,速度慢(如果是遠(yuǎn)程DCOM方式,要謹(jǐn)慎選擇它)、結(jié)構(gòu)復(fù)雜、唯一的好處就是 ATL 對它進(jìn)行了包裝,所以實現(xiàn)起來反而比較簡單。不介紹又不行,因為微軟絕大數(shù)支持事件的組件都是用連接點實現(xiàn)的,咳......討厭的微軟(注4)。


注1:本來設(shè)想多舉幾個例子,因此第一個叫 Event1,可寫完后,感覺程序已經(jīng)比較復(fù)雜了,就沒繼續(xù)再做了。
注2:當(dāng)然,你選擇使用雙接口 Dual 也沒有問題。但要注意到在下面的步驟,增加回調(diào)接口修改 IDL 文件的時候,我們是要使用 Custom(從IUnknown派生,而不是從IDispatch派生)的。
注3:一坨一坨經(jīng)常用來形容一堆一堆的狗屎。
注4:微軟的同志們,玩笑話不要當(dāng)真呀!我還靠著你來吃飯那。

轉(zhuǎn)載于:https://www.cnblogs.com/duzouzhe/archive/2009/07/22/1528899.html

總結(jié)

以上是生活随笔為你收集整理的COM组件设计与应用(十三)(转载)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。