Development environment: VC 6.0 test environment: Windows 2000
This article is written by Alex C. Punnen. It is published in http://www.codeguru.com/, which is translated into Chinese and share it. (Note: I have an improper translation, please also ask you to correct and understand)
Operating environment: Windows 2000 Server, Microsoft Visual C 6
This article will use a clear example to describe the idea of connecting point events in COM. Components will be within a process and connect this service with an MFC client.
What is it?
It is a COM component to provide a method that is returned by the client. In other words, the client gets a callback notification from the COM component.
Maybe you are already familiar with the callback method. Very good, the connection point event is this. Suppose you have a COM object, exposing an IARITHEMATIC interface, and there is an interface method: add (int A, int b). Imagine this method will take a long time and you don't want to wait until it will be implemented. You can use some things to do this time. Therefore, the connection point event will help you solve this problem. You can assign an ExecutionOver (int result) method in the client, and the COM component triggers the ExecutionOver (int result) method after performing the add method.
Therefore, when the client component completes this task, it will call the client's Executionover method. The client may throw this result with a dialog. That is a complete system. Here we will detail how to implement connection between COM with ATL.
How do COM components know to call the Executionover method?
Imagine, the client exposes an Isink interface and has an ExecutionOver (Int result) method. Now, if the client can pass this interface to the COM component, this COM component can call the Executionover method. For example, in the code snippet of the COM component, it looks as follows:
/ / =========================================================================================================================================================================================== ===
Isink * pclientsink;
// (Client Somehow Passs The Isink Interface Pointer
// We Shall See How Later - SO PCLIENTSINK IS Loaded Now
HRESULT ADD (Int A, INT B)
{
PClientSink-> ExecutionOver (A B);
}
/ / =========================================================================================================================================================================================== =====
This is really happening. The rest will make the whole thing simple. Microsoft has defined connectable objects and implements it. Let's start thinking that the COM interface is included in IconnectionPoint and IconnectionPointContainer. This object will implement both interfaces. These two interfaces are defined as follows:
Interface iconnectionPointContainer: iUnknown {
HRESULT ENUMCONNECTIONPOINTS
IEnumConnectionPoints ** ppenum) = 0;
HRESULT FINDCONNECTIONPOINT (REFIID RIID,
IConnectionPoint ** PPCP) = 0;
}
Interface iconnectionPoint: iunknown {
HRESULT GETCONNECTIONINTERFACE (IID * PIID) = 0;
HRESULT GETCONNECTIONPOINTCONTAINER
IConnectionPointContainer ** PPCPC) = 0;
HRESULT ADVISE (IUNKNOWN * PUNK, DWORD * PDWCookie) = 0;
HRESULT UNADVISE (DWORD DWCOOKIE) = 0;
HRESULT ENUMCONNECTIONS (IEnumConnections ** Ppenum) = 0;
}
Now let's start step by step and look at the entire implementation process.
The COM client calls the CocreateInstance method to create a COM object. Once the COM client has a initial interface, this client can ask the COM object to support the connectable event, request the IconnectionPointContainer interface through the QueryinterFace method. If the request is successful, it indicates that the accessible object and returns the IconnectionPointContainer interface pointer, otherwise the object is an unconnectable object.
Once the client knows the COM object supports the connectionable event. After the customer gets the IConnectionPointContainer interface pointer, the member function acquiring the corresponding interface connection point object. If the COM object implements this interface, the COM object will return a connection point interface pointer. Next, the client uses the IConnectionPoint interface and calls iconnectionPoint :: Advise ([in] iUnknown * punk, [out] DWORD * PDWCookie) method to handle the implementation of the callback function. In order to become clearer, the pointer to the IUNKNOWN interface is defined and implemented in the client's exe by the Advise method.
Ok, let us use an actual example to demonstrate the entire implementation process.
Create a COM component using ATL-COM AppWizard, this item is named "ConnectionCom" Right-click on the class view, create an ATL object, as shown
Name Add, Interface (IADD)
Before you click OK, confirm the "Support Connection Point Checkbox" and on the Attributes page.
Click OK.
Note: The created class is displayed in the class view, you will find an IADD and _ADDEvents interface. The latter is precisely a proxy class, which will be implemented in the client. Its appearance is because we selected Connection_Points Check Box.
Add a method 'Add (int A, int b) "to the IADD interface, add a method' ExecutionOver (int result) to the _iaddevents interface. The class view is as follows: Let's take a look at the generated IDL file.
/ / =========================================================================================================================================================================================== ===========
// connectioncom.idl: idl source for connectioncom.dll
//
:
:
Library ConnectionComlib
{
Importlib ("stdole32.tlb");
Importlib ("stdole2.tlb");
[
UUID (AFE854B0-246F-4B66-B26F-A1060225C71C),
Helpstring ("_ ildevents interface")
]
// Old Block - Take this Out
// Dispinterface_iaddevents
// {
// Properties:
//Methods:
// [ID (1), Helpstring ("Method ExecutionOver")]]]
// HRESULT EXECUTIONOVER (intResult);
//};
// To this one -put this in
Interface_iaddevents: IUNKNOWN
{
[ID (1), Helpstring ("Method Executionover")] HRESULT
ExecutionOver (intResult);
}
[
UUID (630B3CD3-DDB1-43CE-AD2F-4F57DC54D5D0),
Helpstring ("Add Class")
]
CoClass Add
{
[default] interface Iadd;
// [default, source] dispinterface _iaddends; Take this Line
// out and put the line below in
[default, source] interface _iaddevents;
}
}
/ / =========================================================================================================================================================================================== ================
Now let's compile the project because we need the type library to use ATL processing. Now right click on the CADD class and click on Implement Connection Point.
Check _iaddevents in the confirmation dialog. The CPROXY_IADDEVETS class is generated and has a Fire_ExecutionOver (int result) method. This class will care about how COM object calls the client interface. Now let us realize the Add method in the original IADD interface.
/ / =========================================================================================================================================================================================== =====
StdMethodimp CADD :: Add (int A, int b)
{
// Todo: Add Your Implementation Code Here
Sleep (2000); // TO Simulate a long process
// OK, Process over now; let's notify the client
Fire_executionover (A B);
Return S_OK;
}
/ / =========================================================================================================================================================================================== ======
Compile the project and register the COM component with the Tools- "Register Control menu.
Now write client calls
Create a New MFC AppWizard (Exe) Dialog Based Project-ConnectionClient. It look like this:
Create a MFC Dialog project -ConnectionClient, as follows:
Create a CSINK class, inherited from _IAdDevents. You can use the class wizard to complete this task, select the general class. Since we inherit from _IadDevents, the header must contain its definition, so copy the connectioncom.h and the connectioncom.tlb file into the client project directory and add these connections to the Sink.h file.
#include "connectioncom.h"
#import "connectioncom.tlb" named_guids raw_interfaces_only
Now, we have another task that implementing each method is defined in the _iddevents interface. (Never forget a COM interface is a pure virtual abstract class, inheriting its class, must implement all of it)
Therefore, let us implement the first method Executionover.
StdMethodimp ExecutionOver (int result)
{
CString Strtemp;
Strtemp.Format ("The Result IS% D", Result);
AfxMessageBox (Strtemp);
Return S_OK ;;
}
Define a count variable private in the CSINK class:
DWORD M_DWREFCOUNT;
And in the csink () constructor, the m_dwrefcount is 0.
Csink :: csink ()
{
m_dwrefcount = 0;
}
The following implementation QueryInterface, AddRef, and Release.
HRESULT stdmethodcalltype Queryinterface (Refiid IID, Void
** PPVObject)
{
IF (IID == IID__IADDEVENTS)
{
m_dwrefcount ;
* pPVObject = (void *) THIS;
Return S_OK;
}
IF (IID == iid_iunknown)
{
m_dwrefcount ;
* pPVObject = (void *) THIS;
Return S_OK;
}
Return E_NOINTERFACE;
}
Ulong stdmethodCallType Addref ()
{
m_dwrefcount ;
Return m_dwrefcount;
}
Ulong stdmethodCallType Release ()
{
Ulong L;
L = m_dwrefcount -;
IF (0 == m_dwrefcount)
DELETE THIS;
Return L;
}
Below, in the Click event of the "Sendockerver" button, add the code:
#include "sink.h" // for ur csink class
#include
Void cconnectionClientdlg :: OnsendtoServer ()
// sendtoServer Button Click Event
{
Updatedata (1);
HRESULT HR;
// Call Coinitialize for Com InitiaLISATION
HR = Coinitialize (NULL);
IF (hr! = s_ok)
Return -1;
// Create an instance of the com object
CComptr
HR = Padd.cocreateInstance (CLSID_ADD);
IF (hr! = s_ok)
Return -1;
IConnectionPointContainer * PCPC;
// iconnectionPoint * PCP;
// There Are Declared As a Dialog's Member
// DWORD DWADVISE;
// Variables, Shown Here for Completeness
// Check if this interface supports connectable objects
HR = Padd-> QueryInterface (IID_ICONNECTIONPOINTCONTAINER,
(void **) & pcpc);
IF (! succeeded (hr))
{
Return HR;
}
//
// OK, IT Does; Now Get The Correct Connection Point Interface
// IN outshr = PCPC-> FindConnectionPoint (IID__IADDEVENTS, & PCP);
IF (! succeeded (hr))
{
Return HR;
}
// We Are Done with the connection point container interface
PCPC-> Release ();
Iunknown * psinkunk;
// Create a Notification Object from Our Csink Class
//
Csink * psink;
Psink = new csink;
IF (null == psink)
{
Return E_FAIL;
}
// Get the Pointer to Csink's Iunknown Pointer (Note We Have
// Implement All this Queryinterface Stuff Earlier In Our
// csinkclass
HR = psink-> queryinterface (IID_IUNKNOWN, (Void **) & psinkunk);
// Pass it to the com through the com # _ _iaddevents
// Interface (PCP) Advise Method; Note That (PCP) WAS Retrieved
// THROUGH THE EARLIER FINDCONNECTOINPOINT CALL
// this is how the com gets our interface, so that it just need
// TO Call the interface method gen it has to notify us
HR = PCP-> Advise (psinkunk, & dwadvise);
// dwadvise is the number return, through which
// iconnectionpoint: unadvise is caled to break the connection
// Now call the com # add method, passing in 2 Numbers
PADD-> add (m_number1, m_number2);
// do wherever u want here; overce addition is here a message box
// Will Pop Up Showing The Result
// pcp-> unadvise (dwadvise); Call this when you need to
// disconnect from Server
PCP-> Release ();
Return HR;
}
Compile the client program and run.