Evolution Programming Resources

  Projects

   Erstellen einer PropertySheet Extension mit Borland C++


Einführung

Mit der Einführung der neuen Shell von Windows 95 und NT 4.0 sind COM Objekte immer wichtiger geworden. Mußten sich früher nur OLE Programmierer mit diesem Thema auseinandersetzen, muß heute jeder der direkt mit der Shell umgeht zumindest die Grundlagen der COM Programmierung verstanden haben.
Um so mehr gilt dies, wenn die Benutzeroberfläche von Windows erweitert werden soll. Eine Erweiterung der Eigenschaften-Dialoges oder des Kontextmenüs ist nur durch die Erstellung eines eigenen COM-Objekts möglich.
Die Erweiterung des Eigenschaftendialogs um eigene Seiten ist sicher die wichtigste Anwendung der Shell-Extensions. Deshalb möchte ich mich in diesem Artikel mit der Erstellung einer solchen Erweiterung mit Hilfe von Borland C++ auseinandersetzen.

COM Objekte mit Borland C++

Leider ist gerade bei diesem wichtigen Thema die Unterstützung durch die Borland Hilfe nicht sonderlich umfangreich. Sie beschänkt sich mehr oder weniger nur auf den lapidaren Hinweis, das dieses Thema dem fortgeschritten Programmierer vorbehalten ist. Jedoch veröffentlicht Borland immer wieder auf seiner Homepage technische Artikel, unter anderem auch zu den Grundlagen von COM und zur ActiveX Programmierung.
Trotz der fehlenden Dokumentation ist die Programmierung durch das OCF (Object Component Framework) von Borland sehr einfach. Man wird dabei durch eine Reihe von Makros und Klassen unterstützt. Am wichtigsten ist dabei die Klasse TUnknown, die die Implementierung der IUnknown Methoden AddRef(), Release() und QueryInterface() übernimmt. In Zusammenarbeit mit den Makros DECLARE_COMBASESX, DEFINE_QI_BASE und DEFINE_COMBASESX lassen sich sehr einfach COM Klassen erstellen.
Nehmen wir beispielsweise an, sie wollen eine COM Klasse erstellen, die die Interfaces IShellExtInit und IShellPropSheetExt implementiert. Dazu ist einfach folgender Code nötig:

// PropSheetExt.h

// Ableiten der Interfaces
DECLARE_COMBASES2(TShellPropSheetExtI,IShellExtInit,IShellPropSheetExt);

// Ableiten der Implementierungsklasse von der Interfaceklasse
class _ICLASS TShellPropSheetExt : public TShellPropSheetExtI
{
  public:
  TShellPropSheetExt();
  virtual ~TShellPropSheetExt();

  // IShellExtInit Methoden
  STDMETHOD(Initialize)(LPCITEMIDLIST pidlFolder, LPDATAOBJECT lpdobj,
    HKEY hkeyProgID);

  // IShellPropSheetExt Methoden
  STDMETHOD(AddPages)(LPFNADDPROPSHEETPAGE lpfnAddPage, LPARAM lParam);
  STDMETHOD(ReplacePage)(UINT uPageID, LPFNADDPROPSHEETPAGE
    lpfnReplaceWith, LPARAM lParam);

  protected:
  // Dateiname, der per Initialize uebergeben wurde
  string file;
};

// PropSheetExt.cpp

#include <ocf/ocreg.h>
#include <ocf/oleutil.h>
#include <shlobj.h>
#pragma hdrstop
#include "PropSheetExt.h"

// QueryInterface fuer IShellExtInit und IShellPropSheetExt-Interfaces 
DEFINE_QI_BASE(IShellExtInit,IID_IShellExtInit.Data1);
DEFINE_QI_BASE(IShellPropSheetExt,IID_IShellPropSheetExt.Data1);

DEFINE_COMBASES2(TShellPropSheetExtI,IShellExtInit,IShellPropSheetExt);

// ... Implementierung der Methoden ...

Durch das Makro DECLARE_COMBASES2 wird eine abstrakte Interface Klasse erstellt, die von IShellExtInit, IShellPropSheetExt und TUnknown erbt. Von dieser abstrakten Klasse kann nun die Implementierungsklasse TShellPropSheetExt abgeleitet werden.
In PropSheetExt.cpp wird durch DEFINE_QI_BASE und DEFINE_COMBASESES2 die QueryInterface Methode für unsere Interface-Klasse erstellt. Durch DEFINE_QI_BASE(IShellExtInit,IID_IShellExtInit.Data1); wird eine globale Funktion mit dem Namen IShellExtInit_QueryInterface erstellt, die für eine übergebe Interface ID (IID) überprüft, ob diese mit der IID von IShellExtInit übereinstimmt. Diese Funktion wird dann im durch DEFINE_COMBASES2 erstellten Code aufgerufen.

Damit haben wir schon fast ein vollständiges COM Objekt. Die DLL, die das COM Object implementiert, muß nur noch die Funktionen DllCanUnloadNow und DllGetClassObject exportieren und implementieren. Durch die Funktion DllCanUnloadNow wird die Lebenszeit der DLL im Speicher gesteuert. Die COM Bibliothek fragt ab und zu diese Funktion ab. Wird dann true zurück gegeben, wird die DLL aus dem Speicher entfernt. Dies darf natürlich nicht geschehen, wenn es noch Programme gibt, die einen Interface-Pointer auf ein Objekt innerhalb diese DLL haben. Über die Funktion DllGetClassObject können die COM Biblioteken einen Zeiger auf ein IClassFactory Interface anfordern, um Objekte innerhalb der DLL zu erstellen.
Sowohl die Implementierung der ClassFactory als auch die Überwachung der Lebenszeit übernimmt für uns eine Klasse namens TRegistrar. Sie Implementiert auch den Selbstregistierungscode unseres COM Objekts. Um unsere Klasse zum vollständigen COM Objekt zu machen ist nur noch folgende Ergänzung von PropSheetExt.cpp nötig:

TPointer<TRegistrar>    registrar;  // globales Registrar-Objekt

// Registrationsdaten (Beschreibung und clsid)
BEGIN_REGISTRATION(PropSheetExtReg)
  REGDATA(clsid,       "{4E3F1F40-86E8-11D1-BC15-08002B3F06BE}" )
  REGDATA(description, "Test Property-SheetExt")
END_REGISTRATION

int WINAPI DllEntryPoint(HINSTANCE hInstance, uint32 reason, LPVOID)
{
  try {
    if(reason == DLL_PROCESS_ATTACH) {
      // Erzeugen der globalen Daten (werden, da sie in TPointer-Objekten
      // gespeichert werden, zusammen mit der DLL geloescht)

      registrar = new TRegistrar(PropSheetExtReg,
                                 TOcComFactory<TShellPropSheetExt>(),
                                 string(""),
                                 hInstance);
    }
  }
  catch(xmsg &msg) {
    MessageBox(0, msg.why().c_str(), "EXCEPTION",
               MB_OK|MB_TASKMODAL);
    return 0;
  }
  catch(...) {
    MessageBox(0, "Exception", "EXCEPTION",
               MB_OK|MB_TASKMODAL);
    return 0;
  }

  return 1;
}

Wie Sie sehen, wird innerhalb der DllEntryPoint Funktion ein TRegistrar Objekt erstellt. Ihm werden die Registrationsdaten übergeben und ein TOcComFactory Objekt. Dies implementiert die ClassFactory, erstellt also jedesmal ein Objekt der Klasse TShellPropSheetExt, wenn ein CoCreateInstance mit einer ClassId von {4E3F1F40-86E8-11D1-BC15-08002B3F06BE} durchgefürt wird.
Übrigens ist es nicht nötig, das der globale Zeiger auf das TRegistrar Objekt "registrar" heißt. Sie können ihm jeden beliebigen Namen geben. Das funktioniert deshalb, da sich alle TRegistrar Objekte automatisch verketten und es statische Funktionen gibt, diese Kette zu durchlaufen.

Registrierung der Property Sheet Extension

Ein Property Sheet Handler muß nicht nur als COM Komponente registiert werden. Zusätzlich muß dem Windows Explorer muß mitgeteilt werden, für welche Dateien, diese Property Sheet Extension angezeigt werden soll. Dies geschieht ebenfalls durch Einträge in der Registierungsdatenbank. Soll beispielsweise eine Property Sheet Extension mit der CLSID 4E3F1F40-86E8-11D1-BC15-08002B3F06BE für Dateien mit der Dateierweiterung .WAD angezeigt werden, dann sind folgende Einträge in der Registrierungsdatenbank notwendig:#

[HKEY_CLASSES_ROOT\.WAD]
@=WadFile

[HKEY_CLASSES_ROOT\WadFile]
@=WadFile Description
[HKEY_CLASSES_ROOT\WadFile\shellex\PropertySheetHandlers]
@=WadPage
[HKEY_CLASSES_ROOT\WadFile\shellex\PropertySheetHandlers\WadPage]
@={4E3F1F40-86E8-11D1-BC15-08002B3F06BE}

Diese Daten sollten am besten während der Selbstregistrierung der COM Komponente in die Datenbank eingetragen werden. Dazu muß eine eigene Klasse von TRegistrar abgeleitet werden.

class TPropSheetExtRegistrar : public TRegistrar
{
  public:
  TPropSheetExtRegistrar(TRegList& regInfo, TComponentFactory callback, 
    string& cmdLine, HINSTANCE hInst);
  
  void ProcessCmdLine(string &cmdLine)
  {
    TRegistrar::ProcessCmdLine(cmdLine);
    
    if(IsOptionSet(amRegServer)) {
      // Komponente wurde aufgefordert sich zu registrieren.
      // Eintragen in Datenbank.
      
      // Zusaetzlichen Registierungscode HIER eintragen.
    } else if(IsOptionSet(amUnregServer)) {
      // Komponente wurde aufgeforder die Registierungsdaten aus der 
      //Datenbank zu entfernen.

      // Zusaetzlichen Deregistierungscode HIER eintragen.
    }
  }
};

Dies funktioniert, da durch die OCF Implementierung von DllRegisterServer() und DllUnregisterServer() die Methode ProcessCmdLine von des globalen Registrar Objekts aufgerufen wird.

Implementierung von IShellExtInit

Diese Schnittstelle besitzt nur eine einzige Methode: Initialize. Ihr wird ein IDataObject Schnittstellenzeiger übergeben. Über diese Schnittstelle kann ermittelt werden, für welche Datei die Property Page angezeigt werden soll.


// Initialize
// Bekommt einen Zeiger auf ein IDataObject-Interface uebergeben. Auf
// diese Weise koennen die selectierten Dateien ermittelt werden. Fuer
// eine PropertySheet-Extension ist i.A. jedoch nur der erste Dateinamen
// von Interesse.
STDMETHODIMP TShellPropSheetExt::Initialize(LPCITEMIDLIST /*pidlFolder*/,
  LPDATAOBJECT lpdobj, HKEY /*hkeyProgID*/)
{
  FORMATETC fmt = {
    CF_HDROP,          // CF_HDROP Format benutzen
    NULL,              // kein spezielles Device
    DVASPECT_CONTENT,  // Embedded-Objekt
    -1,                // ist -1 fuer DVASPECRT_CONTENT
    TYMED_HGLOBAL      // Art des Datentransfers
  };

  STGMEDIUM medium;

  if(lpdobj == NULL)
    // kein Datenobjekt -> Fehler
    return E_FAIL;

  // Benutze das Datenobejekt, um eine Liste der Datein zu holen
  HRESULT hres = lpdobj->GetData(&fmt,&medium);

  // Fehler??
  if(FAILED(hres))
    return E_FAIL;

  // Zwischenspeicher anlegen
  char buffer[MAX_PATH];

  // Nur die erste Datei ist fuer uns interessant.
  if(DragQueryFile((HDROP)medium.hGlobal, 0, buffer, sizeof(buffer))) {
    file = buffer;
    hres = NOERROR;
  } else
    hres = E_FAIL;

  // Daten freigeben
  ReleaseStgMedium(&medium);

  return hres;
}

Implementierung von IShellPropSheetExt

Die Schnittstelle IShellPropSheetExt besitzt zwei Methoden: AddPages und ReplacePage. Im allgemeinen wird nur die Methode AddPages implementiert. ReplacePage kann E_NOTIMPL zurück geben. Die Methode AddPages bekommt einen Zeiger auf eine Funktion übergeben, mit deren Hilfe die Property Sheets eingetragen werden. Dieser Funktion wird einfach der Window Handle des Property Sheets übergeben.


      Top 
 

| © 1998 by 3rd-evolution