Introduction
In this article, I show how to implement connection points and introduce two approaches to receive events for the connectable ATL object.
Implements Connection Points
1. Create a New ATL Project
In this project, we retain the default options of ALT project wizard, that is only selecting the "Dynamic-link library" option and remaining other options unchecked.
2. Create a New ATL Simple Object
Right click on the project that we just created, select "adding class". In the "Adding class" dialog, select "ATL simple object", and then click "Add" button. In the wizard, we input CalcPi
as the short name of the new ATL simple object. In the "Options" panel, we check "Connection Points" options and others remain default. Click "finish" to finish the wizard.
After you specify the options, the Simple Object Wizard generates the skeleton files for you to start adding your implementation. For the class, there is a newly generated header file containing the class definition, a .cpp file for the implementation, and an .RGS file containing registration information. In addition, the IDL file is updated to contain the new interface definition. In this project, it will generate the event interface _ICalcEvents
and Event Proxy class CProxy_ICalcPiEvents
.
3. Add Event Function Definition
In the auto generated idl file, add event function definition for _ICalcEvents
interface:
[
uuid(C341057F-62AA-44C7-B865-26EC97515D29),
helpstring("_ICalcPiCtrlEvents interface")
]
dispinterface _ICalcPiCtrlEvents
{
properties:
methods:
[id(1), helpstring("method OnDigit")] void OnDigit([in] SHORT nIndex, [in] SHORT nDigit);
};
The code adds the definition of event function "OnDigit
", you can also add function definition by the wizard.
4. Implement Event Firing Function
In event proxy class CProxy_ICalcPiEvents
, we add event fire function. The function name is always prefixed with Fire_
, e.g. Fire_OnDigit
, and you can change it to anything else, but for the readability of your code, you’d better use this form. Below is the event firing function:
HRESULT Fire_OnDigit( SHORT nIndex, SHORT nDigit)
{
HRESULT hr = S_OK;
T * pThis = static_cast<t>(this);
int cConnections = m_vec.GetSize();
for (int iConnection = 0; iConnection < cConnections; iConnection++)
{
pThis->Lock();
CComPtr<iunknown> punkConnection = m_vec.GetAt(iConnection);
pThis->Unlock();
if(punkConnection==NULL) continue;
CComQIPtr<idispatch> pConnection=punkConnection.p;
if (pConnection)
{
CComVariant avarParams[2];
avarParams[1] = nIndex;
avarParams[1].vt = VT_I2;
avarParams[0] = nDigit;
avarParams[0].vt = VT_I2;
DISPPARAMS params = { avarParams, NULL, 2, 0 };
hr = pConnection->Invoke(1, IID_NULL, LOCALE_USER_DEFAULT,
DISPATCH_METHOD, ¶ms, NULL, NULL, NULL);
}
}
return hr;
}
So we finish the implementation of the connection points object. You can fire the event through the event firing function (Fire_OnDigit
) and all the objects that connect to the connection points object will receive the event.
From the code of Fire_OnDigit
, we know that the function only find all the event recipient objects, and then call their event receiving function by IDispatch
interface. So the event recipient object must implement IDispatch
interface and can call their event recipient function by invoke method.
Create Event Recipient Objects
Then, we create the event recipient object CalcDigit
, we create an ATL simple object as above, we uncheck the "Connection points" option since we want an event recipient object and not an event source object.
In theory, it's a very easy thing that creates an event recipient object. It is quite easy, in theory, to implement an object that receives events on a single interface. You define a class that implements the interface and connect the object to the event source. We have a CalcPi
class that generates events on the _ICalcPiEvents
dispatch interface. Let's define a CCalcDigit
class that implements _ICalcPiEvents
.
coclass CalcDigit {
[default] dispinterface _ICalcPiEvents;
};
Now, implement the class using ATL as the CCalcDigit
class.
class ATL_NO_VTABLE CCalcDigit :
...
public _ICalcPiEvents,
... { }
Unfortunately, an event interface is typically a dispinterface, so the normal interface-implementation techniques don't work. When you run the MIDL compiler, all you get for the generated _ICalcPiEvents
interface is this:
MIDL_INTERFACE("A924C9DE-797F-430d-913D-93158AD2D801")
_ICalcEvents : public IDispatch
{
};
From the code above, we can get that _ICalcEvents
doesn't contain the event function OnDigit
we just defined in the idl file. Although we implement this interface, we are not implementing the event receiving function really.
Receive Events using Dummy Interface
There're two ways to solve this problem. On approach is that you need to define a dummy dual interface that has the same dispatch methods, dispatch identifiers, and function signatures as the event dispinterface. For example, we can define a dummy interface named ICalcDigit
:
interface ICalcDigit : IDispatch{
[id(1), helpstring("method OnDigit")]
void OnDigit([in] SHORT nIndex, [in] SHORT nDigit);
};
The event recipient class will be:
class ATL_NO_VTABLE CCalcDigit :
public CComObjectRootEx<ccomsinglethreadmodel>,
public CComCoClass<ccalcdigit,>,
public IDispatchImpl<icalcdigit, *wmajor="*/" *wminor="*/">,
public IDispatchImpl<_ICalcPiEvents, &__uuidof(_ICalcPiEvents),
&LIBID_atl_callLib, 1>
We implement IDispatch
interface by deriving our class from IDispatchImpl
class. Then we implement the event receiving function:
STDMETHODIMP_(void,OnDigit) (short nIndex, short nDigit)
{
}
The CCalcDigit
must inherit from _ICalcPiEvents
interface because we need to expose the _ICalcPiEvents
interface to tell the connection points object (event source) that we implement the event interface.
Since we implement ICalcDigit
and _ICalcPiEvents
, and both of them inherit from IDispatch
interface, we must designate which one of IDispatch
interfaces we'd like to expose. We can use the following code to do this:
BEGIN_COM_MAP(CCalcDigit)
COM_INTERFACE_ENTRY(ICalcDigit)
COM_INTERFACE_ENTRY2(IDispatch, ICalcDigit)
COM_INTERFACE_ENTRY(_ICalcPiEvents)
END_COM_MAP()
The code assigns the exposed IDispatch
interface from ICalcDigit
, since ICalcDigit
contains the definition of our event receiving function.
Connect to Event Source
We can connect our event receiving object to event source through the following code:
IUnknown* pUnk = NULL;
IConnectionPointContainer* pConnPtContainer = NULL;
IConnectionPoint* m_pICalcPiConnectionPoint;
hr = CoCreateInstance(CLSID_CalcPi, NULL, CLSCTX_ALL, IID_IUnknown, (void**)&pUnk);
hr = pUnk->QueryInterface(IID_IConnectionPointContainer, (void**)&pConnPtContainer);
ATLENSURE(SUCCEEDED(hr) && pConnPtContainer != NULL);
hr = pConnPtContainer->FindConnectionPoint
(DIID__ICalcPiEvents, &m_pICalcPiConnectionPoint);
CCalcDigit* m_pCalcDigit = new CComObject<ccalcdigit>;
IUnknown* pUnk;
hr=m_pCalcDigit->QueryInterface(IID_IUnknown,(void**)&pUnk);
ATLENSURE(SUCCEEDED(hr));
hr = m_pICalcPiConnectionPoint->Advise(pUnk, &m_dwCookie);
Receive Events by IDispEventImpl and IDispEventSimpleImpl Classes
Derived from IDispEventImpl and IDispEventSimpleImpl classes
We define the following macro:
#define DEFENDANT_SOURCE_ID 0
#define PLAINTIFF_SOURCE_ID 1
#define LIBRARY_MAJOR 1
#define LIBRARY_MINOR 0
typedef IDispEventImpl<defendant_source_id,> DefendantEventImpl;
typedef IDispEventSimpleImpl<plaintiff_source_id,>
PlaintiffEventImpl;</plaintiff_source_id,></defendant_source_id,>
The IDispEventImpl
class requires a type library that describes the dispatch interface. Then we inherit from either or both of these two classes as below:
class ATL_NO_VTABLE CCalcDigit :
public CComObjectRootEx<ccomsinglethreadmodel>,
public CComCoClass<ccalcdigit,>,
public DefendantEventImpl,
public PlaintiffEventImpl</ccalcdigit,></ccomsinglethreadmodel>
Event Sink Map
Then, we need to define event sink map:
BEGIN_SINK_MAP(CCalcDigit)
SINK_ENTRY_EX(DEFENDANT_SOURCE_ID,__uuidof(_ICalcPiEvents),1,DefendantOnDigit)
SINK_ENTRY_INFO(PLAINTIFF_SOURCE_ID,__uuidof(_ICalcPiEvents),
1,PlaintiffOnDigit,&PlaintiffOnDigitInfo)
END_SINK_MAP()
If the class inherits from IDispEventImpl
, we can map event sink function with SINK_ENTRY_EX
macro. This macro doesn't need the event function information, because IDispEventImpl
class will automatically determine the event function definition through the type library that we provide. If the class inherits from IDispEventSimpleImpl
, then we need to use _ATL_FUNC_INFO
structure to provide the function information, and meanwhile map the event sink function with SINK_ENTRY_INFO
macro as below:
static _ATL_FUNC_INFO PlaintiffOnDigitInfo = {
CC_STDCALL, VT_EMPTY, 2, { VT_I2,VT_I2 }};
Implement Event Receiving Function
Then we need to implement event recipient function:
void __stdcall DefendantOnDigit(SHORT nIndex,SHORT nDigit)
{
}
Connect to Event Source
We need to use DispEventAdvise
and DispEventUnadvise
methods to advise and unadvise the connection to event source. These two methods are members of IDispEventImpl
and IDispEventSimpleImpl
classes.
DefendantEventImpl* defendant_imp=(DefendantEventImpl*)m_pCalcDigit;
hr=defendant_imp->DispEventAdvise(m_pCalcPi,&DIID__ICalcPiEvents);
By now, we connect event recipient object to connection points object successfully. If we fire events by Fire_OnDigit
method, then event recipient objects will receive the event.
Receive Events in Script
We can receive events of connection points based ATL object in HTML script, such as JavaScript or VbScript. If you expect the script to receive events successfully from the event source, you'd better create an ATL control object instead of ALT simple object.The VbScript code is as below:
<object id="CalcPiCtrl"
classid="CLSID:268E67E1-56FA-409C-8404-4D617FAA87ED" height="50%">
<script language="vbscript">
sub cmdCalcPi_onClick
CalcPiCtrl.Fire__Event
end Sub
sub CalcPiCtrl_onDigit(index,digit)
alert(index)
end sub
</script>
<input type="button" name="cmdCalcPi" value="Fire Event" /></object>
References
- ATL Internals: Working with ATL 8, Second Edition http://book.douban.com/subject/2287029/
- ATLDuck Sample: Uses Connection Points with ATL http://msdn.microsoft.com/en-us/library/10tkk23z(v=VS.80).aspx