Microsoft DCOM

Autor: Michael Stal

 


Gegenstand dieses Artikels ist das zu CORBA  konkurrierende Modell Microsoft DCOM (Distributed Component Object Model). Microsofts COM (Component Object Model) hat sich ursprünglich auf komponentenbasierte Softwareentwicklung in Windows-Umgebungen fokussiert, versteht sich inzwischen aber ebenfalls als Basistechnologie für verteilte Systeme. Das Ziel des Softwaregiganten aus Redmond lautet dabei schlicht und einfach "COM überall", sei es im Betriebssystem oder im Internet. Die meisten modernen Microsoft-Technologien, egal ob ActiveX, DNA oder OLE, basieren letztendlich auf COM. Grund genug, diese Technologie einmal näher zu durchleuchten.

Historie

Auch in Bezug auf Microsofts DCOM ist ein Rückblick auf die frühe Entstehungsgeschichte hilfreich. Die ursprüngliche Antriebsfeder bestand in der Problematik, eine Kommunikation unabhängig voneinander entwickelter Windows-Applikationen zu ermöglichen. Dadurch lassen sich beispielsweise Daten zwischen verschiedenen Anwendungen über die Zwischenablage transportieren. Auch für die Realisierung dokumenten-zentrierter Ansätze ist eine Kommunikationsinfrastruktur notwendig. Oft zitiertes Beispiel in diesem Kontext ist das Einbetten einer Excel-Tabelle in ein Word-Dokument. Bei Doppelklick des Benutzers auf die eingebettete Tabelle, startet die Excel-Applikation automatisch im Hintergrund und übernimmt die Kontrolle über die grafische Oberfläche. Zu diesem Zweck bedarf es eines standardisierten Verfahrens zur Kommunikation von Excel und Word, zwei völlig unabhängig voneinander enwickelten Applikationen. In der ersten Version von OLE (Object Linking & Embedding), Microsofts Architektur für dokumenten-zentriertes Arbeiten, bildete DDE (Dynamic Data Exchange) die zugrundeliegende Kommunikationsschicht. Leider erwies sich DDE als komplex, langsam und zudem unflexibel. Deshalb konnte es in den Entwicklungsschmieden der Softwarehersteller kaum Fuß fassen. Es bestand somit erneuter Handlungsbedarf. Beim genaueren Blick auf die Problematik erkannte Microsoft, daß sich hinter der Kommunikation von Windows-Anwendungen ein wesentlich allgemeineres Problem verbirgt. Im Grunde genommen soll eine Applikation die Funktionalität beliebiger binärer Komponenten in einer objektorientierten Art und Weise nutzen. Dies unabhängig von den verwendeten Implementierungssprachen und ebenso vom Aufenthaltsort der beteiligten Akteure. Microsofts Ingenieure haben deshalb in OLE 2 als Fundament das sogenannte Component Object Model (COM) entwickelt, das die einheitliche Interaktion beliebiger Komponenten ermöglicht. Während COM anfänglich nur in der Lage war, die Zusammenarbeit auf einer lokalen Maschine zu unterstützen, adressiert das unlängst eingeführte DCOM (Distributed Component Object Model) nunmehr netzweite Kommunikation. Alle modernen Microsoft-Technologien, die entweder das Emblem ActiveX oder DNA (Dynamic InterNet Applications) auf ihrer Brust tragen, basieren letztendlich auf COM/DCOM als tragender Säule. Daher läßt sich ActiveX als Sammelsurium von Technologien wie OLE oder ActiveX-Controls betrachten, die auf Basis von COM/DCOM definiert sind. Dies unterstreicht die fundamentale strategische Bedeutung von COM/DCOM für Microsoft.

Aus Gründen der Vereinfachung, ist in den folgenden Abschnitten nur noch von Microsoft COM die Rede. Gemeint ist dabei aber stets die Kombination der Technologien COM und DCOM. Spätestens mit der nächsten Version von COM beziehungsweise DCOM sollten die Unterschiede ohnehin verschwinden.

Wunschzettel

Um Microsofts Komponententechnologie zu verstehen, ist zunächst ein Blick auf deren ursprünglichen Entwurfsziele aufschlußreich. Bei der Entwicklung bildeten folgende Anforderungen die Antriebsfeder:

Um diese Ziele zu realisieren, verwendet COM als architekturelles Prinzip das Broker-Pattern (siehe [8]). Im folgenden steht deshalb die Umsetzung der genannten Entwurfsziele auf Basis dieses architekturellen Musters im Vordergrund. Zu beachten ist dabei, daß COM im wesentlichen ein Modell darstellt. Allgemein benötigte Funktionalität steht jedoch in Form von dynamischen Bibliotheken zur Verfügung.

 

 <Abb. Broker>: COM folgt dem Broker-Architekturmuster. Clients greifen nicht direkt auf Server zu, sondern benutzen Proxies. Diese besitzen die gleichen Schnittstellen wie die Server-Objekte. Dadurch bleiben alle Aktivitäten für den Client unsichtbar, die für verteilte Kommunikation notwendig sind. Auf der Server-Seite ist der Stub das Pendant zum Proxy.

COM-Schnittstellen

Der Zugriff auf die Funktionalität von Komponenten erfolgt im Component Object Model stets über Schnittstellen. Im Gegensatz zu einem reinen objektorientierten Ansatz, kann ein COM-Objekt seine Funktionalität mit Hilfe mehrerer Schnittstellen zur Verfügung stellen, ist also nicht mehr auf eine einzelne Schnittstelle beschränkt (siehe Abb. COM-Schnittstellen). Jede Schnittstelle enthält dabei eine semantisch zusammengehörige Gruppe von Methoden. Anders betrachtet, repräsentiert jede COM-Schnittstelle eine mögliche Rolle oder Facette eines Objekts. Zwischen dem Anbieter und dem Nutzer einer Schnittstelle kommt dabei ein Kontrakt zustande. Der Nutzer muß die Methoden entsprechend ihrer Spezifikation aufrufen, also in der richtigen Reihenfolge und unter Berücksichtigung der für die Parameter vorgesehenen Wertebereiche. Im Gegenzug garantiert das COM-Objekt, die Schnittstelle gemäß ihrer Spezifikation zu implementieren, also zum Beispiel Methoden wie save() zum Datensichern und nicht etwa zum Löschen zu verwenden. Der Zugriff auf COM-Objekte durch Clients erfolgt dabei stets über Schnittstellenzeiger. Ein direkter Zugriff auf das COM-Objekt ist nicht vorgesehen.

Vergleichen lassen sich COM-Schnittstellen mit abstrakten Klassen in C++, deren Methoden allesamt als "pure virtual" spezifiziert sind. Java-Schnittstellen entsprechen ihren Pendants in Microsoft COM weitestgehend, weshalb Java eine ideale Plattform für die COM-Programmierung darstellt. COM-Klassen sind von ein oder mehreren Schnittstellen abgeleitet. COM-Objekte wiederum lassen sich als Instanzen von COM-Klassen verstehen.

Der eigentliche Clou in der COM-Architektur besteht darin, daß jede COM-Schnittstelle von einer universellen Basischnittstelle IUnknown abgeleitet ist und damit deren Methoden erbt:

 

interface IUnknown {
virtual HRESULT QueryInterface(IID& iid, void **ppv) = 0;
virtual ULONG AddRef() = 0;
virtual ULONG Release() = 0;
};

Diese universelle Schnittstelle bestimmt somit die generische Basisfunktionalität aller COM-Schnittstellen. Wie bereits erläutert, exportiert ein COM-Objekt seine Funktionalität über mehrere Schnittstellen, wobei der Client über Schnittstellenzeiger nur indirekten Zugriff auf die eigentlichen Schnittstellenimplementierungen erlangt. Wie aber kann ein Client Zugriff auf die gesamte Funktionalität eines COM-Objekts erhalten? Die erste Möglichkeit bestünde darin, jedem Client stets sämtliche Schnittstellenzeiger des jeweiligen COM-Objekts zur Verfügung zu stellen. Dies verstößt jedoch gegen das Entwurfsziel der Robustheit, zumal daraus eine enge Kopplung zwischen Client und COM-Objekt resultieren würde. Daher beschreitet COM an dieser Stelle einen anderen Weg. Ist ein Client bereits im Besitz eines Schnittstellenzeigers, so kann er die aus der Schnittstelle IUnknown ererbte Methode QueryInterface() nutzen, um einen Zeiger auf eine beliebige weitere Schnittstelle zu erlangen. Im ersten Parameter (Typ IID&) spezifiziert er zu diesem Zweck die gewünschte Schnittstelle. Daraufhin erhält er im zweiten Parameter den entsprechenden Schnittstellenzeiger zurückgeliefert, sofern das aufgerufene COM-Objekt tatsächlich die angegebene Schnittstelle implementiert. Andernfalls erfolgt die Rückmeldung eines Fehlerstatus. Auf diese Art kann ein Client ein beliebiges COM-Objekt danach befragen (Interface Negotiation), ob dieses eine bestimmte Funktionalität implementiert. Beispielsweise, ob das COM-Objekt die Schnittstelle IPersistFile zur Verfügung stellt, mit der sich COM-Objekte persistent auf eine Datei sichern lassen. Implementieren unterschiedliche COM-Objekte somit dieselbe COM-Schnittstelle, so lassen sie sich bezüglich dieser Schnittstelle polymorph nutzen. Hieraus ist ersichtlich, daß im Gegensatz zu einem weit verbreiteten Vorurteil Polymorphie nicht an Code-Vererbung gebunden ist.

<Abb COM-Schnittstellen>: In COM ist der Zugriff auf Funktionalität eines Server-Objekts nur über Schnittstellen mšglich. Ein Benutzer greift dabei über Schnittstellenzeiger zu, die auf eine Tabelle von Methodenzeigern verweisen. Das physikalische Layout dieser virtuellen Tabelle schreibt Microsoft fest vor.

Die beiden weiteren Methoden von IUnknown , AddRef() und Release(), dienen dem Lebenszyklus-Management von COM-Objekten. Sobald eine Anwendung über einen neuen Schnittstellenzeiger Zugriff auf ein COM-Objekt erlangt, ruft es die Methode AddRef() auf. Dadurch erhält das Objekt Kenntnis über den neuen Nutzer und inkrementiert einen internen Referenzzähler. Benötigt der Client die Schnittstelle nicht mehr, ruft es über den jeweiligen Schnittstellenzeiger Release()auf. In Reaktion darauf dekrementiert das COM-Objekt seinen internen Referenzzähler. Fällt dieser auf 0, läßt sich das Objekt mangels Clients aus dem Speicher entfernen. Da dieser Mechanismus sich auf das kooperative und korrekte Verhalten der beteiligten Clients verläßt, lassen sich Fehlersituationen nicht ausschließen. Ruft ein Client beispielsweise zu oft Release() auf, kann dies durch Freigabe des Objekts zu ungültigen Zeigern in anderen Clients führen.

Binäre Interoperabilität

Bisher standen die Konzepte von COM-Schnittstellen im Vordergrund. Ein Client kann demnach über COM-Schnittstellen auf die Funktionalität beliebiger COM-Objekte zugreifen. Die Implementierung dieser Mechanismen wäre weitgehend trivial, wenn es sich um eine einzelne Applikation und um eine festgelegte Programmiersprache handeln würde. Im Falle von COM liegen Clients und Objekte jedoch als unabhängige binäre Anwendungen vor, deren Realisierung in unterschiedlichen Programmiersprachen erfolgen kann. Wie läßt sich in diesem Kontext Interoperabilität erzielen? COM legt die physikalische Struktur von sogenannten virtuellen Tabellen fest, die auf die eigentlichen Methoden verweisen. Der Zugriff auf die von einem COM-Objekt implementierte Schnittstelle geschieht indirekt über einen Schnittstellenzeiger. Dieser wiederum verweist auf die Tabelle von Methodenzeigern (siehe Abb. COM-Schnittstellen), die ihrerseits die eigentlichen Implementierungen referenzieren. Diese doppelte Indirektion entspricht dem Vorgehen des Microsoft C++-Compilers beim Zugriff auf virtuelle Methoden abstrakter Basisklassen. In C++ verweisen Zeiger auf virtuelle Tabellen, die wiederum Methodenzeiger beinhalten. Dadurch erst ist ein dynamischer Zugriff auf Schnittstellen von COM-Objekten möglich. Wenn der Hersteller einer Programmierumgebung nun den Zugriff auf COM-Objekte ermöglichen möchte, muß er in seinem Produkt das physikalische Layout von virtuellen Tabellen nachbilden. Er kann dies aber vor dem Entwickler verbergen, indem er den Zugriff auf COM-Objekte derart abstrahiert, daß diese wie native Objekte der jeweiligen Programmiersprache erscheinen. Exakt dieses Vorgehen findet sich in Programmiersystemen wie Visual Basic, Delphi, Visual J++ oder Powerbuilder. Naturgemäß ist für die Hersteller von C++-Compilern der Zugriff auf COM-Objekte am leichtesten zu realisieren. Immerhin orientiert sich COM an deren Schema.

Damit allein ist es aber noch nicht getan. Würden in einer Tabelle die Methodenzeiger direkt auf die Implementierung der Schnittstelle im COM-Objekt verweisen, wäre lediglich ein Zugriff auf lokale Objekte möglich, deren Realisierung iin Gestalt dynamisch ladbarer Bibliotheken (DLLs) vorliegt – diese werden zur Laufzeit dynamisch in den Adreßraum des Client eingebunden. Mit Hilfe sogenannter Proxies läßt sich diese Einschränkung jedoch umgehen. Möchte ein Client auf ein Objekt zugreifen, das sich in einem anderen Prozeß befindet und zum Beispiel als ausführbares Programm vorliegt, lassen sich sogenannte Proxies einsetzen (siehe Abb. Broker) . Ein Proxy implementiert dabei exakt dieselben Schnittstellen wie das ihm zugeordnete COM-Objekt, liegt aber im Gegensatz zu dem eigentlichen Zielobjekt als dynamisch ladbare Bibliohek vor. Die Methodenzeiger des Clients verweisen nicht mehr auf die eigentliche Schnittstellenimplementierung des COM-Objekts, sondern stattdessen auf die Schnittstellenimplementierung des Proxy-Objekts. Bei einem Methodenaufruf erfolgt dementsprechend – für den Aufrufer unsichtbar - der Aufruf einer Methode im Proxy statt des direkten Aufrufs der eigentlichen Methodenimplementierung. Aufgabe des Proxy ist die Einrichtung eines Kommunikationskanals mit dem jeweiligen COM-Objekt, die Konvertierung der Parameter (Marshaling), die Weiterleitung der Anfrage und die Rückmeldung der Ergebnisse. Die Kommunikation des Proxy mit dem COM-Objekt erfolgt ebenfalls nicht direkt im COM-Objekt sondern über ein vorgeschaltetes Stub-Objekt. Ein Stub-Objekt befindet sich im Adreßraum des COM-Objekts und tritt diesem gegenüber als Client in Erscheinung. Aufgabe des Stubs ist der Verbindungsaufbau mit dem Client, der Empfang von Aufrufen, das Entpacken von Parametern (Demarshaling) und der eigentliche Aufruf des COM-Objekts (Dispatching). Für die Kommunikation von Proxies und Stubs auf derselben Maschine verwendet COM LRPCs (Lightweigh Remote Procedure Calls), die auf gemeinsamen Speicher basieren und damit sehr gute Performanz aufweisen. Für Kommunikation über Maschinengrenzen finden entfernte Methodenaufrufe (RPCs) Verwendung. Wie bereits erläutert: All dies geschieht transparent für den Programmierer.

Erwähnenswert ist in diesem Zusammenhang ein ungeschriebenes COM-Gesetz. Stellt ein Entwickler eine COM-Schnittstelle, etwa IMyInterface bereit, bleibt diese Schnittstelle für alle Zukunft unveränderbar. Möchte der Entwickler Modifikationen vornehmen, muß er dazu eine neue Schnittstelle definieren, zum Beispiel IMyInterface2. Dadurch läßt sich sicherstellen, daß sich Änderungen niemals auf existierende Clients auswirken können. In einer neuen Version eines COM-Objekts würden die Entwickler beide Schnittstellen, IMyInterface und IMyInterface2 anbieten. Während bisherige Client nach wie vor über IMyInterface auf das Objekt zugreifen würden, könnten jüngere Clients die neue Schnittstelle benutzen.

Noch eine IDL

Spätestens an dieser Stelle stellt sich dem Entwickler die Frage, wer eigentlich bei der Realisierung einer neuen COM-Schnittstelle die benötigten Proxies und Stubs sowie den Code für Marshaling und Demarshaling bereitstellen muß. Diese Aufgabe kann nur der Implementierer selbst leisten. Allerdings läßt Microsoft die COM-Entwickler nicht im Regen stehen, sondern bietet Hilfe durch die Microsoft Interface Definition Language MIDL. Mittels dieser vom DCE-Standard übernommenen und erweiterten Definitionssprache lassen sich COM-Schnittstellen, Typbibliotheken (siehe Abschnitt über Automatisierung) und COM-Klassen festlegen. Hier ein einfaches Beispiel:

// interface IFoo
[
object,
uuid(12345678-1a2b-3d4e-1111-123456789abc),
helpstring("Foo"),
pointer_default(unique)
]
interface IFoo : IUnknown
{
HRESULT set_foo([in] anytype x);
HRESULT get_foo([out] anytype *px);
};

Der Programmierer legt diese Spezifikation in einer Datei ab und startet den MIDL-Compiler, der aus dieser Definition automatisch den benötigten Code für Proxies, Stubs und Datenkonvertierung erzeugt, ebenso wie Typbibliotheken.

Henne und Ei

Nachdem die Struktur und Implementierung von COM-Objekten im Mittelpunkt der bisherigen Betrachtungen stand, stellt sich unweigerlich die Frage, wie ein Benutzer überhaupt Zugriff auf COM-Objekte erhält. Zunächst einmal ist in diesem Zusammenhang die Frage nach der eindeutigen Identifikation von Entitäten innerhalb des Component Object Model zu klären. Um Schnittstellen, Klassen und Objekte eindeutig kennzeichnen zu können, finden in COM sogenannte Globally Unique Identifiers (GUIDs) Verwendung. Diese von der OSF (Open Software Foundation) definierten Identifikatoren haben eine Länge von 128 Bit. Bei ihrer Generierung durch Werkzeuge wie guidgen findet neben der Ethernet-Adresse der installierten Netzwerkkarte zusätzlich Zeitinformation Berücksichtigung. Folglich kann es zwischen unterschiedlichen Quellen von COM-Objekten zu keinen Kollisionen kommen. Identifikatoren werden zum Beispiel für COM-Klassen verwendet (CLSIDs), für Typinformationsbibliotheken (LIBID), für Schnittstellen (IIDs) und für Schnittstellenkategorien (CATIDs). Letztere definieren Sammlungen von Schnittstellen, die eine bestimmte COM-Klasse anbietet oder von außen benötigt.

Im Zusammenhang mit COM-Objekten ist noch darauf hinzuweisen, daß im Gegensatz zu CORBA COM-Objekte keine persistente Identität besitzen. Wer alle Schnittstellenzeiger auf ein bestimmtes COM-Objekt freigibt, kann später nie mehr auf das gleiche Objekt zugreifen. Genau genommen, gibt es in COM überhaupt keine Objekte, sondern lediglich Schnittstellenzeiger auf Objekte.

Zum Adressieren und Lokalisieren von Objekten muß die zugehörige Instrastruktur entsprechende Registrierungsinformation verwalten. Im Falle von Win32-Systemen wie Windows NT oder Windows 95 steht zu diesem Zweck die sogenannte Systemregistratur zur Verfügung. In dieser hierarchisch organisierten Datenbank erfolgt die Eintragung von Information über COM-Klassen, entweder programmatisch oder manuell (siehe Abb. Registry). Als Primärschlüsel dient dabei der Klassenidentifikator (CLSID). Das COM-Laufzeitsystem kann auf Basis dieses Schlüssels feststellen, welches Programm oder welche DLL für ein angegebenes COM-Objekt zuständig ist, und dieses daraufhin aktivieren und aufrufen. Den für die Aktivierung zuständigen Service Control Manager (SCM) behandeln wir im Abschnitt über DCOM.

<Abb. Registry>: Für die Abbildung von Klassen-Identifikatoren auf konkrete Implementierungen verwendet COM die Sektion HKEY_CLASSES_ROOT in der Windows-Registry. Im obigen Beispiel gibt es für die COM-Klasse Search eine Implementierung als aus fqhrbares Programm in c:\bin\shape.exe.
 
Während COM im wesentlichen eine Spezifikation darstellt, implementiert die dynamisch ladbare Bibliothek OLE32.DLL generische API-Funktionen wie zum Beispiel CoCreateInstance(). Der Aufrufer übergibt dieser Methode die CLSID der gewünschten Klasse, worauf das Laufzeitsystem die entsprechende Implementierung mittels der Registrierungsinformation lokalisiert, das Programm oder die Bibliothek gegebenfalls startet und den gewünschten Schnittstellenzeiger an den Aufrufer zurückliefert (siehe Abb. Lokalisierung). Für den Client bleiben all diese Aktivitäten verborgen.

Den bisherigen Ausführungen zufolge ließe sich von jeder COM-Klasse lediglich eine Instanz kreieren. Eine solche Einschränkung wäre jedoch mehr als inakzeptabel. Warum sollten verschiedene Applikationen zum Beispiel nur mit einem einzigen Excel-Objekt zusammenarbeiten können. Um mehrere Objekte pro COM-Klasse instanziieren zu können, ist ein Mechanismus wie der new-Operator in C++ notwendig. Genau diese Art von Factory-Konzept schreibt Microsoft in COM zwingend vor. Dabei muß jede Implementierung einer bestimmten COM-Klasse, sei es ein ausführbares Programm eine oder Bibliothek, die Standardschnittstelle IClassFactory exportieren:

interface IClassFactory : IUnknown {
    HRESULT CreateInstance(IUnknown *punkOuter, const IID& iid, void **ppv);
    HRESULT LockServer(BOOL block);
};

Die Methode LockServer dient lediglich zu Optimierungszwecken und sei nur der Vollständigkeit halber erwähnt. Interessant an dieser Stelle ist die Methode CreateInstance(), deren Aufgabe darin besteht, Objekte einer bestimmten COM-Klasse zu erzeugen. Der Aufrufer verwendet die Methode CoGetClassObject() aus der Standardbibliothek OLE32.DLL, um unter Angabe der gewünschten CLSID einen Schnittstellenzeiger auf die jeweilige Class-Factory zu besorgen. Sodann läßt sich mit CreateInstance() über die der COM-Klasse zugeordneten Factory eine beliebige Anzahl von Objekten erzeugen, zum Beispiel:

IClassFactory *pFactory;
IFoo *pfoo;
// Factory besorgen:
HRESULT hr = CoGetClassObject(clsid, /***/ , NULL, IID_IClassFactory, (void**)&pFactory);
If SUCCEEDED(hr) {
// Mit Hilfe der Factory Objekte instanziieren:
    hr = pFactory->CreateInstance(NULL, iid, (void**) &pfoo);
};

Schaffe, schaffe – Häusle baue

Wer objektorientierte Software entwickelt, denkt dabei unweigerlich an Implementierungsvererbung als fundamentales Mittel für Wiederverwendung von Code. In verteilten Infrastrukturen wie COM oder CORBA wäre die Bereitstellung von Implementierungsvererbung zwar machbar, aber mit riesigem Aufwand verbunden. In COM findet aus genau diesem Grunde eine andere Technologie Einsatz, die ebenfalls die Wiederverwendung von Implementierungen ermöglicht. Die Rede ist hier von sogenannter Aggregation. Darunter ist zu verstehen, daß ein COM-Objekt (Aggregat) Schnittstellen eines anderen Objekts (aggregiertes Objekt) dem Client zur Verfügung stellt als wären es seine eigenen Schnittstellen (siehe Abb. Aggregation). Dies bleibt nur dann für den Client transparent, wenn sich die IUnknown-Implementierung des aggregierten Objekts mit dem umgebenden Aggregat koordiniert. Genau diese Art von Aggregationsmechansimus stellt COM bereit. Bei der Instanziierung von aggregierten Objekten müssen Aggregate Schnittstellenzeiger auf ihre eigene IUnknown-Implementierung als Parameter übergeben. Damit ein Objekt aggregierbar ist, muß es dementsprechend mit seinem umgebenden Objekt zusammen arbeiten.

<Abb Aggregation>: Mittels Aggegation kann ein umgebendes Objekt (Aussen) ein anderes Objekt (Innen) so nutzen, daß dessen Schnittstellen nach außen so erscheinen als handelte es sich um Schnittstellen des umgebenden Objekts. Auf diese Weise ist in COM auch ohne Implementierungsvererbung die Wiederverwendung von Code möglich.

 Automation

Interpretative Umgbungen und Scriptsprachen wie Visual Basic können ebenfalls über Schnittstellen auf die Funktionalität von COM-Objekten zugreifen und dadurch Applikationen wie Microsoft Word oder Visio programmatisch steuern. Wären für diese Art von Automatisierung beliebige COM-Schnittstellen anzusteuern, gestaltete sich dadurch das Leben für die Entwicklerteams von Visual Basic und anderen interpretativen Umgebungen äußerst komplex. Zu deren Arbeitserleichterung hat Microsoft deshalb eine universelle COM-Schnittstelle namens IDispatch erschaffen:

interface IDispatch : IUnknown
{
HRESULT getTypeInfoCount(/* ... */);
HRESULT getTypeInfo(/* ... */);
HRESULT getIDsOfNames(/* ... */);
HRESULT invoke(/* ... */);
};

Diese Schnittstelle erlaubt Anwendungsprogrammen wie Excel einen universellen Zugriffspunkt auf ihre automatisierbare und nach außen angebotene Funktionalität. Information über die Funktionalität stellen die jeweiligen Applikationen in sogenannten Typbibliotheken zur Verfügung, die sich über die Methode getTypeInfo() abfragen lassen. Alle verfügbaren Methoden sind mittels textueller Darstellung beschrieben. Mit Hilfe eines Aufrufs von invoke() teilt der Client mit, welche Methode er mit welchen Parametern aufrufen möchte. Der Programmierer von invoke() ist selbst dafür verantwortlich, den Aufruf an die entsprechende interne Methode weiterzuleiten (dispatching). Da invoke() ganze Zahlen als Adressierungsschema verwendet, die Typbibliothek aber textuelle Information liefert, dient die Methode getIDsOfNames zur Abbildung von Strings auf die entsprechenden Zahlenwerte. Als Wermutstropfen zu Automation bleibt anzumerken, daß zum einen die Menge der für invoke()-Aufrufe verwendbaren Parametertypen durch die in Visual Basic verwendbaren Datentypen beschränkt ist. Zum anderen erfolgt das Weiterleiten von invoke()-Aufrufen dynamisch und entsprechend langsam, ist darüber hinaus für C++-Programmierer nur äußerst aufwendig zu programmieren. Daher sind die meisten Automatisierungsschnittstellen heutzutage als sogenannte Dual Interfaces konzipiert. Diese hybriden Schnittstellen definieren eine IDispatch-Schnittstelle, machen ihre Methoden aber gleichzeitig direkt über Methodenzeiger zugreifbar. Dadurch können Visual Basic Entwickler über IDispatch auf die Operationen zugreifen, während C++-Entwickler dieselbe Schnittstelle als konventionelle COM-Schnittstelle nutzen. Automatisierung ist auch im Zusammenhang mit ActiveX-Controls von Bedeutung, zumal dort IDispatch-Schnittstellen eine fundamentale Rolle spielen, etwa als Eingangschnittstellen zur Ereignismeldung.

Verteilung im Netz

Anfangs war in COM nur vorgesehen, daß Client und Objekt sich auf derselben Maschine befinden. Erst seit Erscheinen von DCOM ist auch ein Interoperabilität über Maschinengrenzen möglich. Die Implementierung von DCOM stützt sich dabei auf das Distributed Computing Environment (DCE) der Open Software Foundation, einem Standard für entfernte Prozeduraufrufe (DCE-RPC). Da DCOM einen objektorientierten Ansatz verfolgt, hat Microsoft objektorientierte RPCs (ORPCs) auf Basis der DCE-RPCs entwickelt. ORPCs unterstützen sowohl verbindungsorientierte als auch verbindungslose Kommunikationsprotokolle. Zielobjekte lassen sich dabei über sogenannte Bindungsinformation adressieren. Beispielsweise würde sich diese Bindungsinformation auf IP-Implementierungen aus dem verwendeten Transportprotokoll, der Hostadresse und der Portnummer zusammensetzen.

Für den Programmierer bleibt weitgehend verborgen, ob die Kommunikation lokal oder entfernt erfolgt. Aus Sicht der Implementierung ergeben sich jedoch gravierende Unterschiede:

Möchte ein Client ein entferntes Objekt instanziieren, muß sich in der lokalen Registratur unter dem entsprechenden Identifikator (CLSID) ein Eintrag befinden, der den Netzwerkknoten des Zielobjekts bezeichnet. Das Laufzeitsystem des Clients und des Zielobjekts arbeiten in diesem Fall zusammen, um die gewünschte Verbindung herzustellen. Die Aktivierung des Zielobjekts erfolgt unter Kooperation der jeweiligen Service Control Manager. Hierbei handelt es sich um Systemprozesse, die bei API-Aufrufen wie zum Beispiel CoCreateInstance dazu dienen, um die Windows-Registry nach dem gewünschten Objekt zu durchsuchen, dieses gegebenfalls zu aktivieren und den entsprechenden Schnittstellenzeiger an den Aufrufer zurückzuliefern (siehe Abb : SCM).

<Abb. SCM>:Auf jedem Rechner in einem COM-System gibt es einen Service Control Manager. Dieser Systemprozeß ist dafür verantwortlich, bei der Instanziierung von Schnittstellen z.B. durch CoCreateInstance() die lokale Registry nach dem gewünschten Objekt zu durchforsten und dieses zu aktivieren. Befinden sich Client und Objekt auf separaten Maschinen, kooperieren die beteiligten SCMs.

Aus Performanzgründen stellt DCOM zusätzliche API-Funktionen bereit. Beispielsweise CoCreateInstanceEx, um gleichzeitig mehrere Schnittstellenzeiger auf dasselbe Objekt zu erhalten. Dadurch ist der Programmierer nicht mehr gezwungen, mehrere QueryInterface-Aufrufe durchzuführen, sofern er mehrere Schnittstellen auf ein Objekt benötigt.

Jeden Server, der Objekte nach außen zur Verfügung stellt, bezeichnet DCOM als Objektexporteur. Folgt der Programmierer dem sogenannten Apartment-Modell für Multithreading, so kann ein DCOM-Objekt dabei nur in einem Thread ablaufen. Bei Verwendung des "Free Threading"-Modells darf die Verarbeitung eines DCOM-Objekts sich auch über mehrere Threads erstrecken. Falls sich ein Server als Objektexporteur betätigt, ordnet ihm das Laufzeitsystem als eindeutigen Identifikator den sogenannten OXID (Object Exporter Identifier) zu. Um auf ein Server-Objekt zuzugreifen, muß ein Client den jeweiligen OXID des für das Objekt verantwortlichen Objektexporteurs spezifizieren. Auf jeder Maschine mit installiertem DCOM-Laufzeitsystem ist ein sogenannter OXID-Zuordungsprozeß damit beauftragt, die entsprechenden Abbildungen von OXIDs auf die eigentliche Bindungsinformation durchzuführen (siehe Abb. OXID-Prozess).

 

<Abb Aktivierung>: Beim Aufruf von CoCreateInstance() überprüft das Laufzeitsystem anhande des Klassenidentifikators (CLSID) in der Registry, wo sich die Implementierung des Objekts befindet. Falls dieses auf einen entfernten Rechner liegt, wird es dort von COM aktiviert und der gewünschte Schnittstellenzeiger an den Aufrufer zurückgeliefert.

Wenn entfernte Prozeduraufrufe zwischen unterschiedlichen Netzwerkknoten erfolgen, könnten die beteiligten Rechner unterschiedliche Hardwarearchitekturen aufweisen und damit unterschiedliche Fomate für die jeweiligen Datentypen. Ein naives Kopieren von Daten zwischen Aufrufer und Server-Objekt ist deshalb nicht möglich. Stattdessen müssen ausgefeiltere Algorithmen zur Konvertierung von Datentypen zum Einsatz kommen. In OSF-DCE definiert die Network Data Representation NDR ein allgemeines maschinenunabhängiges Format für Datentypen. Zu den Aufgaben der Proxies und Stubs gehört deshalb auch die Konvertierung von Datentypen vom lokalen Format in das allgemeine Format (Marshaling) sowie das Rückkonvertieren in das lokale Format (Demarshaling). In DCOM-Systemen ist es allerdings mit der Konvertierung von Standard-Datentypen nicht getan. Schließlich erlaubt DCOM auch das Versenden von Schnittstellenzeigern auf entfernt liegende Objekte. Für diesen speziellen Fall hat Microsoft folgende Regelung getroffen. Zur eindeutigen Bezeichnung eines DCOM-Schnittstellenzeigers dient eine sogenannte Objektreferenz OBJREF, die sich aus folgenden Bestandteilen zusammensetzt:

 

Wie aber ermittelt das DCOM-Laufzeitsystem aus der Objektreferenz das eigentliche Zielobjekt, etwa bei einem Methodenaufruf? Zu diesem Zweck arbeiten der OXID-Zuordnungsprozeß des Aufrufers mit dem OXID-Zuordnungsprozeß des Empfängers zusammen (siehe Abb. OXID-Prozeß). Jeder OXID-Zuordnungsprozeß enthält eine Aufrufschnittstelle IObjectExporter. Deren Namensgebung ist aus mehreren Gründen verwirrend. Zum einen handelt es sich hierbei nicht um eine COM-Schnittstelle, sondern um eine DCE-RPC-Schnittstelle. Zum anderen nicht um die Aufrufschnittstelle eines Objektexporteurs wie der Name implizieren könnte, sondern um die Schnittstelle des OXID-Zuordnungsprozesses. In IObjectExporter befinden sich die drei Methoden ResolveOxid(), SimplePing() und ComplexPing(). Sobald nun ein Client die Methode eines entfernten Objekts über eine gegebene Objektreferenz aufrufen möchte, extrahiert das Laufzeitsystem aus der Objektreferenz die physikalische Adresse des OXID-Zuordnungsprozesses des Empfängers (siehe B4). Anschließend ruft der OXID-Zuordnungsprozeß des Aufrufers die Methode ResolveOxid() der IObjectExporter-Schnittstelle des OXID-Zuordnungsprozesses auf der Zielmaschine auf. Diese Methode ermittelt für die als Parameter mitgelieferte OXID die Bindungsinformation des gewünschten, lokal verfügbaren Objektexporteurs und liefert diese Information als Ergebnis zurück. Mittels der Bindungsinformation ist das im Client befindliche Proxy-Objekt nun in der Lage, einen direkten Kommunikationskanal zum Zielobjekt zu öffnen. Dies alles geschieht wohlgemerkt völlig transparent für den Entwickler.

 

 <Abb. OXID-Prozeß>: OXID-Zuordnungsprozesse verwalten alle Identifikatoren (OXIDs) lokaler Objektexporteure. Zur gegenseitigen Zusammenarbeit implementieren sie die DCE-Schnittstelle IObjectExporter. Sobald ein Client über eine Objektreferenz auf eine entfernte Schnittstelle zugreifen möchte, befragt der Zuordnungsprozeß des Clients den Zuordnungsprozeß des entfernten Hosts (1.) nach der zugehörigen Bindungsinformation (2.). Mit dieser kann sich ein Client direkt mit dem Objekt verbinden.

Da Netzwerkkommunikation im Vergleich zu lokalen Aufrufen relativ langsam abläuft, enthält die DCOM-Architekturen einige Optimierungen. Es führte zum Beispiel zu erheblichen Netzwerkverkehr, würde jeder IUnknown-Aufruf auf ein entferntes Objekt einen entfernten Prozeduraufruf (RPC) auslösen. Um dies zu verhindern, wird jeder Objektexporteur auf der Client-Seite durch ein COM-Objekt repräsentiert, das die Schnittstelle IRemUnknown implementiert. Zudem erfolgt bereits im Proxy eine Gruppierung von IUnknown-Aufrufen. Diese werden dann an die Schnittstelle IRemUnknown nicht einzeln sondern paketweise weitervermittelt. Dadurch lassen sich zum Beispiel Aufrufe zusammenfassen. Erfolgt etwa zweimal ein AddRef() und anschließend ein Release() auf dieselbe Schnittstelle, so genügt die Übertragung eines einzelen AddRef-Aufrufs. Darüber hinaus stellt das client-seitige Proxy eine Schnittstelle IMultiQI bereit, mit der sich Schnittstellenzeiger eines entfernten Objekts mit nur einem Aufruf ermitteln lassen.

Sobald Clients abstürzen, sind sie naturgemäß nicht mehr in der Lage, Release-Aufrufe für die von ihnen benutzten Schnittstellen durchzuführen. Während sich auf einer einzelnen Maschine solche Situationen problemlos erkennen lassen, verhält sich die Situation in einem vernetzten System völlig anders. Die Frage lautet deshalb, wie DCOM sein Lebenszyklusmanagment gestalten muß, um solche Fälle zu bewältigen. DCOM zwingt Clients, regelmäßig alle Server zu kontaktieren, auf denen sie COM-Objekte nutzen. Dieses Verfahren heißt im Fach-Jargon Pinging. Empfängt ein Server über einen gewissen Zeitraum keine Ping-Aufrufe mehr von einem bestimmten Client, so geht er von dessen Absturz aus und reagiert entsprechend. Würde diese Ping-Strategie allerdings naiv implementiert, etwa separat für jede Client/Server-Beziehung, so entstünde daraus eine nicht unerhebliche Netzlast. Daher erfolgen die Ping-Aufrufe nicht zwischen einzelnen Clients und Objektexporteuren, sondern nur zwischen den OXID-Zuordnungsprozessen, von denen jede Maschine genau einen enthält. Und genau das ist die Aufgabe der bereits erwähnten Methoden SimplePing() und ComplexPing() innerhalb der RPC-Schnittstelle IObjectExporter jedes OXID-Zuordnungsprozesses.

Neben Performanzaspekten spielen in verteilten Systemen auch Sicherheitsaspekte eine wichtige Rolle. Nicht jeder Client soll jedes COM-Objekt nach Belieben aufrufen können. Ebenso sollte ein Client vor "böswilligen" Server-Objekten Schutz genießen. In DCOM ist eine Fülle unterscheidlicher Maßnahmen für Authentisierung, Authorisierung und Verschlüsselung vorgesehen, etwa die Rechtervergabe über Access Control Lists (ACL).

Zusatzdienste

In der industriellen Softwarentwiclung ist es nicht mit der Bereitstellung einer objektorientierten Infrastruktur a la DCOM getan. Zusätzlich benötigen Entwickler höhere Dienste für ihre Anwendungen. Zwar kann Microsoft DCOM an dieser Stelle nicht mit dem breiten Fundus von Objektdiensten aufwarten, der beispielsweise CORBA auszeichnet (siehe [8]). Dafür sind die wichtigsten Dienste bereits heute verfügbar:

ActiveX

Auf COM/DCOM basieren zahlreiche weitere Technologien, die sich unter dem Sammelbegriff ActiveX zusammenfassen lassen (siehe Abb. ActiveX-Schnittstellen). Beispielsweise repräsentieren ActiveX-Komponenten Automatisierungsobjekte, die in DLLs verpackt sind und zusätzliche Schnittstellen implementieren. Etwa solche, um Ereignisse an einen umgebenden Container zu melden. Hinter OLE (Object Linking & Embedding) verbergen sich wiederum verschiedene standardisierte COM-Schnittstellen, die die Zusammenarbeit mehrerer Applikationen am selben Dokument regeln, zum Beispiel die Einbettung einer Powerpoint-Folie in ein Word-Dokument. Ebenso basiert die Realisierung von Drag & Drop oder Datentransfers über die Zwischenablage auf COM. Wer über OLE näheres erfahren möchte, sollte sich Kraig Brockschmidt’s Buch zu Gemüte führen (siehe [2]). Alles in allem ist COM als schließende Klammer um die ActiveX-Technologien zu betrachten. Schon daher ist zum Verständnis von ActiveX ein Verständnis von COM unverzichtbar.

<Abb. ActiveX-Schnittstellen>: Alle ActiveX-Technologien basieren auf COM und bestehen aus Sammlungen von COM-Schnittstellen. Beispielsweise sind ActiveX-Dokumente Ÿber Schnittstellen für Embedding, Linking und Am-Ort-Aktivierung (engl. In-place Activation) definiert.

Quo Vadis, COM?

Unlängst hat Microsoft angekündigt, in der Nachfolgeversion COM+ von COM/DCOM einige Erweiterungen vorzunehmen, die dem Entwickler das Leben erleichtern soll. COM+ dürfte fester Bestandteil kommender Windows-Versionen sein. Die Softwareschmiede aus Redmond will ebenfalls unter dem Sammelbegriff DNA (Dynamic interNet Applications) eine Infrastruktur aus Technologien und Werkzeugen bereitstellen, um Internet-Anwendungen effizienter entwerfen zu können. Nebenbei soll DNA auch als Gegengewicht zu Sun’s Java-Technologie und zu CORBA fungieren. Durch Zusammenarbeit mit Partnern wie Siemens-Nixdorf, der Software AG, SAP und anderen Unternehmen will Microsoft die COM-Technologie dabei ebenfalls für Nicht-Windowssysteme nutzbar machen. Beispielsweise bietet die Software AG inzwischen mit EntireX eine komplette DCOM-Portierung auf Unix und anderen Plattformen an.

Fazit

DCOM stellt den nächsten konsequenten Schritt in Microsoft’s Strategie dar, kurz- und mittelfristig alle eigenen Technologien mit COM auf eine gemeinsame Basis zu stellen. Sogar die Betriebssystemimplementierungen selbst sind davon betroffen. Die Integration von COM in das Betriebssystem ist dabei nur ein erster Schritt, die Bereitstellung von Betriebssystemfunktionen als COM-Objekte der nächste. Auf die Bedeutung der Internet-Technologien hat Bill Gates mit verstärktem Engagement reagiert. Folgerichtig sind heutzutage alle COM-basierten Technologien "Internet-aware" und könnten in Zukunft sogar das Internet in seiner Fortentwicklung beeinflußen. Weil die Welt nicht nur aus Windows-Systemen besteht, macht auch die Portierung von COM auf andere Betriebssysteme Sinn. Letztendlich scheint Microsoft die gesamte Informationstechnologie auf die Basis von COM stellen zu wollen, ein durchaus legitimes Ziel. Unter Annahme dieser Hypothese lassen sich übrigens auch einige Entwicklungen und Aussagen von Microsoft wesentlich besser verstehen. Doch COM zeigt sowohl Licht als auch Schatten. Entgegen mancher Behauptung bestand das ursprüngliche Ziel von COM mehr in der Entwicklung einer Integrationstechnologie und weniger in der Bereitstellung einer Verteilungsplattform. Wer DCOM heutzutage zur Entwicklung hochperformanter verteilter Systeme benutzen möchte, sollte sich über Performanzprobleme und Skalierbarkeitsprobleme im Klaren sein. Zum Beispiel hat ein bekanntes Unternehmen in Benchmarktests eine deutliche Unterlegenheit von DCOM gegenüber CORBA-Produkten attestieren müssen. Immerhin weisen CORBA-Implementierungen in diesem Problemfeld einen jahrelangen Vorsprung auf. Zu wenig durchdacht erscheint auch die Konfiguration und das Management von COM-Systemen, speziell in vernetzten Umgebungen. Aus architektureller Sicht stellt sich ActiveX dem Betrachter als fleischgewordenes Chaos dar, das im Laufe der Jahre entstanden ist und die Einarbeitung und Nutzung nicht gerade einfach macht. Hervorzuheben ist an dieser Stelle allerdings die breite und streckenweise sehr gute Unterstützung durch Entwicklungsumgebungen und anderen Werkzeugen. Dadurch bleiben dem Entwickler einige Stolperfallen erspart. Ebenso positiv, daß Microsoft’s Umgebungen aus einem Guß stammen. Von der Implementierung von Komponenten, dem Zugriff auf Datenbanken, dem Aufruf verteilter Objekte bis hin zum dokumenten-zentrierten Arbeiten mit OLE; alles basiert letztendlich auf COM. Aber auch die Konkurrenz um Netscape, IBM, Sun, Oracle, und wie sie auch immer heißen mögen, schläft nicht, sondern führt mit Java und CORBA sehr leistungsfähige und überzeugende Alternativen ins Feld. Aber Konkurrenz belebt bekanntlich das Geschäft.

Referenzen

[1] D. Box: Understanding COM, Addison-Wesley, 1997/98
[2] K. Brockschmidt: Inside OLE 2, Second Edition, Microsoft Press, 1995
[3] F. Buschmann, R. Meunier, H. Rohnert, P. Sommerlad, M. Stal: Pattern-Oriented Software Architecture – A System of Patterns, John Wiley & Sons, 1996
[4] D. Chappel: ActiveX und OLE verstehen, Microsoft Press, 1997
[5] R. Orfali, D. Harkey, J. Edwards: The Essential Distributed Objects Survival Guide, John Wiley , 1996
[6] D. Rogerson: Inside COM – Microsoft’s Component Object Model, Microsoft Press, 1997
[7] R. Sessions: COM and DCOM, John Wiley & Sons, 1997
[8] M. Stal: World Wide CORBA – verteilte Objekte im Netz, OBJEKTspektrum, Heft 6/97

Internetreferenzen

Active Group http://www.activex.org
Microsoft http://www.microsoft.com
Software AG http://www.sagus.com