Evolution Programming Resources |
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.
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:
|
|
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:
|
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.
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:#
|
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.
|
Dies funktioniert, da durch die OCF Implementierung von DllRegisterServer() und DllUnregisterServer() die Methode ProcessCmdLine von des globalen Registrar Objekts aufgerufen wird.
Implementierung von IShellExtInitDiese 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.
|
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.