Connection Points And Asynchronous calls - Part I
By Ashish Dhar

A tutorial on connections points and Aynchronous calls 
 

  • Download server project - 24 Kb
  • Download client project - 28 Kb

    Connection Points are a way of letting Server to notify you when something has happened in the server which you wanted to know of. I started using connection points when I was just learning how to code in COM.I came across a lot of issues including the performance. Eventually I learned the how and whys (not 100% :)) of connection points. This article is a kind of tutorial for those who do not know what connection points are or those who know what they are but haven't ever tried to code them. This article tells you why do you need connection points and what are the alternatives. The article takes you slowly from the need of getting the events back from client to why you need Connection Points at all. Connection Points are implemented using IDispatch interface. This tutorial simulates connection point using Custom Interfaces(IUnknown).

    Given the length of the article I have divided this into two parts. Part I, which is this, discusses Aynchronous calls and why are they useful. This part also tells you about the callbacks. Here we will implement a Server (called Server03) which will demonstrate the need of asynchronous calls. This server will simulate connection points with the IUnknown callback interface. We will also implement one MFC client for the server.

    Part II of this article will demonstrate the connection points in ATL and we will implement a client in ATL, and will see what are the different things one need to take into consideration while implementing a sink in ATL.

    Why do we need notifications from server?

    All COM calls are synchronous meaning when you make a call to a method on an interface, the thread blocks until the method returns. Being synchronous is not bad always but sometimes it is painful. Let's say we have an CoMath object exposes the following method

    Add([in]short nFirst t,[in] short nSecond,[out,retval] short* pResult);

    and the implementation is something like this

    STDMETHODIMP
    CoMath::SomeArithmatics([in]short nFirst,[out,retval] short*pResult)
    {
    	*pResult = 0;
    	try
    	{
    		while(nFirst-- != 10);
    		//do something here...
    	}
    	catch(...)
    	{
    	//catch exceptions here...
    	}
    	return S_OK;
    }

    Now lets say some poor user calls the interface method like this

    short lParam = 9;
    short lResult;
    IMathItfPtr->Add(lParam,&lResult);

    This call makes the method to execute for ever (nFirst never reaches 10) making the caller hang too.

    Now that we know it is a problem What is the solution? There can possibly be many solutions to this problem one of which of course is not to write that bad code first of all. The code shown was not at all a commercial code and but trust me you will definitely find such situations even with the well written code.

    The better solution is aysnchronous calls. At present there is only one COM call which is asynchronous. In future (COM+) you can see more Asynchronous COM methods.

    The aysnchronous calls mean that you send out a command to the Server and you do not need to wait for the call to finish. The server does its job and when it finishes the task it calls the client back.

    Note that I put the calls word in bold on purpose. This is where our basic of connection points starts...Server calls you back. How can that be possible? How does a server know what method to call? I will explain that in detail in a little while. But first lets look how will the aysnchronousthing looks like.

    The server implements BeginOperation(<same parameters as to a regular method>) and stores the paramters in a structure and returns immediately. Now the client no longer waits for the operation to complete and does some other work. Meanwhile the server completes the task and says "ok I am done. who wants to know!" and informs all the interested clients.

    So we need to tell server of some our method that server can call back when it is done what it was doing. If the server has more than one client every client has to tell the server the name of the method which server should call in case of an event. And that becomes the problem. The server will now need to be kind of hardcoded for each and every client method. Wouldn't it be easy if Server always calls the same method whenever it finshes athe job. In other words instead of the client specifying the method name, the server decides what funtion will it call when it finishes the task. And every client implements the same method. Now lets say if server has a number of events to raise then we will need a number of methods, and the server is NOT going to implement these methods. In other words the server defines an interface which defines the various methods which the server will call when it needs to send some notification (or event) to its clients. All the clients need to advise the server of its implementation (called sink) of the interface. The server can maintain a list of all the interested clients and whenever there is an event which the server wants to send to the interested clients, it will simply traverse through the list and send events to all the clients.

    What I have talked about till now is all what connection points is about. The interface which is implemented by the client is basically the sink. The server which publishes the interface is called source. When the client implements the interface (sink) published by the source it needs to let the server know of this interface. In COM a client advises the Server of the sink interface

    Now how does it work.

    First of all we will write a server (object) which will expose a method called Add which takes a short paramter and it raises an event whenever it finishes the task. We call our server Server03.

    Server03 publishes an interface called ICallBackItf which contains a method called OnTaskFinished() which will be called by the server whenever it wants to send an event to the interested clients. All clients which want to receive notifications need to implement this interface. That implementation of the interface is called as a sink. The Client needs to tell the server about the sink. For that purpose the server has a method called AdviseCallBk() method which takes an IUnknown pointer as an [in] parameter and returns a connection. A Cookie is needed to uniquely identify what we call. For breaking the connection there is a corresponding UnAdviseCallBk() which takes the Cookie as a paramter.

    Things will become more clear when we will see the real code.

    To create the server follow the steps below

    Start the Visual C++ IDE and Select File New and TYPE

    We want to create a DLL server so just press Finish on the next Page

    So lets insert our object .Selected Insert and New ATL Object .You will see the object wizard .

    Double click on simple object and enter the Short Name as CoMathServer03.

    Goto to the attributes tab and select the Custom interface and say No to Aggregation

    Press OK and you will get an IDL file called Server03.idl

    Now goto Class View tab in Workspace pane and right click on ICOMathServer03 interface and Select Add Method from the menu. Enter the method Add

    and in Parameters type [in] short nNumber as shown. Similarly add AdviseCallBk and UnAdviseCallBk.

     

     

    We are done with all wizards and everything. Next we will add follow IDL file called Callback.idl and we will add the following defintion in it

    import "oaidl.idl";
    import "ocidl.idl";
    [
    	object,
    	uuid(20846DF1-B4CA-11d3-971F-0050047D51FB), 
    	helpstring("ICallBackItf Interface"),
    	pointer_default(unique)
    ]
    interface ICallBackItf : IUnknown
    {
    	[helpstring("method OnTaskFinished")] HRESULT OnTaskFinished([in] short lResult);
    };
    

    his is the interface our server wants all the clients who want notifications back to be implemented.

    We put it in a seperate IDL because we will need to implement the same interface in the client also. So we will just include this IDL in client side and we should be Ok.

    Now we modify the original Server03.idl file to import the CallBack.IDL by adding this statement

    import "CallBack.idl";

    OK now lets go back to the implementation of CoMathServer03. Here is the implementation of theAdd method

    STDMETHODIMP CCoMathServer03::Add(short nNumber)
    {
    	::EnterCriticalSection(&mLock) ;
    	m_DataFromClient = nNumber;
    	:LeaveCriticalSection(&mLock) ;
    	unsigned long hThreadHandle = 
    			_beginthread(ProcessingThreadFn,//routine that starts thread...
    				     0, //stack size 0 = use same as of primary thread...
    				    (void*)(this)//argument...
    			 	   );
    	return S_OK;
    }
    

    Don't worry the code is simple and you will understand the meaning and need of each and every statement soon.

    First of all whenever a clien calls Add method we store the parameter in a data member called m_DataFromClient. Since we are talking about a simple server we used a data memeber which is also a short type. Advanced serves may think of STL Queues etc. Now whenever we store the parameter passed,we will protect our data member so that we donot overwrite the data. This reminds me of one thing .STA also sometimes need synchronization. Anyway, we create a thread with the _beginthread() API which takes the address of the starting routine of the thread (ProcessingThreadFn in our case) the second param is stack size which we passed 0 (indicating use the same value as that of primary thread) and lastly we passed an paramter to the routine (we passed this for simplicity). So whenever a client calls an add method the method stores the paramter passed and creates a thread and returns immediately. So the client never waits.

    An interesting method is AdviseCallBk

    STDMETHODIMP CCoMathServer03::AdviseCallBk(IUnknown *pICallBk, long *Cookie)
    {
    	ATLASSERT(pICallBk != 0 );
    	*Cookie = m_vecCallBk.Add(pICallBk);
    	return S_OK;
    }

    Firstly we check that the IUnknown pointer is vaid. Look at the next line m_vecCallBk is an object of CComDynamicUnkArray class. Imagine a case where several clients will be interested in the events and they all advise the server of their respective implementations of call back interfaces. So we need to store all the interface pointers and whenever we want to send a notification we traverse through the list and call methods on each and every interface implementation That way all the interested clients are notified.CComDynamicUnkArray is used to hold the dynamically allocated IUnknown pointers.When we call Add method of the class with IUnknown Pointer what we get back is an index in the array so that we recognize our IUnknown pointer. So, we return the index to the client as Cookie which client can store and then pass us whenever it tries to unadvise the Server of its sink. At the Server level all we need to do is find the IUnknown pointer at that particular index and then remove that from the list so next time whenever Server has something to nitify clients with, it does not fire the event to the unadvised client as the IUnknow pointer no longer exists in the list.

    The remaining of the code is very simple. One method which needs a little explaination is Fire_AdditionDone()

    HRESULT CCoMathServer03::Fire_AdditionDone(short lResult)
    {
    	//for each client send the event...
    	for(IUnknown** pp =m_vecCallBk.begin();pp < m_vecCallBk.end();++pp)
    	{
    		if(*pp)
    		{
    			CComQIPtr<ICallBackItf> pCallbackPtr(*pp);
    			ATLASSERT(pCallbackPtr!=NULL);
    			pCallbackPtr->OnTaskFinished(lResult);
    		}
    	}
    	return S_OK;
    }

    The Server loops through the IUnknown list and QIs the IUnknown for ICallBackItf. If the IUnknown pointer for the object supplied does not implement the ICallBackItf we assert.If it is there we simply call the OnTaskFinished method. One more thing important here is that our secondary thread is having a Sleep() statement in it to simulate a long operation.

    If you have understood till this point, the client should not be any mystery. As you can expect that a typical client will create an instance of our componenent CoMathServer03 and if interested in receiving events (which in our current example will be) will call the AdviseCallBk() passing the IUnknown pointer of the sink.At some point client UnAdviseCallBk() passing the Cookie.and it will no longer receive any events.

    The MFC client code is very simple. I have created an MFC client called Server03Client. There is a menu item called CallBack which pops up a dialog called Connection Dialog.

    If you look into the source files of the client you should find the file called IMathSvr03Imp.h This file is the sink we have implemented in the MFC.The ConnectionDlg.cpp/.h files contain the class for connection dialog. In the IDL we included

    import "..\CallBack.idl";

    This inserts the definiton of ICallBackItf and if you see IMathSvr03Imp.h you will find that the CIMathSvr03Impl is implenting the ICallBackItf

    NoteI have added ATL support to this MFC Client just to make things easier. My aim was to show you how we how the callback works and not to teach you MFC etc.If you wish you can implent the client in purely MFC which is a exercise to you.

    Register the Server and run the client. Select CallBack from the main menu and you get this dialog

    Enter some value say 10 in Number edit box and press Fetch.Immediately type in 20 in the Number and press Fetch again.

    You will get two MessageBoxes one after another displaying 100 and 200 respectively.Actually server multiplies Number by 10 .and return You need not to wait till it finshes the operation .You can enter another value and when server finishes task it indicates the result.

    Now you can see that your main thread need not to wait for the server to return before you can start any further work.

    Advantages

    There are several advantages of the aysnchronous calls .Lets say your server is actually talking over the network to some other server and waiting for some data to come back.Lets say it is waiting for an event to be set WaitForSingleObject() and using INFINTE as the timeout parameter.Lets say it never gets back the data and therefore no the event is never set and your poor client is waiiting for ever.It cannot even timeout.

    In the second part of this article we will develop the Server with Connection point.and will see how it works.

     

    Note: When compiling the source code for client, goto Project Settings>>C++ and from Category choose Preprocesor and in the 'Additional include Directories"' enter the path where you extract the server03.

    Source code Server.zip contains the server. Extract the files to a directory .say c:\Code

    Client.zip conatins the client code Extract it to c:\ Code (for simplicity)

    Compile server03 first and then Client.



  • [COMMENTS]

    Copyright © Ashish DHAR -