COM Connection Points

Environment: Windows 2000 Server, Microsoft Visual C++ 6

This article is intended to explain the concept behind connection points with a clear practical example, which will demonstrate an in-process comserver and an MFC client that uses the server.

What Exactly Is It?

It is a method used by a COM object to call back to the client. In other words, clients get call back notification from the COM object.

Perhaps you have heard about callback functions. Well, it goes like this. Suppose you have a COM object that exposes an interface IArithematic, and has a resource intensive method, say Add(int a,int b)—("Anything for simplicity," as the hermit said when he took up residence naked in an Himalayan cave. Sam Weller—Pickwick Papers). Imagine that this method is going to take a lot of time and you do not want to wait until that task is finished. You can use that time for something. So here is where a connection point comes in. You assign a function ExecutionOver(int Result) in your client code, which the COM object can call after it has finished executing the Add method.

So, when the COM object is finished with the task, it calls the client function ExecutionOver (passing the result of addition). The client happily pops up the result in a message box. That's the whole gist. We shall go into the details now.



Click here for a larger image.

How Does the COM Object Know How to Call ExecutionOver??

Imagine that the client exposes an interface ISink, which has a method ExecutionOver(int result). Now, if the client can pass this interface to the COM object, the COM object can happily call ExecutionOver. For example, in the COM the code fragment may look like this:

//===================================================
ISink *pClientSink;
//(Client somehow passes 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 what really happens. The rest is for making this whole thing generic enough. Microsoft has implemented this by defining connectable objects. Let's start by examining the COM interfaces involved in connectionsIConnectionPoint and IConnectionPointContainer. The object (rather than the client) implements both these interfaces.

Both the interfaces are shown below.

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 go one step at a time and see how the whole thing works.

A COM client calls CoCreateInstance to create the COM object. Once the client has an initial interface, the client can ask the object whether it supports any outgoing interfaces by calling QueryInterface for IConnectionPointContainer. If the object answers "yes" by handing back a valid pointer, the client knows it can attempt to establish a connection.

Once the client knows the object supports outgoing interfaces (in other words, is capable of calling back to the client), the client can ask for a specific outgoing interface by calling IConnectionPointContainer::FindConnectionPoint, using the GUID that represents the desired interface. If the object implements that outgoing interface, the object hands back a pointer to that connection point. At that point, the client uses that IConnectionPoint interface pointer and calls IConnectionPoint::Advise( [in] IUnknown *pUnk, [out] DWORD *pdwCookie) to hand over its implementation of the callback interface so that the object can call back to the client. To make it clear once more, the pointer to IUnknown passed to the advise method is the pointer of an interface that's defined and implemented in the client EXE.

Okay, now let's illustrate the whole thing by a practical example.

  1. Create a new ATL-COM AppWizard Project and name it ConnectionCOM.
  2. Right-click on the class view and create a new ATL object.
  3. Name it Add (interface IAdd).

    Before you click the OK button, be sure to check the Support Connection point checkbox.

    Click OK.

Note the classes generated in the class view. You will find one IAdd and _IAddEvents. The latter is just a proxy class; it is to be implemented on the client side. It came because we ticked the Connection_Points check box.

Add a method 'Add(int a,int b) ' to IAdd interface and a method 'ExecutionOver(int Result)' to the _IAddEventsInterface. The class view will be as shown below.

But because we have selected a dual interface and we do not need all that hassle, let's take out the support for IDispatch by editing the IDL file. Below is the original file.

//===========================================================
// ConnectionCOM.idl : IDL source for ConnectionCOM.dll
//
  :
  :

library CONNECTIONCOMLib
{
importlib("stdole32.tlb");
importlib("stdole2.tlb");

[
  uuid(AFE854B0-246F-4B66-B26F-A1060225C71C),
  helpstring("_IAddEvents 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 _IAddEvents; take this line
  //out and put the line below in
  [default, source] interface _IAddEvents ;
  };
};

//================================================================

Whew! The client side is almost finished now. Now, do a build because we need the type library to do a neat thing with ATL. Now, right-click on the CoClass and click Implement Connection Point.

Check _IAddEvents in the ensuing dialog box.

A CProxy_IAddEvets class is generated with the Fire_ExecutionOver(int result) method. This will take care of how the COM object will call the client interface (and takes care of multiple clients calling the same COM DLL and other such issues). Now, let's implement our old IAdd Interface Add method.

//=====================================================

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;
}
//======================================================

Do a build and the COM is ready. Make sure that the COM is registered.

Now the Client Side

Create a new MFC AppWIzard(exe) Dialog based project—ConnectionClient. It looks like this:

Now comes the main part.

We create a CSink class that derives form _IAddEvents. You can use the class wizard for it. You have to supply the header where the _IAddEvents interface is defined. For that, copy the ConnectionCOM.h and ConnectionCOM.tlb files to your client EXE's project folder and add these lines to Sink.h file.

#include "ConnectionCOM.h"
#import "ConnectionCOM.tlb" named_guids raw_interfaces_only

Now we have the additional task of implementing each method defined in the _IAddEvents interface. (Never forget that a COM interface is just a pure abstract base class and that the derived class has to implement all of its methods.)

So, let's implement the first ExecutionOver:

STDMETHODIMP ExecutionOver(int Result)
  {
  CString strTemp;
  strTemp.Format("The result is %d", Result);
  AfxMessageBox(strTemp);
  return S_OK;;

  };

Now comes 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;
  }

We are now almost there.

Now, on the dialog class on the SendToServer Button Click event, we shall do the last bit of coding.

#include "Sink.h"          // for our CSink class
#include <atlbase.h>       // for ATL smart pointers

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<IAdd> pAdd;
hr =pAdd.CoCreateInstance(CLSID_Add);
if(hr != S_OK)
  return -1;

IConnectionPointContainer  * pCPC;
  //IConnectionPoint       * pCP;
  //these 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 our case IID_IAddEvents

  hr = 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
  //implemented all this QueryInterface stuff earlier in our
  //CSinkclass

hr = pSink->QueryInterface (IID_IUnknown,(void **)&pSinkUnk);

//Pass it to the COM through the COM's _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 needs
//to call the interface method when it has to notify us

hr = pCP->Advise(pSinkUnk,&dwAdvise);

//dwAdvise is the number returned, through which
//IConnectionPoint:UnAdvise is called to break the connection

//now call the COM's add method, passing in 2 numbers
  pAdd->Add(m_number1 ,m_number2);
//do whatever u want here; once 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;
}

Now, do a build of the dialog EXE. Now fire away. That's it for connection points.

Downloads

Download demo project - 71 Kb


Comments

  • How to write Mousedown event with IConnectionPointContainer interface?

    Posted by tinapatel29 on 05/29/2006 05:40am

    Pls write with ex. thnx. Tina

    Reply
  • Nice sample provided

    Posted by Agha Ali Jaffer on 05/19/2005 09:04am

    fine concept

    Reply
  • I tested in NT Service(EXE), but it doesn't work

    Posted by nimitz on 04/09/2004 03:16pm

    Anyone who knows how to write Connection Point in Service(EXE) can tell me how to do via email(nimitz@tsinghua.org.cn). Thank you very much! And example is also welcome.

    Reply
  • Really Nice Example

    Posted by Legacy on 02/21/2004 12:00am

    Originally posted by: Vineet Kmar

    This article is a good work to understand the connection point handling.The explanation ia very clear.I wanted this type of example,as it gives a chance to move furthur.

    Reply
  • asychronous call

    Posted by Legacy on 09/29/2003 12:00am

    Originally posted by: xialingxue

    as the author show us in the sample,pAdd->Add() ought to be works asychronous call,but in fact ,the method work still sychronus. then I wonder if the call of pClientSink->ExecutionOver(a+b) is usefullessness.

    • Add() function blocks

      Posted by imhawley on 06/25/2004 12:08pm

      The reason the call isn't asynchronous is because it calls Fire_ExecutionOver() from within it's body. This brings up a dialog box, a blocking operation, the Add() function cannot return until Fire_ExecutionOver() returns (until the dialog box has been closed). It is necessary therefore to decouple the Add() function from it's call. One method of doing this would be to place the parameters into a deque of events (with suitable locking objects attached if needed) and return immeditately. The CoClass implementing IAdd would have then to have it's own thread, reading the parameters from the same deque, performing the necessary actions (i.e. perform the Add()) and then calling the Fire_ExecutionOver() from with it's own thread. I have to say though, I don't know if the sample is setup to allow calling the connection point across threads, but that might merely be solved by entering the multi-threaded appatment prior to creaing the IAdd interface. Hope that helps (should you see my response!) Ian

      Reply
    Reply
  • a question

    Posted by Legacy on 07/30/2003 12:00am

    Originally posted by: Sway

    In my opinion, this feature of COM should act like a working thread. However, while COM's Add is working, I tried to move the dialog window - it didn't respond unitil the Add method is returned. How come?

    Reply
  • ExecutionOver in the sink does not work

    Posted by Legacy on 05/28/2003 12:00am

    Originally posted by: Meghana Parwate

    I downloaded the example and executed the client. I do not get any message box from ExectionOver method in the Sink. Any knowledge abt why this happens?

    • How do I fire a Client event from the COM Server( Inside the Thread Function)

      Posted by mamthababu on 02/08/2005 12:24pm

      Hi,
         I am newbie to ATL/COM. I really appreciate your work(particularly your technical article on COM Connection Points).
      
         May I know how do I create a thread in the COM Server (my own) method and then fire the Client event from the (newly Created) Thread's function?
      
         When I create a Thread using the below code, it reports an error saying "global functions do not have 'this' pointers".
      
      hThread = CreateThread( NULL,                        // default security attributes 
      0,                           // use default stack size  
      ThreadFunction,                  // thread function 
      reinterpret_cast< void* >(this),                // argument to thread function 
      0,                           // use default creation flags 
      &dwThreadId);                // returns the thread identifier 						 
      if (hThread == NULL) 
      {
       DWORD dw = GetLastError();
       char szBuf[80]; 
       sprintf(szBuf, "CreateThread Function Failed -  GetLastError returned %u\n",dw); 
       MessageBox(NULL, szBuf, "Error", MB_OK); 
       return FUNCTIONFAILED;
       }
       else 						  
      MessageBox(NULL,"CreateThread SUCCESS.","Info",MB_OK);
      
      Or say for example, in your article(COM Connection Points), how do I call the add(number1, number2)inside a Thread.
      
      Any help would be greately appreciated.
      
      Many thanks in advance,

      Reply
    • Firing Events does not always work properly

      Posted by Beecken on 04/14/2004 03:12am

      Yes, I had a similar problem when using VB for the client part. It seems that events get lost while the VB window doesn't have the focus. This happened quite often. So I decided to have a mutex protected FIFO queue for results reading all pending msgs upon any event received as well as on timer events. After all: I still prefer a TCP/IP stream or a Named Pipe!

      Reply
    Reply
  • COM Connection Points

    Posted by Legacy on 05/01/2003 12:00am

    Originally posted by: Lee T

    Great example! It really helps a lot. Simple and elegant!

    Lee

    Reply
  • Excellent tutorial.

    Posted by Legacy on 04/01/2003 12:00am

    Originally posted by: MrCeri

    Excellent tutorial, thanks! My only minor gripe would be that the client stuff isn't too easy to follow if you haven't used MFC before, I found I had to refer to the source files within the downloadable zip. As I say, that's only a very minor criticism though.

    Thanks again!

    Ceri
    -----------------------
    http://www.mrceri.co.uk

    Reply
  • A Good Example of handling COM events

    Posted by Legacy on 01/15/2003 12:00am

    Originally posted by: Vitaly

    You can find a good example of using connection points here: http://homepage.eircom.net/~codexpert/sink/

    Regards,
    Vitaly

    P.S. Best XP-style tooltips, menus and hyperlinks can be found on http://www.tooltips.net


    Reply
  • Loading, Please Wait ...

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • On-demand Event Event Date: September 10, 2014 Modern mobile applications connect systems-of-engagement (mobile apps) with systems-of-record (traditional IT) to deliver new and innovative business value. But the lifecycle for development of mobile apps is also new and different. Emerging trends in mobile development call for faster delivery of incremental features, coupled with feedback from the users of the app "in the wild." This loop of continuous delivery and continuous feedback is how the best mobile …

  • As mobile devices have pushed their way into the enterprise, they have brought cloud apps along with them. This app explosion means account passwords are multiplying, which exposes corporate data and leads to help desk calls from frustrated users. This paper will discover how IT can improve user productivity, gain visibility and control over SaaS and mobile apps, and stop password sprawl. Download this white paper to learn: How you can leverage your existing AD to manage app access. Key capabilities to …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds