previous page
next page

Creating an Object That Is an Event Recipient

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 Demagogue class that generates events on the _ISpeakerEvents dispatch interface. Let's define a CEarPolitic class (clearly one ear of the body politic) that implements _ISpeakerEvents.

coclass EarPolitic {
    [default] dispinterface _ISpeakerEvents;
};

Now, implement the class using ATL as the CEarPolitic class.

class ATL_NO_VTABLE CEarPolitic :
  ...
  public _ISpeakerEvents,
  ... {
public:
  ...
BEGIN_COM_MAP(CEarPolitic)
  COM_INTERFACE_ENTRY(IDispatch)
  COM_INTERFACE_ENTRY(_ISpeakerEvents)
...
END_COM_MAP()

// _ISpeakerEvents
  STDMETHOD(GetTypeInfoCount)(UINT* pctinfo);
  STDMETHOD(GetTypeInfo)(UINT itinfo, LCID lcid,
    ITypeInfo** pptinfo);
  STDMETHOD(GetIDsOfNames)(REFIID riid, LPOLESTR* rgszNames,
    UINT cNames, LCID lcid, DISPID* rgdispid);
  STDMETHOD(Invoke)(DISPID dispidMember, REFIID riid, LCID lcid,
    WORD wFlags, DISPPARAMS* pdispparams, VARIANT* pvarResult,
    EXCEPINFO* pexcepinfo, UINT* puArgErr);
};

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 _ISpeakerEvents interface is this:

MIDL_INTERFACE("A924C9DE-797F-430d-913D-93158AD2D801")
_ISpeakerEvents : public IDispatch
{
};

Instead of being able to simply implement the methods of the interface as regular C++ member functions, we must implement the IDispatch interface and support, at a minimum, the Invoke method. Invoke is a tedious method to write for any nontrivial event interface.

Another alternative is to use the IDispatch interface implementation that the IDispatchImpl template class provides. Unfortunately, the template class requires parameters describing a dual interface, not a dispinterface. To use IDispatchImpl, you need to define a dummy dual interface that has the same dispatch methods, dispatch identifiers, and function signatures as the event dispinterface.

This has more implications than are usually apparent. A dispinterface is not immutable, unlike a regular COM interface. If you don't control the definition of the dispinterface, it might change from release to release. (It's not that likely to change, but it is possible.) This means your dual interface needs to change as well. This implies that you cannot document the dual interface because, after it is published, it is immutable because some client may implement it. Because you should not describe the dual interface in a type library (because that documents it), you cannot use the universal type library-driven-marshaler and need a remoting proxy/stub for the dual interface. These are all theoretical issues because the dual interface, in this case, is an implementation detail that is specific to the implementation class, but the issues give us motivation enough to look for another solution.

On a slightly different note, what if you want to receive the same events from more than one event source and you want to know which source fired the event? For example, let's say you want to implement an EarPolitic class that acts as a judge listening to _ISpeakerEvents from both a Defendant object and a Plaintiff object. Each object is a source of OnWhisper, OnTalk, and OnYell events, but the judge needs to keep track of who is saying what.

This requires you to implement the _ISpeakerEvents interface multiple times: once for each event source. Providing separate implementations of any interface multiple times in a class requires each implementation to be in a separate COM identity. Two typical solutions to this problem are member-wise composition (in which each implementation is in a nested class) and something similar to tear-off interfaces (in which each implementation is in a separate class).

The IDispEventImpl and IDispEventSimpleImpl Classes

To avoid these issues, ATL provides two template classes, IDispEventImpl and IDispEventSimpleImpl, that provide an implementation of the IDispatch interface for an ATL COM object. Typically, you use one of these classes in an object that wants to receive event callbacks. Both classes implement the dispatch interface on a nested class object that maintains a separate COM identity from the deriving class. This means that you can derive from these classes multiple times when you need to implement multiple source-dispatch interfaces.

The IDispEventImpl class requires a type library that describes the dispatch interface. The class uses the typeinfo at runtime to map the VARIANT parameters received in the Invoke method call to the appropriate types and stack frame layout necessary for calling the event-handler member function.

template <UINT nID, class T, const IID* pdiid = &IID_NULL,
          const GUID* plibid = &GUID_NULL,                
          WORD wMajor = 0, WORD wMinor = 0,               
          class tihclass = CComTypeInfoHolder>            
class ATL_NO_VTABLE IDispEventImpl :                      
    public IDispEventSimpleImpl<nID, T, pdiid>            
{ ... }                                                   

The IDispEventSimpleImpl class doesn't use a type library, so it's a more lightweight class. You use it when you don't have a type library or when you want to be more efficient at runtime.

template <UINT nID, class T, const IID* pdiid>
class ATL_NO_VTABLE IDispEventSimpleImpl :    
    public _IDispEventLocator<nID, pdiid>     
{ ... }                                       

When using the IDispEventSimpleImpl class, you must provide an _ATL_FUNC_INFO structure containing information that describes the expected parameters for the event handler.

struct _ATL_FUNC_INFO {                                        
    CALLCONV cc;            // Calling convention              
    VARTYPE vtReturn;       // VARIANT type for return value   
    SHORT nParams;          // Number of parameters            
    VARTYPE pVarTypes[_ATL_MAX_VARTYPES]; // Array of parameter
                                          // VARIANT type      
};                                                             

Notice that IDispEventImpl derives from the IDispEventSimpleImpl class. The IDispEventSimpleImpl class calls the event handler based on the information in an _ATL_FUNC_INFO structure. You can provide the structure statically (at compile time) by referencing the structure in the sink map (described later in this chapter).

When you provide no structure reference, the IDispEventSimpleImpl class calls the virtual method GetFuncInfoFromId to get an _ATL_FUNC_INFO structure for the event handler associated with the specified DISPID. You provide the structure dynamically by overriding GetFuncInfoFromId and returning the appropriate structure when called. You must use GetFuncInfoFromId when you want to call different event methods based on the locale provided by the event source.

Here's the default implementation provided by the IDispEventSimpleImpl class:

//Helper for finding the function index for a DISPID             
virtual HRESULT GetFuncInfoFromId(const IID& iid,                
  DISPID dispidMember,                                           
  LCID lcid,                                                     
  _ATL_FUNC_INFO& info) {                                        
  ATLTRACE(_T("TODO: Classes using IDispEventSimpleImpl "        
    "should override this method\n"));                           
  ATLASSERT(0);                                                  
  ATLTRACENOTIMPL(_T("IDispEventSimpleImpl::GetFuncInfoFromId"));
}                                                                

The IDispEventImpl class overrides this virtual method to create the structure from the typeinfo in the specified type library.

Implementing an Event Sink

The easiest way to implement one or more event sinks in a nonattributed ATL object is to derive the object one or more times from IDispEventImplonce for each unique event interface coming from each unique event source. Here's the template class specification once more:

template <UINT nID, class T, const IID* pdiid = &IID_NULL,
          const GUID* plibid = &GUID_NULL,                
          WORD wMajor = 0, WORD wMinor = 0,               
          class tihclass = CComTypeInfoHolder>            
class ATL_NO_VTABLE IDispEventImpl :                      
    public IDispEventSimpleImpl<nID, T, pdiid>            
{ ... }                                                   

The nID parameter specifies an identifier for the event source that is unique to the deriving class T. You'll see in Chapter 11, "ActiveX Controls," that when the event source is a contained control and the event recipient is a composite control, the identifier is the contained control's child window identifier.

A composite control can default all other IDispEventImpl template parameters, but an arbitrary COM object must specify all parameters except the last. The pdiid parameter specifies the GUID for the event dispinterface that this class implements. The dispinterface must be described in the type library specified by the plibid parameter and the type library major and minor version numbers, wMajor and wMinor. The tihclass parameter specifies the class to manage the type information for the deriving class T. The default CComTypeInfoHolder class is generally acceptable.

The more efficient way to implement one or more event sinks in an ATL object uses the IDispEventSimpleImpl class and needs no type library at runtime. However, you must provide the necessary _ATL_FUNC_INFO structure, as described previously. When using the IDispEventSimpleImpl class, you need to specify only the nID event source identifier, the deriving class T, and the pdiid GUID for the event dispinterface:

template <UINT nID, class T, const IID* pdiid>
class ATL_NO_VTABLE IDispEventSimpleImpl :    
    public _IDispEventLocator<nID, pdiid>     
{ ... }                                       

Using the easier technique, let's redefine the CEarPolitic class to implement the _ISpeakerEvents dispatch interface twice: once for a Demagogue acting as a Defendant and once for a different Demagogue acting as a Plaintiff. We have a type library, so we use the IDispEventImpl class to implement the sink for the Defendant object's _ISpeakerEvents callbacks. We use the IDispEventSimpleImpl class for the Plaintiff object's _ISpeakerEvents callbacks to demonstrate the alternative implementation. We typically introduce a typedef for each event interface implementation, to minimize typing and mistakes.

static const int DEFENDANT_SOURCE_ID = 0 ;
static const int PLAINTIFF_SOURCE_ID = 1 ;

class CEarPolitic;

typedef IDispEventImpl<DEFENDANT_SOURCE_ID,
  CEarPolitic,
  &__uuidof(_ISpeakerEvents),
  &LIBID_ATLINTERNALSLib,
  LIBRARY_MAJOR, LIBRARY_MINOR> DefendantEventImpl;

typedef IDispEventSimpleImpl<PLAINTIFF_SOURCE_ID,
 CEarPolitic, &__uuidof(_ISpeakerEvents)> PlaintiffEventImpl;

In this example, we arbitrarily chose 0 and 1 for the event source identifiers. The identifiers could have been any numbers. Now, we need to derive the CEarPolitic class from the two event-implementation classes:

class ATL_NO_VTABLE CEarPolitic :
    ...
    public DefendantEventImpl,
    public PlaintiffEventImpl {

// Event sink map required in here

};

The Event Sink Map

The IDispEventSimpleImpl class's implementation of the Invoke method receives the event callbacks. When the event source calls the Invoke method, it specifies the event that has occurred using the DISPID parameter. The IDispEventSimpleImpl implementation searches an event sink table for the function to call when event DISPID occurs on dispatch interface DIID from event source identifier SOURCE.

You specify the beginning of the event sink map using the BEGIN_SINK_MAP macro within the declaration of the class that derives from IDispEventImpl or IDispEventSimpleImpl. You map each unique SOURCE/DIID/DISPID TRiple to the proper event-handling method using the SINK_ENTRY, SINK_ENTRY_EX, and SINK_ENTRY_INFO macros.

  • SINK_ENTRY(SOURCE, DISPID, func). Use this macro in a composite control to name the handler for the specified event in the specified contained control's default source interface. This macro assumes the use of IDispEventImpl and assumes that you call the AtlAdviseSinkMap function to establish the connection. The AtlAdviseSinkMap function assumes that your class is derived from CWindow. All in all, the SINK_ENTRY macro isn't very useful for nonuser-interface (UI) objects that want to receive events.

  • SINK_ENTRY_EX(SOURCE, DIID, DISPID, func). Use this macro to indicate the handler for the specified event in the specified object's specified source interface. This macro is most useful for non-UI objects that want to receive events and for composite controls that want to receive events from a contained control's nondefault source interface.

  • SINK_ENTRY_INFO(SOURCE, DIID, DISPID, func, info). This macro is similar to the SINK_ENTRY_EX macro, with the addition that you can specify the _ATL_FUNC_INFO structure to be used when calling the event handler. You typically use this macro when using the IDispEventSimpleImpl class.

You end the event sink map using the END_SINK_MAP macro.

The general form of the table is as follows:

BEGIN_SINK_MAP(CEarPolitic)
   SINK_ENTRY_EX(SOURCE, DIID, DISPID, EventHandlerFunc)
   SINK_ENTRY_INFO(SOURCE, DIID, DISPID, EventHandlerFunc, &info)
...
END_SINK_MAP()

In the CEarPolitic example, there are three events, all from the same dispatch interface but coming from two different event sources. Therefore, we need six event sink map entries. We can use the SINK_ENTRY_EX macro to identify the event handlers for the Defendant event source. We don't need to specify the _ATL_FUNC_INFO structure because the IDispEventImpl base class uses the type library to provide the appropriate structure at runtime. We need to use the SINK_ENTRY_INFO macro for the Plaintiff object. Because we used the IDisp-EventSimpleImpl base class, we need to provide the function information structure describing each event method.

Each function information structure describes one event method. The structure contains the calling convention, the VARIANT type of the return value of the method, the number of parameters, and the VARIANT types of each of the parameters. The calling convention should be set to CC_STDCALL because that's what the IDispEventSimpleImpl class expects the event handlers to use.

Here are the function prototypes for the Plaintiff's three event methods and their information structures (all identical in this example). Event handlers typically do not return a valuethat is, they are void functions. Note: The proper vtreturn value in the _ATL_FUNC_INFO structure to represent a void function return is VT_EMPTY, not VT_VOID.

void __stdcall OnHearPlaintiffWhisper(BSTR bstrText);
void __stdcall OnHearPlaintiffTalk(BSTR bstrText);
void __stdcall OnHearPlaintiffYell(BSTR bstrText);

static const int DISPID_WHISPER = 1 ;
static const int DISPID_TALK    = 2 ;
static const int DISPID_YELL    = 3 ;

_ATL_FUNC_INFO OnHearWhisperInfo = {
  CC_STDCALL, VT_EMPTY, 1, { VT_BSTR }};
_ATL_FUNC_INFO OnHearTalkInfo = {
  CC_STDCALL, VT_EMPTY, 1, { VT_BSTR }};
_ATL_FUNC_INFO OnHearYellInfo = {
  CC_STDCALL, VT_EMPTY, 1, { VT_BSTR }};

Here's the event sink map for the CEarPolitic object:

BEGIN_SINK_MAP(CEarPolitic)
  SINK_ENTRY_EX(DEFENDANT_SOURCE_ID, __uuidof(_ISpeakerEvents),
    DISPID_WHISPER, OnHearDefendantWhisper)
  SINK_ENTRY_EX(DEFENDANT_SOURCE_ID, __uuidof(_ISpeakerEvents),
    DISPID_TALK, OnHearDefendantTalk)
  SINK_ENTRY_EX(DEFENDANT_SOURCE_ID, __uuidof(_ISpeakerEvents),
    DISPID_YELL, OnHearDefendantYell)
  SINK_ENTRY_INFO(PLAINTIFF_SOURCE_ID, __uuidof(_ISpeakerEvents),
    DISPID_WHISPER, OnHearPlaintiffWhisper, &OnHearWhisperInfo)
  SINK_ENTRY_INFO(PLAINTIFF_SOURCE_ID, __uuidof(_ISpeakerEvents),
    DISPID_TALK, OnHearPlaintiffTalk, &OnHearTalkInfo)
  SINK_ENTRY_INFO(PLAINTIFF_SOURCE_ID, __uuidof(_ISpeakerEvents),
    DISPID_YELL, OnHearPlaintiffYell, &OnHearYellInfo)
END_SINK_MAP()

One caution: The sink map contains a hard-coded DISPID for each event. This means that the sink map technically is specific to a particular dispinterface version. COM allows the DISPIDs in a dispinterface to change from version to version. Now, this isn't something that often happensand a control vendor that does such a thing is just asking for tech support calls and angry customers. But it is allowed.

The only absolutely correct way to obtain a DISPID is to ask the object implementing the dispinterface at startup for the DISPID corresponding to an event. For a source interface, this is impossible because when you build the sink, you're implementing the source interface, so there's no object to query. The only realistic option is to read the object's type library at runtime. ATL, Visual Basic, and MFC don't support this. They all assume that the DISPIDs in a dispinterface will never change, as a questionable performance optimization.

The Callback Methods

The callback method specified by the sink entry macros must use the __stdcall calling convention. The parameters for each callback method specified in the sink map must agree in type and number with the corresponding event method as described in the type library. Here are the Defendant's event methods; they are identical to the Plaintiff's, as expected:

void __stdcall OnHearDefendantWhisper(BSTR bstrText);
void __stdcall OnHearDefendantTalk(BSTR bstrText);
void __stdcall OnHearDefendantYell(BSTR bstrText);

After two remaining steps, the CEarPolitic class will be complete: Implement the callback methods and establish the connection between a CEarPolitic instance and a Demagogue (which invokes the _ISpeakerEvents methods).

We use the following simple implementation for each of the event handlers:

void __stdcall CEarPolitic::OnHearDefendantTalk(BSTR bstrText) {
  CComBSTR title ;
  CreateText(title, OLESTR("defendant"),
    OLESTR("talking"), m_defendant);

  MessageBox(NULL, COLE2CT(bstrText), COLE2CT(title), MB_OK);
}

void CEarPolitic::CreateText(CComBSTR& bstrText,
  LPCOLESTR strRole,
  LPCOLESTR strAction,
  LPUNKNOWN punk) {
    bstrText = m_bstrName;
    bstrText += OLESTR(" hears the ");
    bstrText += strRole;
    bstrText += OLESTR(" (");

    CComQIPtr<INamedObject> spno = punk;
    CComBSTR bstrName;
    HRESULT hr = spno->get_Name (&bstrName) ;

    bstrText.AppendBSTR(bstrName);
    bstrText += OLESTR(") ");

    bstrText += strAction;
}

Connecting the Event Sink to an Event Source

When your class is a composite control, you should use the AtlAdviseSinkMap function to establish and remove the connections between all the source interfaces of the contained controls listed in the sink map and your IDispEventImpl implementation(s). This method uses the event source identifier as a child window identifier. Using the CWindow::GetDlgItem method, AtlAdviseSinkMap navigates to a child window handle and, from there, to the contained control's IUnknown interface. From the IUnknown interface, it gets the IConnectionPointContainer interface and the appropriate connection point, and calls its Advise method.

template <class T>                                   
inline HRESULT AtlAdviseSinkMap(T* pT, bool bAdvise);

You must use the AtlAdviseSinkMap function to establish the connections any time you use the IDispEventImpl class and you specify only the first two template parameters, using default values for the rest. Not specifying the source interface implies using the default source interface for the event source. The AtlAdvise-SinkMap method determines the default source interface, if unspecified, for each event source and establishes the connection point to that interface.

When your class isn't a composite control, as in the ongoing example, you must explicitly call the DispEventAdvise method of each of your IDispEventSimpleImpl (or derived) base classes to connect each event source to each event sink implementation. The pUnk parameter to the DispEventAdvise method is any interface on the event source; the piid parameter is the desired source dispatch interface GUID. The DispEventUnadvise method breaks the connection.

template <UINT nID, class T, const IID* pdiid>                 
class ATL_NO_VTABLE IDispEventSimpleImpl : ... {               
...                                                            
    HRESULT DispEventAdvise(IUnknown* pUnk, const IID* piid);  
    HRESULT DispEventUnadvise(IUnknown* pUnk, const IID* piid);
...                                                            
}                                                              

Here is the IListener interface. We added it to the EarPolitic coclass to provide a means of determining whether a COM object can listen to a Defendant and a Plaintiff. It also provides the ListenTo and StopListening methods to establish and break the connection point between a Speaker event source and the Defendant or Plaintiff event sink.

interface IListener : IDispatch {
  typedef enum SpeakerRole { Defendant, Plaintiff } SpeakerRole;

  [id(1)] HRESULT ListenTo(SpeakerRole role, IUnknown* pSpeaker);
  [id(2)] HRESULT StopListening(SpeakerRole role);
};

The implementation of these methods is straightforward. ListenTo calls the DispEventAdvise method on the appropriate event sink to establish the connection:

STDMETHODIMP CEarPolitic::ListenTo(SpeakerRole role,
  IUnknown *pSpeaker) {
  HRESULT hr = StopListening(role) ; // Validates role
  if (FAILED(hr)) return hr ;

  switch (role) {
  case Defendant:
    hr = DefendantEventImpl::DispEventAdvise (pSpeaker,
      &DIID__ISpeakerEvents);
    if (SUCCEEDED(hr))
      m_defendant = pSpeaker;
    else
      Error(OLESTR("The defendant does not support listening"),
        __uuidof(IListener), hr);
    break;

  case Plaintiff:
    hr = PlaintiffEventImpl::DispEventAdvise (pSpeaker,
      &DIID__ISpeakerEvents);
    if (SUCCEEDED(hr))
      m_plaintiff = pSpeaker;
    else
      Error(OLESTR("The Plaintiff does not support listening"),
        __uuidof(IListener), hr);
    break;
  }
  return hr;
}

The StopListening method calls DispEventUnadvise to break the connection:

STDMETHODIMP CEarPolitic::StopListening(SpeakerRole role) {
  HRESULT hr = S_OK;
  switch (role) {
  case Defendant:
    if (m_defendant)
      hr = DefendantEventImpl::DispEventUnadvise (m_defendant,
        &DIID__ISpeakerEvents);

    if (FAILED(hr))
       Error (OLESTR("Unexpected error trying to stop listening "
         "to the defendant"), __uuidof(IListener), hr);

    m_defendant = NULL;
    break;

  case Plaintiff:
    if (m_plaintiff)
      hr = PlaintiffEventImpl::DispEventUnadvise(m_plaintiff,
        &DIID__ISpeakerEvents);
    if (FAILED(hr))
      Error(OLESTR("Unexpected error trying to stop listening "
        "to the Plaintiff"), __uuidof(IListener), hr);

    m_plaintiff = NULL;
    break;

  default:
    hr = E_INVALIDARG;
    break;
  }

  return hr;
}

In summary, use the IDispEventImpl and IDispEventSimpleImpl classes to implement an event sink for a dispatch interface. Call the DispEventAdvise and DispEventUnadvise methods of each class to establish and break the connection.

Derive your class directly from the source interface when the source is a simple COM interface. Call the AtlAdvise and AtlUnadvise global functions to establish and break the connection. When you need to implement the same source interface multiple times, use one of the various standard techniques (nested composition, method coloring, separate classes, or intermediate base classes) to avoid name collisions.


previous page
next page
Converted from CHM to HTML with chm2web Pro 2.75 (unicode)