和之前讲述的IDispatch的实现类似,相比MFC,ATL实现可连接对象更加简单,编写完IDL,使用MIDL编译即可完成大部分工作。
这里我们使用ATL实现一个Speek组件对象,可通过ISpeek接口说话,根据通过可连接点通知客户说话分贝的大小。
建立一个ATL工程,右键添加类->选择ATL简单对象,
填写组件命名后,如下:
然后选择为当前接口实现连接点,如下:
勾选连接点,然后我们编写IDL如下,具体可参考ATL实现COM一文。
import "oaidl.idl";
import "ocidl.idl";
[
object,
uuid(554A2800-7862-43EC-9DE6-799CB11AF71B),
dual,
nonextensible,
helpstring("ISpeek 接口"),
pointer_default(unique)
]
interface ISpeek : IDispatch{
[id(1), helpstring("方法Speek")] HRESULT Speek([in] LONG nVolume);
};
[
uuid(163A45FC-1B75-4328-85CA-CFFDBD7A3F5C),
version(1.0),
helpstring("AtlConnectPoint 1.0 类型库")
]
library AtlConnectPointLib
{
importlib("stdole2.tlb");
[
uuid(E44310A3-B5FB-4F97-AFB2-A69287993C23),
helpstring("_ISpeekEvents 接口")
]
dispinterface _ISpeekEvents
{
properties:
methods:
[id(1), helpstring("方法OnWhispter")] void OnWhistper([in] LONG nVolume);
[id(2), helpstring("方法OnTalk")] void OnTalk([in] LONG nVolume);
};
[
uuid(A7588351-C619-41FA-AEB8-8C92A814B9EA),
helpstring("Speek Class")
]
coclass Speek
{
[default] interface ISpeek;
[default, source] dispinterface _ISpeekEvents;
};
};
可以看到,
这里生成的组件对象是Speek,注意此时前面带有标识[default,source],标识默认的源接口
ISpeek接口由于我们勾选的是双重接口,所以从IDisptach派生,
生成的可连接点是_ISpeekEvents,这里我们指定对应的方法OnWhisper和OnTalk的id分别为1和2,注意类型是dispinterface
点击编译,
此时生成的组件对象定义如下:
class ATL_NO_VTABLE CSpeek :
public CComObjectRootEx,
public CComCoClass,
public IConnectionPointContainerImpl,
public CProxy_ISpeekEvents,
public IDispatchImpl
其中
IConnectionPointContainerImpl实现了连接点容器
IDispatchImpl实现了接口ISpeek
CProxy_ISpeekEvents实现了对应的连接点
查看接口映射表和连接点映射表如下:
BEGIN_COM_MAP(CSpeek)
COM_INTERFACE_ENTRY(ISpeek)
COM_INTERFACE_ENTRY(IDispatch)
COM_INTERFACE_ENTRY(IConnectionPointContainer)
END_COM_MAP()
BEGIN_CONNECTION_POINT_MAP(CSpeek)
CONNECTION_POINT_ENTRY(__uuidof(_ISpeekEvents))
END_CONNECTION_POINT_MAP()
分别将接口和连接点加入对应的容器中管理。
我们在自动生成的_ISpeekEvents_CP.h文件中查看CProxy_ISpeekEvents实现如下:
#pragma once
template
class CProxy_ISpeekEvents :
public IConnectionPointImpl
{
public:
HRESULT Fire_OnTalk( LONG nVolume)
{
HRESULT hr = S_OK;
T * pThis = static_cast(this);
int cConnections = m_vec.GetSize();
for (int iConnection = 0; iConnection < cConnections; iConnection++)
{
pThis->Lock();
CComPtr punkConnection = m_vec.GetAt(iConnection);
pThis->Unlock();
IDispatch * pConnection = static_cast(punkConnection.p);
if (pConnection)
{
CComVariant avarParams[1];
avarParams[0] = nVolume;
avarParams[0].vt = VT_I4;
DISPPARAMS params = { avarParams, NULL, 1, 0 };
hr = pConnection->Invoke(2, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, ¶ms, NULL, NULL, NULL);
}
}
return hr;
}
HRESULT Fire_OnWhistper( LONG nVolume)
{
HRESULT hr = S_OK;
T * pThis = static_cast(this);
int cConnections = m_vec.GetSize();
for (int iConnection = 0; iConnection < cConnections; iConnection++)
{
pThis->Lock();
CComPtr punkConnection = m_vec.GetAt(iConnection);
pThis->Unlock();
IDispatch * pConnection = static_cast(punkConnection.p);
if (pConnection)
{
CComVariant avarParams[1];
avarParams[0] = nVolume;
avarParams[0].vt = VT_I4;
DISPPARAMS params = { avarParams, NULL, 1, 0 };
hr = pConnection->Invoke(1, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, ¶ms, NULL, NULL, NULL);
}
}
return hr;
}
};
可以看到,实际具体连接点实现是IConnectionPointImpl,
这里自动生成Fire_OnTalk和Fire_OnWhistper分别通知客户执行对应操作。
我们需要做的只有实现ISpeek接口,根据传入的说话声大小回调通知客户操作,如下:
STDMETHODIMP CSpeek::Speek( LONG nVolume )
{
if (nVolume <= 200) //小声说话
{
Fire_OnWhistper(nVolume);
}
else //大声说话
{
Fire_OnTalk(nVolume);
}
return S_OK;
}
ATL中接收器的实现一般采用IDispEventSimpleImpl来实现,这个其实就是IDisptach的另一种查表实现,其原理和MFC中的DISPATCH_MAP非常类似,只不过这里叫做SINK_MAP。
这里我将其做成一个通用的模板类,可以直接套用,如下:
//定义事件源,可以自己指定,不同的事件源保持不同即可。多个事件源时,以此区分
#define SOURCE_ID_SPEEK 1
class CDispEventHandlerBase
{
public:
//定义不同事件源的不同DISP ID的TypeInfo
static _ATL_FUNC_INFO OnWhisperInfo;
static _ATL_FUNC_INFO OnTalkInfo;
};
template
class CDispEventHandler:public CDispEventHandlerBase,
public IDispEventSimpleImpl
{
public:
//定义不同事件源的不同DISP ID
static const int SPEEK_DISP_ID_WHISPER = 1;
static const int SPEEK_DISP_ID_TALK = 2;
//定义不同事件源的不同DISP ID的处理函数,子类中必须实现对应的函数
void STDMETHODCALLTYPE OnWhisper(LONG nVolume)
{
T* pT = static_cast(this);
return pT->OnWhisper(nVolume);
}
void STDMETHODCALLTYPE OnTalk(LONG nVolume)
{
T* pT = static_cast(this);
return pT->OnTalk(nVolume);
}
//定义事件处理函数映射表
BEGIN_SINK_MAP(T)
SINK_ENTRY_INFO(SOURCE_ID_SPEEK, __uuidof(_ISpeekEvents), SPEEK_DISP_ID_WHISPER, OnWhisper, &OnWhisperInfo)
SINK_ENTRY_INFO(SOURCE_ID_SPEEK, __uuidof(_ISpeekEvents), SPEEK_DISP_ID_TALK, OnTalk, &OnTalkInfo)
END_SINK_MAP()
};
__declspec(selectany) _ATL_FUNC_INFO CDispEventHandlerBase::OnWhisperInfo = {CC_STDCALL, VT_EMPTY, 1, {VT_I4}};
__declspec(selectany) _ATL_FUNC_INFO CDispEventHandlerBase::OnTalkInfo = {CC_STDCALL, VT_EMPTY, 1, {VT_I4}};
这里我们在主对话框中继承CDispEventHandler类,如下:
class CMainDlg : public CDialogImpl,
public CDispEventHandler
BOOL ConnectSource()
{
HRESULT hr;
hr = CoCreateInstance(__uuidof(Speek), NULL, CLSCTX_INPROC_SERVER, __uuidof(ISpeek), (LPVOID*)&m_pSpeek);
if(FAILED(hr) && !m_pSpeek)
{
return FALSE;
}
hr = CDispEventHandler::DispEventAdvise(m_pSpeek, &__uuidof(_ISpeekEvents));
if (FAILED(hr))
{
return FALSE;
}
return TRUE;
}
BOOL DisConnectSource()
{
HRESULT hr = E_FAIL;
hr = CDispEventHandler::DispEventUnadvise(m_pSpeek, &__uuidof(_ISpeekEvents));
if (FAILED(hr))
{
return FALSE;
}
if (m_pSpeek && SUCCEEDED(m_pSpeek->Release()))
{
m_pSpeek = NULL;
return TRUE;
}
return FALSE;
}
连接和断开连接的时候,只需要借助一个源对象的接口和待连接的接口IID即可。
那么,我么剩下我们需要做的就很简单了,实现源对象通知时,客户需要做的事情,这里弹框提示,如下:
/****************************接收器实现**********************************/
void STDMETHODCALLTYPE OnWhisper(LONG nVolume)
{
CString csStrInfo;
csStrInfo.Format(L"小声说话,分贝:%d", nVolume);
::MessageBox(NULL, csStrInfo, L"通知", MB_OK);
}
void STDMETHODCALLTYPE OnTalk(LONG nVolume)
{
CString csStrInfo;
csStrInfo.Format(L"大声吼叫,分贝:%d", nVolume);
::MessageBox(NULL, csStrInfo, L"通知", MB_OK);
}
ATL实现可连接对象和连接点方法下载链接
原创,转载请注明来自http://blog.csdn.net/wenzhou1219