Programming COM with ATL and MFC has always been somewhat cryptic considering the ease with which Visual Basic creates them for us. Perhaps one of the most used but feared is the callback features. This article tries to understand a bit more about callbacks rather than jeopardize the readers with mighty names like source and sinks.
I assume that most of the readers favour ATL for COM development and are comfortable with interfaces, editing IDL file and fiddling with methods and properties of an interface without much help from the wizard!
Consider a very typical client server scenario with the server being the COM component (may be anything in process, out of process, service, etc.) and the client being a VC++ or a VB program that wants to make use of the services provided by your COM server. Consider that your client program calls the method of the server, consider for example
add() (Wow!!!). Suppose that the server starts with the time consuming job of 'adding'. Your client application may go on with it's work and when it requires the result back from the server...now that's a real problem! Conventional interfaces provide for one way communication from the client to the server. Now, how will the server notify the client that it is ready with the result. In a typical COM terminology, we call the server interface as "SOURCE" and client side event handler as "SINK". We will use these terms where they are more appropriate, for remaining parts of discussions we can well stick with our 'client-server' common names. Now we have a wide range of alternatives. If you are good in Computer Organization and Architecture, you may be knowing of things like polling, interrupt based I/O, etc.
- The first choice at our disposal is similar to the polling technique. The client continuously checks with a property or a boolean method that will indicate if the operation is complete. The client is stuck in a loop till a '
true' is returned flagging the completion (success or failure) of the operation at the server side. However simple, this definitely is not an efficient or elegant solution. A better option would be for the server to be able to notify the client that it is done with its job and the results may be taken back by the client.
- This method is similar to the interrupt based I/O (no direct comparison expected). Add an interface to your server's .idl file. This interface declares the function that is to be executed once the results are available with the server. The server's IDL file declares the interface but does not implement it.
The job of implementation of this interface is left to the client. Such a client needs to provide the function definition to handle the result given by the server. Consider that the server is providing an interface called
_IServerEvents, the VB6 client code can be written as:
Private Sub _IServerEvents_CallbackFunction()
Similarly, for a C++ client assume that we have a class <PRE lang=c++> class CMyClient: public _IServerEvents
//Your code goes here
CMyClient available. This class is inherited from your
_IServerEvents interface. The class overrides the
CallbackFunction() to handle the post callback handling.
- Although the solution in previous step is satisfying enough, it puts an added restriction on your client to implement the provided interfaces. A much transparent solution is offered by ATL wizard when we add a class to the project.
Now open your project's IDL file and add the following to it:
<PRE lang=c++> dispinterface _IServerEvents
coclass tag, add the following lines:
<PRE lang=c++> [
[default,source] dispinterface _IServerEvents;
Add a method to the
dispinterface just like any conventional interface. In our case let it be
CallbackFunction. Now the most important thing, in the class view, right click on the
CoClass and choose 'Implement Connection Point...'. A single dialog will pop up. Press OK and you are done.
You will see that the function
Fire_CallbackFunction(...) is implemented for you. Whenever you need to notify the client, you call this function with the required parameter. Remember that the IDL file should have attribute
[in] for the parameter of the callback function.
The last part is to implement a client in VB6. This is real easy. While declaring the variable for the server class, use '
WithEvents' to enable your application to receive events from the server. Now that makes things really easy in VB.
However, the code for implementing the client in VC++ is not that straight forward. As this article is just to give you a taste of callbacks, I will prefer not to delve into unnecessary details. However, if any one is determined enough to code the client in VC++ I will be more than happy to help out. Please post me a message and I will answer you or even post a separate article describing the process.
While writing the code for a project, I needed to pass back a
variant through the callback function. When I compiled my code (VC++ 6.0 with SP5 !!!), I got a warning: conversion to
bool, possible loss of data. On running my client I found that it was actually getting value as '
True'. The callback function worked perfectly fine for
int, etc! Ultimately I figured out that, I had to use the
InternalCopy() method of
CComVariant class to copy the parameter value to the
CComVariant array. Please be careful and never assume that wizard generated code is never error prone :)