Die Community zu .NET und Classic VB.
Menü

COM als Middleware - Seite 8

 von 

2.3 Eventfähigkeit, IConnectionPoint & IConnectionPointContainer
Nächste Seite >>
2.1 Interfaces, schnittstellenbasierte Programmierung
<< Vorherige Seite

2.2 Instanzierung von Objekten, IClassFactory  

Das Erstellen von Objekten, also das Instanzieren von Klassen, erfolgt unter COM über eine Klassenfabrik [ClassFactory], präziser als Objektfabrik bezeichnet. Um eine COM-Klasse zu instanzieren, muss sie mindestens die Standardschnittstelle IClassFactory implementieren. Die Schnittstelle legt über ihre zwei Methoden typischerweise das Verhalten zur Instanzbildung offen:

CreateInstance Instanziert eine COM-Klasse mittels der angegebenen IID und gibt einen Zeiger auf IUnknown des neuen COM-Objekts zurück. Alternativ beschreibt die OLE-API CoCreateInstance einen Wrapper dieser Methode und ermöglicht auch hiermit eine Instanzierung. Intern erfolgt nach dem Aufruf von CreateInstance die Generierung der neuen Instanz in der Regel durch die einfache Verwendung des new-Operators. Wird das neue Objekt als Teil einer Aggregation erstellt, dann ist über einen zusätzlichen Parameter IUnknown des aggregierenden Objekts zu übergeben, ansonsten ein Nullzeiger.
LockServer Hält ein Objekt dauerhaft im Speicher, auch wenn der Referenzzähler von IUnknown zu Null dekrementiert und dadurch den Server freigeben würde. Alternativ lässt sich für diese Funktionalität die als Wrapper fungierende OLE-API CoLockObjectExternal bemühen. Durch die Bereitstellung dieser Sperre ist ein Objekt bei erneutem Bedarf unmittelbar präsent. Für Objekte die oft ge- und wieder entladen werden müssen, bringt dieses Vorgehen einen deutlichen Performancegewinn.

Benötigt ein Client ein bestimmtes Objekt, ist zunächst via der OLE-API CoGetClassObject ein Zeiger auf IClassFactory des Servers zu ermitteln, um dann im weiteren dort eine nicht initialisierte Instanz der geforderten Klasse generieren zu lassen. Bedarf es lediglich einer einzigen Instanz, reicht der einmalige Aufruf von CoGetClassObject; für jede weitere ist demnach die Methode CreateInstance in Anspruch zu nehmen. Bei Erfolg erhält der Client den Schnittstellenzeiger auf IUnknown und kann dann wahlweise die statische VTable verwenden oder durch QueryInterface weitere Schnittstellen, wie beispielsweise die dynamische IDispatch anfordern. Zwischen Client und Server bleibt die Verbindung zum referenzierten Objekt so lange bestehen, bis die Freigabe des Zeigers durch IUnknown::Release erfolgt. Zu berücksichtigen gilt es, dass das Objekt zum Clientprozess gehört unabhängig davon, ob ein InProc- oder OutProc-Server vorliegt.


Abbildung 10: Erstellung eines Objekts mit IClassFactory

Ein Server muss also eine Klassenfabrik besitzen, um überhaupt erst seiner Funktion als Objektexporteur gerecht zu werden. ClassFactories beschreiben nicht vermeintlich die Objektwurzel einer Hierarchie, sondern sind gewöhnliche, parallele COM-Objekte und unterliegen somit denselben Regularien, wie beispielsweise der Anmeldung unter einer eindeutigen IID im System, der Referenzzählung und den Sperrmöglichkeiten etc.. IClassFactory lässt keine Parameterübergabe an den Konstruktor der neuen Instanz einer Klasse zu, so dass in einem solchen Fall das Klassenobjekt seitens des Entwicklers durch eine entsprechende Implementierung der Schnittstelle zu ersetzen ist.

Die abgeleitete Standardschnittstelle IClassFactory2 erweitert die Klassenfabrik um die Möglichkeit Komponenten während der Instanzierung zu lizensieren. Der Server exportiert nur bei Übergabe eines gültigen Lizenzschlüssels das angeforderte Objekt an den Client.

2.2.1 GUIDs

COM-Schnittstellen müssen aus Kompatibilitätsgründen eindeutig bezeichnet sein. Bedingt durch die mögliche Verteilung, sprich Vernetzung von Komponenten, führte Microsoft ein global gültiges Benennungsschema ein. Basis ist ein 128-Bit [2128 = 3,4 · 1038] breiter Schlüssel namens UID [Universally Unique Identifier], geläufiger unter der Bezeichnung GUID [Global Unique Interface Identifier]. Seine Darstellung erfolgt üblicherweise als 32-stelliger Hexadezimalstring, umschlossen von geschweiften Klammern:

                 { 8         - 4    - 4    - 4    - 12             }
IUnknown         { 0000 0000 - 0000 - 0000 - C000 - 0000 0000 0046 }
IDispatch        { 0002 0400 - 0000 - 0000 - C000 - 0000 0000 0046 }
IClassFactory    { 0000 0001 - 0000 - 0000 - C000 - 0000 0000 0046 }

Die Generierung eines einzigartigen Schlüssels vollzieht sich bei der Erstellung einer neuen Schnittstelle durch einen Algorithmus aus örtlichen den Faktoren Uhrzeit, Länder- und Rechnerkennung sowie einer eindeutigen Netzwerkkarten-ID plus einer Zufallszahl. Die eindeutige Kennzeichnung von Netzwerkkarten unterliegt einem globalen Industriestandard. Fehlt eine solche Karte, garantiert Microsoft nur die lokale Eindeutigkeit.

Die Erstellung einer neuen GUID lässt sich wahlweise mit Tools wie GUUIDGEN [GUI-Anwendung] bzw. GUIDGEN [Commandline] oder durch die OLE-API CoCreateGuid umsetzen. Laut VBC-99 ergeben sich bei 10 Millionen Aufrufen des Algorithmus pro Sekunde bis zum Jahr 5770 keinerlei Wiederholungen.

Da die wenigsten Compiler 128-Bit Typen unterstützen, findet die Bearbeitung von GUIDs gewöhnlich in 16 bytebreiten Strukturen statt. Die OLE-API unterstützt neben der Funktion CoCreateGuid deren Bearbeitung durch eine kleine Auswahl von weiteren Funktionen, wie IsEqualGUID, IsEqualCLSID, StringFromCLSID, CLSIDFromString, StringFromGUID. Basierend auf dem Konzept der GUID finden sich in der COM-Terminologie zusätzliche, abgeleitete Bezeichner:

CLSID, ClassID Bezeichner einer CoClass [Komponente]
IID Beschreibt ein Interface eindeutig
LIBID Benennung einer Typenbibliothek
APPID ID die einen OutProc-Server beschreibt
ProgID Bezeichnet einen textuellen, programmatischen Alias für eine GUID. Anhand einer ProgID lässt sich ein Interface nach dem Muster Server.Methode leichter assoziieren. Maximal zulässige Stringlänge ist 39 Zeichen.
CatID ID einer Komponentenkategorie, basierend auf einer GUID. Im Registry Schlüssel HKEY_CLASSES_ROOT\Component Categories, befindet sich eine textuelle Aufschlüsselung der Komponentengruppen.

2.2.2 Binden von Komponenten

Binden [Binding] verknüpft die Instanz einer Klasse mit einer Variablen. Grundsätzlich sind mit dem frühen und dem späten Binden zwei Bindungsarten unterscheidbar. Das frühe Binden [Early Binding] bezieht sich auf die Übersetzungszeit, d. h. dem Compiler oder einem Interpreter sind vor dem Ausführen einer Anwendung die binären Referenzen auf die Methoden eines typisierten Objekts bekannt. Entstehende Verweise können daher hart gesetzt werden. Beim späten oder dynamischen Binden [Late Binding] hingegen, ergeben sich die untypisierten Beziehungen erst dynamisch zur Laufzeit. COM bietet durch die Bereitstellung der OLE-Automation mittels IDispatch eine dritte Bindungsart. Besitzt ein Objekt kein Custominterface, sondern verfügt lediglich über eine Dispatchschnittstelle, dann lassen sich auch hier bereits während der Übersetzungszeit binäre Referenzen erstellen. Abweichend vom frühen Binden, erfolgt das Verweisen über IDispatch nicht über eine VTable, sondern mittels eines Zeigers auf die Methode Invoke und anhand der zur Kompilierung ermittelten DISPID sowie der vorbereiteten Parameterliste. Es gilt, folgende Begriffe festzuhalten:

Sehr frühes Binden Binäres Binden zur Übersetzungszeit an eine VTable. Voraussetzung: Objekt, Methodenname und Parameter sind zur Entwurfszeit bekannt.
Frühes Binden Binäres Binden zur Übersetzungszeit über die vorzeitige Ermittlung der DISPID einer Methode und dem gesetzten Verweis auf IDispatch.Invoke sowie Bestückung der zugehörigen Parameterliste. GetIDsOfNames wird nicht bemüht. Voraussetzung: Objekt, Methodenname und Parameter sind zur Entwurfszeit bekannt.
Spätes Binden Dynamisches Binden zur Laufzeit mittels IDispatch. Objekt und oder Methodennamen sind zur Übersetzungszeit unbekannt. Für diese Bindungsart muss die verwendete Programmiersprache untypisierte Variablen unterstützen. Die DISPID wird über GetIDsOfNames ermittelt.

2.2.3 Binding und Performance

Die unterschiedlichen Bindungsarten einer Komponente an einen Client können das Laufzeitverhalten der Anwendung maßgeblich beeinflussen. Zu berücksichtigen ist hier neben der Bindungsform auch der Komponententyp [InProc und OutProc] sowie deren Ort [Lokal oder Remote]. Die untenstehende Tabelle zeigt die gängigen Implementierungsmöglichkeiten auf. Sie stellt deren zeitliche Spezifika zu komponentenunabhängigen Techniken, wie den prozeduralen Ansatz und die Verwendung privater Klassen ins Verhältnis. Der GUI-orientierte Komponententyp Ocx als Sonderform der Dll bleibt unberücksichtigt. Die Messungen erfolgten unter Windows 2000, für reine COM-Komponenten fand ein 800 MHz-Rechner Verwendung, für die DCOM-Ermittlung wurde ein 350 MHz PC über Twisted Pair und 100 MBit/Sec vernetzt. Die Ergebnisse lassen sich anhand der beiliegenden Quelltexte verifizieren [Samples\Binding]. Die Auflösung ist die Zeit in ms für 100.000 Aufrufe zweier Methoden, deren Prototypen identisch sind. Die erste Methode stellt lediglich eine Hülle dar und enthält entgegen der zweiten keinerlei Implementierungen. Dadurch lässt sich der durch das Binden bedingte Overhead [Ohead] ermitteln:


Abbildung 11: Performancemessung unterschiedlicher Bindungsarten

Die Diskrepanz zwischen sehr früher Bindung [VTable] und später Bindung mit IDispatch ist leicht zu erkennen. Die Verwendung der Dispatchschnittstelle verlangsamt die Kommunikation mit lokalen Komponenten bei einer ActiveX-Dll um den Faktor 35 und bei einer ActiveX-Exe immer noch fast um das 5-fache. Allerdings schwindet der Zeitvorteil mit dem knapp 3-fachen vergleichsweise zur Bedeutungslosigkeit, wenn die Erstellung der Objekte via DCOM auf einem entfernten Rechner erfolgt. Auffallend ist auch der bindungsunabhängige Unterschied zwischen Exe und Dll im lokalen Betriebssystem. Die Dll ist gegenüber der Exe ca. 77 mal bzw. 580 mal [späte / sehr frühe Bindung] schneller. Dies erklärt sich dadurch, dass die Dll prozessintern und die Exe über Prozessgrenzen hinweg via LRPC [Lightweight RPC] kommuniziert und dadurch Overhead erzeugt. Auf einem Remotesystem hingegen liegt die Performance von Exe und Dll sehr dicht beieinander, da die Dll dort selbstverständlich nicht mehr prozessintern arbeiten kann. Ihr Hosting auf dem entfernten Rechner übernimmt ein Surrogateprozess [s. Kapitel 2.0]. Zwar wird dem Client auf diese Weise die Prozessnähe suggeriert, tatsächlich verhält sich die Komponente durch den Co-Prozess aber wie ihre prozessexterne Variante, die ActiveX-Exe. Gegebenenfalls sollte daher bei Remotekomponenten die ActiveX-Exe eher Vorzug finden, da diese neben der Einsparung des Surrogateprozesses auch über den Vorteil des Multithreadings verfügt. Wesentliches Fazit ist die grundsätzliche Bevorzugung des sehr frühen Bindens gegenüber jeder Form von Dispatchbindung. Die Messergebnisse lassen im weiteren erkennen, dass speziell in verteilten Komponenten der Reduzierung der von DCOM beanspruchten Bandbreite eine besondere Bedeutung beizumessen ist. So erzeugt beispielsweise der Aufruf einer entfernten Methode ohne Parameter rund 200 Byte Netzwerktraffic. In erster Linie führt neben der VTable-Bindung auch die Verringerung der Parameteranzahl oder deren Komprimierung zu einer Beschleunigung der Kommunikation zwischen Server und Client. Zudem erfährt die Verbindung durch den Verzicht auf Zeiger [ByReference] bei der Parameterübergabe eine Performancesteigerung. Dem Server wird unter dieser Voraussetzung angedeutet, dass der Client keine Rückgabe über ein Argument wünscht und erstellt daher lediglich eine lokale Kopie [ByValue] des Wertes. In Visual Basic ist dies, entgegen der Defaulteinstellung ByRef leicht durch die strikte Verwendung des Schlüsselwortes ByVal zu erreichen.

Erwähnenswert ist in diesem Zusammenhang der ungebundene Ado-Recordset. Diese Datenbankstruktur ist seitens Microsoft für DCOM-Anwendung optimiert und bietet somit in speziellen Fällen eine ideale Form des Datentransfers.

Nächste Seite >>
2.3 Eventfähigkeit, IConnectionPoint & IConnectionPointContainer
<< Vorherige Seite
2.1 Interfaces, schnittstellenbasierte Programmierung