Component Object Model

Das Component Object Model [kəmˈpoʊnənt ˈɒbdʒɪkt ˈmɒdl] (abgekürzt COM) ist eine von Microsoft entwickelte Technik zur Interprozesskommunikation unter Windows. COM-Komponenten können sowohl in Form von Laufzeitmodulen (DLLs) als auch als ausführbare Programme umgesetzt sein. COM soll eine leichte Wiederverwendung von bereits geschriebenem Programmcode ermöglichen, zum Teil auch über Betriebssystemgrenzen hinweg. COM-Komponenten können unabhängig von der Programmiersprache eingesetzt werden.

Das Component Object Model wurde von Microsoft 1992 mit der grafischen Benutzeroberfläche Windows 3.1 eingeführt.

Architektur

COM basiert auf dem Client-Server-Modell. Ein COM-Client erzeugt eine COM-Komponente in einem sogenannten COM-Server und nutzt die Funktionalität des Objektes über COM-Schnittstellen. Der Zugriff auf Objekte wird innerhalb eines Prozesses durch sogenannte COM-Apartments synchronisiert.

COM-Server

Unter einem COM-Server versteht man ein Laufzeitmodul (Dynamic Link Library) oder ein ausführbares Programm, das in einer COM-unterstützenden Programmiersprache erstellt wurde und COM-Komponenten anbietet und erstellen kann. Es gibt drei Typen von COM-Servern:

In-process-Server

Im Falle des In-process-Servers ist die COM-Komponente in einer DLL implementiert (sie tragen unter Windows oft die Dateiendung OCX). Diese DLLs müssen die Funktionen DllGetClassObject(), DllCanUnloadNow(), DllRegisterServer() und DllUnregisterServer() exportieren. Wird eine COM-Komponente eines In-process-Servers erzeugt, so wird der zugehörige Server (ein Server kann mehrere COM-Komponenten anbieten) in den Prozess des Clients geladen. In-process-Server sind besonders schnell, da der Zugriff auf die Funktionen der COM-Komponenten ohne Umwege erfolgt. Nachteilig ist, dass auf diese Weise jeder Prozess eigenen Speicherplatz mit den benutzten COM-Komponenten belegt und keine gemeinsame Speichernutzung möglich ist. ActiveX verwendet dieses Modell.

Local Server

Local Server sind unter Windows ausführbare Programme, die COM-Komponenten implementieren. Bei der Erzeugung einer COM-Komponente wird dieses Programm gestartet (sofern es nicht schon läuft) – dies bedeutet, dass ein ausführbares Programm vorliegen muss, eine DLL kann hier nicht aufgerufen werden. Zur Kommunikation zwischen Client und Server wird ein vereinfachtes RPC-Protokoll (Remote Procedure Call) benutzt. Local Server haben den Vorteil, dass sie nur einmal gestartet werden müssen und dann viele Clients bedienen können, was weniger Speicherplatz belegt. Zudem lassen sich so recht leicht Datenzugriffe auf einen gemeinsamen Datenbestand synchronisiert von mehreren laufenden Clients durchführen (wie zum Beispiel in Microsoft Outlook). Die Zugriffe über RPC sind allerdings langsamer.

Remote Server

Befinden sich Server und Client in einem Rechnernetz, so kommt DCOM (Distributed COM) zum Einsatz. Der Einsatz von DCOM ermöglicht grundsätzlich den Betrieb von Server und Client auf unterschiedlichen Betriebssystemen.

DCOM benutzt im Gegensatz zum Local Server ein vollständig implementiertes RPC, was die Aufrufe jedoch (auch bei sehr geringer Netzwerkauslastung) deutlich verlangsamt. Die Implementierung vom DCOM unterscheidet sich von der von COM mit Local Server zusätzlich noch durch den vorgeschalteten Protokollstack.

COM-Schnittstelle

Die COM-Schnittstelle dient der Kommunikation zwischen Client und Server. Eine COM-Komponente kann dazu über allgemein definierte und vorgegebene Schnittstellen (zum Beispiel IUnknown, IDispatch) sowie über spezielle Schnittstellen angesprochen werden.

Jede Schnittstelle hat eine weltweit eindeutige Identifikationsnummer, die GUID (Globally Unique Identifier). Dadurch können auch mehrere Schnittstellen mit demselben Namen existieren (aber nicht mit derselben GUID).

Um eine programmiersprachenübergreifende Client/Server-Kommunikation zu ermöglichen, findet an der Schnittstelle das sogenannte Marshalling statt, das die auszutauschenden Daten in eine vordefinierte Binärrepräsentation wandelt.

Eine Schnittstelle erfüllt die Funktion einer abstrakten Klasse, die lediglich virtuelle Elementfunktionen enthält, die (wegen der Trennung von Deklaration und Implementierung) in der VTable alle auf 0 gesetzt werden. Die C-Version einer Schnittstelle ist entsprechend eine Struktur, die Funktionszeiger enthält. Die erzeugten COM-Objekte nutzt man dabei über Zeiger auf deren Schnittstellen.

Wenn ein COM-Objekt eine Schnittstelle implementiert, muss es alle Methoden der Schnittstelle überschreiben, also die VTable füllen. Dabei sind mindestens die drei Methoden von IUnknown zu implementieren, die für das Lebenszyklusmanagement zuständig sind und eventuell vorhandene, weitere implementierte Schnittstellen offenlegen.

Eine Schnittstelle sieht in der für COM-Komponenten nutzbaren IDL (Interface Definition Language) wie folgt aus (als Beispiel dient das Interface IUnknown):

// Standardschnittstelle aller COM-Komponenten
[
    object,
    uuid(00000000-0000-0000-C000-000000000046)
]
interface IUnknown {
    [restricted]
    HRESULT _stdcall QueryInterface([in] GUID* rrid, [out] void** ppvObj);
    [restricted]
    unsigned long _stdcall AddRef();
    [restricted]
    unsigned long _stdcall Release();
}

Jede Schnittstelle muss über eine Schnittstellen-Vererbung die Funktionen der hier gezeigten Schnittstelle IUnknown definieren, da dieses die grundlegenden Funktionen für COM implementiert. Eine weitere Vererbung der Schnittstellendefinitionen ist möglich.

Da Programmiersprachen wie Visual Basic Script keine Typen kennen, hat Microsoft eine weitere Möglichkeit entwickelt, Funktionen aus COM-Schnittstellen aufzurufen. Für diese Möglichkeit muss die Schnittstelle die Funktionen der Schnittstelle IDispatch definieren. Dies ermöglicht es, eine COM-Komponente über IDispatch.Invoke() anzusprechen, ohne dass der COM-Client die Typbibliothek des Servers kennen muss. Da der Zugriff über das Dispatch-Interface sehr viel langsamer als der Zugriff über ein typisiertes Interface ist, wird oft beides implementiert (Dual Interface), so dass bei Programmiersprachen, die Zeiger beherrschen, beide Zugriffsmöglichkeiten zur Verfügung stehen.

COM-Komponente

Eine COM-Komponente bietet die aufrufbaren Funktionen über eine oder mehrere COM-Schnittstellen an. Die Erzeugung des Objektes erfolgt durch die Implementierung von IClassFactory.CreateInstance() im COM-Server.

Die Lebensdauer eines Objektes wird mittels Referenzzählung gesteuert. Eine COM-Komponente lebt nur so lange, wie die Differenz der Aufrufe von AddRef() (am Beginn der Verwendung einer Instanz) und Release() (Freigabe nach Verwendung der Instanz) nicht 0 ergibt.

Eine COM-Komponente kann mehrere Schnittstellen anbieten. Dies ist in bestimmten Situationen auch notwendig, um ein Programm erweitern zu können, ohne andere Programme neu kompilieren zu müssen, denn der Compiler kodiert die aus der VTable gelesenen Einsprungadressen der vom Client aufgerufenen Funktionen unter bestimmten Umständen fest. Wird die Schnittstelle einer Komponente später geändert, kann sich die Einsprungadresse ändern, was die Funktionstüchtigkeit des Clients beeinträchtigen würde. Zur Erweiterung der Serverfunktionalität wird also stattdessen eine weitere Schnittstelle implementiert.

Eine Vererbung von COM-Komponenten (Aggregation) ist durch die Anforderungen der Binärkompatibilität nur in wenigen Programmiersprachen möglich. Dazu wird die zu vererbende Komponente über explizite Durchleitung der Schnittstellen über die erbende Komponente veröffentlicht.[1]

COM-Client

Der Client ist das Programm, das

  • möglicherweise ein Objekt einer COM-Komponente über einen COM-Server erzeugt und
  • die von der COM-Komponente angebotenen Funktionen benutzt.

Der Client kennt die Funktionen, die von der COM-Komponente angeboten werden, da diese in den entsprechenden COM-Schnittstellen deklariert sind. Die Veröffentlichung von Schnittstellen erfolgt entweder über Typbibliotheken oder Beschreibungen in der IDL (Interface Definition Language).

Apartments

COM-Objekte werden bei der Erzeugung immer einem sogenannten Apartment zugeordnet. Dabei handelt es sich um transparente Rahmen, die zur Synchronisierung von Methodenaufrufen mehrerer Objekte dienen, die mit unterschiedlichen Anforderungen an die Threadsicherheit arbeiten. Wird COM nicht mitgeteilt, dass eine entsprechende Komponente threadsicher ist, wird COM nur einen Aufruf gleichzeitig an ein Objekt erlauben. Threadsichere Komponenten können auf jedem Objekt beliebig viele Aufrufe gleichzeitig ausführen.

Geschieht ein Aufruf im gleichen Apartment zwischen verschiedenen Objekten, ist kein Marshalling erforderlich. Wird jedoch eine Schnittstelle über Apartmentgrenzen hinweg benutzt, muss ein Marshalling erfolgen.

Jeder Thread, der COM verwenden möchte, muss sich vor der ersten Verwendung einer COM-Funktionalität einem Apartment zuordnen (MTA) oder ein neues Apartment erstellen (STA). Dies geschieht über die Funktion CoInitialize(). Programmiersprachen mit integrierter COM-Unterstützung (zum Beispiel VB6 und die meisten .Net-Framework-Sprachen) führen diese Zuordnung oft automatisch durch.

Jede COM-Komponente wird bei Erzeugung einem Apartment zugeordnet. Falls die Apartment-Anforderungen der erzeugten Komponente zum Apartment des erzeugenden Threads passen, wird das Objekt dem gleichen Apartment zugeordnet. Bei Aufrufen über Prozessgrenzen hinweg liegen die beiden Objekte immer in verschiedenen Apartments. Die Zuordnung zu einem Apartment kann während der Lebensdauer des Objektes nicht geändert werden.

Es gibt drei Arten von Apartments:[2]

  • Single Threaded Apartments (STA) besitzen genau einen Thread und beliebig viele Objekte. Es können beliebig viele STAs in einem Prozess existieren. Es erfolgt nur ein Aufruf gleichzeitig an das aufzurufende Objekt. Die restlichen Aufrufe warten in einer Warteschlange auf die Freigabe des Apartment-Threads. Dies impliziert, dass zwei Objekte in demselben STA auch von zwei verschiedenen Clients nicht parallel aufgerufen werden können. Als Besonderheit wird das erste in einem Prozess initialisierte STA automatisch zum Main-STA. Pro Prozess gibt es nur genau ein Main-STA; alle Objekte, die keine explizite Anforderung an das Apartment stellen, werden in diesem erzeugt.
  • Multi Threaded Apartments (MTA) besitzen beliebig viele Threads. Es gibt in einem Prozess allerdings maximal ein MTA. Dadurch können von mehreren Clients gleichzeitig Aufrufe an das gleiche oder auch verschiedene Objekte erfolgen. Die Anforderungen an die Implementierung der Komponenten sind wegen der notwendigen Threadsicherheit und Reentranz sehr hoch.
  • Neutral Threaded Apartments (NTA) haben keine Threadaffinität. Es gibt in einem Prozess allerdings maximal ein NTA. Jedes Objekt in einem NTA kann von einem STA/MTA Apartment ohne Threadübergang aufgerufen werden. Der Thread wird also kurzzeitig in das NTA ausgeliehen, um damit aus Performancegründen das Marshalling zu überspringen. Neutral Threaded Apartments wurde mit Windows 2000 eingeführt, um die Vorzüge von MTA (meist kein Marshalling notwendig) mit den Vorzügen von STA (keine threadsichere Implementierung notwendig) zu vereinen.

Funktionalität

Durch den Einsatz von COM gibt es die Möglichkeiten

  • sprachunabhängig,
  • versionsunabhängig,
  • plattformunabhängig,
  • objektorientiert,
  • ortsunabhängig,
  • automatisierend

zu programmieren. Viele der Funktionen des „Windows Platform SDKs“ sind über COM zugänglich. COM ist die Basis, auf der OLE-Automation und ActiveX aufbauen. Mit der Einführung des .NET-Frameworks verfolgte Microsoft allerdings die Strategie, COM unter Windows durch dieses Framework abzulösen. Im Folgenden werden die einzelnen Punkte der Aufzählung genauer erläutert.

Sprachunabhängigkeit

COM-Komponenten sind unabhängig von der Programmiersprache. COM unterstützt den sogenannten Binärstandard. Die erzeugte Binärdatei stellt einerseits die implementierten Funktionen zur Verfügung und andererseits eine Schnittstelle, die diese Funktionen aufzeigt. Mit Hilfe der Schnittstelle ist es möglich, von anderen Programmen aus die Funktionen zu verwenden. Dabei wird mit Konzepten aus dem Bereich Verteilte Systeme gearbeitet.

Versionsunabhängigkeit

Ein weiterer Vorteil beim Einsatz von COM ist es, dass man die Verwaltung von neuen Softwarefeatures einfach in eine bestehende Anwendung integrieren kann. Oftmals kann es Probleme geben, wenn man herstellerneutrale oder herstellerübergreifende Softwarekomponenten mit weiteren Funktionen ausstattet. Dadurch kann zwar die eigene Software erweitert werden, jedoch besteht die Gefahr, dass andere Software, die ebenfalls die herstellerübergreifenden Komponenten verwendet, nicht mehr funktionsfähig bleibt.

COM bietet eine robuste Möglichkeit an, um eine Softwarekomponente mit neuen Funktionen zu erweitern. Dies wird dadurch ermöglicht, dass mehrere Schnittstellen in einer Header-Datei zusammengefasst werden können. Der folgende C++-Programmcode verdeutlicht dies:

//
// Interface mathematik.h
//
class IStandardMathFunctions : public IUnknown
{
public:
    STDMETHOD(Addieren)(long, long, long*)
    STDMETHOD(Subtrahieren)(long, long, long*)
};

class IAdvancedMathFunctions : public IUnknown
{
public:
    STDMETHOD(Fibonacci)(short, long*)
}

Diese Header-Datei namens mathematik.h enthält zwei Schnittstellen. Die erste Schnittstelle könnte beispielsweise die herstellerübergreifenden Funktionen anbieten, die von verschiedenen Programmen verwendet werden. Durch die zweite Schnittstelle IAdvancedMathFunctions wird diese Softwarekomponente erweitert. Weitere Schnittstellen können jederzeit hinzugefügt werden. Die alten Schnittstellen und darin enthaltenen Funktionen gehen dabei nicht verloren. Das Hinzufügen neuer Schnittstellen statt des Veränderns derselben ist so die von Microsoft gedachte Form, Softwarekomponenten zu erweitern, da so keine Inkonsistenzen entstehen.

Plattformunabhängigkeit

x64-Applikationen können dank Marshalling auf 32-bittige COM-Server zugreifen (und umgekehrt). Der COM-Server muss dann in einem eigenen Prozess laufen und seine Objekte können demnach nicht als In-process-Server instanziiert werden. COM-Applikationen sind jedoch fast ausschließlich auf die Windows-Betriebssystemfamilie und von dieser unterstützte Hardware angewiesen, eine Plattformunabhängigkeit war konzipiert[3][4], wurde aber wohl nur in wenigen Ausnahmefällen realisiert.

Objektorientierung

Beim Einsatz von COM wird objektorientiert gearbeitet. Trotzdem können COM-Komponenten auch zum Beispiel in C erstellt und genutzt werden, da die Schnittstellen tatsächlich eine Sammlung von Funktionszeigern sind (abstrakte Klasse in C++, struct in C).

Ortsunabhängigkeit

COM ist ortsunabhängig, d. h., dass die einzelnen COM-Komponenten an einer zentralen Stelle (Registrierungsdatenbank) angemeldet werden und so der Zugriff auf die Komponenten unabhängig von ihrem eigentlichen Ort erfolgen kann.

Automatisierung

Das Steuern von Anwendungen über COM-Schnittstellen wird als Automatisierung bezeichnet. Von dieser Anwendungsmöglichkeit wird häufig im Rahmen von OLE (Object Linking and Embedding) Gebrauch gemacht.

Sicherheit

Durch eine Sicherheitslücke in der RPC-Implementierung von DCOM wurde die Angriffsweise des bekannten Wurms W32.Blaster möglich.

Siehe auch

Literatur

  • Peter Loos: Go to COM. Das Objektmodell im Detail betrachtet. 1. Auflage. Addison-Wesley, 2000, ISBN 978-3-8273-1678-3
  • Olaf Zwintzscher: Software-Komponenten im Überblick. Einführung, Klassifizierung & Vergleich von JavaBeans, EJB, COM+, .Net, CORBA, UML 2. W3L, Herdecke 2005, ISBN 3-937137-60-2

Weblinks

Einzelnachweise

  1. COM_INTERFACE_ENTRY Macros. Archiviert vom Original; abgerufen am 1. Juli 2023.
  2. Understanding COM Apartments, Part I. Archiviert vom Original; abgerufen am 1. Juli 2023.
  3. Christian Gross: Building COM Components on UNIX. Microsoft, Juli 1998, abgerufen am 29. Mai 2016 (englisch).
  4. Kersten Auel: DCOM für Unix: jetzt von der Open Group. Heise Medien, 8. März 1999, abgerufen am 29. Mai 2016.