COM組件設計與應用(十三)--事件和通知(VC6.0)

來源:互聯網
上載者:User
 

下載原始碼

一、前言
  我的 COM 組件運行時產生一個視窗,當使用者雙擊該視窗的時候,我需要通知調用者;
  我的 COM 組件用線程方式下載網路上的一個檔案,當我完成任務後,需要通知調用者;
  我的 COM 組件完成一個鐘錶的功能,當預定時間到達的時候,我需要通知調用者;
  ... ... ... ...
  本回書開始話說 COM 的事件、通知、連接點......這些內容比較多,我分兩次(共四回)來介紹。

二、通知的方法
  當程式甲方內部發生了某個事件的時候,需要通知乙方,無非使用幾個方法:
 

通知方式 簡單說明 評論
直接訊息 PostMessage()
PostThreadMessage()
向視窗或線程發個訊息 你什麼時候執行我就不管啦
SendMessage() 馬上執行訊息響應函數 不執行完訊息處理函數不會返回
SendMessage(WM_COPYDATA...) 發訊息的同時,還可以帶過去一些自訂的資料 比較常用,所以單獨列了出來
間接訊息 InvalidateRect()
SetTimer()
......
被調用的函數會發送相關的一些訊息 這樣的函數太多了
回呼函數 GetOpenFileName()...... 當使用者改變檔案選擇的時候,執行回呼函數 嗨!哥們,這是我的電話,有事就言語一聲。

  在 COM 的時代,以上這些方法就基本上不能玩轉了,因為...您想呀 COM 組件是運行在分布式環境中的,地球另一邊電腦上啟動並執行組件,怎麼可能給你的視窗發訊息那?當然不能!(但話又說回來,對於 ActiveX 這樣只能在本地啟動並執行組件,當然也可以發送視窗訊息的啦。)
 
 回呼函數的方式,是設計 COM
通知方法的基礎。回呼函數,本質上是預先把某一函數的指標告訴我,當我有必要的時候,就直接呼叫該函數了,而這個回呼函數做了什麼,怎麼做的,我是根本不
關心的。好了,問你個問題:啥是 COM
的介面?介面其實就是一組相關函數的集合(這個定義不嚴謹,但你可以這麼理解哈)。因此,在COM中不使用“回呼函數”而是使用“回調介面”(說的再清楚
一些,就是使用一大堆封裝好的“回呼函數”集) ,回調介面,我們也叫“接收器介面”。


圖一、用戶端傳遞接收器介面指標給COM。當發生事件時,COM調用接收器介面函數完成通知

本回樣本程式完成的功能是:
  用戶端啟動組件(Simple11.IEvent1.1)並得到介面指標 IEvent1 *;
  調用介面方法 IEvent1::Advise() 把用戶端內部的一個接收器(sink)介面指標(ICallBack *)傳遞到元件伺服器中;
  調用 IEvent1::Add() 去計算兩個整數的和;
  但是計算結果並不通過該函數返回,而是通過 ICallBack::Fire_Result() 返回給用戶端;
  當用戶端不再需要接受事件的時候,調用 IEvent1::Unadvise() 斷開和組件的聯絡。

三、組件實現步驟
1、建立一個工作區(WorkSpace)
2、在工作區中,建立一個 ATL 工程(Project)。樣本程式中工程名稱叫 Simple11,接受全部預設選項。
3、ClassView 中,執行滑鼠右鍵功能表命令 New Atl Object...,添加 ALT 類。
   3-1、左側分類 Category 選擇 Objects,右側 Objects 選擇 SimpleObject(其實就是預設項目)
   3-2、名稱 Name 卡片中,輸入組件名稱。樣本程式中是 Event1(注1)
   3-3、屬性 Attributes 卡片中,修改介面類型 Interface 為定製的 Custom(注2)
4、ClassView 中,選擇介面(IEvent1),滑鼠右鍵菜單添加函數 Add Method...


圖二、增加介面函數 Add([in] long n1,[in] long n2)


圖三、增加介面函數 Advise([in] ICallBack *pCallBack,[out] long *pdwCookie)


圖四、增加介面函數 Unadvise([in] long dwCookie)

 
 你應該注意到了,在Add()函數中,並沒有[out]、[retval] 這樣的 IDL 屬性,嘿嘿,因為我們本來就不打算通過 Add()
函數直接得到計算結果。不然怎麼示範回調介面呀:-) 另外,在函數 Advise()中,需要返回一個整數
dwCookie,這是幹什嗎?道理很簡單,因為我們的組件想同時支援多個對象的回調串連。因此當用戶端傳遞一個介面給我們組件的時候,我返回給它唯一的
一個 cookie 號碼來表示身份,將來中斷連線的時候 Unadvise(),它需要把這個 cookie
身份號再給我,這樣我就知道是誰想斷開了。
5、增加回調介面 ICallBack 的 IDL 定義。開啟 IDL 檔案並手工輸入(黑體字部分為手工輸入的) ,然後儲存:

import "oaidl.idl";
import "ocidl.idl";
[
object,
uuid(7E659BB1-FB79-4188-9661-65CA22B6A3E6),// 這個 IID 可以用 GUDIGEN.EXE 產生

helpstring("ICallBack Interface"),
pointer_default(unique)
]
interface ICallBack : IUnknown
{

};

[
object,// 以下內容同樣本程式,當然如果是你自己產生的程式就肯定有差別的啦
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;
// 需要手工輸入,據說 VB 使用的話,不能有 [source,default] 屬性
[source, default] interface ICallBack;
};
};

6、增加回調介面函數


圖五、增加回調介面函數

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


圖六、增加介面函數 Fire_Result([in] long nResult)

我們計算整數和,得到結果後,就是要靠這個回調介面函數去反饋給用戶端呀。

7、添加組件內部儲存回調介面指標的數組
  剛才已經說過,我們這個組件打算支援多個對象的回調串連,因此我們要使用一個數組來儲存。在 ClassView 中,選擇 CEvent1 類,增加成員變數 Add Member Variable...


圖七、增加儲存 ICallBack * 的數組

  當然,儲存一個數組可以有多種方式。樣本程式比較簡單,定義了一個10個元素空間的成員陣列變數。如果你已經學會了使用 STL,那麼你也可以用 vector 等容器來實現。注意!注意!注意!在建構函式中別忘了初始化數組元素為 NULL

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

STDMETHODIMP CEvent1::Add(long n1, long n2)
{
long nResult = n1 + n2;
for( int i=0; i<10; i++)
{
if( m_pCallBack[i] )  // 如果回調介面有效
m_pCallBack[i]->Fire_Result( nResult );// 則發出事件/通知
}

return S_OK;
}

STDMETHODIMP CEvent1::Advise(ICallBack *pCallBack, long *pdwCookie)
{
if( NULL == pCallBack )// 居然給我一個null 指標?!
return E_INVALIDARG;

for( int i=0; i<10; i++)// 尋找一個儲存該介面指標的位置
{
if( NULL == m_pCallBack[i] )// 找到了
{
m_pCallBack[i] = pCallBack;// 儲存到數組中
m_pCallBack[i]->AddRef();// 指標計數器 +1

*pdwCookie = i + 1;// cookie 就是數組下標
  // +1 的目的是避免使用0,因為0表示無效

return S_OK;
}
}
return E_OUTOFMEMORY;// 超過10個串連,記憶體不夠用啦
}

STDMETHODIMP CEvent1::Unadvise(long dwCookie)
{
if( dwCookie<1 || dwCookie>10 )// 這是誰乾的呀?亂給參數
return E_INVALIDARG;

if( NULL == m_pCallBack[ dwCookie - 1 ] )// 參數錯誤,或該介面指標已經無效了
return E_INVALIDARG;

m_pCallBack[ dwCookie -1 ]->Release();// 指標計數器 -1
m_pCallBack[ dwCookie -1 ] = NULL;// 空出該下標的數組元素

return S_OK;
}

四、用戶端實現步驟
  大家下載樣本程式後,去瀏覽用戶端的實現程式吧。這裡我只說明一下關於接收器是如何構造的:


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

 
 從 ICallBack 派生一個類 CSink。確認後 IDE 會有一個警告,說它找不到 ICallBack
的標頭檔,不用理它,因為只有當編譯的時候,#import 才會為我們產生 xxxx.tlh、xxxx.tli 檔案,這些檔案就有
ICallBack 的聲明啦。
  這裡 ICallBack 是 COM 介面,因此 CSink 是不能案例化的,如果你去編譯,會得到一坨一坨(注3)的錯誤,報告說你沒有實現 virtual 函數。然後,我們可以按照錯誤報表,去實現所有的虛函數:

// STDMETHODIMP 是宏,等價於 long __stdcall
STDMETHODIMP CSink::QueryInterface(const struct _GUID &iid,void ** ppv)
{
*ppv=this;// 不管想得到什麼介面,其實都是對象本身
return S_OK;
}

ULONG __stdcall CSink::AddRef(void)
{return 1;}// 做個假的就可以,因為反正這個對象在程式結束前是不會退出的

ULONG __stdcall CSink::Release(void)
{return 0;}// 做個假的就可以,因為反正這個對象在程式結束前是不會退出的

STDMETHODIMP CSink::raw_Fire_Result(long nResult)
{
... ...// 把計算結果顯示在視窗中
return S_OK;
}

、小結
  COM
組件實現事件、通知這樣的功能有兩個基本方法。今天介紹的回調介面方式非常好,速度快、結構清晰、實現也不複雜;下回書介紹連接點方式(Support
Connection Points),連接點方法其實並不太好,速度慢(如果是遠程DCOM方式,要謹慎選擇它)、結構複雜、唯一的好處就是 ATL
對它進行了封裝,所以實現起來反而比較簡單。不介紹又不行,因為微軟絕大數支援事件的組件都是用連接點實現的,咳......討厭的微軟(注4)。

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

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.