Lade Inhalt...

Automatisierte Anpassung von .NET-Komponenten an einen kanonischen Interaktionsstil

©2006 Diplomarbeit 165 Seiten

Zusammenfassung

Inhaltsangabe:Zusammenfassung:
Die komponentenbasierte Software-Entwicklung ist die Lösung der Softwaretechnik zu einer Modularisierung der Software, die zu einer erhöhten Wiederverwendung, Qualität, Wartbarkeit und Flexibilität führt. Es haben sich mittlerweile mehrere Komponentenmodelle (COM – Component Object Model von Microsoft, Java Beans, Enterprise Java Beans, .NET, CORBA Component Model usw.) mit verschiedenen Vor- und Nachteilen in der Software-Industrie etabliert. Die Investitionen in die Entwicklung von Komponenten sind in den letzten Jahren enorm gestiegen und es besteht bereits ein beachtlicher Bestand an Software-Komponenten.
Immer häufiger wird der Weg der Integration einer bestehenden Komponente als die Neuentwicklung gewählt. Wegen der Heterogenität der Komponentenmodelle und ihrer Laufzeitumgebungen kann man leider nicht ohne weiteres eine Komponente für eine bestimmte Laufzeitumgebung in einer anderen Laufzeitumgebung, die mit der ersten inkompatibel ist, nutzbar machen. Wenn die Interaktionsstile in den beiden Umgebungen aufrufbasiert und die Unterschiede nur technischer Natur sind, kann eine aufrufbasierte Middleware für Fernaufrufe wie CORBA weiterhelfen, die Implementierungen in vielen Programmiersprachen vorzuweisen hat. Wenn die Laufzeitumgebungen vom Prinzip her unterschiedliche Interaktionsstile aufweisen, kann der Ansatz von Prof. Dr.-Ing. Klaus-Peter Löhr zum Einsatz kommen, der dieser Arbeit zugrunde gelegt wurde.
Er stellt eine weitgehende Generalisierung des generator-gestützten Vertreter-Treiber-Ansatzes von CORBA dar (bekannt im Englischen als „Proxy/Driver“ oder „Stub/Skeleton“). Ziel ist es, dass die Vermittlung zur Komponente für den Nutzer völlig transparent abläuft, so dass der Anschein erweckt wird, als wäre die Komponente speziell für die gewünschte Umgebung geschaffen worden. Zu diesem Zweck wird ein kanonischer Interaktionsstil definiert, auf den alle zu unterstützenden Interaktionsstile abgebildet werden können. Die Schnittstelle der Komponente wird in AID (Abstract Interface Definition), eine speziell für den kanonischen Interaktionsstil entwickelte Sprache, beschrieben. Nun können auf dieser Basis die Generatoren für die gewünschte Umgebung bzw. die Umgebung der Komponente einen Vertreter bzw. einen Treiber zur Komponente erstellen. Vertreter und Treiber kommunizieren über einen Kommunikationskanal, da sie in unterschiedlichen Umgebungen laufen.
Im Rahmen dieser Arbeit wurde eine Lösung zur […]

Leseprobe

Inhaltsverzeichnis


Inhalt

Kurzfassung

1 Einleitung und Zielsetzung
1.1 Wiederverwendung von Software
1.2 Komponentenbasierte Software-Entwicklung
1.3 Integration von Komponenten
1.4 Interaktionsstile und Komponenten
1.5 Integration von Komponenten mit einem inkompatiblen Interaktionsstil
1.6 Zielsetzung
1.7 Gliederung dieser Arbeit

2 Vermittlung zwischen unterschiedlichen Interaktionsstilen
2.1 Grundlagen
2.2 Kanonischer Interaktionsstil
2.3 Lebensdauer und Exemplare einer AID-Komponente
2.4 Aufgaben des Vertreters und Treibers
2.5 Beschreibung der Schnittstellen der Komponente in AID
2.6 Generierung von Vertreter und Treiber
2.7 Wahl der Middleware und des Vermittlungsprotokolls
2.8 Umsetzung des kanonischen Interaktionsstils auf Basis von CORBA
2.9 Das AID-IDL Mapping
2.9.1 Mapping von primitiven Typen
2.9.2 Mapping von COMPONENT
2.9.3 Mapping von TUPLE
2.9.4 Mapping von EXCEPTION
2.9.5 Mapping von Feldern
2.9.6 Mapping von INTERFACE
2.9.7 Mapping von INIT
2.9.8 Mapping von IN-Ereignissen
2.9.9 Mapping von OUT-Ereignissen
2.9.10 Mapping von INOUT-Ereignissen
2.9.11 Mapping von OUTIN-Ereignissen

3 .NET-Komponenten – Grundlagen und Interaktionsstil
3.1 Was ist eine .NET-Komponente?
3.2 Laufzeitumgebung für .NET-Komponenten
3.3 Interaktionsstil in .NET
3.3.1 Erzeugung von Komponentenexemplaren
3.3.2 Zerstörung von Komponentenexemplaren
3.3.3 Bindung zu einem Komponentenexemplar
3.3.4 Primitive zur Kommunikation mit der Komponente
3.3.5 Schnittstelle einer .NET-Komponente
3.3.6 Typsystem in .NET
3.4 Weitere Aspekte
3.4.1 Lokale und entfernte Nutzung von Komponenten
3.4.2 Asynchrone Aufrufe
3.4.3 Vererbung und Implementierung von Schnittstellen
3.4.4 Importierte Schnittstellen
3.4.5 Abstrakte Methoden, Eigenschaften und Ereignisse
3.4.6 Statische Methoden, statische Eigenschaften etc

4 Vermittlung zwischen dem kanonischen und dem Interaktionsstil von .NET im Treiber
4.1 Abbildung des Typsystems von .NET in AID
4.1.1 Primitive Typen
4.1.2 Klassen ohne wesentliche Funktionalität, die als Verbundtypen verwendet werden
4.1.3 Verbundtypen (Strukturen)
4.1.4 Ausnahmen
4.1.5 Klassen mit Funktionalität
4.1.6 Abstrakte Klassen und Schnittstellen
4.1.7 Delegaten
4.2 Steuerung der Lebensdauer der Komponentenexemplare
4.2.1 Erzeugung und Initialisierung
4.2.2 Zerstörung und Freigabe
4.3 Abbildung der Schnittstelle einer .NET-Komponente in AID
4.3.1 Methoden
4.3.2 OneWay-Methoden
4.3.3 Ereignisse
4.3.4 Eigenschaften
4.3.5 Mitgliedsvariablen
4.3.6 Geerbte Methoden, Eigenschaften und Ereignisse
4.3.7 Abstrakte Methoden, Eigenschaften und Ereignisse
4.3.8 Statische Methoden, Eigenschaften und Ereignisse
4.4 Umsetzung des Treibers
4.4.1 IIOP.NET – Eine CORBA-Implementierung für .NET
4.4.2 Umsetzung des IIOP-Servers
4.4.3 AID_System
4.4.4 Grundgerüst zu einer AID-Schnittstelle
4.4.5 Umsetzung der Klasse [Name]_IN
4.4.6 Die Klasse [Name]_OUT
4.4.7 Umsetzung der Klasse [Name]_OUTIntern
4.4.8 Klassen zu AID-Tupeln
4.4.9 Klassen zu AID-Ausnahmen
4.4.10 Klassen zur Konvertierung von Feldern

5 Vermittlung zwischen dem kanonischen und dem Interaktionsstil von .NET im Vertreter
5.1 Abbildung von AID in .NET
5.1.1 Primitive Typen
5.1.2 Felder
5.1.3 Schlüsselwort COMPONENT
5.1.4 AID-Tupeln
5.1.5 AID-Ausnahmen
5.1.6 AID-Schnittstellen
5.2 Umsetzung des Vertreters
5.2.1 Klasse AIDSystemProxy
5.2.2 Klasse AIDSystemException
5.2.3 Gerüst zu einer AID-Schnittstelle
5.2.4 Vertreter-Klasse einer AID-Schnittstelle
5.2.5 Schnittstelle [Name]Out
5.2.6 Klasse [Name]_OUTImpl
5.2.7 Klassen zu AID-Tupeln
5.2.8 Klassen zur Konvertierung von Feldern

6 Umsetzung eines Generators zu automatisierter Erzeugung von Treibern und Vertretern für .NET
6.1 Ablauf der Generierung
6.2 Parsen der AID-Datei
6.3 AID-Datenbaum
6.4 Generierung des Quellcodes
6.5 Übersetzung und Erstellung einer Vertreter- bzw. Treiber-Assembly
6.6 Konfiguration und Inbetriebnahme des Generators

7 Zusammenfassung und Ausblick

Anhang A. Syntax, Typsystem und Semantik von AID

Anhang B. Auflistung der Annotationen für die Treiber-Generierung zu .NET-Komponenten

Anhang C. Auflistung der Annotationen für die Vertreter-Generierung in .NET zu AID-Komponenten

Anhang D. Installation und Konfiguration des Generators für den Interaktionsstil in .NET (aid_net.exe)

Anhang E. Anleitung zur Erstellung von Treibern von .NET-Komponenten

Anhang F. Anleitung zur Erstellung von Vertretern von AID-Komponenten in .NET

Quellenverzeichnis

Eidesstattliche Erklärung

Kurzfassung

Die komponentenbasierte Software-Entwicklung ist die Lösung der Softwaretechnik zu einer Modularisierung der Software, die zu einer erhöhten Wiederverwendung, Qualität, Wartbarkeit und Flexibilität führt. Es haben sich mittlerweile mehrere Komponentenmodelle (COM – Component Object Model von Microsoft, Java Beans, Enterprise Java Beans, .NET, CORBA Component Model usw.) mit ver­schiedenen Vor- und Nachteilen in der Soft­ware-In­dustrie etabliert. Die Investitionen in die Entwicklung von Komponenten sind in den letzten Jahren enorm gestiegen und es be­steht bereits ein beachtlicher Bestand an Soft­ware-Komponenten. Immer häufiger wird der Weg der Integration einer bestehenden Kom­po­nente als die Neuentwicklung gewählt. Wegen der Heterogenität der Komponentenmo­delle und ihrer Laufzeitumgebungen kann man lei­der nicht ohne weiteres eine Komponente für eine bestimmte Laufzeitumgebung in einer anderen Laufzeitumgebung, die mit der ersten inkompatibel ist, nutzbar machen. Wenn die Interaktions­stile in den beiden Umge­bungen aufrufbasiert und die Unterschiede nur technischer Natur sind, kann eine auf­ruf­basierte Middleware für Fernaufrufe wie CORBA weiterhelfen, die Implemen­tierungen in vielen Pro­grammiersprachen vorzuweisen hat. Wenn die Laufzeitumge­bungen vom Prinzip her un­terschiedliche Interaktionsstile aufweisen, kann der Ansatz von Prof. Dr.-Ing. Klaus-Peter Löhr zum Einsatz kommen, der dieser Arbeit zugrunde gelegt wurde. Er stellt eine weitge­hende Generalisierung des generator-gestützten Vertreter-Treiber-Ansatzes von CORBA dar (be­kannt im Englischen als „Proxy/Driver“ oder „Stub/Skeleton“). Ziel ist es, dass die Vermittlung zur Komponente für den Nutzer völlig transparent abläuft, so dass der An­schein erweckt wird, als wäre die Komponente speziell für die gewünschte Umgebung ge­schaffen worden. Zu diesem Zweck wird ein kanonischer Interaktionsstil de­finiert, auf den alle zu unterstützenden Interaktionsstile abgebildet werden können. Die Schnittstelle der Komponente wird in AID (Abstract Interface Definition), eine speziell für den kanoni­schen Interaktionsstil entwickelte Sprache, beschrieben. Nun können auf dieser Basis die Gene­ratoren für die gewünschte Umgebung bzw. die Umgebung der Kompo­nente einen Vertre­ter bzw. einen Treiber zur Komponente erstellen. Vertreter und Treiber kommunizie­ren über einen Kommunikationskanal, da sie in unterschiedlichen Umgebun­gen laufen.

Im Rahmen dieser Arbeit wurde eine Lösung zur Realisierung des kanonischen Interak­ti­onsstils zwischen Vertreter und Treiber mittels CORBA-Fernaufrufen konzeptionell erarbei­tet und praktisch um­gesetzt. Nach einer Analyse des Interaktionsstils und der Schnittstellen von .NET-Kompo­nenten wurde die Abbildung des Interaktionsstils von .NET auf den kanoni­schen und umge­kehrt spezifiziert und im Vertreter und Treiber für das .NET Framework praktisch realisiert. Zu guter Letzt werden die Vertreter und Treiber mit Hilfe des entwi­ckelten Ge­nerators automatisiert generiert.

Die Semantik, die Syntax und das Typsystem von AID mussten zusammen mit Prof. Löhr, seinen wissenschaftlichen Mitarbeitern und Daniel Nowak[1] genau konkretisiert werden. Nur dadurch konnte ein für alle Generatoren verbindliches AID-IDL Mapping spezifiziert werden. Parallel zu dieser Arbeit liefen die Entwicklungsarbeiten an Generatoren für die Inter­ak­ti­onsstile bei Message-Driven Beans, Unix-Filter-Programmen und Komponenten, die über Java Tuplespaces interagieren.

Markenrechtlicher Hinweis

Die in dieser Arbeit wiedergegebenen Firmen-, Markennamen und Warenzeichen können auch ohne besondere Kennzeichnung geschützte Namen oder Marken sein und sind Eigentum des jeweiligen Herstellers.

1 Einleitung und Zielsetzung

1.1 Wiederverwendung von Software

Wiederverwendung wird als zentraler Ansatz in der Softwaretechnik angesehen, qualitative Software wirtschaftlich zu erstellen. Neben der Möglichkeit, Methodologie und Wissen wäh­rend der Softwareentwicklung wiederzuverwenden, kann man auch auf fertigen Quell­code oder bereits übersetzten Programmcode in Form von Komponenten zurückgreifen. Wäh­rend die Wiederverwendung von Quellcode bei kleinen Programmen und innerhalb kleiner Teams insbesondere wegen der hohen Leistung bei der Ausführung noch sinnvoll ist, führt sie bei großen Projekten zur unproduktiven Software-Entwicklung. Zum einen muss immer wieder sehr viel Detailwissen zu dem Quellcode mitgegeben werden. Zum anderen können die Anforderungen bezüglich der Wartbarkeit, Qualität, Konfigurierbarkeit, Flexibi­lität und der Austauschbarkeit von Teilen der Software im laufenden Betrieb von einer mo­nolithi­schen Software nicht erfüllt werden, wenn sie bei kleinsten Änderungen immer wieder von Null an neu übersetzt werden muss. Die Lösung ist eine Modularisierung der Software in Form von ausführbaren Komponenten.

1.2 Komponentenbasierte Software-Entwicklung

Der Begriff „Software-Komponente“ wird insbesondere in technischen Abhandlungen zu verschiedenen Komponenten-Modellen nicht konsistent genutzt, dennoch wird häufig eine Definition gegeben, die auf Szyperski zurückzuführen ist [Szyperski, 2002]. „Software-Kom­ponenten sind ausführbare Software-Einheiten, die unabhängig hergestellt, erworben und konfiguriert werden und aus denen sich funktionierende Gesamtsysteme zusammen­setzen lassen“ [Gräbe, 2004]. Eine Zusammensetzung eines Systems aus Komponenten von un­terschiedlichen Herstellern setzt natürlich voraus, dass die Komponenten nach be­stimmten Vorschriften erstellt wurden. Die Vorschriften zur Erstellung, Beschreibung der Schnitt­stel­len und Abhängigkeiten sowie Erzeugung und Ressourcennutzung werden in einem Kom­ponentenmodell festgelegt. Der „Erfinder“ des Komponentenmodells stellt eine Kom­po­nentenplattform zur Verfügung, auf der die Komponenten ausgeführt werden. Die Kom­po­nentenplattform ist zuständig für die Erzeugung der Komponenten und die Sicher­stellung der reibungslosen Kommunikation zwischen ihnen.

1.3 Integration von Komponenten

Im Laufe der Zeit wurden bereits mehrere Generationen von Komponentenmodellen ent­worfen. Einige der Komponentenmodelle, die momentan Anwendung finden, seien hier er­wähnt: Java Beans [JavaBeans], Enterprise Java Beans [EJB], COM (Component Object Model) [COM], .NET Components [Löwy, 2003], CCM (CORBA Component Model) [CCM]. Das Ge­meinsame der hier aufgelisteten Komponentenmodelle ist, dass sie objektorientiert sind und die Interaktion zwischen den Komponenten und der Umgebung (in EJB und CCM nur zum Teil) aufrufbasiert ist. Dennoch ist eine Zusammenarbeit zwischen Komponenten aus unterschiedlichen aufrufbasierten Komponentenmodellen ohne weiteres nicht möglich. Denn jede der Komponenten läuft nur in ihrer gewohnten Umgebung. Es wird eine „Brücke“ benötigt, die die Aufrufe aus der einen Umgebung in die andere weiterleitet und zurück­transportiert. So könnte man beispielsweise aus Java heraus auf COM-Komponenten zugreifen. Dies wird durch Java-COM Bridge [JCOM] ermöglicht. Solche und ähnliche Bestre­bungen zielen darauf ab, aufwendige und komplexe Software-Komponenten über Kompo­nentenplattformen hinweg zu integrieren. Die Investitionen zur Umsetzung der „Brücke“ sollten sich ab der zweiten oder dritten Komponente, die mit ihr in der fremden Umgebung integriert werden, lohnen. Ganz allgemein lassen sich aufrufbasierte Komponenten auf Ba­sis von CORBA [CORBA] integrieren, von der Implementierungen in vielen Programmier­sprachen bereits existieren. Dabei wird der Vertreter-Treiber-Ansatz angewendet. In der fremden Umgebung weist der Vertreter die Schnittstelle der Komponente auf und nimmt die Aufrufe für sie entgegen. Der Treiber leitet die Aufrufe an die Komponente weiter, die er vom Vertreter über einen Kommunikationskanal erhalten hat. Da sich die Aufrufe in beiden Umgebungen als Konzept weitgehend entsprechen, bestehen die Schwierigkeiten meis­tens in den unterschiedlichen Typsystemen und lassen sich durch Anpassung des Daten­formats lösen.

Komponenten aus unterschiedlichen Umgebungen lassen sich auch mit den J-Integra Pro­dukten von Intrinsyc Software International Inc. [J-Integra] integrieren. Mit J-Integra Espresso lässt sich z. B. aus .NET heraus über RMI-IIOP auf J2EE/EJBs und Java RMI Objekte zugrei­fen.

1.4 Interaktionsstile und Komponenten

Der Begriff „Interaktionsstil“ bezeichnet die Art und Weise, wie zwei oder mehrere Objekte miteinander kommunizieren und auf das Verhalten der jeweils anderen Kommunikations­partner reagieren. Dabei wird unter anderem geregelt, wie sich die Kommunikationspartner zusammenfinden, wie eine Bindung zwischen ihnen entsteht, wer aktiv im Kommunika­ti­onsprozess sein darf und welche elementaren Einheiten bei der Kommunikation verwendet werden. Bei dem aufrufbasierten Interaktionsstil z. B. gibt es immer genau zwei Entitäten, deren Beziehung mit „Aufrufer-Aufgerufene“ klassifiziert werden kann. Als Entitäten kom­men Prozeduren, Funktionen, Methoden und Eigenschaften in Frage, je nachdem, welche Begrifflichkeiten in der Programmiersprache genutzt werden, in der die Entitäten definiert worden sind. Klassischerweise muss der Aufrufer die Einsprungadresse des Auf­gerufenen kennen und die notwendigen Parameter bereithalten. Die Umgebung trans­portiert[2] die Para­meter zu dem Aufgerufenen und übergibt ihm die Kontrolle, so dass er selbst weitere Aktivitäten veranlassen kann. Nachdem er fertig ist, wird die Kontrolle und vielleicht einige Parameter zu seinem Aufrufer zurücktransportiert. In einigen Umgebungen können auch asynchrone Aufrufe realisiert werden. In diesem Fall gibt der Aufrufer die Kontrolle nicht ab, sondern ein zusätzlicher Thread übernimmt die Ausführung des Aufrufs. Aus Sicht der Komponente ändert sich aber nichts, wenn der Aufruf asynchron ausgeführt wird.

Einen anderen Interaktionsstil findet man z. B. bei Message-Driven Beans [Almaer, 2006], die über einen Java Messaging Service mit ihrer Umgebung kommunizieren [Löhr, 2003]. Die Interaktion findet anhand von asynchronen Nachrichten statt, d. h., das Absenden und Empfangen einer Nachricht sind zeitlich entkoppelt. Der Absender einer Nachricht ist wie­der frei, sobald er die Nachricht an den Nachrichtendienst übergeben hat, der sich um die Auslieferung der Nachrichten kümmert. In der Zwischenzeit wird die Ausführung von Ab­sender und Empfänger unabhängig voneinander fortgesetzt. Im Allgemeinen handelt es sich bei den Nachrichten um Aufträge oder Ereignisse, die der Empfänger zu erledigen hat oder auf die er reagieren soll. Ein Message-Driven Bean erhält die Nachrichten im Push-Mo­dus, indem der Nachrichtendienst bei ihm die Methode onMessage() aufruft. Obwohl hier der Anschein erweckt wird, dass der Interaktionsstil doch aufrufbasiert wäre, ist dies nicht der Fall. Wenn man den Message-Driven Bean als Komponente betrachtet, die nach den Vorschriften des Komponentenmodells für Enterprise Java Beans gebaut wurde, enthält die Schnittstelle der Komponente nicht bloß die Methode onMessage(), sondern sie ist vielmehr die Beschreibung, über welche Kanäle die Nachrichten kommen, welche Nachrichtentypen die Komponente akzeptiert und wie sie sie verarbeitet. Wenn die gleiche Funktionalität in einer typischen aufrufbasierten Komponente verpackt werden sollte, würde sie wahr­scheinlich eher mehrere Methoden haben, die den unterschiedlichen Nachrichtentypen und Zwecken der Nachrichten entsprechen, und die Ergebnisse, die der Message-Driven Bean über einen oder mehrere Nachrichten-Kanäle liefert, müssen hier entweder als Ergebnis-Parameter zurückgegeben werden oder der Auftraggeber muss sich bei der Komponente für Rückrufe registrieren.

Einen wieder ganz anderen Interaktionsstil können wir finden, indem wir uns von der klas­sischen Vorstellung vom Komponentenmodell lösen. Man kann z. B. als Komponenten­plattform das Betriebssystem ansehen und die Programme sind die Komponenten, die über Kanäle Daten miteinander austauschen können [Löhr, 2003]. Standardmäßig kann jedes Programm mit dem Betriebssystem über den Standardeingabe-, Standardausgabe- und Fehler-Kanal kommunizieren. Das Betriebssystem seinerseits kann die Kanäle umlei­ten und mit anderen Kanälen verbinden. So ist es z. B. möglich, die Ergebnisse aus einem Programm an ein anderes Programm weiterzugeben und so eine Pipeline-Verarbeitung durch eine ganze Reihe von Programmen per Befehl an das Betriebssystem einzurichten. Die Interaktion ist in diesem Fall flussbasiert. Die Daten fließen nur in einer Richtung. Die einzige Einheit, die vom Datenfluss sinnvoll abgegrenzt werden kann, ist der Byte. Die Daten weisen sonst keine Struktur auf, die dem Empfänger helfen würde, sie besser zu interpretieren. Das Einzige, wovon er durch das Betriebssystem benachrichtigt wird, ist, ob der Datenfluss zu Ende ist. Einige Programme arbeiten zeilenbasiert oder setzen ein be­stimmtes Format der Daten voraus, andere wiederum verarbeiten den Bytestrom ohne irgendeine logische Struktur darin zu suchen. Diesen Interaktionsstil nennen wir Pipe&Filter und die Komponenten Filter-Komponenten.

Die Integration von einer Filter-Komponente in einer aufrufbasierten Umgebung wird durch die Inkompatibilität der Interaktionsstile deutlich erschwert. Wie auch im Falle der Message-Driven Bean ist es hier nicht mehr ausreichend, einfach die Primitive des einen Interaktions­stils in der anderen Umgebung weiterzuleiten, weil es einfach keine Eins-zu-Eins-Entspre­chungen gibt. Umgekehrt ist es mindestens genauso schwierig, eine aufrufbasierte Kom­ponente als eine Filter-Komponente oder Message-Driven Bean aussehen zu lassen. Den­noch bleibt die Wiederverwendung von Komponenten, auch über Grenzen eines Interak­tionsstils hinweg, wünschenswert.

1.5 Integration von Komponenten mit einem inkompatiblen Interak­tionsstil

Ein guter Ansatz, wie Komponenten mit einem inkompatiblen Interaktionsstil zu diesem in der gewünschten Umgebung benutzt werden können, wird in [Löhr, 2003] vorgeschlagen. Dabei handelt es sich nicht um einen Ansatz, der bei zwei bestimmten Interaktionsstilen Abhilfe schafft, sondern um einen generellen Ansatz, bei dem versucht wird, zwischen je zwei Paaren von Interaktionsstilen zu vermitteln. Um dies zu erreichen, definiert man einen sog. kanonischen Interaktionsstil, also einen grundlegenden Interaktionsstil, der aus sol­chen Primitiven besteht, mit deren Hilfe man alle zu unterstützenden Interaktionsstile nachbilden kann. Man stelle sich vor, dass ein Komponentenexemplar ein Zustandsauto­mat sei, der nur mittels ein- und ausgehender Ereignisse mit seiner Umgebung interagiert. Die auf diese Weise konstruierte kanonische Schnittstelle der Komponente wird in AID (Abstract Interface Definition) beschrieben. Eine Schnittstellendefinition in AID ist im We­sentlichen eine Auflistung der ein- und ausgehen­den Ereignisse, die die Komponente ak­zeptiert bzw. auslöst. Die Ereignisse können Para­meter haben, deren Typen, Anzahl und Reihenfolge durch die Ereignisdefinition festgelegt ist. Auf die genaue Syntax von AID wird später eingegangen. Auf Basis der Beschreibung der Schnittstellen in AID werden automa­tisch mit zwei unterschiedlichen Generatoren ein Vertreter der Komponente in der ge­wünschten Umgebung und ein Treiber für die Kom­ponente, der zusammen mit der Kom­ponente in ihrer gewohnten Umgebung ausgeführt wird, erstellt. Wir sollten uns mit dieser kurzen Darstellung des Ansatzes fürs Erste begnü­gen. Der Ansatz wird ausführlicher im nächsten Kapitel beschrieben. Es muss jedoch klar geworden sein, dass für jeden der zu unterstützenden Interaktionsstile und Umgebungen ein Paar von Generatoren umgesetzt werden muss – jeweils ein Generator für die Vertre­ter-Erzeugung und einer für die Treiber-Erzeugung zu einem bestimmten Interaktionsstil. Wenn einmal die Generatoren fertig sind, kann man theoretisch Komponenten aus jedem der unterstützten Interaktionsstile in jedem anderen Interaktionsstil nutzen.

1.6 Zielsetzung

Diese Diplomarbeit hat zum Ziel, einen Entwurf und eine Umsetzung von den Vertreter- und Trei­ber-Generatoren für den aufrufbasierten Interaktionsstil in .NET im Rahmen des AID-Projektes in der Arbeitsgruppe „Systemsoftware“ von Prof. Dr.-Ing. Klaus-Peter Löhr im Institut für Informatik an der Freien Universität in Berlin vorzulegen. Dem AID-Projekt ist der in [Löhr, 2003] beschriebene Vertreter-Treiber-Ansatz zur Integration von Komponenten in Umge­bungen mit einem inkompatiblen Interaktionsstil zugrunde gelegt. Diese Diplomar­beit, zu­sammen mit der Diplomarbeit von Daniel Nowak, stellten die ersten Implementie­rungen von Generatoren auf breiter Basis dar und deswegen müssen viele Entwurfsent­scheidungen, die in [Löhr, 2003] offen geblieben sind, getroffen werden. Zunächst muss entschieden werden, welche Middleware und Protokolle zur Übertragung der Ereignisse des kanoni­schen Interaktionsstils zwischen Vertreter und Treiber eingesetzt werden. Eine Bedingung, die dabei Berücksichtigung finden soll, ist, dass Vertreter und Treiber auf ver­schiedenen Rechnern laufen können. Der Entwurf und die Umsetzung eines eigenen Ver­mittlungsproto­kolls und Middleware ist eine Alternative, die nicht auszuschließen ist, falls keine passen­den gefunden werden. Das Typsystem und die Syntax von AID müssen ge­nauer spezifi­ziert werden. Falls Erweiterungen in AID notwendig sind, um nicht berücksich­tigte, für alle Interaktionsstile gemeinsame Konzepte umzusetzen, werden diese vorge­nommen. Erst nach diesen für das ganze AID-Projekt relevanten Entscheidungen kann man die Vorgehensweise zur Abbildung des eigenen Interaktionsstils festlegen. Es muss analysiert werden, was eine .NET-Komponente ist, welche Primitive die Schnittstellen von .NET-Komponenten aufweisen, wie werden Komponenten-Exemplare erzeugt, wie wird die Schnittstelle ange­sprochen. Da­nach folgt das Spezifizieren der Abbildung zwischen dem kanonischen und dem Interak­tionsstil in .NET. Des Weiteren müssen auch die Annotationen festgelegt wer­den, die je­weils bei der Vertreter- und Treiber-Erzeugung zum Einsatz kom­men. Bei der Um­setzung der Generatoren sollen Benutzerfreundlichkeit, Konfigurierbarkeit und Erwei­ter­barkeit Beach­tung finden.

1.7 Gliederung dieser Arbeit

Nach einer Einführung in das Thema und die Zielsetzung der Diplomarbeit in diesem Ka­pitel wird im nächsten Kapitel der Ansatz von [Löhr, 2003] zur Integration von Kompo­nen­ten in Umgebungen mit einem inkompatiblen Interaktionsstil ausführlich erläutert. Es wird jedoch zusätzlich der Anschluss zur praktischen Umsetzung geschaffen, indem die Anfor­derungen zu der Middleware und dem Vermittlungsprotokoll dargelegt und die Wahl von CORBA als Middleware begründet werden. Auf dieser Basis kann nun festgelegt werden, wie die AID-Konstrukte und die Möglichkeiten der AID-Plattform auf IDL-Deklarationen und CORBA-Funktionalität ganz konkret abgebildet werden. Kapitel 3 gibt Ihnen eine Einführung in die Welt der .NET-Komponenten. Es wird definiert, was eigentlich eine .NET-Komponente ist und was für ein Interaktionsstil in .NET herrscht. Kapitel 4 und 5 sind der theoretischen Abbildung der AID-Konstrukte auf .NET-Konstrukte und umgekehrt, der Vermittlung im Trei­ber bzw. Vertreter und deren Umsetzung gewidmet. Kapitel 6 stellt den Generator zur Er­zeugung von Vertretern und Treibern für das .NET Framework vor. Zum Schluss kommen Zusammenfassung und ein Ausblick zur Weiterentwicklung der AID-Plattform in einem weiteren Kapitel.

Der Hauptteil dieser Diplomarbeit konzentriert sich auf die Fragen „Wie?“ und „Warum?“ bei der Beschreibung der Konzepte und der Umsetzung und eignet sich somit nicht ganz als Referenz zu z. B. allen AID-Konstrukten, die es gibt, zu den möglichen Annotationen, die bei der Vertreter- bzw. Treiber-Generierung zum Einsatz kommen könnten, oder zu der Installation und Inbetriebnahme des Vertreters, des Treibers und des Generators. Diese an sich sehr nützlichen Informationen wurden komplett und entsprechend strukturiert in Hin­blick der Nutzung als Referenz in den Anhängen A bis F untergebracht.

Die praktische Umsetzung in dieser Arbeit umfasst die Entwicklung des Generators (aid_net.exe), der sowohl Vertreter als auch Treiber für das .NET Framework 2.0 generieren kann, und die Entwicklung eines universellen IIOP-Servers zum Treiber. Beide wurden mit Hilfe der Entwicklungsumgebung Microsoft Visual Studio 2005 programmiert. Die Entwick­lungsprojekte zusammen mit einer Web-basierten Dokumentation zum Quellcode werden auf einer CD gebrannt und dieser schriftlichen Arbeit beigelegt.

2 Vermittlung zwischen unterschiedlichen Interakti­onsstilen

In diesem Kapitel wird genauer auf den Ansatz in [Löhr, 2003] eingegangen, der dieser Diplomarbeit zugrunde gelegt wird. Im Laufe der Entwurfsphase dieser Arbeit und der Dip­lomarbeit von Daniel Nowak, die sich mit dem Interaktionsstil bei Message-Driven Beans befasst und zeitgleich mit dieser bearbeitet wurde, wurden viele Entwurfsentscheidungen getroffen, die zunächst als ausgearbeitete Lösungsvorschläge von Daniel Nowak und mir unterbreitet und zusammen mit Prof. Löhr und seinen wissenschaftlichen Mitarbeitern Karsten Otto und Karl Pauls ausdiskutiert wurden. Hauptsächlich handelt es sich dabei um die Wahl der Middleware, das Festlegen des Kommunikationsprotokolls und der Schnitt­stelle zwischen Vertreter und Treiber sowie ein genaues Spezifizieren des Typsystems, der Syntax und Semantik von AID. Es wurde zwischen Daniel Nowak und mir abgesprochen und von Prof. Löhr zugestimmt, dass ich in meiner Diplomarbeit die Wahl der Middleware, die dabei geltenden Rahmenbedingungen und das Protokoll zwischen Vertreter und Trei­ber erläutern werde und Daniel Nowak dagegen ausführlich über die Spezifikation und die Erweiterungen von AID berichten wird.

2.1 Grundlagen

Der Ansatz in [Löhr, 2003] zur Integration von Komponenten in fremden Umgebungen mit einem inkompatiblen Interaktionsstil zu dem der Komponente ist eine Generalisierung des Vertreter-Treiber-Ansatzes, der aus verschiedenen Middlewares für Fernaufrufe bekannt ist. Der Vertreter wird in der Umgebung ausgeführt, in der die Komponente benötigt wird, und weist eine für diese Umgebung typische Schnittstelle auf, die eigentlich die Kompo­nente hätte haben müssen, wenn sie für diese Umgebung umgesetzt worden wäre. Der Treiber hingegen läuft in der Umgebung der Komponente und vermittelt zwischen dem Vertreter und der Kompo­nente. Der Vertreter nimmt die Anfragen oder Nachrichten, die eigentlich an die Kompo­nente gerichtet werden, und schickt sie an den Treiber. Er seiner­seits gibt sie an die Kom­ponente weiter. Der Kreis schließt sich, indem der Treiber die Ant­worten, Nachrichten oder Anfragen von der Komponente aus wieder zurück an den Ver­treter liefert und dieser sie an seinen Interaktionspartner weiterleitet. Abbildung 1 stellt dies bildlich weiter unten dar.

Es handelt sich bei diesem Ansatz um einen generischen Ansatz, mit dem versucht wird, zwischen je zwei Paaren von Interaktionsstilen zu vermitteln. Dazu müssen das Vermitt­lungsprotokoll und die Interaktion zwischen dem Ver­treter und Treiber so funktionieren, dass keine für die Interaktionsstile beider Umgebungen relevanten Informationen verloren gehen. Es wird ein Basisinteraktionsstil benötigt, auf dem alle zu unterstützenden Interak­tionsstile abgebildet werden können, und umgekehrt, von ihm zu jedem der Interaktions­stile vermittelt werden kann. Diesen Interaktionsstil wer­den wir kanonischen In­teraktionsstil nennen.

Abbildung in dieser Leseprobe nicht enthalten

Abbildung 1. Vertreter-Treiber-Ansatz

Anmerkung:

Die genutzten Symbole sind an denen angelehnt, die im Zusammenhang mit CORBA Com­ponent Model genutzt werden [CORBA-CCM].

Abbildung in dieser Leseprobe nicht enthalten

2.2 Kanonischer Interaktionsstil

Der kanonische Interaktionsstil besteht aus solchen Primitiven, die einerseits elementar genug sind, um die einfachsten Primitiven aus allen zu unterstützenden Interaktionsstilen nachbilden zu können, aber andererseits geeignet kombiniert werden können, sodass auch komplexe Interaktionsmuster realisiert werden können. Nach [Löhr, 2003] wird die Kompo­nente als ein Zustandsautomat angesehen, der nur über ein- und ausgehende Ereignisse mit der Außenwelt kommuniziert. Diese neue abstrakte (kanonische) Schnittstelle der Komponente wird vom Treiber der Komponente den eventuellen Vertretern der Kompo­nente in unterschiedlichen Umgebungen zur Verfügung gestellt (siehe Abbildung 2).

Abbildung in dieser Leseprobe nicht enthalten

Abbildung 2. Kanonische Schnittstelle einer Komponente

Die kanonische Schnittstelle stellt eine Auflistung der Ereignisdefinitionen dar, nach denen der Treiber im Namen der Komponente Ereignisse akzeptiert bzw. aussendet. Eine Ereig­nisdefinition hat einen Identifikator und bestimmt eindeutig, in welche Richtung hinsichtlich der Komponente Ereignisse nach dieser Definition fließen sowie die Typen, die Anzahl und die Reihenfolge der Parameter, die Ereignisse nach dieser Definition abliefern. Als Typen kommen primitive Typen, Tupeln, Schnittstellen und Felder in Frage. Tupeln sind Ver­bundtypen, die mehrere Mitgliedsvariablen haben können und bei Übertragung zwi­schen zwei Umgebungen serialisiert werden. Bei Parametern vom Typ Schnittstelle wird dagegen nur ein Fernverweis in der fremden Umgebung erstellt.

Prinzipiell bei Interaktion zwischen zwei Entitäten ist eine Aktion seitens einer der Entitäten sehr häufig eigentlich eine Reaktion auf eine vorhergehende Aktion oder Reaktion seitens der anderen Entität. Eine solche Bezugnahme zwischen Ereignissen ist daher auch im ka­nonischen Interaktionsstil unverzichtbar. In [Löhr, 2003] wird vorgeschlagen, dass wenn eine Ein- und eine Ausgabe-Ereignisdefinition den gleichen Identifikator haben, dann stel­len Ereignisse nach der zweiten Ereignisdefinition eindeutig eine Reaktion auf Ereig­nisse nach der ersten Ereignisdefinition dar. Während dies bei Anfrage-Antwort-Interaktio­nen sehr praktisch sein kann, ist es aber bei komplizierteren Interaktionsmustern, bei de­nen die Rechnung auf lange Folgen von Reaktionen auf Reaktionen getragen werden muss, nicht mehr anwendbar. Es wurde jedoch festgestellt, dass das Letztere in keinem der Inter­akti­onsstile, die durch die AID-Plattform in näherer Zukunft unterstützt werden sollen (.NET-Komponenten, Java Beans, Message-Driven Beans, Unix Filter und Komponenten mit Inter­aktion über Java Tuplespaces), gebraucht wird.

Beispiel:

- Ein synchroner Methodenaufruf stellt eine Anfrage-Antwort-Interaktion dar. Mit der Ant­wort werden die Kontrolle und die Rückgabeparameter, falls vorhanden, zurückgegeben. Im kanonischen Interaktionsstil wird dies mit Hilfe von einem Paar von Ereignisdefinitio­nen realisiert, die denselben Identifikator haben. Zuerst erfolgt die Eingabe-Ereignisdefi­nition mit den entsprechenden Parametern der aufzurufenden Methode und anschlie­ßend vollzieht sich die Ausgabe-Ereignisdefinition mit den Parametern, die zurückgege­ben werden oder einen Rückgabewert der Methode darstellen.

2.3 Lebensdauer und Exemplare einer AID-Komponente

Bei vielen Laufzeitumgebungen für Komponenten (z. B. .NET-Komponenten, Java Beans, Unix Filter), unabhängig vom Interaktionsstil, kann der Nutzer einer Komponente ihre Le­bensdauer selbst beeinflussen. Ihm wird die Möglichkeit angeboten, mehrere Komponen­tenexemplare zu erzeugen und mit Hilfe von Initialisierungsparametern initialisieren zu las­sen. Sobald er ein privates Komponentenexemplar nicht mehr benötigt, kann er es zerstö­ren lassen. Dieselbe Funktionalität muss auch eine „Laufzeitumgebung“ von AID-Kompo­nenten bereitstellen. Ein Exemplar einer AID-Komponente ist im Prinzip ein Exemplar der Kompo­nente in ihrer gewohnten Umgebung und ein Exemplar des dazugehörigen Treibers. Das heißt nicht automatisch, dass jedes neue Exemplar einer AID-Komponente unbedingt ein neues Exemplar der ursprünglichen Komponente benötigt. Es hängt vielmehr davon ab, ob überhaupt mehrere Exemplare der Komponente möglich sind und es obliegt dem Trei­ber-Generator zu entscheiden, ob jedes der Treiber-Exemplare immer an ein neues Kompo­nentenexemplar gebunden ist oder alle mit demselben Komponentenexemplar inter­agie­ren.

Bei bestimmten Interaktionsstilen (.NET-Komponenten, Java Beans) können Komponenten­exemplare auf die Dienste von anderen Komponentenexemplaren zugreifen, während sie eine Aufgabe erledigen. Aus Effizienzgründen sollten die Exemplare von solchen Kompo­nenten in demselben Adressraum ausgeführt werden. Um dies zu unterstützen soll es möglich sein, dass mehrere Treiber von unterschiedlichen Komponenten, die in der glei­chen Um­gebung ausgeführt werden, in ein und demselben Deployment Unit [3] implementiert wer­den. Die dazugehörigen Verwaltungsfunktionen zur Erzeugung und Zerstörung von Ex­emplaren von AID-Komponenten werden in derselben Einheit bereitgestellt.

2.4 Aufgaben des Vertreters und Treibers

Vertreter und Treiber übernehmen eine vermittelnde Rolle – sie vermitteln immer zwischen dem kanonischen und einem konkreten Interaktionsstil der Umgebung, in der sie einge­setzt werden. Es können folgende vier funktionelle Blöcke in ihnen erkannt werden:

Abbildung in dieser Leseprobe nicht enthalten

Zur Verdeutlichung dient die folgende Abbildung.

Abbildung in dieser Leseprobe nicht enthalten

Abbildung 3. Aufteilung des Vertreters und Treibers in funktionellen Blöcken

Einem würde die vollständige Symmetrie von Vertreter und Treiber auf der Abbildung auf­fallen. Sie ist allerdings nicht gegeben, da der Vertreter in seiner Umgebung als Stellver­treter der Komponente und der Treiber als Stellvertreter des Nutzers der Komponente auf­treten. Sie sind also jeweils auf der Seite des anderen Interaktionspartners.

Vertreter und Treiber müssen eventuell noch eine Push[4] /Pull[5] -Anpassung vornehmen. So­wohl die Interaktion in der Umgebung des Vertreters als auch die in der Umgebung der Komponente sowie auch die Interaktion mit der Middleware, die für die Übertragung der Ereignisse zwischen Vertreter und Treiber im Einsatz ist, kann in Push- oder Pull-Modus erfolgen, dazu noch unterschiedlich für ein- und ausgehende Ereignisse. Einer Anpassung bedarf es, wenn die ein- oder ausgehenden Primitive des Interaktionsstils des Vertreters oder der Komponente in einem anderen Modus als dem der Middleware realisiert wer­den. Z. B. wenn die ausgehenden (hinsichtlich der Komponente) Ereignisse von der Middle­ware im Push-Modus geliefert werden und sie dagegen in der Umgebung des Ver­treters im Pull-Modus realisiert werden, muss der Ver­treter die Ereignisse puffern und vom Interes­senten abrufen lassen.

2.5 Beschreibung der Schnittstellen der Komponente in AID

Nachdem die Rolle und Funktion des Vertreters und Treibers im Vertreter-Treiber-Ansatz genau spezifiziert werden können, ist es nahe liegend, diese generieren zu lassen. Eine Generierung kann jedoch nur auf Basis einer formellen Beschreibung erfolgen. Deswegen werden die Schnittstellen der Komponente und andere Aspekte des kanonischen Interak­tionsstils in Bezug auf Besonderheiten der Komponente in AID (Abstract Interface Defini­tion) beschrieben. Im Laufe der Entwurfsphase dieser und der Diplomarbeit von Daniel Nowak wurden das Typsystem, die Syntax und Semantik von AID genauer spezifi­ziert. Eine ausführli­che Beschreibung von AID kann man Anhang A entnehmen. Eine Argu­mentation zu den Erweiterungen in AID findet man in der Arbeit von Daniel Nowak.

Um ein Gefühl dafür zu bekommen, was es in AID gibt, werden die Grundelemente und ihre Deklaration kurz skizziert.

Schnittstellen in AID enthalten Deklarationen von Initialisierungsparametern (Konstruktoren) und Ereignissen und werden mit dem Schlüsselwort INTERFACE eingeleitet. Konstruktoren haben eine Parameterliste und werden mit dem Schlüsselwort INIT deklariert. Ereignisse haben ebenfalls eine Parameterliste, aber können mit einem von vier Schlüsselwörtern de­klariert werden – IN, OUT, INOUT und OUTIN. Die ersten beiden sind zur Deklaration von einfachen Ein- und Ausgabe-Ereignissen, die letzten zwei dienen zur Deklaration von Paa­ren von Ereignissen, die eine Anfrage-Antwort-Interaktion abbilden. Dazu das folgende Beispiel:

INTERFACE Polygon

INIT [Point] vertices

INIT [Point] vertices, Color linecolor, Color background

INOUT getLinecolor RETURNS Color linecolor

INOUT setLinecolor Color line

OUT changed

Im Beispiel oben wird eine Schnittstelle Polygon definiert, die zwei explizite Konstruktoren enthält. Der parameterlose Konstruktor ist implizit immer vorhanden. Mit dem ersten Kons­truktor kann man die Eckpunkte des Polygons als Feld vom Typ Point und mit dem zwei­ten noch dazu die Linienfarbe und Hintergrundfarbe übergeben. Die beiden INOUT-Ereig­nisse dienen dazu, die Linienfarbe zu erfragen und zu setzen. Das OUT-Ereignis wird aus­gelöst, sobald eine Änderung am Polygon durchgeführt wird. Das Ereignis setLinecolor könnte theoretisch ein IN-Ereignis sein, aber der Interaktionsstil der Komponente ist mögli­cher­weise einer, bei dem das zugrunde liegende Primitiv im ursprünglichen Interak­tionsstil syn­chron ausgeführt wird. Bei INOUT- und OUTIN-Ereignissen hat man die Möglich­keit, Rück­gabeparameter mit dem Schlüsselwort RETURNS und mögliche Ausnahmen mit EXCEPT zu spezifizieren.

Im oberen Beispiel sind Color und Point eigentlich Tupel. Sie werden mit dem Schüsselwort TUPLE deklariert und können Mitgliedsvariablen von beliebigen Typen enthalten.

TUPLE Color

int red,

int green,

int blue

TUPLE Point

double x,

double y

Ausnahmen werden im Prinzip wie Tupel deklariert, nur dazu wird das Schlüsselwort EXCEPTION verwendet.

Alle bisher beschriebenen Deklarationen werden einem Element COMPONENT (durch END COMPONENT geschlossen) untergeordnet. So kann man einen globalen Namen aller Dekla­ration in der AID-Datei vergeben und durch das Annotieren dieses Elements globale gene­rator-spezifische Parameter festlegen.

COMPONENT Drawing

TUPLE …

EXCEPTION …

INTERFACE …

END COMPONENT

2.6 Generierung von Vertreter und Treiber

Ein zentraler Punkt des Vertreter-Treiber-Ansatzes ist immer die automatisierte Generie­rung von Vertretern und Treibern. Eine manuelle Erstellung würde sehr fehleranfällig sein. Zudem würde dies unser Ziel zu mehr Wiederverwendung von Komponenten beinahe zu­nichte machen, da die Umsetzung des Vertreters und Trei­bers nicht trivial ist. Vielmehr müssen alle generierten Vertreter und Treiber dasselbe Ver­mittlungsprotokoll umsetzen, um garantieren zu können, dass sich jeder von den generierten Vertretern zu einer Kompo­nente mit ihrem Treiber verständigen kann. Es wird zu jedem der zu unterstützenden Inter­aktionsstile ein Paar von Generatoren benötigt – jeweils ein Generator für die Vertre­ter-Er­zeugung und einer für die Treiber-Erzeugung für einen be­stimmten Interaktionsstil. Damit ist aber auch die AID-Plattform vollständig.

Die Generatoren können auf Basis von AID das Vermittlungsprotokoll und die kanonische Schnittstelle zwischen Vertreter und Treiber genau umsetzen. Wie im Einzelnen die Schnittstelle aus der kanonischen Beschreibung in die Schnittstelle des Interaktionsstils des Vertreters umgewandelt wird, oder was genau der Treiber bei einem eingehenden oder ausgehenden Ereignis vollziehen muss, können sie selbst nicht eindeutig bestimmen, wenn mehrere Alternativen im echten Interaktionsstil zur Verfügung stehen. Z. B. kann man ein eingehendes Ereignis aus dem kanonischen Interaktionsstil auf eine Methode oder eine Set-Eigenschaft in einem objektorientierten aufrufbasierten Interaktionsstil abbilden, wel­che genau von beiden muss der Anwender spezifizieren. Diese Angaben sind interak­tionsstil­spezifisch und können bei Vertreter- und Treiber-Generierung unterschiedlich sein. Sie werden direkt zu den entsprechenden Definitionen in AID annotiert. Auf die Annotatio­nen in AID für den Interaktionsstil in .NET wird später ausführlicher eingegangen.

2.7 Wahl der Middleware und des Vermittlungsprotokolls

Ein Punkt ist bisher völlig offen geblieben – welche Middleware zwischen Vertreter und Treiber eingesetzt werden soll und wie das Vermittlungsprotokoll aussieht, mit dem die Primitive des kanonischen Interaktionsstils übertragen werden. Bei der Wahl oder Imple­mentierung der Middleware und des Vermittlungsprotokolls gelten folgende Rahmenbedin­gungen:

- Über die Middleware müssen mit Hilfe des Vermittlungsprotokolls alle Primitiven des kanonischen Interaktionsstils eindeutig übertragbar sein.
- Die Middleware bietet eine sichere Kommunikation (Verlorengehen, Duplikate und Vertauschen von Nachrichten werden erkannt).
- Die Middleware soll aus möglichst vielen Programmiersprachen bedienbar sein und in möglichst vielen Laufzeitumgebungen eingesetzt werden können.
- Die Middleware soll zur Laufzeit keine umfangreiche Infrastruktur voraussetzen, ins­besondere wenn Vertreter und Treiber auf demselben Rechner laufen.

Im Prinzip könnte man eine reine TCP-Verbindung zwischen dem Vertreter und Treiber auf­bauen und mittels geeignet gekennzeichneter Nachrichten, in denen die Parameter von AID-Ereignissen entsprechend kodiert sind, AID-Ereignisse übertragen. Das Problem an einer eigenen Implementierung von der Middleware und dem Vermittlungsprotokoll ist der Aufwand. Bereits existierende Middleware für heterogene Umgebungen und unterschied­liche Programmiersprachen wie z. B. die Implementierungen von CORBA benötigten Jahre, um Reifegrad und Stabilität zu erreichen, die eine reibungslose Nutzung ermögli­chen. Eine Verkürzung der Entwicklungsdauer kann nur durch enorme Abstriche bei der Funktionalität, Benutzbarkeit, Flexibilität, Management und Sicherheit erreicht werden. Deswegen blieb die Implementierung einer eigenen Middleware für die AID-Plattform eine letzte Alternative für den Fall, dass keine andere Möglichkeit gefunden wird.

Vor der endgültigen Entscheidung wurden Web Services [WebServices], XML-RPC [XML-RPC] und CORBA als mögliche Middleware auf Tauglichkeit zu den Zwecken der AID-Plattform geprüft. Web Services wurden als sehr schwergewichtig aufgrund der textbasierten Proto­kolle HTTP und SOAP sowie des erforderlichen Web-Servers eingestuft und passten somit nicht zu einigen Anwendungsszenarien mit kleinkörnigen Komponenten. XML-RPC [XML-RPC] ist eine leichtgewichtige Middleware für Fernaufrufe über das Internet auf Basis von HTTP und XML. Es gibt bereits zahlreiche Implementierungen von XML-RPC in unterschiedli­chen Programmiersprachen und ein Vorteil gegenüber Web Services ist, dass man keinen zusätzlichen Server benötigt, sondern ein Bestandteil der jeweiligen Implementierung ist ein simpler HTTP-Server. Das Protokoll ist jedoch weiterhin textbasiert und gewisse Zweifel über den Reifegrad, die Stabilität und weitere Unterstützung konnten nicht ausgeräumt wer­den. Letztlich wurde CORBA als Middleware für die AID-Plattform ausgewählt. Nennens­werte Vorteile sind der zugrunde liegende Standard, das binäre Protokoll (IIOP – Internet Inter-ORB Protocol) zur Übertragung der Fernaufrufe, das Mapping in vielen Programmier­sprachen, die native Unterstützung von Ausnahmen, Fernverweise und URI (corbaloc) von fernaufrufbaren Objekten und die Austauschbarkeit von unterschiedlichen Implementie­run­gen von CORBA für eine Programmiersprache.

2.8 Umsetzung des kanonischen Interaktionsstils auf Basis von CORBA

Die Ereignisse des kanonischen Interaktionsstils werden in CORBA durch Fernaufrufe von Methoden realisiert. CORBA hat zwei Arten von Fernaufrufen zu bieten – den synchronen Aufruf, bei dem der Aufrufer bis zum Abarbeiten der aufgerufenen Methode wartet und eventuelle Ergebnisse erhält, und den sog. Oneway-Aufruf, bei dem der Aufrufer gleich nach Betätigen des Aufrufs entbunden wird, aber auch keine Ergebnisse zurückbekommen kann. Paare von AID-Ereignissen (INOUT und OUTIN), die das Anfrage-Antwort-Interak­ti­onsmuster darstellen, werden durch einen synchronen Fernaufruf verwirklicht. Alle übri­gen AID-Ereignisse (IN und OUT) werden als Oneway-Fernaufrufe ausgeführt. Die Ereignis­dekla­rationen in AID werden also nach diesem Schema zur geeigneten Methodendeklara­tionen. Es muss jedoch berücksichtigt werden, dass die Methoden zu IN- und INOUT-Ereig­nissen nicht in derselben CORBA-Schnittstelle deklariert werden, die die Methoden zu den OUT- und OUTIN-Ereignissen enthält, denn die Ersten werden vom Vertreter und die Letzte­ren vom Treiber der Komponente aufgerufen. Die erste Schnittstelle werden wir konse­quen­terweise _IN und die zweite _OUT nennen. OUT- und OUTIN-Ereignisse werden somit ähnlich wie Ereignisse in Java nach dem Beobachter-Entwurfsmuster umgesetzt. Der Inte­ressent eines Ereignisses registriert sich bei der Komponente für dieses Ereignis und wenn ein Er­eignis vom selben Typ tatsächlich ausge­löst wird, wird der Interessent mit dem Er­eignis beliefert. Die Registrierung für Ereignisse und ihre Auslieferung erfolgt durch Me­thodenauf­rufe. Tatsächlich registriert sich der Ver­treter einer Komponente in unserer Um­setzung für alle möglichen OUT- und OUTIN-Ereig­nisse einer Komponente auf einmal, indem er die _OUT Schnittstelle implementiert und einen Verweis darauf dem Treiber der Kompo­nente bereits bei seiner Erzeugung durch die Verwaltungsfunktionen übergibt. Abbildung 4 stellt dies bildlich dar.

Abbildung in dieser Leseprobe nicht enthalten

Abbildung 4. Interaktion zwischen Vertreter und Treiber mittels CORBA

Die Verwaltungsfunktionen zur Erzeugung und Zerstörung von Komponentenexemplaren werden dem Vertreter durch die Implementierung der CORBA-Schnittstelle AID_System im Deployment Unit [6] des Treibers zur Verfügung gestellt. AID_System enthält zu jedem der Kon­struktoren einer AID-Schnittstelle (plus eins für den parameterlosen Konstruktor) jeweils eine Methode, die den Namen der Schnittstelle und _NEW hinten dran trägt. Die Methoden übernehmen die Parameter­listen der Konstruktoren, wobei ein zusätzlicher Parameter vom Typ der _OUT Schnitt­stelle zur AID-Schnittstelle vorangestellt und ein Verweis auf das neue erstellte Treiber-Exemplar zurückgegeben wird. Der Parameter vom Typ der _OUT Schnitt­stelle dient zur Registrie­rung des Vertreters für die OUT- und OUTIN-Ereignisse des kanoni­schen Interaktionsstils, wie weiter oben bereits beschrieben. Der zurückgegebene Fern­verweis ist in der Realität vom Typ der _IN Schnittstelle zur AID-Schnittstelle. Weiterhin implementiert AID_System auch zu jeder AID-Schnittstelle entsprechende _DISPOSE Metho­den, die anhand des über­gebenen Verweises das entsprechende Treiber-Exemplar zerstö­ren.

Damit ein Vertreter sich vor der ersten Erzeugung eines Treiber-Exemplars einen Fernver­weis von AID_System holen kann, ist es erforderlich, dass eine Server-Anwendung ein Ex­emplar von AID_System aus dem Deployment Unit des Treibers erzeugt und dieses bei der Middleware registriert. Der Vertreter benötigt dann das URI (URL, corbaloc oder IOR) des Objekts, um auf dieses zugreifen zu können. Das Objekt von AID_System sollte so lange leben, bis der Prozess, der ihn registriert hat, vom Anwender beendet wird.

2.9 Das AID-IDL Mapping

CORBA schreibt vor, dass die durch CORBA exportierten Schnittstellen und die dabei benö­tigten Typen, Ausnahmen etc. in IDL (Interface Definition Language) [OMG IDL] beschrieben werden, damit die entsprechenden CORBA-Vertreter und CORBA-Treiber erzeugt werden können. IDL ist Teil des CORBA-Standards. Obwohl die IDL zu einer AID ungeachtet des Interaktionsstils der Komponente immer dieselbe sein sollte, ist dies praktisch nicht der Fall, da Abweichungen in der jeweiligen Implementierung von CORBA nicht ausgeschlossen werden können. Diese führen zu Unterschieden in den Namensräumen von bestimmten Deklarationen wie z. B. Sequences von primitiven Typen, mit denen man Arrays in IDL reali­siert. Die Generierung des Treibers einer AID-Komponente schließt also die Generierung der IDL-Datei ein, die später bei der Generierung des Vertreters der AID-Komponente ver­wendet wird.

Nachfolgend wird das genaue Mapping von AID in IDL beschrieben.

2.9.1 Mapping von primitiven Typen

In der folgenden Tabelle werden alle primitiven Typen in AID mit ihren Entsprechungen in IDL aufgelistet.

Abbildung in dieser Leseprobe nicht enthalten

Die Abbildung von string auf CORBA::WStringValue ist dem wstring vorzuziehen, weil WStringValue ein Value Type in CORBA darstellt und somit den null-Wert annehmen kann.

2.9.2 Mapping von COMPONENT

Der Name vom Element COMPONENT bestimmt den Namen des Moduls in IDL, in dem die weiteren Deklarationen erfolgen, wobei ein AID_ vorangestellt wird.

Abbildung in dieser Leseprobe nicht enthalten

In den weiteren Beispielen wird stets angenommen, dass das Element COMPONENT den Namen Drawings hat.

2.9.3 Mapping von TUPLE

Abbildung in dieser Leseprobe nicht enthalten

2.9.4 Mapping von EXCEPTION

Abbildung in dieser Leseprobe nicht enthalten

2.9.5 Mapping von Feldern

Felder aus AID werden auf valuetype sequence abgebildet. In IIOP.NET (die Implementierung von CORBA für .NET, die in dieser Diplomarbeit eingesetzt wurde) besteht eine Besonder­heit an dieser Stelle. Die Deklarationen eines Feldes erfolgen im Modul org.omg.BoxedArray._System bei primitiven Typen und in org.omg.BoxedArray.AID_[COMPONENT] für Tupel. Wünschenswert wäre, wenn die Deklara­tionen von Feldern ebenfalls im Modul AID_[COMPONENT] wie alle anderen Deklarationen möglich wären. Experimente mit der ge­wünschten Deklaration schlugen jedoch fehl. Des Weiteren ist der Name des Feldes auch fest – es fängt mit seq an, dann kommt eine Zahl, die den Dimensionen des Feldes ent­spricht, dann folgt „_“ und der Name des Typs der Elemente im Feld. Bei eindimensionalen Feldern sieht die Deklaration dann so aus:

Abbildung in dieser Leseprobe nicht enthalten

Mehrdimensionale Felder werden durch Deklarationen von Sequences aus Sequences reali­siert. Im Beispiel unten wird ein zweidimensionales Feld vom Typ double gezeigt.

Abbildung in dieser Leseprobe nicht enthalten

2.9.6 Mapping von INTERFACE

Zu jeder Schnittstelle in AID werden zwei Schnittstellen in IDL deklariert – eine _IN und eine _OUT Schnittstelle. Die _IN Schnittstelle enthält die Methoden zu den IN- und INOUT-Ereig­nissen und die _OUT diese zu den OUT- und OUTIN-Ereignissen. Des Weiteren werden eine Methode _NEW, die dem parameterlosen Konstruktor der Schnittstelle entspricht, und eine Oneway-Methode _DISPOSE in AID_System vorgesehen. Die _IN Schnittstelle enthält noch die Methode AID_SetCallback, die ebenfalls zu den Verwaltungsfunktionen zählt. Den Na­men _IN, _OUT, _NEW, _DISPOSE usw. von Schnitt­stellen oder Methoden in AID_System wird immer der Name der AID-Schnittstelle vorange­stellt.

Abbildung in dieser Leseprobe nicht enthalten

2.9.7 Mapping von INIT

Konstruktoren in AID-Schnittstellen sind als Methoden _NEWn in AID_System wiederzufin­den, wobei n die laufende Nummer des Konstruktors in der Schnittstelle plus eins ist. Den eigentlichen Parametern des Konstruktors wird der Parameter vom Typ der _OUT Schnitt­stelle vorangestellt, mit dem der Vertreter sich bei dem Treiber für Rückrufe registriert. Die Rückrufe realisieren OUT- und OUTIN-Ereignisse.

Abbildung in dieser Leseprobe nicht enthalten

2.9.8 Mapping von IN-Ereignissen

IN-Ereignisse werden auf Oneway-Methoden mit der gleichen Parameterliste wie das Er­eignis in der _IN Schnittstelle abgebildet.

Abbildung in dieser Leseprobe nicht enthalten

2.9.9 Mapping von OUT-Ereignissen

OUT-Ereignisse werden ganz analog zu IN-Ereignissen auf Oneway-Methoden mit der glei­chen Parameterliste wie das Ereignis, jedoch in der _OUT Schnittstelle abgebildet.

Abbildung in dieser Leseprobe nicht enthalten

2.9.10 Mapping von INOUT-Ereignissen

INOUT-Ereignisse werden durch Methoden in der _IN Schnittstelle realisiert. Parameter vor dem Schlüsselwort RETURNS sind in-Parameter in IDL, die nach RETURNS, wenn sie mit einem Parameter vor RETURNS übereinstimmen, sind inout- und wenn nicht, sind sie ein­fach out-Parameter. Ausnahmen nach dem Schlüsselwort EXCEPT werden entsprechend mit dem Schlüsselwort raises in IDL aufgelistet.

Abbildung in dieser Leseprobe nicht enthalten

2.9.11 Mapping von OUTIN-Ereignissen

Das Mapping von OUTIN-Ereignissen ist ganz analog zu dem bei den INOUT-Ereignissen. Einziger Unterschied ist, dass die Methode, die das Ereignis abbildet, in der _OUT Schnitt­stelle zu deklarieren ist.

3 .NET-Komponenten – Grundlagen und Interaktionsstil

Dieses Kapitel dient zur Einführung in die Welt der .NET-Komponenten. Darauf aufbauend werden in den nächsten Kapiteln die Abbildung zwischen den AID-Deklarationen und .NET-Konstrukten und die Vermittlung im Vertreter und Treiber beschrieben. Hier werden also grundlegende Fragen beantwortet wie z. B., was eigentlich eine .NET-Komponente ist, wie die Laufzeitumgebung für .NET-Komponenten aussieht und welcher Interaktionsstil in ihr herrscht. Des Weiteren werden alle Aspekte mit Bezug auf Schnittstellen und Nutzung von Komponenten beleuchtet, die bei der Übersetzung des Interaktionsstils von .NET in den ka­nonischen und umgekehrt berücksichtigt werden sollen. Es sei darauf hingewiesen, dass diese Arbeit sich auf die Version 2.0 des Standards ECMA-335 „Common Language Infrastructure“ [ECMA335] bezieht, der hier einfachhalber mit dem Marketing-Namen .NET bezeichnet wird.

3.1 Was ist eine .NET-Komponente?

Der Begriff .NET-Komponente ruft wegen seiner inkonsistenten Nutzung sehr häufig unter­schiedliche Vorstellungen hervor. Diejenigen, die sich auf die Deployment-Ebene eines Ge­samtsystems konzentrieren und betrachten, aus welchen zu installierenden binären Ein­heiten es besteht, würden feststellen, dass eigentlich eine .NET Assembly eine .NET-Kom­ponente darstellt. Die .NET Assembly ist die kleinste auslieferbare, konfigurierbare und ver­sionierbare Einheit in .NET. Das Manifest einer Assembly enthält auch eine genaue Be­schreibung der Abhängigkeiten dieser Assembly von anderen Assemblies. Damit passt die .NET Assembly mit ihren Eigenschaften ganz gut zu der Definition, die in Abschnitt 1.2 Komponentenbasierte Software-Entwicklung bereits zitiert wurde und auf Szyperski zurückzu­führen ist. Sie besagt noch einmal: „Software-Komponenten sind ausführbare Soft­ware-Einheiten, die unabhängig hergestellt, erworben und konfiguriert werden und aus de­nen sich funktionierende Gesamtsysteme zusammensetzen lassen“ [Gräbe, 2004].

Es gibt jedoch auch die Sicht des Entwicklers eines Systems, der Komponentenexemplare erzeugt, die Schnittstellen der Komponenten bedient und somit die Anbindung der Kompo­nenten in seiner Software bewerkstelligt. In diesem Fall liefert die Vorstellung, dass eine .NET Assembly eine Komponente ist, keine gute Abstraktion dafür, was ein Komponenten­exemplar ist, wie man es erzeugt und wie die Schnittstelle der Komponente aussieht. In der Realität beinhaltet häufig eine Assembly viele öffentliche Klassen mit ihren eigenen Schnitt­stellen, von denen man mehrere Exemplare unabhängig voneinander erzeugen kann. MSDN Library [MSDNLibrary.NET] postuliert selbst, dass eine Komponente eine Klasse ist, die die Schnittstelle ComponentModel.IComponent unmittelbar oder indirekt implemen­tiert und einen Konstruktor ohne Parameter oder mit einem Parameter vom Typ IContainer be­sitzt. Und tatsächlich behandelt das .NET Framework solche Klassen etwas spezieller und bietet ihnen die Möglichkeit, mit dem Container zu interagieren, in dem Exemplare von ihnen hinzugefügt sind. Die Entwicklungsumgebung von Microsoft für .NET (Visual Studio 2005) macht reichlich Gebrauch von solchen Komponenten. Alle Controls wie beispiels­weise Textfelder, Auswahllisten etc. sind Komponenten im oberen Sinne und können auf Formu­laren, die die Rolle des Containers übernehmen, platziert werden. Auf Formulare können ebenfalls unsichtbare Komponenten wie beispielsweise Timer und Datenbankan­bindungs­komponenten hinzugefügt werden.

Eine andere Art von Komponenten sind die .NET Enterprise Components, für die spezielle Komponentendienste von COM+ angeboten werden. Da gilt ebenfalls, dass Dienste für Klassen und nicht für ganze Assemblies erbracht werden [Löwy, 2001, S. 258 - 296].

In dieser Arbeit wird jede öffentliche Klasse aus einer Assembly zusammen mit den benö­tigten inneren Klassen aus der gleichen Assembly als jeweils eine .NET-Komponente auf­gefasst. Die Schnittstelle der Komponente ist die Schnittstelle der öffentlichen Klasse. Ein Exemplar von der öffentlichen Klasse stellt ein Komponentenexemplar dar. Mit dieser Abs­traktion von .NET-Komponenten werden auch sog. Code Components miteinbezogen, also Komponenten, die weder in Containern hinzugefügt werden noch IComponent imple­mentie­ren, aber eine bestimmte Funktionalität oder Algorithmus anzubieten haben.

3.2 Laufzeitumgebung für .NET-Komponenten

Hinter der Abkürzung .NET steht eigentlich eine Umsetzung der Technologie nach dem Standard ECMA-335 „Common Language Infrastructure“ (CLI) [ECMA335]. Die Laufzeitum­gebung nach diesem Standard wird Common Language Runtime (CLR) genannt. Sie ist im Prinzip eine virtuelle Maschine, die den Zwischencode (Common Intermediate Language), in den die .NET Anwendungen zunächst übersetzt worden sind, interpretiert und mittels eines JIT-Compilers (Just-In-Time Compiler) nochmals zu den endgültigen Maschineninstruktionen für die jeweilige Systemarchitektur übersetzt. Gleichzeitig kümmert sich die Laufzeitumge­bung um das dynamische Laden von Assemblies aus einem sekundären Speicher oder Internet, ermöglicht das Erzeugen und Zerstören von Komponentenexemplaren sowie die Kommunikation zwischen ihnen. Da ein wichtiges Ziel von CLI die Plattformunabhängigkeit der .NET Anwendungen ist, soll zu jedem der zu unterstützenden Betriebssysteme jeweils eine Implementierung von CLR vorgenommen werden. Die Implementierung von CLR für Windows hat Microsoft .NET Framework genannt. Andere CLR sind auch für andere Be­triebssysteme vorhanden (für FreeBSD und MacOS – Rotor, für Linux-Derivate – Mono).

3.3 Interaktionsstil in .NET

Ein Interaktionsstil lässt sich generell dadurch charakterisieren, wie Komponentenexemp­lare erzeugt und zerstört werden, wie die Bindung zu einer Komponente hergestellt wird, welche Primitive zur Kommunikation mit der Komponente existieren, wie die Schnittstelle einer Komponente aussieht und welches Format die bei der Interaktion ausgetauschten Daten aufweisen.

3.3.1 Erzeugung von Komponentenexemplaren

Die Laufzeitumgebung wird durch die Instruktion newobj der Zwischensprache (Common Intermediate Language) angewiesen, ein neues Objekt einer Klasse zu erzeugen [ECMA335, S. 124]. Dabei werden der Name der Klasse und der aufzurufende Konstruktor angege­ben. Konstruktoren bieten die Möglichkeit, dass neue Komponentenexemplare gleich bei der Erzeugung mit Initialisierungsparametern versorgt werden und so in einen bestimmten Initialzustand versetzt werden. Man kann beliebig viele Komponentenexemp­lare erzeu­gen (beschränkt durch den vorhandenen Systemspeicher) und jedes besitzt eine eigene Identität. Die Laufzeitumgebung unterscheidet sehr streng die Komponenten­ex­emplare durch ihre Identität und es ist Teil des Interaktionsstils, dass der Nutzer sich darum kümmern muss, Verweise auf die gewünschten Komponentenexemplare bei einem Aufruf bereitzustellen. In .NET entscheidet der Nutzer einer Komponente zur Laufzeit selbst, wann er ein oder mehrere Exemplare von ihr erzeugt. Im Gegensatz dazu gibt es Interak­tionsstile wie z. B. diesen bei Message-Driven Beans, bei denen die Umgebung (das Server-Pro­gramm) entscheidet, wann Exemplare (häufig genau eins) von der Kompo­nente erzeugt werden (manchmal bereits beim Deployment der Komponente).

3.3.2 Zerstörung von Komponentenexemplaren

Exemplare von Komponenten werden nicht explizit von ihren Nutzern zerstört. Die Lauf­zeitumgebung übernimmt die Entsorgung von nicht genutzten Komponentenexemplaren automatisch durch den parallel laufenden Garbage Collector (GC). Vor der eigentlichen Frei­gabe des von einem Objekt belegten Speichers wird sein Finalizer aufgerufen. Mit Hilfe des Finalizers kann das Objekt nicht durch die Laufzeitumgebung verwaltete Ressourcen frei­geben. Nicht verwaltete Ressourcen sind diejenigen, die durch Aufrufe an die API des dar­unter liegenden Betriebssystems reserviert wurden. Ihre Freigabe erfordert einen explizi­ten Aufruf an dieselbige API. Solche Ressourcen sind z. B. Datei-Händler, Händler zu Da­tenbankverbindungen und durch Marshal.AllocHGlobal reservierte Speicher. Da der GC ne­benläufig ausgeführt wird, ist der Zeitpunkt nicht vorhersehbar, in dem der Finalizer eines Objekts aufgerufen wird, und deswegen bleiben Ressourcen u. U. zu lange reserviert, ob­wohl sie nicht mehr benötigt werden. Es empfiehlt sich, dass eine Komponente das sog. Dispose Pattern umsetzt. Wenn der Nutzer einer Komponente die Arbeit mit ihr erledigt hat, sollte er die Dispose-Methode der Komponente aufrufen, um damit von ihr belegte Res­sourcen deterministisch freizugeben [NETDispose].

3.3.3 Bindung zu einem Komponentenexemplar

Die Bindung zu einem Komponentenexemplar erfolgt durch einen Speicherverweis. Wer einen Verweis auf die Komponente besitzt, kann auf ihre Schnittstelle zugreifen. Einige Komponenten bieten die Möglichkeiten, über unterschiedliche Veränderungen ihres Zu­stands Interessenten zu informieren. Dazu benötigt die Komponente selbst Speicherver­weise auf die Interessenten. Wenn eine Komponente eine Aufgabe an eine andere Kom­ponente delegieren möchte, benötigt sie auch einen Verweis auf die andere Komponente. Dazu erstellt die Komponente selbst ein privates Exemplar von der anderen Komponente oder erhält den Verweis durch Aufruf des Nutzers, der das Exemplar selbst erzeugt oder anderweitig bekommen hat.

3.3.4 Primitive zur Kommunikation mit der Komponente

Die Laufzeitumgebung von .NET bietet grundsätzlich den synchronen lokalen Aufruf als Mittel zur Interaktion zwischen Komponenten. Dabei werden die Kontrolle und eventuelle call-by-value und call-by-reference Parameter vom Aufrufer zum Aufgerufenen übertragen[7]. Wenn die aufgerufene Operation abgearbeitet wird, bringt die Laufzeitumgebung die Kon­trolle und einen eventuellen Rückgabewert und call-by-reference Parameter zum Aufrufer zurück[8]. Alle Interaktionen erfolgen nach dem Push-Prinzip. Dies bedeutet, dass derjenige, der eine Botschaft mitzuteilen hat, selbst aktiv ist. Auf Fernaufrufe und Asynchronität wird später in diesem Kapitel eingegangen.

3.3.5 Schnittstelle einer .NET-Komponente

Die Schnittstelle einer Komponente besteht aus Methoden, Eigenschaften und Ereignissen. Öffentlich zugängliche Mitgliedsvariablen stellen eine Besonderheit dar. Im Folgenden werden diese Elemente einzeln behandelt.

3.3.5.1 Methoden

Eine Methode hat einen Namen, einen oder keinen Rückgabewert und eine Liste mit Pa­rametern. Parameter können nach der call-by-value und call-by-reference Semantik überge­ben werden. Des Weiteren können in einer Schnittstelle überladene Methoden deklariert werden. Sie haben denselben Namen, aber unterschiedliche Parameterlisten [Kühnel, 2003, S. 186 - 215].

Beispiele:

Abbildung in dieser Leseprobe nicht enthalten

Methoden können Ausnahmen auswerfen. In .NET sind die Ausnahmen kein Bestandteil der formellen Deklaration einer Methode. Die Behandlung von möglichen Ausnahmen kann bei einigen Komponenten, je nachdem, wie sie entworfen wurden, eine unabdingbare Vor­aussetzung für die nahtlose Anbindung der Komponenten im eigenen Projekt werden. Eine undifferenzierte Behandlung mit dem Basistyp aller Ausnahmen (System.Exception) ist nicht ausreichend, da es erforderlich sein könnte, dass man den Typ einer Ausnahme auswerten muss, um auf diese Ausnahme geeignet reagieren zu können und nicht einfach die Aus­nahme protokollieren zu lassen. Es gehört zum guten Stil, dass die Ausnahmen, die eine Komponente auswerfen kann, in ihrer Dokumentation beschrieben werden.

3.3.5.2 Eigenschaften

Eigenschaften sehen bei Nutzung wie Mitgliedsvariablen einer Klasse aus. Man kann ihren Wert unmittelbar in Ausdrücken verwenden und ihnen einen Wert zuweisen. Dabei wird jedoch nicht auf eine Variable zugegriffen, sondern die get-Operation bzw. die set-Opera­tion der Eigenschaft wird aufgerufen. Wenn lediglich die get-Operation realisiert ist, hat man eine Eigenschaft nur zum Lesen, wenn ausschließlich die set-Operation entsprechend eine Eigen­schaft, die nur gesetzt werden kann. Eine Eigenschaft hat wie eine Mitgliedsva­riable einen Namen und einen Typ [Kühnel, 2003, S. 215 - 229].

Beispiel:

Abbildung in dieser Leseprobe nicht enthalten

3.3.5.3 Ereignisse

Die Erfinder von .NET haben sich entschieden, spezielle Konstrukte für Ereignisse, die häu­fig mit dem Beobachter-Entwurfsmuster in Verbindung gebracht werden, vorzusehen, um den Umgang mit ihnen zu erleichtern und die Aussagefähigkeit des Quellcodes zu steigern. Ereignisse werden mit dem Schlüsselwort event deklariert und haben den Typ eines Dele­gats. Ein Delegat ist ein Objekt, das den Verweis auf eine oder mehrere Methoden gleicher Signatur kapselt, wobei man zwischen der Deklaration eines Delegats und einem Delegat-Exemplar unterscheiden muss. Die Deklaration erfolgt mit dem Schlüsselwort delegate und man gibt den Namen und die Parameterliste des Delegats an. Ein Exemplar kann man mit new erzeugen, dann kommt der Name des Delegats und anschließend werden in Klam­mern das Objekt (bei Instanz-Methoden) oder der Klassenname (bei statischen Methoden) und der Methodenname mit Punkt voneinander getrennt angegeben. Ereignisse sind im Prinzip Ex­emplare von Multicast-Delegaten. Mit der Deklaration des Ereignisses wird ein Multicast-Delegat erzeugt, der intern mehrere Exemplare von Unicast-Delegaten verwalten kann. Die verwiesenen Methoden in einem Multicast-Delegat werden sequenziell nachein­ander beim Aufruf des Delegats abgearbeitet. Für ein Ereignis registriert man sich mit dem Operator „+=“ und meldet sich mit „-=“ wieder ab, wobei nach den Operatoren ein Delegat-Exemplar erwartet wird. Das Ereignis kann nur in Methoden oder Eigenschaft der Klasse, in der das Ereignis deklariert ist, ausgelöst werden, indem das Ereignis wie eine Methode aufgerufen wird. Dabei ist prüfen, ob überhaupt jemand sich für dieses Ereignis registriert hat, indem geschaut wird, ob der Multicast-Delegat null ist.

Beispiel:

Abbildung in dieser Leseprobe nicht enthalten

3.3.5.4 Öffentlich zugängliche Mitgliedsvariablen

Öffentlich zugängliche Mitgliedsvariablen gehören nicht zu der Schnittstelle einer Kompo­nente, da .NET-Schnittstellen grundsätzlich keine Mitgliedsvariablen enthalten dürfen. Aber da eine Klasse Mitgliedsvariablen enthalten kann und eine Komponente in .NET praktisch mit einer Klasse gleichzusetzen ist und die Interaktion mit der Komponente möglicher­weise erfordert, dass der Nutzer der Komponente den Wert der Variable ausliest oder ihr einen Wert zuweist, sollten Mitgliedsvariablen ähnlich wie Eigenschaften vom Treiber der Kom­ponente unterstützt werden.

3.3.6 Typsystem in .NET

Das Typsystem von .NET setzt sich aus primitiven Typen, Strukturen (Verbundtypen) und Verweistypen, wie z. B. Klassen und Delegaten, zusammen. Die Verbundtypen weisen die Besonderheit auf, dass sie wie Klassen u. a. Methoden, Eigenschaften und Operatoren be­sitzen können. Die Delegaten, die man vielleicht aus anderen objektorientierten Program­miersprachen in dieser Form nicht kennt, wurden weiter oben im Zusammenhang mit den Ereignissen in .NET ausreichend erläutert. Weiteres zum Typsystem von .NET findet man in [Kühnel, 2003, S. 77 - 86] und zahlreichen anderen Büchern über .NET und C#.

3.4 Weitere Aspekte

In diesem Abschnitt werden besondere Aspekte behandelt, die durch die Beschreibung weiter oben nicht angesprochen wurden, die durchaus wichtig sind und eine Berücksichti­gung bei der Übersetzung zwischen den Interaktionsstilen finden sollten.

3.4.1 Lokale und entfernte Nutzung von Komponenten

Wenn ein Komponentenexemplar in einer anderen Applikationsdomäne[9] (Application Do­main) als sein Nutzer ausgeführt wird, sprechen wir von einer entfernten Nutzung der Komponente. Die Interaktion mit der Komponente findet mittels Fernaufrufen statt. Die Er­zeugung und Verwaltung von Komponentenexemplaren kann in diesem Fall nicht immer durch den Nutzer der Komponente beeinflusst werden. Es gibt die sog. Client Activated Ob­jects und Server Activated Objects. Man kann schon aus den Namen erahnen, dass die Ser­ver Activated Objects durch das Server-Programm aktiviert (erzeugt) werden. Bei den Client-Activated-Objects hat wiederum der Nutzer die Kontrolle über die Lebensdauer der Kompo­nente. Er kann durch entsprechende Anweisungen veranlassen, dass für ihn in der frem­den Applikationsdomäne ein Exemplar der Komponente bereitgestellt wird [Löwy, 2003, S. 249 - 257].

In dieser Arbeit wird nur die lokale Anbindung von Komponenten behandelt. Die entfernte Nutzung von Komponenten stellt eine Erweiterung der Nutzungsmöglichkeiten von .NET-Komponenten dar, ist aber nicht essenziell für die theoretische Abhandlung der Überset­zung zwischen dem .NET-Interaktionsstil und dem AID-Interaktionsstil. Mit Hilfe von ent­sprechenden Annotationen zu einer AID-Schnittstelle kann auch dieser Fall behandelt wer­den.

3.4.2 Asynchrone Aufrufe

Normalerweise erfolgt die Interaktion mit einer Komponente synchron. Wenn man zusätzli­che Threads zur Abarbeitung der aufgerufenen Methoden beauftragt, sodass man auf das Ende der Ausführung einer aufgerufenen Methode nicht warten muss, sofern die Ergeb­nisse nicht gleich benötigt werden, spricht man von asynchronen Aufrufen. Wie in Abschnitt 3.3.4 Primitive zur Kommunikation mit der Komponente bereits dargelegt wurde, unterstützt die Laufzeitumgebung von .NET grundsätzlich nur synchrone Aufrufe. Die Zwischensprache CIL (Common Intermediate Language) enthält explizit weder Instruktionen zu asynchronen Aufrufen, noch besitzen Kopfdaten von Methoden in CIL einen Bit oder Byte dafür, dass die Methode asynchron aufgerufen werden würde. Dies bedeutet, dass asynchrone Aufrufe mittels der Instruktionen für synchrone Aufrufe realisiert werden müssen.

Wenn eine Komponente selbst keine asynchrone Variante einer Operation bereitstellt, kann sich der Nutzer der Komponente selbst helfen. Dies geht klassisch in .NET durch das Erzeugen eines neuen Threads und die Übergabe eines Exemplars von ThreadStart-Delegat an den Konstruktor des Threads und anschließenden Aufruf von Start(). Eine weitere Vari­ante stellt die Nutzung der vom Compiler in jedem Delegat generierten Methoden Begin­Invoke und EndInvoke dar. Mit BeginInvoke wird die Ausführung angestoßen, mit EndInvoke wird auf das Ende gewartet und die Ergebnisse werden abgeholt. Wenn man beim Aufruf von BeginInvoke ein Exemplar vom Delegat AsyncCallback übergibt, wird man durch den Aufruf der Methode, die in dem AsyncCallback-Delegat gekapselt ist, benachrich­tigt. BeginInvoke nutzt einen Thread aus dem Thread-Pool, der standardmäßig von der Lauf­zeitumgebung für eine .NET-Anwendung eingerichtet wird. Die Komponente bleibt bei die­ser Vorgehensweise unbetroffen und im Prinzip findet eine Verlagerung des eigentlichen Aufrufs einer Methode der Komponente von ihrem Nutzer zu der BeginInvoke Methode der Delegat-Klasse statt.

Beispiel:

Abbildung in dieser Leseprobe nicht enthalten

Mit dem Einzug der immer häufiger werdenden verteilten Anwendungen wurde jedoch er­kannt, dass es nützlich sein würde, wenn bestimmte Operationen einer Komponente we­gen ihres Laufzeitverhaltens standardmäßig asynchron angeboten würden. Dann bräuch­ten alle Aufrufer dieser Operation sich nicht mehr darum zu kümmern. Sie müssen jedoch wissen, dass die Operation asynchron ausgeführt wird und die Ergebnisse nicht gleich nach Rückgabe der Kontrolle feststehen. In .NET gibt es bereits ein Muster dafür, wie man eine asynchrone Ausführung von solchen Operationen sehr elegant bereitstellt und es setzt auf Delegaten mit ihren BeginInvoke und EndInvoke Methoden auf. Im Prinzip implementiert man zwei zusätzliche Methoden zu einer Operation mit den Namen BeginOperation und EndOperation[10]. BeginOperation erzeugt einen Delegat mit Verweis auf die synchrone Imple­mentierung der Operation und ruft BeginInvoke des Delegats anschließend auf. EndOpera­tion ruft entsprechend EndInvoke von demselben Delegat auf. Mehr ist dazu nicht nötig. Bei diesem Ansatz versteckt die Komponente den Code zur Umsetzung des asynchronen Auf­rufs.

Eine Unterart der asynchronen Aufrufe stellen die OneWay-asynchronen Aufrufe dar. Wenn eine Methode mit dem Attribut [OneWay] versehen ist, wird der ganze Verwaltungsaufwand vermieden, mit dem eine Synchronisierung mit dem Aufruf von EndInvoke ermöglicht wird und die Ergebnisse aufbewahrt werden. Dies bedeutet insbesondere, dass dem Aufrufer keine Ausnah­men und keine Rückgabewerte geliefert werden. [OneWay] hat keine Wirkung bei lokalen unmittelbaren Aufrufen (also wenn die Methode nicht über das .NET Remoting und nicht über BeginInvoke eines Delegats aufgerufen wurde) [Löwy, 2003, S. 117 - 142], [Rammer, 2002, S. 52 - 67].

Beispiel:

Abbildung in dieser Leseprobe nicht enthalten

Nach dieser Ausführung dürften drei Sachen klar geworden sein. Erstens sind asynchrone Aufrufe in .NET ein unangefochtenes Recht des Aufrufers, aber damit hat die Komponente nichts zu tun. Wenn die Methoden der Komponente nicht gegen nebenläufige Aufrufe ge­schützt sind und dies aber für das fehlerfreie Funktionieren der Komponente erforderlich ist, handelt der Aufrufer auf eigene Verantwortung. Zweitens, wenn die Komponente irgendwelche asynchronen Operationen bereitstellen will, tut sie das mit Hilfe von ganz nor­malen Methoden. Es ist natürlich wichtig, dass die Semantik dieser Methoden genau in der Dokumentation der Komponente beschrieben ist. Wenn diese Methoden mit ihren Para­metern in AID abbildbar sind, dann erhält man praktisch die von der Komponente angebo­tene Asynchronität auch in der AID-Schnittstelle der Komponente wieder. Und drittens, eine Methode mit dem Attribut [OneWay] wird bei einem Fernaufruf asynchron ausgeführt. Dies sollte bei der Abbildung der Methode in AID berücksichtigt werden.

3.4.3 Vererbung und Implementierung von Schnittstellen

Die Zwischensprache von .NET (CIL) ist objektorientiert und unterstützt Vererbung, Imple­mentierung von Schnittstellen und Polymorphismus. Somit können Komponenten einen Teil ihrer Schnittstelle erben oder durch die Implementierung einer Schnittstelle erhalten. Es ist wichtig zu untersuchen, inwiefern diese Features bei der Nutzung einer Komponente eine Rolle spielen.

Einer der Gründe für den Erfolg der objektorientierten Programmierung ist, dass man mit Objekten von verschiedenen Klassen und der Beziehung zwischen ihnen die reale Welt besser abbilden kann. Eine wichtige Beziehung ist dabei die Generalisierung [UML]. Mit ihrer Hilfe wird ausgedrückt, dass die Objekte einer Klasse alle Merkmale einer anderen Klasse aufweisen. Mit Merkmalen sind hier die Zustandsvariablen, aber auch die Schnitt­stellen der Objekte gemeint. Wenn eine Klasse eine Schnittstelle implementiert, wird aus­gedrückt, dass alle Objekte dieser Klasse die implementierte Schnittstelle aufweisen. Eine wichtige Konsequenz daraus ist, dass immer, wenn ein Objekt von einer generelleren Klasse oder vom Typ einer Schnittstelle gefordert wird, ein Objekt von einer Klasse über­geben werden kann, die die Merkmale der generelleren erbt oder die geforderte Schnitt­stelle implementiert. Dies soll auch bei der Abbildung von .NET-Schnittstellen in AID berück­sichtigt werden.

Beispiel:

Abbildung in dieser Leseprobe nicht enthalten

3.4.4 Importierte Schnittstellen

Sehr häufig greift eine Komponente auf die Funktionalität einer anderen Komponente zu. Alle Methoden, Eigenschaften etc., die die erste Komponente von der zweiten aufruft, wird importierte Schnittstelle bezeichnet. Auf welchem Komponentenexemplar die Aufrufe aus­geführt werden, ist flexibel und kann meistens vom Nutzer der Komponente beeinflusst werden, indem er den entsprechenden Verweis auf das Komponentenexemplar selbst übergibt. Hier können noch zwei Fälle unterschieden werden: Es liegt bereits eine Imple­mentierung der importierten Schnittstelle bei oder der Nutzer der Komponente soll die Schnittstelle selbst implementieren. Bei der zweiten Variante spricht man allgemein von Rückrufen. Im oberen Beispiel ist Figure eine importierte Schnittstelle zu Drawing. Imple­mentierungen dieser Schnittstelle liegen in der Assembly der Komponente bei (Polygon, Circle). FigureTransformer ist auch eine importierte Schnittstelle zu Drawing, diesmal muss der Benutzer der Komponente ein Exemplar auf seine Implementierung bei dem Aufruf von transformAllFigures übergeben. Solche Nutzungsszenarien sollten bei der Vermittlung zwi­schen AID und .NET berücksichtigt werden.

3.4.5 Abstrakte Methoden, Eigenschaften und Ereignisse

Wie im vorigen Abschnitt angedeutet, kann es vorkommen, dass der Nutzer einer Kompo­nente eine von ihr genutzte Schnittstelle implementieren und einen Verweis darauf der Komponente übergeben soll. Es kann auch sein, dass der geforderte Typ eigentlich eine abstrakte Klasse ist, in der einige Methoden, Eigenschaften und Ereignisse implementiert sind und einige davon nicht, also mit dem Schlüsselwort abstract versehen sind. Im Bei­spiel weiter oben ist Figure eine abstrakte Klasse, in der die Methode Draw() abstrakt ist und die Eigenschaften BackgroundColor und LineColor hingegen bereits implementiert sind. Der Benutzer der Komponente Drawing kann seine eigenen Figuren bauen und in Drawing einfügen, indem er von Figure erbt und die Methode Draw() implementiert. Diese Möglich­keit im Interaktionsstil von .NET wird eine große Herausforderung bei der Vermittlung zwi­schen .NET und der AID-Plattform darstellen.

3.4.6 Statische Methoden, statische Eigenschaften etc.

In der Praxis könnte der Fall auftreten, dass wichtige Methoden oder Funktionalität für die Nutzung einer Sammlung von Komponenten überhaupt als Klassenmethoden, Klassenva­riablen, Klasseneigenschaften und Klassenereignisse bereitgestellt werden. Diese sollen in AID auch exportiert werden, weil man auf diese Funktionalität u. U. vielleicht nicht verzich­ten kann. Sehr häufig werden Fabrik-Methoden durch statische Methoden realisiert.

4 Vermittlung zwischen dem kanonischen und dem Interaktionsstil von .NET im Treiber

Dieses Kapitel befasst sich mit der Abbildung der Schnittstellen von .NET-Komponenten auf AID-Schnittstellen. Dabei wird auf die Erläuterungen und Ansichten, die im vorigen Kapitel zu den Grundlagen der .NET-Komponenten vermittelt wurden, aufgebaut. Die Abbildung schließt nicht nur die Angabe des entsprechenden AID-Textes ein, sondern auch die erfor­derlichen Annotationen, die dem Generator helfen, den Treiber passend zu erstellen. An­schließend wird auf die vermittelnde Rolle des Treibers und seine Umsetzung eingegan­gen.

Dieses Kapitel soll nicht als Referenz dienen, wie man einen Treiber zu einer .NET-Kompo­nente praktisch erstellt. Dazu dient der Anhang dieser Arbeit. Anhang B enthält die vollstän­dige Liste und Beschreibung aller Annotationen, die bei der Treiber-Generierung zum Ein­satz kommen könnten. Anhang E stellt eine Anleitung dar und erläutert die einzelnen Schnitte, die bei einer Treiber-Generierung unternommen werden sollten.

4.1 Abbildung des Typsystems von .NET in AID

4.1.1 Primitive Typen

Die Abbildung der primitiven Typen wird tabellarisch gezeigt. Leider können einige primitive Typen aus .NET auf keine Typen der aktuellen Version von AID abgebildet werden.

Abbildung in dieser Leseprobe nicht enthalten[11] [12] [13]

4.1.2 Klassen ohne wesentliche Funktionalität, die als Verbundtypen verwendet werden

Häufig werden in einer Anwendung Klassen benötigt, die keine nennenswerte Funktionali­tät enthalten und nur als Verbund von mehreren Variablen dienen, damit andere Klassen damit leichter umgehen können. Solche Klassen werden auf AID-Tupeln abgebildet. AID-Tupel werden serialisiert, zur fremden Umgebung transportiert, auf entsprechenden Klas­sen oder Strukturen abgebildet und dort unmittelbar genutzt. Zu allen Mitgliedsvariab­len der Klasse, einschließlich den privaten, soll jeweils eine Mitgliedsvariable im entspre­chen­den AID-Tupel vorgesehen werden, damit ein reibungsloses Funktionieren auf der Kompo­nentenseite garantiert werden kann. Eigenschaften können ebenfalls im AID-Tupel aufge­nommen werden, dann wird aber die zugrunde liegende Variable wahrscheinlich nicht mehr benötigt. Die Funktionalität von Eigenschaften kann also angesprochen werden, die von Methoden hingegen nicht.

Zum Zwecke der Übertragung durch die Middleware wird für jedes Tupel in AID eine seria­lisierbare Klasse im Treiber generiert. Objekte von dieser Klasse werden zwischen Vertre­ter und Treiber in serialisierter Form ausgetauscht. Der Treiber muss jedoch die Objekte der ursprünglichen Klasse in Objekte der serialisierbaren Klasse umwandeln. Dabei kön­nen zwei Verfahren zum Einsatz kommen. Das Standardverfahren besteht darin, die Werte aller Mitgliedsvariablen mittels Reflection [14] den Mitgliedsvariablen der jeweils ande­ren Klasse zuzuweisen. Dieses Verfahren entspricht der Annotation |Mapping=Reflection zum AID-Tupel. Die Werte werden dabei eins zu eins übernommen. Da bei der Nutzung von Re­flection eindeutig darauf hingewiesen werden muss, ob eine Mitgliedsvariable oder Eigen­schaft gesetzt bzw. gelesen wird, braucht der Generator auch entsprechende Annotationen zu den einzelnen Mitgliedsvariablen im AID-Tupel - |Field für Mitgliedsvariablen und |Property für Eigenschaften in der ursprünglichen Klasse.

Beispiel:

Abbildung in dieser Leseprobe nicht enthalten

Vorab wird darauf hingewiesen, dass einige Beispiele absichtlich zur besseren Anschau­lichkeit der verschiedenen Varianten konstruiert sind. Der Leser wird darum gebeten, da­von abzusehen, wenn der Quellcode in den Beispielen aus soft­waretechnischer Sicht et­was umständlich aussieht.

Im Beispiel oben wird die Klasse Point als Tupel in AID deklariert, weil sie nach Ansicht des Verfassers der AID-Beschreibung keine nennenswerte Funktionalität enthält und auf diese verzichtet werden kann. Die privaten Mitgliedsvariablen x und y werden unter den gleichen Namen in AID aufgenommen. Die Mitgliedsvariable col wird nach Wunsch des Verfassers durch die Annotation |Property an die Eigenschaft color gebunden. Die Methoden SetCoord und GetCoord sind über die AID-Plattform in der fremden Umgebung nicht mehr nutzbar, aber in der Laufzeitumgebung, in der die Exemplare von Point leben, durchaus. Die Über­nahme der Werte per Reflection entspricht der Annotation |Mapping=Reflection, die aber nicht hingeschrieben werden muss, da sie Standardeinstellung ist.

Das andere Verfahren zur Umwandlung erfolgt nach den strikten Vorgaben des Benutzers und ist für Fälle gedacht, in denen bei der Umwandlung zwischen dem Typ des Tupels in der Middleware und dem ursprünglichen Typ gleichzeitig eine Wertanpassung an einer der Mitgliedsvariablen stattfinden soll. Es wird mit der Annotation |Mapping=Custom aktiviert und der Benutzer ist dann verpflichtet, die ganze Umwandlung in Form von Quellcode in C# selbst umzusetzen. Dies wird jedoch in einer separaten add-on Datei gemacht, die den gleichen Namen hat wie die AID-Datei mit ein „.addon“ hinten dran (z. B. file.aid.addon). Der Umwandlungscode wird bei der Generierung des Treibers als Rumpf der statischen Me­thoden ToOriginalType() und ToMiddlewareType() in der serialisierbaren Klasse übernom­men. Die Methoden liefern jeweils ein Objekt von der ursprünglichen Klasse bzw. von der serialisierbaren Klasse. Der einzige Parameter heißt obj und kommt jeweils von der umzu­wan­delnden Klasse. Der Quellcode der Methoden wird in XML-ähnlichen Blöcken angege­ben. Der Name eines Blocks setzt sich aus dem Namen des AID-Tupels und dem Namen der Methode getrennt durch einen Punkt zusammen und wird mit </> abgeschlossen. Zur Ver­deutlichung dient das Beispiel weiter unten.

Beispiel:

Abbildung in dieser Leseprobe nicht enthalten

Im Beispiel oben soll das Tupel Date_VT die Struktur System.DateTime aus .NET repräsen­tieren. Es ist ja bekannt, dass jede Programmiersprache fast immer ein eigenes Datums­format hat, dem die Anzahl der Millisekunden zu einem bestimmten Zeitpunkt zugrunde gelegt wird. Da dieses Konzept aber über Programmiersprachengrenzen hinweg nicht eins zu eins übertragen werden kann, muss man sich etwas einfallen lassen. Man könnte z. B. ein Datum als Tupel mit Jahr, Monat und Tag als Integer-Zahlen übertragen. Der Um­wandlungscode wird in den Blöcken <Date_VT.ToOriginalType> und <Date_VT.ToMiddlewareType> der add-on Datei (Bibliothek.aid.addon) zur AID-Datei (Biblio­thek.aid) angegeben. In diesem Beispiel kann man das Standardverfahren zur Um­wand­lung nicht nutzen, da die Eigenschaften Day, Month and Year von DateTime nur einen le­senden Zugriff erlauben (haben nur get-Operation) und per Reflection nicht ge­setzt werden können.

4.1.3 Verbundtypen (Strukturen)

Die Verbundtypen aus .NET, die mit dem Schlüsselwort struct deklariert werden, werden mit der Einschränkung, dass Variablen oder Parametern von diesen Typen kein Null-Wert zu­gewiesen werden darf, auf AID-Tupeln abgebildet. Die Abbildung erfolgt nach den gleichen Regeln wie im vorigen Abschnitt beschrieben. System.DateTime im oberen Beispiel ist ei­gentlich eine .NET-Struktur. Strukturen in .NET dürfen auch Methoden und Eigenschaften enthalten und Schnittstellen implementieren.

[...]


[1] Daniel Nowak hatte zur Aufgabe, in seiner Diplomarbeit die Generatoren zur Erstellung von Vertretern und Treibern zu Message-Driven Beans zu entwickeln.

[2] Normalerweise werden die Parameter nur auf den Stapel (Stack) gelegt.

[3] zu installierende ausführbare Einheit – in .NET ist dies die Assembly

[4] Push-Modus – der Erzeuger einer Nachricht ist aktiv und drängt sie dem Konsumenten auf.

[5] Pull-Modus – der Konsument ist aktiv und holt die Nachricht vom Erzeuger selbst ab.

[6] zu installierende ausführbare Einheit – in .NET ist dies die Assembly

[7] Die Laufzeitumgebung legt in Wirklichkeit die Parameter einfach auf den Stapel (Stack) und von da nutzt der Aufgerufene sie unmittelbar.

[8] In Wirklichkeit werden die Rückgabe-Parameter ebenfalls auf den Stapel (Stack) gelegt.

[9] Applikationsdomänen (Application Domains) sind die Abstraktion von Prozessen des Betriebssystems in der .NET-Laufzeitumgebung.

[10] BeginOperation und EndOperation könnten auch ganz anders heißen.

[11] Eine genaue Beschreibung der Typen in AID finden Sie in Anhang A.

[12] string ist eine Abkürzung von System.String und eigentlich ein Verweistyp. Wegen der erweiterten Unterstützung und Operatorüberladung sieht es von der Nutzung her aus, als wäre es ein primitiver Typ.

[13] decimal gehört eigentlich zum Extended numerics library von CLI (Common Language Infrastructure) [ECMA-335], sieht bei der Nutzung wie ein primitiver Typ aus.

[14] Reflection ist ein Mittel, um auf die Variablen, Methoden und Eigenschaften eines Objekts dynamisch zuzugreifen, wenn man seinen Typ zur Laufzeit nicht kennt.

Details

Seiten
Erscheinungsform
Originalausgabe
Jahr
2006
ISBN (eBook)
9783832497842
ISBN (Paperback)
9783838697840
DOI
10.3239/9783832497842
Dateigröße
1.5 MB
Sprache
Deutsch
Institution / Hochschule
Freie Universität Berlin – Mathematik und Informatik, Informatik
Erscheinungsdatum
2006 (August)
Note
1,5
Schlagworte
corba generator vertreter programmieren
Zurück

Titel: Automatisierte Anpassung von .NET-Komponenten an einen kanonischen Interaktionsstil
book preview page numper 1
book preview page numper 2
book preview page numper 3
book preview page numper 4
book preview page numper 5
book preview page numper 6
book preview page numper 7
book preview page numper 8
book preview page numper 9
book preview page numper 10
book preview page numper 11
book preview page numper 12
book preview page numper 13
book preview page numper 14
book preview page numper 15
book preview page numper 16
book preview page numper 17
book preview page numper 18
book preview page numper 19
book preview page numper 20
book preview page numper 21
book preview page numper 22
book preview page numper 23
book preview page numper 24
book preview page numper 25
book preview page numper 26
book preview page numper 27
book preview page numper 28
book preview page numper 29
book preview page numper 30
book preview page numper 31
book preview page numper 32
book preview page numper 33
book preview page numper 34
165 Seiten
Cookie-Einstellungen