Die Community zu .NET und Classic VB.
Menü

COM als Middleware - Seite 11

 von 

2.5 Proxies und Stub als Stellvertreter  

Eine tragende Eigenschaft von COM ist die Ortstransparenz von Komponenten. Der Zugriff auf einen ortsungebundenen OutProc-Server stellt sich, abgesehen von seiner Performance [s. Kapitel 2.2.3], für den Client über Prozessgrenzen hinaus stets gleich dar. Tatsächlich referenziert der Client die Schnittstellen des Servers nicht unmittelbar, sondern über den Umweg elementarer COM-Dienste. COM legt im Adressraum des Clients einen emulierenden Stellvertreter an, den sogenannten Proxy [engl.: Bevollmächtigter, Vertreter]. Das Schnittstellenverhalten eines Proxies ist identisch mit dem des Servers. Methodenaufrufe und deren Rückgabewerte stellen sich dem Client gegenüber dar, als handele es sich bei dem Server um eine InProc-Variante. Der Proxy benötigt hierbei keine Kenntnisse über Codeimplementierungen, er nimmt lediglich den Methodenaufruf mit seinen Parametern entgegen und leitet diese an den Server weiter. Das Ausführen der eigentlichen Operation findet auf dem Server selbst statt. Der Aufruf einer prozessentfernten Methode stellt sich aus Sicht des Clients stets gleich dar:

  • Methodenaufruf durch den Client an den transparenten Proxy.
  • Kopieren der Methode und ihrer Parameter in einen Puffer durch den Proxy.
  • Der Proxy etabliert [bei erstmaliger Kontaktaufnahme zum Objekt] eine Verbindung zum Server.
  • Senden eines zuvor erstellten Datenpakets durch den Proxy an den Server mit anschließendem Warten auf ein entsprechendes Antwortpaket. [synchroner Ablauf]
  • Der Proxy extrahiert das Antwortpaket in die erwarteten Rückgabewerte.
  • Abgeben der Prozesskontrolle durch den Proxy an den Client mit den Rückwerten.

Der Server empfängt die vom Proxy des Clients versendeten Pakete nicht direkt, sondern ebenfalls über einen Mittler, seinem Pendant, den sogenannten Stub [engl.: Stichleitung, Stumpf]. Da auch für den Server das Paradigma der Ortstransparenz gilt, muss er ebenfalls über einen prozessinternen Stellvertreter des prozessexternen Clients verfügen. Der Stub wandelt die vom Proxy übermittelten Pakete entsprechend zurück, schiebt die entschlüsselten Parameter in den Laufzeitstapel des Stacksegments und verbiegt den Instruktionszeiger auf den Codebereich der betroffenen Funktion. Die Methode wird gemäß der Anforderung, einem InProc-Server kongruent ausgeführt. Ihr Rückgabewert findet seinen Weg in gleicher Weise, jetzt in umgekehrter Richtung über den etablierten Kommunikationskanal zum Proxy des Clients zurück.


Abbildung 13: Architektur eines Proxy- / Stub-Systems

Die Aggregation mehrerer Objekte in einem Server erfordert das Vorhandensein jeweils eines Proxy- und Stub-Managers. Dies begründet sich aus dem clientseitigen Anspruch der Single Identity, der dem Server eine unique Darstellung abverlangt. Der Proxy-Managers ist Teil des clientseitigen Proxies; den Stub-Manager implementiert der Server.

Da COM Stubs und Proxies in Form von Standardobjekten offen legt, werden sie auch als COM-Bibliotheksobjekte bezeichnet. Proxies und Stubs sind reguläre InProc-Komponenten der oleaut32.dll die der Client bzw. der Server in seinen jeweiligen Prozessraum instanziert. Benutzerdefinierte Stubs und Proxies lassen sich via MIDL-Compiler [s. Kapitel 2.1.4] erstellen, sind aber dann explizit in der Registry über RegSrv32.exe anzumelden. Beide Servertypen starten bei der ersten Instanzierung der ihnen übergeordneten Komponente.

2.5.1 Marshaling

Der Informationstransport zwischen Proxy und Stub beruht auf RPC, einem IPC-Standard [Inter-Process Communication] der OSF. Es gilt zwischen dem lokalen LRPC [Lightweight Remote Procedure Call] und dem RPC für verteilte Komponenten zu unterscheiden. Die RPC-Funktionalitäten sind in der Systembibliothek rpcrt4.lib öffentlich definiert. Das Packen von Daten durch den Proxy einerseits ist durch den Begriff Marshaling geprägt, das Dechiffrieren seitens des Stubs andererseits als Unmarshaling gekennzeichnet. Ein Entwickler verfügt über drei verschiedene Implementierungsarten des Marshalings:

Typenbibliotheksmarshaling
Typenbibliotheksmarshaling, auch Universal Marshaling genannt, repräsentiert die Basisvariante des Marshalings und bietet für das Packen der Methodenparameter eine Sammlung der gebräuchlichsten Datentypen an. Für jene, durch das Attribut oleautomation definierten Typen, legt COM fertige Marshaling-Implementierungen offen. Da diesen Codeumsetzungen der Schnittstellen-Aufbau des Servers unbekannt ist, muss zuvor eine Schnittstellenbeschreibung durch die Erstellung einer Typenbibliothek erfolgen.

Standardmarshaling
Standardmarshaling erweitert das Typenbibliotheksmarshaling von OLE-Automations-Typen auf sämtliche in IDL zulässige Variablenarten, wie beispielsweise Strukturen und Enumerationen. Dadurch lässt sich zum Einen jedes Custominterface beschreiben und zum Anderen via MIDL-Compiler der spezifische Marshaling-Code automatisch generieren.

Benutzerdefiniertes Marshaling
Diese Form des Marshalings dient gegenüber den gängigen Marshalingvarianten der Performancesteigerung und bedarf eines hohen Implementierungsaufwandes. Standard- und Typenbibliotheksmarshaling behandeln Parameterübergaben breitbandig und damit unoptimiert. Ein lesender Zugriff auf Konstanten seitens des Clients verursacht bei jeder Anfrage Netzwerkverkehr, sinnvoll wäre in einem solchen Fall beispielsweise a priori, einmalig eine Kopie aller unveränderlicher Werte an den Client zu übermitteln und damit den Traffic zu reduzieren. Solche Strategien erfordern eine individuelle Implementierung des Marshalings mittels der von COM unterstützten Standardschnittstelle IMarshal. Benutzerdefiniertes lokales Marshaling arbeitet meist auf Basis der schnellen Kommunikationstechnik Memory Mapped Files, respektive Shared Memory. Der Performancevorteil für Remotezugriffe ist bei benutzerdefiniertem Marshaling verschwindend gering und daher eher unüblich.

2.5.2 Multithreading und Apartments

In der Multitasking-Umgebung Windows folgen Threads, koordiniert durch einen Scheduler, quasiparallel verschiedenen Programmfäden. Jeder neue Prozess erhält einen Primärthread, auch Mainthread genannt, der durch weitere Threads, dann als Sekundärthreads oder Leightweight Processes bezeichnet, Unterprozesse bilden kann.

Im Zusammenspiel systemweit gültiger und damit prozessübergreifender Softwarestrukturen ergeben sich in Middleware-Architekturen wie COM maßgebliche Probleme bei der Threadsicherheit globaler Daten. Beispielsweise gilt es unter Einfluss mehrerer Threads festzulegen, ob verschiedene Instanzdaten derselben Komponente sowie deren globale Daten einen gegenseitigen Schutz erfahren. Definitionsbedarf ergibt sich ebenfalls bei der Prioritäten-Handhabung einer Methode im Kontext unterschiedlicher Threads, als auch bei der Festlegung des Gültigkeitsbereich eines threadspezifisch erworbenen Schnittstellenzeigers.

Die daher notwendige Einführung des sogenannten Apartmentmodells unter COM bietet eine eindeutige und abgrenzende Beschreibung sowie die Problemlösung der Threadbeziehungen zwischen Servern und Clients. Ein Apartment fasst eine oder mehrere Komponenten in einem Kontext zusammen, um ihnen dadurch die gleichen threadbezogenen Attribute zu verleihen. Ein Apartment ist weder ein Thread, noch ein Objekt, sondern beschreibt vielmehr den Verhaltensraum für die Threadbeziehung eines Objekts. Ein Client muss vor dem ersten Zugriff auf ein COM-Objekt mit den API-Funktionen CoInitialize, CoInitializeEx und OleInitialize dessen Apartment festlegen. Während seiner verbleibenden Laufzeit darf eine Instanz ihr eingangs zugewiesenes Apartment nicht verlassen bzw. nicht in ein anderes "umziehen". Es gilt, drei gültige Apartmenttypen zu unterscheiden:

STA Single Threaded Apartment: STAs beinhalten ein oder mehrere Objekte. Die Ausführung und Operationen auf diese können nur durch den ursprünglichen instanzierenden Thread erfolgen. Schnittstellenzeiger auf das Objekt haben nur lokalen Charakter. Ein Weiterreichen an externe Threads durch globale Variablen führt unweigerlich zu einem Absturz. Abhilfe schafft nur die zusätzliche Implementierung eines sogenannten Stream-Objekts mittels der API-Funktionen CoMarshalInterThreadInterfaceInStream und CoGetInterfaceAndReleaseStream. Globale Daten der Komponente sind gegen konkurrierenden Zugriff durch den Entwickler explizit zu schützen. COM serialisiert über einen zwischen Proxy und Stub installierten RPC-Stack mit FiFo-Verhalten den Aufruf der Methoden eines STA-Objektes. Die Abarbeitung der Queue leistet das Apartment auf Basis der Nachrichtenschleife eines unsichtbar angelegten Fensters namens OleMainThreadWndClass. Der serielle Transport über RPC und Stack bedingt einerseits einen Performanceverlust und andererseits einen sequentiellen Programmablauf.
MTA Multi Threaded Apartment: Ein MTA enthält ein oder mehrere Objekte. Operationen auf ein Objekt dürfen von unterschiedlichen Clients quasi parallel ausgeführt werden. Statt einer Nachrichtenpufferung wie bei STAs erstellt COM für MTAs eigenständig Threads, verwaltet diese in einem Thread-Pool und terminiert sie nach Bedarf. Die Größe eines Pools kann daher variieren. Die Performance eines MTAs ist gegenüber STAs deutlich besser, da der Aufruf direkt über RPC ohne Stack und Fensterschleife an den betroffenen Thread des Pools erfolgt. Die Gewährleistung eines sicheren Zugriffs auf Methoden und globale Daten kann nur durch Implementierungen des Entwicklers erfolgen.
NTA Neutral Threaded Apartment. Ein NTA enthält ein oder mehrere Objekte. Wie beim MTA dürfen Clients zwar aus unterschiedlichen Threads auf die Apartmentobjekte zugreifen, allerdings betreibt das threadlose NTA kein Pooling. Die Operation auf ein NTA-Objekt sperrt das Apartment während des Zugriffs für sämtliche derzeit unbeteiligte Clients. Der Vorteil eines NTAs ist sein unkompliziertes Zusammenspiel mit STAs und MTAs. Legt beispielsweise ein Prozess eine MTA- als auch STA-fähige Komponente erstmalig in einem STA-Thread an, dann instanziert COM bei einer zweiten Bindung in ein gefordertes prozessexternes MTA das neue Objekt automatisch in das STA. Der zweite Client ist gezwungen, mit der langsameren Variante zu agieren, obwohl er das schnellere MTA unterstützt. NTAs lösen diesen Konflikt, indem sie beide Apartmentvarianten gleichzeitig bedienen können. Der Beiname Rental Apartment [rental, engl.: Miet-Appartement] impliziert bildlich die Arbeitsweise eines NTAs: Es wurde maßgeblich für den temporären Zugriff über Apartmentgrenzen ohne Threadwechsel hinaus entworfen. Dadurch begründet sich auch sein bevorzugter Einsatz im Zusammenhang mit MTS [s. Kapitel 2.7]. Neutrale Apartments sind erst ab Windows 2000 verfügbar, ATL 3.0 bietet für sie noch keine Unterstützung.

In einem Prozess lassen sich die drei unterschiedlichen Apartmenttypen miteinander kombinieren. Einschränkend sei jedoch bemerkt, dass MTA und NTA jeweils nur einmal pro Prozess STAs allerdings in beliebiger Anzahl angelegt werden dürfen. Der Threadkontext prozessexterner OutProc-Komponenten verhält sich identisch mit dem eines Clients. Einem InProc-Server hingegen ist bezüglich seines Threadmodells in der Registry ein Apartmentattribut zuzuordnen. Gültige Attribute hierfür sind: -, Apartment, Free, Both und Neutral.

Nach Aufruf der einschlägigen API-Funktionen CreateProcess oder CreateThread ist der neue Thread in einem weiteren Schritt COM anzumelden, um ihn einem Apartment zuzuordnen. Die Zuweisung in ein entsprechendes Apartment erfolgt über die Konstanten COINIT_MULTITHREADED und COINIT_APARTMENTTHREADED der API-Funktion CoInitializeEx. Die Funktion CoUninitialize entfernt den Thread wieder aus seinem Apartment.