COM組件設計與應用(十五)--連接點(vc6.0)

來源:互聯網
上載者:User
 

下載原始碼

一、前言

  上回書介紹了回調介面,在此基礎上,我們理解連接點就容易多了。

二、原理


圖一、連接點組件原理圖。左側為用戶端,右側為服務端(組件對象)

  看著好複雜呀......呵呵,其實簡單的緊:(注1)
1、一個 COM 組件,允許有多個連接點對象(IConnectionPoint)。
   也就是說可以有多個發生“事件”的源頭。就有3個連接點;
2、管理這些連接點的介面叫“連接點容器”(IConnectionPointContainer)。
  
連接點容器介面特別簡單,因為只有2個函數,一個是 FindConnectionPoint(),表示尋找你想要的連接點;另一個是
EnumConnectionPoints(),表示列出所有的連接點,然後你去選擇使用哪個。在實際的應用中,尋找法使用最多,佔90%,而枚舉法使用
只佔 10%,一般在支援第三方的外掛程式(Plug in)時才使用。(你想寫個 IE 的外掛程式嗎?我們後面就要講到啦)
3、每一個連接點,可以被多個用戶端的接收器(Sink)串連;
   這個我們已經熟悉啦,還記得我們在上回書中為了管理多個回調介面,使用了 cookie 的方式進行區別嗎?!

三、實現組件(一)

1、建立一個工作區(WorkSpace)
2、在工作區中,建立一個 ATL 工程(Project)。樣本程式中工程名稱叫 Simple15,接受全部預設選項。
3、ClassView 中,執行滑鼠右鍵功能表命令 New Atl Object...,添加 ALT 類。
4、左側分類 Category 選擇 Objects,右側 Objects 選擇 SimpleObject(其實就是預設項目)。
5、名稱 Name 卡片中,輸入組件名稱。樣本程式中是 DispConnect。

6、屬性 Attributes 卡片中,介面類型選 Dual 雙介面。注意一定要選擇 Support Connection Points 來支援連接點。

7、ClassView 中,選擇介面(IDispConnect),滑鼠右鍵菜單添加函數 Add Method...

8、增加函數。和上回書的程式一樣,增加一個介面Function Compute加法,但通過連接點介面返回計算結果。

9、下面該增加“事件”函數了。選擇事件介面(_IDispConnectEvents),添加函數。

10、該函數用來返回 Add() 函數的計算結果。

11、切換到 FileView 卡片,編譯IDL檔案。當然你也可以直接編譯全部工程。其實編譯的目的是為了從IDL檔案產生TLB檔案,因為 VC 的 IDE 環境只有知道了 TLB 後,才能產生下面的“事件代理類的程式碼”。

12、建置事件代理類程式碼。選擇組件類對象(CDispConnect),執行滑鼠右鍵菜單“實現連接點”

13、選擇你要讓 IDE 幫你產生哪個連接點的代理程式碼。我們這個組件只有一個連接點,那隻好選擇它了。 (在樣本二中,我們需要實現兩個連接點,那個時候,你就要選擇兩個了)

14、到此,VC 的 IDE 終於幫咱們完成了所有的架構,下面該咱們自己寫真正的任務代碼啦。

STDMETHODIMP CDispConnect::Add(long n1, long n2)
{
long nVal = n1 + n2;
Fire_Result( nVal );// 調用IDE幫我們產生的代理函數代碼,發出事件

return S_OK;
}

15、修正 IDE 產生的代碼中的錯誤。你不用死記硬背錯誤點,只要編譯一下就會報出錯誤了。一般 VC6 幫我們產生的程式碼中,有2個地方可能會有BUG。一是開啟標頭檔,找到連接點影射宏,修改如下:

BEGIN_CONNECTION_POINT_MAP(CDispConnect)
CONNECTION_POINT_ENTRY(DIID__IDispConnectEvents)// 修改 IID_XXXX 為 DIID_XXXX
END_CONNECTION_POINT_MAP()

 
 這個錯誤簡直可恨,既然我們使用的是雙介面連接點,它產生的程式碼居然不會判斷嗎?另一個可能的錯誤可能發生在代理類中的 Fire_xxxx()
函數中。在樣本程式中的 Fire_Result()
函數代碼,大家自己去閱讀,簡單說就是迴圈地取得每個和自己連線物件(每個cookie表示的對象)的介面指標,(如果是自動化介面,則再取得
IDispatch
介面指標),然後呼叫事件函數。你不理解它現在沒有太大的關係,不過在後面的樣本二中,它給我們產生的代碼是有錯誤的,我們需要進行修改。這是後話,待會
兒再說。

四、實現調用者(一)

1、建立一個 MFC 工程(Project)。樣本程式中的工程名稱叫 Use。
2、按照咱們以前所學的知識,添加 #import、AfxOleInit()、......不多浪費口條了。如果你還不會,那麼請重新從“第四回”再次閱讀。 (注2)
3、這裡只介紹一下重點部分。我們需要在調用者工程中,增加“接收器”對象。還記得上回書中的增加“回調接收器”對象的方法嗎?上回中,我們的回調介面是從 IUnknown 繼承下來的。本回中,由於我們的組件是雙介面(Dual)的,連接點也是雙介面的,因此這次我們的接收器要從 IDispatch 派生啦。

4、完成 CSink 類的介面函數(虛函數)

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::GetTypeInfoCount(unsigned int *)
{return E_NOTIMPL;}// 不用實現,反正也不用

STDMETHODIMP CSink::GetTypeInfo(unsigned int,unsigned long,struct ITypeInfo ** )
{return E_NOTIMPL;}// 不用實現,反正也不用

STDMETHODIMP CSink::GetIDsOfNames(const struct _GUID &,unsigned short ** ,unsigned int,unsigned long,long *)
{return E_NOTIMPL;}// 不用實現,反正也不用

STDMETHODIMP CSink::Invoke(
long dispID,
const struct _GUID &,
unsigned long,
unsigned short,
struct tagDISPPARAMS * pParams,
struct tagVARIANT *,
struct tagEXCEPINFO *,
unsigned int *)
{// 只需要實現這個就足夠啦
switch(dispID)// 根據不同的dispID,完成不同的回呼函數
{
case 1:
......// 這裡就能接收到 COM 發出的事件啦
break;
case 2:
......// 事件的代號 dispID 其實就是 IDL 檔案中的連接點函數的id(n)的號碼
break;
default:break;
}
return S_OK;
}

、樣本(二)

  樣本程式中的第2個組件(MultConnect),我們再增加一個連接點( _IDispConnectEvents2 )。這個介面對象負責完成一個時鐘,每間隔一定的毫秒就向調用者發出“時鐘事件”。增加第二個連接點的方法是要手工修改 IDL 檔案

......
library MULTCONNECTLib
{
importlib("stdole32.tlb");
importlib("stdole2.tlb");

...... // 第一個,ATL 架構預設給我們產生的連接點介面描述
[ // 需要手工增加第二個或更多個連接點
uuid(F81DB93F-4F63-4A55-8114-A32BC78466D3), // CLSID 可以用 GUIDGEN.EXE 來產生
helpstring("_IDispConnectEvents2 Interface")
]
dispinterface _IDispConnectEvents2
{
properties:
methods:
};

[
uuid(9461BE82-0D64-4E3B-B0DB-2306D1BFE3F0), // 這是樣本程式的類型庫ID,肯定和你產生的不一樣的啦
helpstring("DispConnect Class")
]
coclass DispConnect
{
[default] interface IDispConnect;
[default, source] dispinterface _IDispConnectEvents;
[source] dispinterface _IDispConnectEvents2; // 別忘了,這裡還有一行呢
};
};

 
 好了,和前面的方式一樣,增加介面函數、編譯IDL檔案、讓IDE幫我們實現代理類代碼、輸入程式碼、修改架構代碼中的BUG。在樣本中,我們的事件
函數叫 HRESULT Timer([in] VARIANT varData),varData
中傳遞一個時間類型(VT_DATA)的資訊(注3)。下面我們來看一下代理類代碼中的錯誤:

HRESULT Fire_Timer(VARIANT varDate)
{
CComVariant varResult;
T* pT = static_cast(this);
int nConnectionIndex;
CComVariant* pvars = new CComVariant[1];
int nConnections = m_vec.GetSize();

for (nConnectionIndex = 0; nConnectionIndex < nConnections; nConnectionIndex++)
{
   pT->Lock();
   CComPtr sp = m_vec.GetAt(nConnectionIndex);
   pT->Unlock();
   IDispatch* pDispatch = reinterpret_cast(sp.p);
   if (pDispatch != NULL)
   {
     VariantClear(&varResult);
     // 原始代碼,這裡居然是 pvars[0]=&varData?愚蠢之極!只好你自己修改啦
    pvars[0] = varDate;

     DISPPARAMS disp = { pvars, NULL, 1, 0 };
     pDispatch->Invoke(0x1, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, &disp, &varResult, NULL, NULL);
   }
}
delete[] pvars;
return varResult.scode;
}

  在編寫調用者用戶端代碼方面,如果你需要接收時鐘事件,那麼可以仿照樣本一再從 IDispatch 派生一個時鐘接收器。大家下載案例程式碼,裡面有豐富的注釋資訊。

六、小結

  連接點,尤其是雙介面的連接點,在遠程(DCOM)環境上運行效率是比較低的。如果你只想完成簡單的“通知”功能,那麼前一回中的“回調介面”是一個明智的方案,並且可以運行在DCOM環境上。連接點方案當然也很重要,因為微軟的許多應用程式(IE、Office......)都支援連接點,並且 ActiveX 只能通過連接點介面提供“事件”功能。所以,咱們還是都掌握為善吧。善哉 、善哉......

注1:金庸老先生的武俠小說裡,總是用“XX 緊”來表示“很 XX”。我也學一學,嘿嘿。
注2:如果看了好幾遍,您老人家還不會的話,那隻好......先別學了。5555

3:DATA 類型就是是8位元組的double,它的整數部分表示從
1899年12月30日開始的總天數,小數部分表示當天的時間已經渡過了一天的多少分之一。這個時間類型,用VARIANT表示,就是VT_DATE類
型,MFC 中用 COleDateTime 表示。樣本程式中有對該類型的操作示範。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.