Programmieren mit C++

Allgemeines

ATL

Thread Events

Was kann man tun, wenn ein VB Client ein ATL-Objekt mit Arbeitsthread einsetzt, das in der IDE läuft aber als Kompilat abstürzt?

Frage

Wird ein ATL-Objekt mit einem Arbeitsthread eingesetzt, muss dieser häufig Events auslösen, um den Threadstatus anzuzeigen. Dabei entsteht das Problem, dass die Events in ein anderes Apartment eintreten müssen, in dem die Sink-Interfaces nicht gültig sind, sodass einige Clients die Events nicht erhalten – u.a. VB.

Nachfolgend wird für dieses Problem eine einfache Lösung vorgestellt, die ohne PostMessage() auskommt. Dazu muss lediglich der vom Wizzard erzeugte Proxy-Code ausgetauscht werden, um die Events korrekt auszulösen. Der Lösungsansatz basiert auf der Global Interface Table (GIT). Die GIT speichert eine Liste der Marshalled Interface Zeiger, sodass das Marshalling für Request beliebig oft für jedes Apartment in einem Prozess aufgehoben werden kann, egal, ob der Zeiger auf ein Objekt oder Proxy verweist.

Zur einfachen Wiederverwendbarkeit des Codes wurde dazu kurzerhand eine spezialisierte Klasse CComDynamicUnkArray entwickelt, die für den Zugriff auf die Sink-Interfaces auf die GIT zurückgreift. Abgeleitet ist die Klasse CComDynamicUnkArray_GIT von CComDynamicUnkArray.

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

Der Konstruktor der Klasse entspricht dem Standard.

public:
 CComDynamicUnkArray_GIT() : CComDynamicUnkArray()
 { 
  GIT = NULL;
  CoCreateInstance(CLSID_StdGlobalInterfaceTable, 
                   NULL, 
                   CLSCTX_INPROC_SERVER, 
                 __uuidof(IGlobalInterfaceTable), 
                   reinterpret_cast< void** >(&GIT) );
 }

Hauptaufgabe des Destruktors ist das Aufräumen der Klasse, insbesondere der Aufruf von clear().

 ~CComDynamicUnkArray_GIT()
 {
  clear();
  if( GIT != NULL )
  {
   GIT->Release();
  }
 }

Die Methoden Add() und Remove() werden inline implementiert und sind hier zunächst nur als Deklaration enthalten.

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

Zum Ermitteln des Interfaces verwendet der Proxy die Funktion GetAt(), die einen Zeiger vom Typ CComPtr returniert. Intern reicht die Methode den Aufruf zunächst an die Basisklasse weiter, die bereits eine vollständige Implementation von GetAt() besitzt. Mit den nachfolgenden Verifikationen bekommt der modifizierte Code dann nachfolgende Gestalt.

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;

Für das Ermitteln der Interface-Referenz benötigt GetInterfaceFromGlobal() im ersten Parameter das gewünschte globale Interface und sein Objekt, während die IID des registrierten globalen Interface im zweiten Parameter übergeben wird. Im dritten Parameter zeigt schließlich noch der indirekte Zeiger auf das gewünschte Interface.

   HRESULT hr = GIT-> GetInterfaceFromGlobal(
                          CookieMap[dwCookie], 
                        __uuidof(IUnknown), 
                          reinterpret_cast< void** >(&ppv) );

Im Erfolgsfall wird ppv als Interface-Referenz returniert.

   if( hr == S_OK )
   {
    return ppv;
   }
 
  }
  return (IUnknown*)dwCookie;
 }

Die Aufräumarbeiten leisten die Methode clear(), die das von RegisterInterfaceInGlobal() returnierte Cookie wieder freigibt.

void clear()
{
  CComDynamicUnkArray::clear(); 
 
  if( GIT != NULL )
  {
   map< DWORD, DWORD >::iterator iter;
   for (iter = CookieMap.begin(); 
        iter != CookieMap.end(); 
        ++iter )
   {
    GIT->RevokeInterfaceFromGlobal( iter->second );
   }
  }
  CookieMap.clear();
}

Die zuvor bereits verwendete CookieMap ist als Map im geschützten Bereich deklariert.

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

Für die Vervollständigung der Klasse fehlen jetzt noch die beiden inline-Funktionen Add() und Remove(). Auch diese können auf Services der Basisklassen zurückgreifen, sodass sich die komplette Methode Add() vereinfacht zu:

inline DWORD CComDynamicUnkArray_GIT::Add(IUnknown* pUnk)
{
  DWORD Result = CComDynamicUnkArray::Add( pUnk );
 
  HRESULT hr;
  DWORD   pdwCookie = 0;

Die Parameter der Methode RegisterInterfaceInGlobal() übernehmen diesmal in punk den Zeiger auf das Interface vom Typ riid des Objekt enthaltenden globalen Interface, sowie die IID des zu registrierenden Interface und im letzten Parameter pdwCookie einen Zeiger auf das Cookie, das einem Aufrufer in einem anderen Apartment Zugriff auf den Interface-Zeiger erlaubt.

  if( GIT != NULL )
  {
   hr = GIT-> RegisterInterfaceInGlobal( pUnk, 
                                       __uuidof(IUnknown), 
                                         &pdwCookie );
  }
  if( hr == S_OK )
  {
  CookieMap[Result] = pdwCookie;
  }
 
 return Result;
}

Die Methode Remove() übergibt in CookieMap[dwCookie] das von RegisterInterfaceInGlobal() returniert Cookie und zerstört es.

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] );
 
    CookieMap.erase(dwCookie);
   }
  }
  return Result;
}

In den vom Wizzard generierten Proxy-Dateien sind die nachfolgenden Änderungen vorzunehmen. Zunächst wird der Block

template 
class CProxy_ISchedulerEvents : 
public IConnectionPointImpl

durch die nachfolgende Deklaration ersetzt.

#include "CProxyEvent.h"
 
template 
class CProxy_ISchedulerEvents : 
public IConnectionPointImpl

Des Weiteren ist die erzeugte Event-Methode Activate() durch den neuen, nachfolgenden Code zu ersetzen.

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, &IID_IDispatch > pDispatch( sp.p );

  if (pDispatch.p != 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;
}

Zu guter Letzt sind noch zwei kleine Ersetzungen vorzunehmen. Die Zeile

IDispatch* pDispatch = reinterpret_cast< IDispatch* >(sp.p) 

wird ersetzt durch

CComQIPtr< IDispatch, &IID_IDispatch > pDispatch( sp.p )

und

pDispatch != NULL 

durch die geänderte Anweisung

pDispatch.p != NULL

Nach diesen Änderungen ist das Problem der Eventauslösung unter ATL Arbeitsthreads gelöst.

Lösung





Sachgebiet


© 2009-2012 by Alojado Publishing. Alle Rechte vorbehalten. Ausgewiesene Marken gehören ihren jeweiligen Eigentümern.
Mit der Benutzung dieser Seite erkennen Sie die Nutzungsbedingungen und die Datenschutzerklärung an. Der Betreiber übernimmt keine Haftung für den Inhalt verlinkter externer Internetseiten.
Seite erzeugt 2012-05-20 03:23:05 von textarchiv.alojado.de