Callback in COM/DCOM

COM/DCOM supports the design of a long background tasks. Some of the use-cases like file downloading from internet or copying a large file between drives. A function starts the background process and returns the control to client. Client then monitor the progress and status of the task using a callback function. These callback functions are often known as events.

Server provides an API to register a function callback using a function pointer. Client has to implement that function and the address reference has to be passed to server. Server registers this function pointer as event callback and calls the same as the events are generated. This design is applicable for C or non-OOP languages.

COM/DCOM is object oriented and a simple callback function pointer passing to the server cannot fit into the design. COM/DCOM goes with object oriented approach and it defines a class for callback design. Events or callback functions are member functions of this class and client creates an object and implements these events and passes the address reference of the object to the server. This callback class can have n-number of event functions and thus server can call any event as needed. This design eliminates the need of registering individual functions to the COM server.

Callback event interface

One of the useful example for describing event is WebBrowser control COM/DCOM. We are taking an imaginary minimal class to understand this events. IWebBrowser2 is the interface to interact with InternetExplorer COM class. This has Navigate(URL) method which can trigger events. DWebBrowserEvents2 is the event interface for client.

/* Internet Explorer */
class IWebBrowser2 {
  public:
    virtual void Navigate(char * strURL) = 0;
};

/* Event Class */
class DWebBrowserEvents2 {
  public:
  virtual void DownloadBegin() = 0;
  virtual void DownloadComplete() = 0;
  virtual void ProgressChange(long Progress, long ProgressMax) = 0;

};

CInternetExplorer server

CInternetExplorer is our imaginary server implementation. This is to understand how CreateInstance/QueryInterface Advice, Unadvice functions are used while implementing a COM server with callback functions.

#include <windows.h>
#include <conio.h>
#include <iostream>

using namespace std;

/* Internet Explorer server class */
class CInternetExplorer: public IWebBrowser2
{
  private: DWebBrowserEvents2 * m_pEvent;
  HANDLE m_hTask;

  private: CInternetExplorer() {
    m_pEvent = NULL;
    m_hTask = 0;
  }

public:
  void Advice(DWebBrowserEvents2 * pEvent, ULONG * nCookie)
  {
    cout << "Advice: callback object(0x" << pEvent << ")\n";
    m_pEvent = pEvent;
  }
  void Unadvice(ULONG nCookie) {
    cout << "Unadvice: callback object(deassigned)\n";
    m_pEvent = NULL;
  }
  static CInternetExplorer * CreateInstance()
  {
    return new CInternetExplorer();
  }
  virtual void Navigate(char * strURL)
  {
    HANDLE thread;
    cout << "called Navigate(" << strURL << ")\n";
    thread = CreateThread(NULL, 0,
                          CInternetExplorer::CreateNavigateTask,
                          this, 0, NULL);
    m_hTask = thread;
  }
  virtual void Release()
  {
    if (m_hTask)
    {
      CloseHandle(m_hTask);
    }
    m_hTask = 0;
    delete this;
  }
  static DWORD WINAPI CreateNavigateTask(LPVOID pArg)
  {
    long Progress = 0, ProgressMax = 100;
    CInternetExplorer * _this = (CInternetExplorer * ) pArg;

    /* Host IP resolved, HTTP connected */
    if (_this->m_pEvent)
    {
      _this->m_pEvent->DownloadBegin(); /* call back*/
    }
    Sleep(100);
    /* Download progress */
    for (Progress = 0; Progress <= 100; Progress += 10) {
      if (_this->m_pEvent)
        _this->m_pEvent->ProgressChange(Progress, ProgressMax); /* call back*/
      Sleep(1000);
    }
    /* Download 100% done */
    if (_this->m_pEvent)
      _this->m_pEvent->DownloadComplete(); /* call back*/

    CloseHandle(_this->m_hTask);
    _this->m_hTask = 0;
    return 0;
  }

};

Client callback class and object

Client implements DWebBrowserEvents2 interface using a class ClientEvents. This class has three events or abstract methods (pure virtual functions).

/* Client callback event functions in event class */
class ClientEvents: public DWebBrowserEvents2
{
  public: void DownloadBegin() {
    cout << "Callback: DownloadBegin() ";
  }
  void DownloadComplete() {
    cout << "Callback: DownloadComplete() ";
  }
  void ProgressChange(long Progress, long ProgressMax) {
    cout << "Callback: ProgressChange "
         << (int)((float)(Progress * 100) / ProgressMax) << "%\n";
  }

};

/* Client's event object */
ClientEvents myEvents;
int main(int argc, char * argv[])
{
  CInternetExplorer * pWebBrowser;
  ULONG nCookie;

  /* Object Creation */
  pWebBrowser = CInternetExplorer::CreateInstance();

  /* Pass callback object address to server */
  pWebBrowser->Advice(&myEvents, &nCookie);

  /* call a function which calls back our events */
  pWebBrowser->Navigate("http://www.google.com/");

  /* Wait till all events are called by server */
  getch();

  /* deassign callback object */
  pWebBrowser->Unadvice(nCookie);

  /* Release object */
  pWebBrowser->Release();
  return 0;
}

COM callback output

Advice: callback object(0x00478B50)
called Navigate(http://www.google.com/)
Callback: DownloadBegin()
Callback: ProgressChange 0%
Callback: ProgressChange 10%
Callback: ProgressChange 20%
Callback: ProgressChange 30%
Callback: ProgressChange 40%
Callback: ProgressChange 50%
Callback: ProgressChange 60%
Callback: ProgressChange 70%
Callback: ProgressChange 80%
Callback: ProgressChange 90%
Callback: ProgressChange 100%
Callback: DownloadComplete()
Unadvice: callback object(deassigned)
Press any key to continue

Web Browser Control events

This is the actual DWebBrowserEvents2 event disp interface taken from IDL file. It has many events but we are showing only these three events.

#define DISPID_PROGRESSCHANGE     108
#define DISPID_DOWNLOADCOMPLETE   104
#define DISPID_DOWNLOADBEGIN      106

[
  uuid(34A715A0-6587-11D0-924A-0020AFC7AC4D), /* IID_DWebBrowserEvents2 */
  helpstring("Web Browser Control events interface"),
  hidden
]
dispinterface DWebBrowserEvents2
{
properties:
  methods:

  [id(DISPID_PROGRESSCHANGE),
  helpstring("Fired when download progress is updated."), helpcontext(0x0000)]

  void ProgressChange([in] long Progress, [in] long ProgressMax);

  [id(DISPID_DOWNLOADCOMPLETE),
  helpstring("Download of page complete."), helpcontext(0x0000)]

  void DownloadComplete();

  [id(DISPID_DOWNLOADBEGIN),
   helpstring("Download of a page started."), helpcontext(0x000)]
   
  void DownloadBegin();

}
  
[
uuid(0002DF01-0000-0000-C000-000000000046), /* CLSID_InternetExplorer */
helpstring("Internet Explorer Application."),
]

coclass InternetExplorer
{
  [default]     interface   IWebBrowser2;
  interface   IWebBrowserApp;
  [default, source] dispinterface DWebBrowserEvents2;
  [source]      dispinterface DWebBrowserEvents;
}

Web Browser Control server

Let us understand the server side code of Web Browser Control. Here are some imaginary source code of QueryInterface/CreateInstance(), FindConnectionPoint, Advise, Unadvise. Here we have

HRESULT CInternetExplorer::QueryInterface(REFIID riid, void** ppv)
{
  if(riid == IID_IUnknown) {
    *ppv = (void*)this;
  } else if(riid == IID_IWebBrowser2) {
    *ppv = (void*)this;
  } else if(riid == IID_IConnectionPointContainer) {
    *ppv = (IConnectionPointContainer*)this;
  } else if(riid == IID_IConnectionPoint) {
    *ppv = (IConnectionPoint*)this;
  } else {
        *ppv = NULL;
        return E_NOINTERFACE;
  }
  AddRef();
  return S_OK;
}

HRESULT CInternetExplorer::FindConnectionPoint(REFIID riid, 
    IConnectionPoint** ppCP)
{
  /* If we support the source interface, then QI for *./ /* the connection point. */

  if(riid == IID_DWebBrowserEvents2) {
    return QueryInterface(IID_IConnectionPoint, 
                          (void**)ppCP);
  }

  return E_NOINTERFACE;
}

HRESULT CInternetExplorer::Advise(IUnknown* pUnknown, DWORD* pdwCookie)
{
  /* Hard-code the cookie value. */
  *pdwCookie = 1;

  /* Get a pointer to the sink's source interface. */
  return pUnknown->QueryInterface(IID_DWebBrowserEvents2, 
                                  (void**)&g_pWebBrowserEvents2);
}

HRESULT CInternetExplorer::Unadvise(DWORD dwCookie)
{
  /* Ignoring dwCookie; only one connection is */
  g_pWebBrowserEvents2->Release();
  return NOERROR;
}

CIExplorerEvents client class

Client should implement a class from IDispatch and should override QueryInterface and Invoke. Server queries the interface of interface IID IID_IDispatch during advice function is called. Server then triggers the events by calling back the Invoke with dispid as DISPID_PROGRESSCHANGE or DISPID_DOWNLOADCOMPLETE or DISPID_DOWNLOADBEGIN. Client should handle these event codes and call its appropriate UI events.

class CIExplorerEvents : public IDispatch 
{
private:
  DWORD m_nref;

public:
  CSinkObjectDemoDlg *m_pDlgMain;
  
  long __stdcall QueryInterface(const struct _GUID & iid,LPVOID* ppvObject);
  unsigned long __stdcall AddRef(void);
  unsigned long __stdcall Release(void);
  long __stdcall GetTypeInfoCount(unsigned int *);
  long __stdcall GetTypeInfo(unsigned int,unsigned long,struct ITypeInfo ** );
  long __stdcall GetIDsOfNames(const struct _GUID &,
  unsigned short ** ,unsigned int,unsigned long,long *);
  long __stdcall Invoke(long,
  const struct _GUID &,
    unsigned long,
    unsigned short,
    struct tagDISPPARAMS *,
    struct tagVARIANT *,
    struct tagEXCEPINFO *,
    unsigned int *);
};
CIExplorerEvents::CIExplorerEvents()
{
    m_nref = 0;
}
long __stdcall CIExplorerEvents::QueryInterface(
  const struct _GUID & iid,
  LPVOID* ppvObject
)
{ 
  /* Match the interface and return the proper pointer */
  if ( iid == IID_IUnknown) {

    *ppvObject = dynamic_cast<IUnknown*>( this );

  } else  if ( iid == IID_IDispatch) {

    *ppvObject = dynamic_cast<IDispatch*>( this );

  } else {

    /* It didn't match */
    *ppvObject = NULL;
    return E_NOINTERFACE;
  }

  /* Increment refcount and return to client */
  this->AddRef();
  
  /* Return success return S_OK; } ULONG __stdcall CIExplorerEvents::AddRef(void) { return ++m_nref; } ULONG __stdcall CIExplorerEvents::Release(void) { return --m_nref; } long __stdcall CIExplorerEvents::GetTypeInfoCount(UINT *) { return E_NOTIMPL; } long __stdcall CIExplorerEvents::GetTypeInfo( unsigned int, unsigned long, struct ITypeInfo ** ) { return E_NOTIMPL; } long __stdcall CIExplorerEvents::GetIDsOfNames( const struct _GUID &, unsigned short ** , unsigned int, unsigned long, long * ) { return E_NOTIMPL; } long __stdcall CIExplorerEvents::Invoke( long dispid, const struct _GUID &riid, unsigned long, unsigned short, struct tagDISPPARAMS *pDispParam, struct tagVARIANT *, struct tagEXCEPINFO *, unsigned int *) { switch (dispid) { case DISPID_PROGRESSCHANGE: /* Call the UI callback event */ */
      m_pDlgMain->ProgressChange(pDispParam->rgvarg[0].lVal,
                                 pDispParam->rgvarg[1].lVal);
      break;
    case DISPID_DOWNLOADCOMPLETE:
      /* Call the UI callback event */
      m_pDlgMain->DownloadComplete();
      break;
    case DISPID_DOWNLOADBEGIN:
      /* Call the UI callback event */
      m_pDlgMain->DownloadBegin();
      break;
    default:
  }
  return 0;
}

About our authors: Team EQA

You have viewed 1 page out of 67. Your COM/DCOM learning is 0.00% complete. Login to check your learning progress.

#