ATL: Firing Events from Worker Threads

WEBINAR: On-demand webcast

How to Boost Database Development Productivity on Linux, Docker, and Kubernetes with Microsoft SQL Server 2017 REGISTER >

Environment: VC6 SP4, NT4 SP3

If You have ever the problem : "VB client keep crashing when compiled and not in the IDE if it has a worker thread" then here is a possible solution.

In most cases of my work I develop ATL objects with worker threads. In the worker threads there must be often fire events for signalling thread states. The problem of firing events from worker thread is that they must enter another apartment. In this apartment the sink interfaces are not valid and thats why some clients would not be receive the events (eg VB). I found a simple solution for that. (A solution without PostMessage.)

Only the proxy code of wizard must be changed, to get the firing of events right. I use the global interface table (GIT) for my solution. "The GIT holds a list of marshaled interface pointers that, on request can be unmarshaled any number of times to any number of apartment in your process, regardless of whether the pointer is for an object or a proxy."(Professional ATL COM Programming, Dr Richard Grimes).

I wrote a new specialized class for CComDynamicUnkArray which use the GIT for accessing the sink interfaces.


Specialized class CComDynamicUnkArray_GIT

class CComDynamicUnkArray_GIT : public CComDynamicUnkArray
{
private:
 IGlobalInterfaceTable*  GIT;

public:
 CComDynamicUnkArray_GIT() : CComDynamicUnkArray()
 { 
  GIT = NULL;

  CoCreateInstance(CLSID_StdGlobalInterfaceTable, 
                   NULL, 
                   CLSCTX_INPROC_SERVER, 
                   __uuidof(IGlobalInterfaceTable), 
                   reinterpret_cast< void** >(&GIT) );
 }

 ~CComDynamicUnkArray_GIT()
 {
  //clean up the class
  clear();
  if( GIT != NULL )
  {
   GIT->Release();
  }
 }

 DWORD Add(IUnknown* pUnk);
 BOOL Remove(DWORD dwCookie);

 //The proxy code use this function to get the interface !
 CComPtr GetAt(int nIndex)
 {
  DWORD dwCookie = (DWORD)CComDynamicUnkArray::GetAt( nIndex );

  if( dwCookie == 0 )
  return NULL;

  if( CookieMap.find( dwCookie ) == CookieMap.end() )
  {
   return (IUnknown*)dwCookie;
  }

  if( GIT != NULL )
  {
   CComPtr   ppv;

   HRESULT hr = GIT->GetInterfaceFromGlobal(
    CookieMap[dwCookie], //Cookie identifying the desired global
                         //interface and its object
    __uuidof(IUnknown),  //IID of the registered global interface
    reinterpret_cast< void** >(&ppv) //Indirect pointer
                                     //to the desired interface
   );

   if( hr == S_OK )
   {
    return ppv;
   }

  //Should never be reached, a ASSERT or exception is possible
  }
  return (IUnknown*)dwCookie;
 }

 //clean up the GIT
 void clear()
 {
  CComDynamicUnkArray::clear(); 

  if( GIT != NULL )
  {
   map< DWORD, DWORD >::iterator iter;
   for (iter = CookieMap.begin(); 
        iter != CookieMap.end(); 
        ++iter )
   {
    GIT->RevokeInterfaceFromGlobal(
     iter->second      //Cookie that was returned from 
     //RegisterInterfaceInGlobal
    );
   }
  }
  CookieMap.clear();
 }

protected:
 map< DWORD, DWORD > CookieMap;
};

 inline DWORD CComDynamicUnkArray_GIT::Add(IUnknown* pUnk)
 {
  DWORD Result = CComDynamicUnkArray::Add( pUnk );

  HRESULT hr;
  DWORD   pdwCookie = 0;
  if( GIT != NULL )
  {
   hr = GIT->RegisterInterfaceInGlobal(
    pUnk,               //Pointer to interface of type riid
                        //of object containing global interface
    __uuidof(IUnknown), //IID of the interface to be registered
    &pdwCookie          //Supplies a pointer to the cookie that
                        //provides a caller in another apartment
                        //access to the interface pointer
   );
  }
  if( hr == S_OK )
  {
  CookieMap[Result] = pdwCookie;
  }

 return Result;
 }

 inline BOOL CComDynamicUnkArray_GIT::Remove(DWORD dwCookie)
 {
  BOOL Result = CComDynamicUnkArray::Remove( dwCookie );

  if( GIT != NULL )
  {
   if( CookieMap.find( dwCookie ) != CookieMap.end() )
   {
    GIT->RevokeInterfaceFromGlobal(
     CookieMap[dwCookie] //Cookie that was returned from 
                         //RegisterInterfaceInGlobal
		);

   CookieMap.erase(dwCookie);
   }
  }
  return Result;
 }

Changes in proxy generated files:

Change:

template <class T>
class CProxy_ISchedulerEvents : 
public IConnectionPointImpl<T, 
                            &DIID__ISchedulerEvents, 
                            CComDynamicUnkArray>

To

#include "CProxyEvent.h"

template <class T>
class CProxy_ISchedulerEvents : 
public IConnectionPointImpl<T, 
                            &DIID__ISchedulerEvents, 
                            CComDynamicUnkArray_GIT>

In the proxy generated event method you must make a replace as follow:

Change:

VOID Fire_Activate(IBundle * pBundle, BSTR ActivationTime)
{
 T* pT = static_cast< T* >(this);
 int nConnectionIndex;
 CComVariant* pvars = new CComVariant[2];
 int nConnections = m_vec.GetSize();

 for (nConnectionIndex = 0; 
      nConnectionIndex < nConnections; 
      nConnectionIndex++)
 {
  pT->Lock();
  CComPtr< IUnknown > sp = m_vec.GetAt(nConnectionIndex);
  pT->Unlock();
  IDispatch* pDispatch = reinterpret_cast< IDispatch* >(sp.p);
  if (pDispatch != NULL)
   {
   pvars[1] = pBundle;
   pvars[0] = ActivationTime;
   DISPPARAMS disp = { pvars, NULL, 2, 0 };
   pDispatch->Invoke(0x1, IID_NULL, LOCALE_USER_DEFAULT, 
                     DISPATCH_METHOD, &disp, NULL, NULL, NULL);
  }
 }
 delete[] pvars;
}

To:

VOID Fire_Activate(IBundle * pBundle, BSTR ActivationTime)
{
 T* pT = static_cast< T* >(this);
 int nConnectionIndex;
 CComVariant* pvars = new CComVariant[2];
 int nConnections = m_vec.GetSize();

 for (nConnectionIndex = 0; 
      nConnectionIndex < nConnections; 
      nConnectionIndex++)
 {
  pT->Lock();
  CComPtr< IUnknown > sp = m_vec.GetAt(nConnectionIndex);
  pT->Unlock();
  CComQIPtr< IDispatch > pDispatch( sp );
  if (pDispatch != NULL)
  {
   pvars[1] = pBundle;
   pvars[0] = ActivationTime;
   DISPPARAMS disp = { pvars, NULL, 2, 0 };
   pDispatch->Invoke(0x1, IID_NULL, LOCALE_USER_DEFAULT, 
                     DISPATCH_METHOD, &disp, NULL, NULL, NULL);
  }
 }
 delete[] pvars;
}

Simple make a replace with IDispatch* pDispatch = reinterpret_cast< IDispatch* >(sp.p) to CComQIPtr< IDispatch > pDispatch( sp ), that's all !

Downloads

Download CEventProxy.h source - 1 Kb
Download example source (inludes VBasic client) - 62 Kb


Comments

  • Developer

    Posted by Geoff on 05/20/2014 01:06pm

    Bug here. In the Add() method you need to change if( hr == S_OK ) { CookieMap[Result] = pdwCookie; } to if( hr == S_OK ) { CookieMap[(DWORD)pUnk] = pdwCookie; }

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

Top White Papers and Webcasts

  • In order for IT service providers to succeed, it's paramount that they find a competitive advantage and continually develop new ways to find additional revenue streams. IT service providers need to be able to do it all for their clients – from managing entire technology infrastructures to responding quickly to a multitude of end-user needs. With a growing number of issues to resolve and limited technicians at hand, how can IT service providers operate efficiently while providing top-notch service …

  • The software-defined data center (SDDC) and new trends in cloud and virtualization bring increased agility, automation, and intelligent services and management to all areas of the data center. Businesses can now more easily manage the entire lifecycle of their applications and services via the SDDC. This Aberdeen analyst report examines how a strong foundation in both the cloud and internal data centers is empowering organizations to fully leverage their IT infrastructure and is also preparing them to be able …

Most Popular Programming Stories

More for Developers

RSS Feeds

Thanks for your registration, follow us on our social networks to keep up-to-date