Download atl_internals - 119.65 KB

Introduction   

In this article, I show how to implement connection points and introduce two approaches to receive events that for the connectable ATL object.

Implements connection points 

1.Create a new ATL project 

In this project, we remain the default options of ALT project wizard,that is only selecting the "Dynamic-link library" option and remaining other options unchecked. 

2.Create new ATL simple object 

Right clicked 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 remained defaut.clicked "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 add the definition of event functioin "OnDigit", you can also add funtion 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 prefix with Fire_,e.x. Fire_OnDigit,and you can change it to anyone else, but for the readible 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 it’s event receiving fucntion
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 definion 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;
}
</idispatch></iunknown></t>

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 object 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 ceate an ATL simple object as above, we uncheck the "Connection points" option since we create and event recipient object and not an event source object .

In theory it's a very easy thing that create 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 implements the event receiving fucntion really.

Receive event by dummy interface

There're two ways to solve this problem. On approach 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_PiSvrLib*/&LIBID_atl_callLib, /* wMajor = */ 1></icalcdigit,></ccalcdigit,></ccomsinglethreadmodel>

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 that we implement the event interface.

Since we implement ICalcDigit and _ICalcPiEvents, and both of them inherit from IDispatch interface, so we must designate which one of IDispatch interface we expose, we can use 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 assign the exposed IDispatch interface come 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 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);</ccalcdigit>

Receive event by IDispEventImpl and IDispEventSimpleImpl Classes

Derived from IDispEventImpl and IDispEventSimpleImpl classes

We define 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 one 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 don't need the event function information, because IDispEventImpl class will automatic 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 the recipient object will receive the event.

Receive events in script

We can receive events of connection points ATL object in HTML srcipt, such as Javascript or VbScript. If you expect the script to receive events sucessfully 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
推荐.NET配套的通用数据层ORM框架:CYQ.Data 通用数据层框架
新浪微博粉丝精灵,刷粉丝、刷评论、刷转发、企业商家微博营销必备工具"