Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Practical ATL: Connection Point and Two Approaches to Receive Events

0.00/5 (No votes)
29 Mar 2011 1  
In this article, I show how to implement connection points and introduce two approaches to receive events for the connectable ATL object.

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 every object that connects to the connection points object,
// call its event receiving function
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)
{
 // Parameters of event receiving function, same as the definition of event function
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 };
// Dispid is the id of event function in idl file
hr = pConnection->Invoke(/*dispid*/1, IID_NULL, LOCALE_USER_DEFAULT, 
		DISPATCH_METHOD, &params, 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, /* wMajor = */ 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)
{
// event process code
}

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;
// Create an instance of connection points ATL object
hr = CoCreateInstance(CLSID_CalcPi, NULL, CLSCTX_ALL, IID_IUnknown, (void**)&pUnk);
//Get IconnectionPointContainer interface
hr = pUnk->QueryInterface(IID_IConnectionPointContainer, (void**)&pConnPtContainer);
	ATLENSURE(SUCCEEDED(hr) && pConnPtContainer != NULL);
// Find ICalcPiEvents connection point
hr = pConnPtContainer->FindConnectionPoint
	(DIID__ICalcPiEvents, &m_pICalcPiConnectionPoint);
// Connect to event source
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)
{
	// event process code
}

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

  1. ATL Internals: Working with ATL 8, Second Edition http://book.douban.com/subject/2287029/
  2. ATLDuck Sample: Uses Connection Points with ATL http://msdn.microsoft.com/en-us/library/10tkk23z(v=VS.80).aspx

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here