Rating: none
Andrew Whitechapel (view profile) April 12, 2001 |
In the normal COM scheme of things, the communication between client and server is driven by the client. The client creates the server's COM object, and makes calls into the object as it needs to. The object generally sits there passively waiting for calls from clients. However, sometimes, things happen which make the server want to notify the client, and these things might be asynchronous to anything the client is doing, and perhaps only peripherally associated with any client activity. For example, if your control has a user interface, and the user clicks on it, you might want to notify the container. Or, if your object encapsulates some business rule that has been broken, you might want to tell your client. Or, say an object that looks after a database might be updating its table indexes, and connected clients need to know when this processing is finished so that they can continue retrieving rowsets.
To code a solution to these requirements, you only really have two choices: client-initiated polling; and object-initiated notification. With polling, the client decides when to ask if some condition has been met (control clicked, business rule broken, indexes updated, etc). Clearly, this is inefficient. Much better is the solution where the object initiates the notification - since, after all, the object knows immediately when the notification is required, and only the one call is needed, instead of the iterative polling calls. In order for this to work in a COM universe, it requires the client to implement an interface that the object makes calls on. This hooks into all the usual advantages of specifying COM interfaces, and logically represents the model since for the purposes of the event notification, the object is acting like a client, and the client is acting like a server.
However, the object still specifies the interface - this, too, is reasonable, since the object must clearly know about and support the interface event though it doesn't actually implement it. Also, the interface can be described in the server's IDL code and built into its type library, while the client on the other hand is unlikely to have any IDL code of its own. Since the object is the source of the calls on this outgoing interface, this interface is called a source interface. The client is the sink for calls on this interface.
Writing a source interface in IDL is very simple, eg: The Finished event will be fired by the COM object whenever it deems appropriate. There's nothing in the IDL for the interface itself that tells us whether the interface is a source or sink interface. It can be either, depending on how it's used. Whether it's a source interface is determined by our particular object, so we specify it in the coclass section of our IDL: Note that you can have two default interfaces, so long as one is a source interface and the other isn't. Also, remember that the source object does NOT implement the source interface - it merely defines it through its type library. The client will actually implement the source interface, having read the server's type library to find out the details of this interface.
COM objects can support more than one connection point. In order to do this, the source object must implement the IConnectionPointContainer interface. This interface allows the client object to obtain a connection point.
IConnectionPointContainer is a simple interface, with only two methods: FindConnectionPoint returns a pointer to the connection point specified by the IID passed by the client object, and EnumConnectionPoints returns an IEnumConnectionPoints enumerator that allows the holder to walk through all of the connection points supported by the source object.
So, the source object implements IConnectionPointContainer. The object also maintains a collection of connection points that can be searched and enumerated via the methods in IConnectionPointContainer. Each connection point maintains a list of active connections, set up by calls to the connection point's Advise method and ended by calls to the connection point's Unadvise method.
For a dispatch interface, you have to implement all of the methods of IDispatch, but your implementation of Invoke doesn't have to implement every method in the dispatch interface -- it can just return an error (DISP_E_MEMBERNOTFOUND) for methods it doesn't support (in other words, events it doesn't care to handle).
So, because it's easier for a language such as VB or VBA to get event calls on a dispatch interface rather than on a custom interface, that's all they support. If you're writing your own sink in C++ and you don't care about other clients, you can use a custom interface for improved performance. But in most cases, events are relatively rare, so usually the performance isn't a big issue.
If you want to have an event interface that can be high performance and compatible with VB, implement two equivalent source interfaces (one custom and one dispatch) with different interface IDs (IIDs), and make the dispatch interface the default so VB will be happy.
You may also add the IObjectSafety interface - this marks a component as safe for scripting. Again, the ATL supports this with the IObjectSafetyImpl class. Finally, you must add an entry for IProvideClassInfo to the COM map; however, because this interface serves as the base for IProvideClassInfo2, you need not add IProvideClassInfo to the multiple inheritance list.
OK, we've had a quick review of the basics of events in ATL, we've considered why you might want a dispinterface event instead of a custom event interface, we've seen how to implement a 'one-object-to-many-clients' event solution, and implemented a scriptable runtime sink.
(continued)
The source interface
[ uuid(1F8C0130-CDF5-11d3-B77E-00104BDC292F),
helpstring("_IRobotEvents Interface")
]
dispinterface _IRobotEvents
{
properties:
methods:
[id(1), helpstring("method Finished")] HRESULT Finished();
};
coclass Robot
{
[default] interface IMotion;
[default, source] dispinterface _IRobotEvents;
};
IConnectionPoint and IConnectionPointContainer
On the other hand, the source object will implement a connection point. Your source object will have exactly one connection point for each outgoing interface. Connection points are separate COM objects, but they're created by your object, not by CoCreateInstance, and typically implement only two interfaces: IUnknown and IConnectionPoint.
Sequence of calls to set up an event
Modifying a Server to Support Events
The following steps detail the modifications necessary to an existing ATL object in order to support firing an event through a dispinterface. If you were creating a new COM object from scratch, you'd make it support connection points by choosing that option in the ATL Object Wizard. In this case, you'll duplicate the effects of choosing that option by adding that support manually.
[uuid(1F8C0130-CDF5-11d3-B77E-00104BDC292F),
helpstring("_IRobotEvents Interface")
]
dispinterface _IRobotEvents
{
properties:
methods:
};
template <class T>
class CProxy_IRobotEvents :
public IConnectionPointImpl<T, &DIID__IRobotEvents,
CComDynamicUnkArray>
{
public:
HRESULT Fire_Finished()
{
CComVariant varResult;
T* pT = static_cast
Modifying a Client to Support Events
Again, suppose you already have an MFC client. The next section lists the steps needed to add event sink support, so that you can clearly see what the extra code involved should be. In this exercise, you will add a new class and additional code so that the client can receive events from the COM object server that you modified in the previous exercise. You will use an ATL class as the base class for your sink object.
try
{
m_pIS = ISomethingPtr(CLSID_Robot);
}
catch (_com_error &e)
{
MessageBox(e.ErrorMessage());
}
void CMfcClientDlg::OnButton1()
{
try
{
int j = 0;
m_pIS->SomeFunc(&j);
char s[8];
wsprintf(s, "%d", j);
MessageBox(s);
}
catch (_com_error &e)
{
MessageBox(e.ErrorMessage());
}
}
#include <atlbase.h>
extern CComModule _Module;
#include <atlcom.h>
#include <atlimpl.cpp>
CComModule _Module;
_Module.Init(NULL, m_hInstance);
// declaration of dialog object, and DoModal, etc
_Module.Term();
#include "stdafx.h"
#include "EventHandler.h"
m_pHandler = new CEventHandler;
IUnknownPtr pUnk = m_pIS;
m_pHandler->DispEventAdvise(pUnk);
IUnknownPtr pUnk = m_pIS;
m_pHandler->DispEventUnadvise(pUnk);
delete m_pHandler;
Dispinterface vs VTBL Events
Recall that the type of interface for our events is a dispinterface. Why, you might ask? Well, despite the fact that dispatch interfaces are slower, they do simplify things into just an implementation of IDispatch. Also, with a dispatch interface, the object receiving the calls doesn't have to provide implementations of all the methods in the interface. Remember that when you implement a regular custom (or, for that matter, dual) interface, you must implement all of the methods on that interface (at least to return E_NOTIMPL).
Server with VTBL Event
We'll continue with our ATL object, and modify the current version so that its event interface is also exposed by a VTBL (custom) interface, ie an interface derived from IUnknown.
[uuid(Fresh guid here),
helpstring("...")
]
interface IFinishedEvent : IUnknown
{
};
template <class T>
class CProxyIFinishedEvent
: public IConnectionPointImpl<T,
&IID_IFinishedEvent,
CComDynamicUnkArray>
{
public:
HRESULT Fire_Finished()
{
HRESULT ret;
T* pT = static_cast<T*>(this);
int nConnectionIndex;
int nConnections = m_vec.GetSize();
for (nConnectionIndex = 0;
nConnectionIndex < nConnections;
nConnectionIndex++)
{
pT->Lock();
CComPtr<IUnknown> sp = m_vec.GetAt(nConnectionIndex);
pT->Unlock();
IFinishedEvent* pIFinishedEvent =
reinterpret_cast<IFinishedEvent*>(sp.p);
if (pIFinishedEvent != NULL)
ret = pIFinishedEvent->Finished();
}
return ret;
}
};
CProxy_IRobotEvents<CRobot>::Fire_Finished();
CProxyIFinishedEvent<<Robot>::Fire_Finished();
Client supporting VTBL Events
Now modify your current client (with dispinterface event support), so that it implements the custom event interface on the ATL Robot server instead of the dispinterface event.
STDMETHODIMP CEventHandler::raw_Finished()
{
AfxMessageBox("Got a VTBL event");
return S_OK;
}
CComObject<CEventHandler> *pHandler;
CComObject<CEventHandler>::CreateInstance(&pHandler);
HRESULT rc = AtlAdvise(m_pIM, pHandler->GetUnknown(),
IID_IFinishedEvent, &m_dwCookie);
if (m_pIM)
AtlUnadvise(m_pIM, IID_IFinishedEvent, m_dwCookie);
Multiple Clients, Single Server
If your COM object resides in a DLL server, it will be loaded into the address space of a client - if there are multiple clients, there will be multiple instances of the DLL, one for each client. However, in some situations, your server will be performing some central, poolable activity such that multiple clients need to connect to the same instance of the server's COM object(s). In this case, you need to set up your server as an EXE server, and register the class factory or factories for multiple use (the default with the ATL COM AppWizard generated code). Also, you need to set your COM object class factory for single object creation.
Runtime Sinks
Some clients - notably scripting clients - might wish to implement a connection point sink at runtime. In order to support this, your component needs to support IProvideClassInfo2. This extends IProvideClassInfo with the GetGUID method, which allows a client to retrieve an object's outgoing interface IID for its default event set. In an ATL object, you can use IProvideClassInfo2Impl to support this.
public IProvideClassInfo2Impl<&CLSID_Simple,
&DIID__ISimpleEvents,
&LIBID_SIMPLESINKLib>,
public IObjectSafetyImpl<CSimple,
INTERFACESAFE_FOR_UNTRUSTED_CALLER>
COM_INTERFACE_ENTRY(IProvideClassInfo)
COM_INTERFACE_ENTRY(IProvideClassInfo2)
COM_INTERFACE_ENTRY(IObjectSafety)
<OBJECT ID="Simple"
CLASSID="CLSID:64C7097E-5FB7-11D4-A44B-00104BDC292F">
</OBJECT>
<INPUT TYPE="BUTTON" ID="b1"
VALUE="Invoke Event-Firing Method">
<SCRIPT LANGUAGE="VBS">
sub b1_OnClick()
Simple.m1
end sub
sub Simple_e1()
MsgBox "Received Event"
end sub
</SCRIPT>
'COM, ATL' 카테고리의 다른 글
http://blog.naver.com/c_user/40052542493 - 블로그 링크 com atl 자료 링크 (1) | 2008.08.28 |
---|---|
what is different between dispinterface and interface in IDL ? (0) | 2008.08.28 |
The AtlSink.exe sample demonstrates how to implement a dispinterface sink by using the Active Template Library (ATL) in Visual C++ (1) | 2008.08.28 |
표준 마샬링 좋은 예제 (1) | 2008.08.28 |
ATL and Dual Interfaces (0) | 2008.08.28 |
ATL_COM Event 사용 하기. COM (0) | 2008.08.20 |
Property 와 Method 클라이언트 사용 구현부분 참조 (1) | 2008.08.19 |