程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C >> C語言基礎知識 >> 基於VC編寫COM連接點事件的分析介紹

基於VC編寫COM連接點事件的分析介紹

編輯:C語言基礎知識

COM 中的典型方案是讓客戶端對象實例化服務器對象,然後調用這些對象。然而,沒有一種特殊機制的話,這些服務器對象將很難轉向並回調到客戶端對象。COM 連接點便提供了這種特殊機制,實現了服務器和客戶端之間的雙向通信。使用連接點,服務器能夠在服務器上發生某些事件時調用客戶端。

原理如下圖:

有了連接點,服務器可通過定義一個接口來指定它能夠引發的事件。服務器上引發事件時,要采取操作的客戶端會向服務器進行自行注冊。隨後,客戶端會提供服務器所定義接口的實現。

客戶端可通過一些標准機制向服務器進行自行注冊。COM 為此提供了 IConnectionPointContainer 和 IConnectionPoint 接口。

COM 連接點服務器的客戶端可用 C++ 和 C# 托管代碼來編寫。C++ 客戶端會注冊一個類的實例,該類提供了接收器接口的實現。托管客戶端會注冊單個事件的委托,因而會按每個事件通知方法創建單個接收器,具體參考C#的互操作部分內容。

一、連接點程序編寫
1、使用ATL建立組件程序。
2、添加ATL     SIMPLE     OBJECT,支持連接點事件。
注:如果當時沒有現在連接點事件,可以在.idl文件中手動添加。比如
代碼如下:

 [
       uuid(57CCB7A5-F3B6-4990-91CD-33A82E1AAA46),
       helpstring("IFunEvent dispinterface")
    ]
    dispinterface _IFunEvent
    {
       properties:
           //  事件接口沒有任何屬性
       methods:
           [id(1), helpstring("方法OnResult")] HRESULT OnResult([out,retval] LONG* retval);
           [id(2), helpstring("方法OnType")] HRESULT OnType([in] LONG nType);
}

3、因為支持連接點事件,這樣將會自動生成一個     _XXXEVENT源接口。我們在其中增加想要觸發的方法。
4、選擇組件下的事件對象,彈出對話框選擇添加方法。可以繼續添加多個方法…
5、實現方法(其實組件裡只是做方法的申明,客戶調用時才實現這些方法)。實現時選中組件/類,按右鍵,在彈出菜單中選中implement     connection....
就會產生CProxy_xxxEvent類,裡面有Fire函數的實現,都是自動生成的。
6、完成組件的其他接口函數。
組件的連接點編寫比較簡單,關鍵是如何在客戶端實現事件監聽與接收。在.NET下很容易實現。但在VC中比較繁瑣。
二、連接點客戶端實現(VC)
1、包含“工程_i.h”頭文件,引入“工程.tlb”ole庫文件。比如:
#include "ATLDemo_i.h"
#import "ATLDemo.tlb" named_guids raw_interfaces_only
2、創建一個類:由_IXXXEvent派生過來。(XXX為實際事件名)
實現類各個虛函數重載,如果_IXXXEvent是IUnkown接口只需要重載QueryInterface、AddRef、Release函數;如果_IXXXEvent是雙向接口需要重載實現IUnkown接口三個函數和IDispatch接口四個函數。
實現事件功能,通過函數、用SINK_ENTRY_INFO實現事件的映射、Invoke函數裡面實現(通過事件ID)三種方法之一來實現。
用SINK_ENTRY_INFO實現事件的映射
如:
代碼如下:

BEGIN_SINK_MAP(CEventSink)
    SINK_ENTRY_INFO(1,DIID__INew01Events,DISPID_MSG,Msg,&MsgInfo)
END_SINK_MAP()

我在組件中定義了一個Msf函數,所以在這裡對其進行消息隱射。然後實現Msg方法。
3、如何調用
3.1使用工程支持COM,使用afxoleinit或者CoInitialize/Un CoInitialize
3.2得到組件接口
3.3得到連接點容器,查找連接點。
3.4利用Advise將一個監聽對象傳給組件,這樣當事件發生的時候事件就會響應。在不使用時,通過UnAdvise來斷開連接點事件。同時也利用AfxConnectionAdvice將監聽對象傳給組件接口。
3.5 釋放資源。
具體代碼如下:
代碼如下:

#pragma once

#include "ATLDemo_i.h"
#import "ATLDemo.tlb" named_guids raw_interfaces_only

class CSkin : public _IFunEvent
{
public:
    CSkin(void);
    ~CSkin(void);
private:
    DWORD       m_dwRefCount;
public:
    STDMETHODIMP Fire_OnType( LONG nType)
    {
       CString    strTemp;
       strTemp.Format(_T("The result is %d"), nType);
       AfxMessageBox(strTemp);
       return S_OK;;
    }
    HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void **ppvObject)
    {
       if (iid == DIID__IFunEvent)
       {
           m_dwRefCount++;
           *ppvObject = (void *)this;
           return S_OK;
       }

       if (iid == IID_IUnknown)
       {
           m_dwRefCount++;          
           *ppvObject = (void *)this;
           return S_OK;
       }

       return E_NOINTERFACE;
    }
    ULONG STDMETHODCALLTYPE AddRef()
    {
       m_dwRefCount++;
       return m_dwRefCount;
    }

    ULONG STDMETHODCALLTYPE Release()
    {
       ULONG l;

       l  = m_dwRefCount--;

       if ( 0 == m_dwRefCount)
       {
           delete this;
       }

       return l;
    }

    HRESULT STDMETHODCALLTYPE GetTypeInfoCount(
       /* [out] */ __RPC__out UINT *pctinfo)
    {
       return S_OK;
    }

    HRESULT STDMETHODCALLTYPE GetTypeInfo(
       /* [in] */ UINT iTInfo,
       /* [in] */ LCID lcid,
       /* [out] */ __RPC__deref_out_opt ITypeInfo **ppTInfo)
    {
       return S_OK;
    }

     HRESULT STDMETHODCALLTYPE GetIDsOfNames(
       /* [in] */ __RPC__in REFIID riid,
       /* [size_is][in] */ __RPC__in_ecount_full(cNames) LPOLESTR *rgszNames,
       /* [range][in] */ UINT cNames,
       /* [in] */ LCID lcid,
       /* [size_is][out] */ __RPC__out_ecount_full(cNames) DISPID *rgDispId)
    {
       return S_OK;
    }

     /* [local] */ HRESULT STDMETHODCALLTYPE Invoke(
       /* [in] */ DISPID dispIdMember,
       /* [in] */ REFIID riid,
       /* [in] */ LCID lcid,
       /* [in] */ WORD wFlags,
       /* [out][in] */ DISPPARAMS *pDispParams,
       /* [out] */ VARIANT *pVarResult,
       /* [out] */ EXCEPINFO *pExcepInfo,
       /* [out] */ UINT *puArgErr)
    {
       switch(dispIdMember) // 根據不同的dispIdMember,完成不同的回調函數,事件函數的ID編號
       {
       case 2:
           {
              // 1st param : [in] long lValue.
              VARIANT varlValue;
              long lValue = 0;
              VariantInit(&varlValue);
              VariantClear(&varlValue);
              varlValue = (pDispParams->rgvarg)[0];
              lValue = V_I4(&varlValue);
              Fire_OnType(lValue);
           }
           break;
       default:   break;
       }

       return S_OK;
    }
};

#include "StdAfx.h"
#include "Skin.h"

CSkin::CSkin(void)
{
     m_dwRefCount =0;
}

CSkin::~CSkin(void)
{
}

實現部分:
代碼如下:

CoInitialize(NULL);

    CComPtr<IFun> pFun;
    HRESULT hr = pFun.CoCreateInstance(CLSID_Fun);
    if(hr!=S_OK)
    {
       return ;
    }

    IConnectionPointContainer *pCPC;
    hr = pFun->QueryInterface(IID_IConnectionPointContainer,(void **)&pCPC);
    if(!SUCCEEDED(hr))
    {
       return ;
    }
    IConnectionPoint *pCP;
    hr = pCPC->FindConnectionPoint(DIID__IFunEvent,&pCP);
    if ( !SUCCEEDED(hr) )
    {
       return ;
    }
    pCPC->Release();

    IUnknown *pSinkUnk;
    CSkin *pSink = new CSkin();
    hr = pSink->QueryInterface(IID_IUnknown,(void **)&pSinkUnk);
    DWORD dwAdvise;
    hr = pCP->Advise(pSinkUnk,&dwAdvise);//接收器與連接點建立關聯

    LONG c = 0;
    pFun->Add(1,5,&c);
    //pCP->Unadvise(dwAdvise) //斷開連接點事件
    pCP->Release();
    pFun.Release();

    CoUninitialize();

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved