29.01.2014 Aufrufe

Beleg (.pdf - 1.63 MB) - Technische Universität Dresden

Beleg (.pdf - 1.63 MB) - Technische Universität Dresden

Beleg (.pdf - 1.63 MB) - Technische Universität Dresden

MEHR ANZEIGEN
WENIGER ANZEIGEN

Erfolgreiche ePaper selbst erstellen

Machen Sie aus Ihren PDF Publikationen ein blätterbares Flipbook mit unserer einzigartigen Google optimierten e-Paper Software.

TECHNISCHE UNIVERSITÄT DRESDEN<br />

FAKULTÄT INFORMATIK<br />

INSTITUT FÜR SOFTWARE- UND MULTIMEDIATECHNIK<br />

PROFESSUR FÜR COMPUTERGRAPHIK UND VISUALISIERUNG<br />

PROF. DR. STEFAN GUMHOLD<br />

Großer <strong>Beleg</strong><br />

API für Plugin und Treiber basierte<br />

Softwareentwicklung<br />

Michael Voß<br />

(Mat.-Nr.: 2821139)<br />

Betreuer: Prof. Dr. rer. nat. Stefan Gumhold<br />

<strong>Dresden</strong>, 27. April 2007


Aufgabenstellung<br />

Entwicklung von plattformunabhängigen Konzepten für die Realisierung von Plugins und Treiber in<br />

computergraphischen Anwendungen.


1<br />

Inhaltsverzeichnis<br />

1 Einführung 3<br />

2 Grundlagen 5<br />

2.1 Hostanwendung, Plugins und Schnittstellen . . . . . . . . . . . . . . . . . . . . . . . . 5<br />

2.2 Programmiersprache . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5<br />

2.2.1 „Native“ Sprachen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5<br />

2.2.2 .NET Framework und Mono . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6<br />

2.2.3 Interoperabilität . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7<br />

2.3 Bibliotheken . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8<br />

2.3.1 Statische Bibliotheken . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8<br />

2.3.2 Dynamische Bibliotheken . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9<br />

2.3.3 Windows und dynamische Bibliotheken . . . . . . . . . . . . . . . . . . . . . . 11<br />

2.3.4 Linux und dynamische Bibliotheken . . . . . . . . . . . . . . . . . . . . . . . . 12<br />

2.4 Beispiele für Pluginsysteme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12<br />

2.4.1 Adobe Photoshop Plugins . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12<br />

2.4.2 Qt Plugins . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13<br />

3 Die Plugin API für C++ 15<br />

3.1 Typenreflektion mit dynamic_cast . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15<br />

3.2 Schnittstellendefinitionen in PluginDefs . . . . . . . . . . . . . . . . . . . . . . . . . . 16<br />

3.2.1 Plugin_interface_class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17<br />

3.2.2 Iplugin_base . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17<br />

3.2.3 Irenderable . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18<br />

3.2.4 Id3d9_plugin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18<br />

3.2.5 IPluginEventCallback . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19<br />

3.2.6 Ikeyboard_event_handler . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19<br />

3.2.7 Imouse_event_handler . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20<br />

3.3 Die Verwaltungsklasse plugin_loader . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20


2<br />

3.3.1 Laden neuer Plugins . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21<br />

3.3.2 Pluginliste . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21<br />

3.3.3 Zugriff auf Plugins . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22<br />

3.4 Plugin Bibliotheken schreiben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22<br />

3.4.1 plugin_factory_list_class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23<br />

3.4.2 Pluginfabriken . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24<br />

4 Die Plugin API für .NET 26<br />

4.1 Typenreflektion im .NET Framework . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26<br />

4.2 Schnittstellen in PluginDefs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28<br />

4.2.1 Die Standardschnittstelle iplugin_base . . . . . . . . . . . . . . . . . . . . . . . 29<br />

4.2.2 Iplugin_event_callback . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29<br />

4.3 PluginLoader.NET . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30<br />

5 Beispielanwendungen 31<br />

5.1 DirectX Beispiel für C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31<br />

5.1.1 Die Hostanwendung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32<br />

5.1.2 Die Dll MeshDll . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32<br />

5.1.3 Die DLL InteractiveRoom . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33<br />

5.2 OpenGL Beispiel für C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33<br />

5.2.1 OpenGLHost . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34<br />

5.2.2 Die Pluginbibliothek OGLDLL . . . . . . . . . . . . . . . . . . . . . . . . . . 34<br />

5.3 Beispiel für .NET . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35<br />

5.3.1 Hostanwendung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35<br />

5.3.2 Das Assembly PluginDLL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36<br />

6 Zusammenfassung 37<br />

Literaturverzeichnis 38<br />

Abbildungsverzeichnis 40<br />

A Quelltexte 41<br />

B Verzeichnisse auf der CD zum <strong>Beleg</strong> 46


1. EINFÜHRUNG 3<br />

1 Einführung<br />

Arbeitet man an einem kommerziellen Software-Projekt, muss man sich irgendwann entscheiden, welche<br />

Funktionen implementiert werden sollen. Diese Entscheidung wird im Normalfall mit Hilfe des<br />

Verhältnisses zwischen Nutzen und Kosten getroffen. Spezielle Wünsche werden selten berücksichtigt,<br />

weil sie für den durchschnittlichen Kunden kaum eine Verbesserung bringen. Oft ändern sich aber die<br />

Anforderungen an die Software im Verlauf des Nutzungszeitraums und je nach Anwender. Aus diesem<br />

Grund ist es vorteilhaft, wenn der Software ohne erneutes Kompilieren Funktionen hinzugefügt werden<br />

können. Weiterhin gibt es einige Anwendungsbereiche, wo es von Vorteil ist, wenn viele kleine Projekte<br />

in einer gemeinsamen Oberfläche zusammengefasst werden können. Dabei sollte die grafischen<br />

Benutzerschnittstellen nicht für jedes Projekt einzeln gestaltet werden, sondern einheitlich und zentral.<br />

Manchmal ist es nötig, dass eine Anwendung je nach Bedarf ihre grafischen Ausgaben mit Hilfe von<br />

DirectX, OpenGL oder den Betriebssystemfunktionen realisiert. Hier wäre es schön, wenn die Anwendung<br />

nicht für alle Systeme komplett neu geschrieben werden müsste und wenn die Wahl des System<br />

so dynamisch wie möglich ist. Dies alles kann durch Pluginsysteme realisiert werden. Solche Systeme<br />

sind in der Lage Programmteile, welche Plugins genannt werden, einer Anwendung dynamisch hinzuzufügen.<br />

Das Wort Plugin kommt vom englischen „to plug in“ und bedeutet soviel wie „einstöpseln“<br />

oder „anschließen“. Das Erweitern von Anwendungen durch Plugins wird im Rahmen vorher definierter<br />

Grenzen, den Schnittstellen, durchgeführt. Schnittstellen sind eine Art Regelung der Kommunikation<br />

zwischen Anwendung und hinzugefügten Programmteil. So wird es möglich, Erweiterungen für Applikationen<br />

zu schreiben, von denen die Schnittstellen bekannt sind, auch wenn der Quellcode unbekannt<br />

bleibt.<br />

Viele moderne Anwendungen für Desktoprechner unterstützen die Erweiterung mittels Plugins. So können<br />

spezielle Inhalte wie Animationen, PDF Dateien und Videos über den Internetbrowser Firefox [15]<br />

betrachtet werden. Für das 3d Modellierungs- und Animationsprogramm 3d Studio MAX [4] sind z. B.<br />

physikalische Systeme, Stoffsimulationen und verschiedene Partikeleffekte als Erweiterungen im Internet<br />

zu finden. Ein weiteres Beispiel ist Adobe Photoshop [1]. Für diese Bildbearbeitungssoftware gibt es<br />

zahlreiche Plugins. Sie werden Filter genannt und verändern das aktuell ausgewählte Bild in spezifischer<br />

Weise.<br />

In diesem <strong>Beleg</strong> geht es darum, eine Programmierschnittstelle (API) zu entwickeln, mit deren Hilfe ei-


1. EINFÜHRUNG 4<br />

ne pluginbasierte Anwendung schnell und einfach geschrieben werden kann. Diese API soll sowohl in<br />

einem Windows Betriebssystem, als auch unter Linux kompilierbar sein. Die API soll zudem flexibel<br />

und erweiterbar sein. Weiterhin soll Untersucht werden ob eine einheitliche API für C++ - und .NET<br />

Anwendungen möglich ist. Der Anwendungsfall an dem sich bei der Entwicklung der Plugin API orientiert<br />

wurde, ist eine grafische Applikation die als Benutzerschnittstelle fungiert und Plugins, die bis auf<br />

die Interaktion mit dem Benutzer, komplett selbständige Programme darstellen. Die API wurde in den<br />

Namensraum cgv::utils::plugins geschrieben und hält sich an die vorgegebene Namenskonvention.


2. GRUNDLAGEN 5<br />

2 Grundlagen<br />

2.1 Hostanwendung, Plugins und Schnittstellen<br />

Jedes Pluginsystem besteht im wesentlichen aus zwei Komponenten. Zum Einen aus der Hostanwendung,<br />

welche als solche vom Anwender gestartet wird und den Rahmen der Funktionalität bestimmt.<br />

Zum Anderen aus den Plugins, welche von der Hostanwendung geladen werden und dieser neue Funktionen<br />

hinzufügen können. Plugins werden typischer Weise in dynamische Bibliotheken verpackt. Zur<br />

Kommunikation zwischen Hostanwendung und Plugin werden Schnittstellen definiert. Diese Schnittstellen<br />

müssen für beide Seiten gleich sein und vor dem Kompilieren der Hostanwendung angegeben<br />

werden. Aus diesem Grund dürfen einmal definierte Schnittstellen in der hier vorgestellten API nicht<br />

mehr geändert werden, da sonst neue Plugins nicht mehr mit alten Anwendungen kompatibel wären<br />

bzw. alte Plugins mit neuen Anwendungen. Sie können aber zur Erweiterung der API als Basisklassen<br />

genutzt werden, wenn sie für eine neue Schnittstelle notwendig sind.<br />

2.2 Programmiersprache<br />

Im Bereich der Programmiersprachen ist mit Einführung von .NET [13], und dessen Portierung auf Linux:<br />

Mono [14], eine Einteilung in „managed“ und „native“ oder auch „unmanaged“ entstanden. Bei dieser<br />

Unterteilung werden alle Sprachen und Programme die das .NET Framework nutzen als „managed“<br />

bezeichnet. Alle übrig bleibenden Sprachen werden „native“ oder „unmanaged“ genannt. Die im Rahmen<br />

dieses <strong>Beleg</strong>es erstellte API ist dieser Aufteilung folgend auch zweigeteilt.<br />

2.2.1 „Native“ Sprachen<br />

Der „native“ Teil der Plugin API besteht aus drei statischen Bibliotheken und einigen C++ Headern<br />

und ist für den Gebrauch mit C++ gedacht. Die Bibliotheken enthalten Schnittstellen für Plugins, eine<br />

Verwaltungsklasse zur Nutzung von Plugins in Anwendungen und eine Verwaltungsklasse für alle in<br />

einer Pluginbibliothek enthaltenen Plugins.


2. GRUNDLAGEN 6<br />

2.2.2 .NET Framework und Mono<br />

Abbildung 2.1: CTS Typenhierarchie ([7])<br />

Das .NET Framework wurde von Microsoft als Konkurrenz zu Java entwickelt und sollte den Anforderungen<br />

von verteilten Anwendungen genügen. Es besteht im wesentlichen aus einer virtuellen Maschine,<br />

welche Common Language Runtime (CLR) genannt wird und dem Framework selbst. Die Implementierung<br />

orientiert sich an der CLI (Common Language Infrastructure), einem Standard, der Systeme<br />

spezifiziert, die sprach- und plattformneutrale Anwendungsentwicklung und -ausführung ermöglichen.<br />

Wie bei Java wird beim Kompilieren zunächst eine Art Zwischencode (CIL für Common Intermediate<br />

Language) erzeugt. Ein in CIL kompiliertes Programm bzw. Programmteil wird als Assembly bezeichnet.<br />

Assemblies können sowohl selbständig ausführbare Dateien (in Windows EXE-Dateien) als auch<br />

Bibliotheken sein. Sie enthalten neben dem Code in CIL zusätzlich Metadaten, welche das Assembly und<br />

darin enthaltene Definitionen näher beschreiben. Zum Ausführen eines Assemblies durch die virtuelle<br />

Maschine, im Falle von .NET der CLR, wird der Zwischencode in „nativen“ Maschinencode umgewandelt.<br />

Diese Art der Kompilierung in Maschinencode direkt vor dem Ausführen wird just-in-time (JIT)<br />

Kompilierung genannt. Da dies negativ auf die Startzeit eines Assemblies wirkt, kann für „zeitkritische“


2. GRUNDLAGEN 7<br />

Anwendungen, also Anwendungen die schnell gestartet werden sollen, bereits vor der Ausführung durch<br />

die CLR, „nativer“ Maschinencode erzeugt werden. Dieses Verfahren nennt sich NGen (Native Image<br />

Generation) und wird typischer Weise bei der Installation der Anwendung ausgeführt. Es wird also wie<br />

beim JIT kompilieren ein auf das System angepasster und optimierter „nativer“ Maschinencode erzeugt,<br />

der nicht für die Weitergabe und Ausführung auf anderen Systemen gedacht ist. Assemblies werden in<br />

CIL dem Binärformat der CLR weitergegeben. Ein weiterer Bestandteil der CLI ist ein wohldefiniertes<br />

Typensystem. Das Common Type System (CTS) stellt ein einheitliches System von Datentypen bereit.<br />

Abbildung 2.1 zeigt die Typenhierarchie des CTS.[7]<br />

Im Gegensatz zu Java, ist das .NET Framework an keine Programmiersprache gebunden. Neben C#, welches<br />

extra für die effiziente Nutzung des Frameworks entworfen wurde, gibt es eine Vielzahl von .NET<br />

fähigen Programmiersprachen. Einige wie C++ mit Managed Extensions (C++/CLI) oder Delphi.NET<br />

können sowohl „managed“ .NET Code als auch „unmanaged“ Code kompilieren. Da bei jedem .NET<br />

Kompiler ein Zwischencode (CIL) erzeugt wird, ist die Wahl der Sprache keine Einschränkung bezüglich<br />

der Kompatibilität. So können .NET Sprachen alle Komponenten und Bibliotheken nutzen die von<br />

einer beliebigen anderen .NET Sprache erzeugt wurden.<br />

Das .NET Framework ist zur Zeit nur für die Windows Betriebssysteme ab Windows 98 erhältlich. Um<br />

auch unter Linux Assemblies erzeugen zu können, muss man auf Open-Source Projekte wie Mono zurückgreifen.<br />

Das Mono-Projekt[14] orientiert sich ebenfalls an der CLI und beinhaltet eine eigene virtuelle<br />

Maschine, eine zum .NET Framework kompatible Sammlung von Klassenbibliotheken sowie einen<br />

Kompiler für die Sprache C#. Mono gibt es neben Linux auch für Mac OS X, Sun Solaris, BSD und<br />

Windows. Dadurch ist es möglich, Anwendungen für das .NET Framework auch unter anderen Betriebssystemen<br />

als Windows auszuführen.<br />

Der „managed“ Teil der Plugin API besteht aus zwei Assemblies. Diese Klassenbibliotheken stellen eine<br />

Verwaltungsklasse und Schnittstellen für Plugins bereit.<br />

2.2.3 Interoperabilität<br />

Bei der Entwicklung vom .NET Framework wurde auch auf die Zusammenarbeit mit „unmanaged“ Code<br />

geachtet. Allerdings bezieht sich diese Zusammenarbeit vor allem auf COM Projekte. COM steht für<br />

„component object model“ und bezeichnet eine Technologie, welche eine von der Programmiersprache<br />

unabhängige Möglichkeit der Objektbeschreibung und Objekterzeugung bietet (für weitere Informationen<br />

siehe [16]). Diese von Microsoft entwickelte Technologie findet aber nur in Windows Betriebssystemen<br />

Verwendung. Für eine plattformunabhängige API ist COM also ungeeignet.<br />

Im .NET Framework existiert neben der Unterstützung von COM noch eine weitere Variante der Inter-


2. GRUNDLAGEN 8<br />

operabilität. Diese nennt sich Platform Invoke Service oder auch PInvoke Service. PInvoke unterstützt<br />

Aufrufe von exportierten globalen Methoden und nicht statischen Memberfunktionen einer „unmanaged“<br />

DLL. Dies geschieht über das DllImport Attribut. Die dabei notwendige Konvertierung der „nativen“<br />

Typen in „managed“ Typen und umgekehrt wird Marshaling genannt (siehe [18]). Leider gibt es keine<br />

Möglichkeit, eine Instanz einer Klasse (Objekte) zu konvertieren. Es müssten also Wrapperklassen<br />

geschrieben werden, welche für jede Methode des zu konvertierenden Objektes, eigene Funktionszeiger<br />

besitzt. Diese Herangehensweise ist aber für eine flexible Plugin API, wie die hier vorgestellte, sehr<br />

hinderlich. Es müsste jeweils in C++ und .NET die Schnittstelle und eine zugehörige Wrapperklasse<br />

definiert werden. Dies bedeutet gegenüber der Aufteilung der API in einen .NET und einen C++ Teil,<br />

zwei zusätzliche Klassen. Es wurde aus diesem Grund von einer einheitlichen API abgesehen und die angesprochene<br />

Teilung vollzogen. Ist eine Zusammenarbeit von .NET Hostanwendung und „unmanaged“<br />

Plugin (oder auch umgekehrt) unbedingt nötig, dann kann auf die Managed Extensions von C++ zurückgegriffen<br />

werden. Die Hostanwendung besteht dann aus einem „managed“ und einem „unmanaged“ Teil<br />

und es ist dem Entwickler der Hostanwendung überlassen, sich um die Kommunikation zwischen .NET<br />

und C++ zu kümmern. Dies bedeutet im Normalfall deutlich weniger Aufwand, weil die Hostanwendung<br />

die Art der genutzten Plugins kennt. Sie muss unter diesem Gesichtspunkt also weniger flexibel sein.<br />

2.3 Bibliotheken<br />

Eine Bibliothek bezeichnet in der Programmierung eine kompilierte Sammlung von Funktionen, Variablen<br />

und Konstanten, welche von Programmen genutzt werden können. Häufig verwendete Programmteile<br />

werden auf allen gängigen Betriebssystemen in solche Bibliotheken ausgelagert. Sie müssen dann<br />

bei der Programmierung einer Anwendung nicht erneut geschrieben werden, sondern es werden die benötigten<br />

Teile aus der Bibliothek in das Programm eingebunden. Dieses Einbinden wird Linken genannt<br />

und es geschieht nach dem kompilieren des Programms. Es wird zwischen drei Arten des Linkens unterschieden.<br />

Dem statischen Linken, dem festen Linken und dem dynamischen Linken.<br />

2.3.1 Statische Bibliotheken<br />

Bei dem statischen Linken hängt ein sogenannter Linker der kompilierten Anwendung die benötigten<br />

Programmteile der Bibliotheken an. Weiterhin werden die entsprechende Verweise und Funktionsadressen<br />

angepasst, so dass die Anwendung danach den kompletten Programmcode enthält und beim Ausführen<br />

die statisch eingebundene Bibliothek nicht mehr benötigt. Bibliotheken die für das statische Linken<br />

vorgesehen sind werden statische Bibliotheken genannt. Das statische Linken führt zu einer immensen


2. GRUNDLAGEN 9<br />

Abbildung 2.2: Statisches Binden einer statischen Bibliothek<br />

Redundanz, da die Bibliotheksfunktionen in allen Programmen „physisch“ vorkommen, die sie verwenden.<br />

Dies hat nicht nur Nachteile für den benötigten Speicherplatz, sondern auch für die Wartbarkeit des<br />

Codes der Bibliotheken. Enthält beispielsweise eine Funktion einer statischen Bibliothek einen Fehler,<br />

dann ist der Fehler auch in allen Programmen enthalten, die diese Funktion statisch eingebunden haben.<br />

Für eine Behebung des Fehlers reicht es nicht die statische Bibliothek zu ändern, es müssen auch alle<br />

Programme neu kompiliert (und gelinkt) werden, die diese Funktion verwendenden. Der größte Vorteil<br />

vom statischen Linken ist, dass die Bibliothek zum Ausführen der Programme nicht mehr benötigt<br />

wird. Außerdem können der Kompiler und der Linker, beim erzeugen der fertigen Anwendung, den<br />

Programmierer auf nicht vorhandene Funktionen und auf Fehler bezüglich der Kompatibilität zwischen<br />

Anwendung und Bibliothek hinweisen.<br />

2.3.2 Dynamische Bibliotheken<br />

Für eine Funktionsbibliothek eines Betriebssystem sind die Nachteile des statischen Linkens sehr gravierend,<br />

da sie Funktionen enthält, die von vielen Programmen benötigt werden. Da eine solche Funktionsbibliothek<br />

dem Betriebssystem angehört, ist sie sehr wahrscheinlich auch beim der Ausführung des<br />

Programms vorhanden. Dies ermöglicht einen späteren Zeitpunkt des Bindens der Bibliothek an das<br />

Programm. Bibliotheken die beim Ausführen (zur Laufzeit) eines sie nutzenden Programms gebunden<br />

werden, nennt man dynamische Bibliotheken. Diese Bibliotheken werden nur dann in den physischen


2. GRUNDLAGEN 10<br />

Speicher (Arbeitsspeicher) geladen, wenn sie dort noch nicht vorhanden sind. Sie befinden sich also<br />

höchstens einmal im Hauptspeicher und verbleiben dort, bis alle sie verwendenden Prozesse beendet<br />

sind. Beim Binden an ein Programm wird die Bibliothek in dessen virtuellen Adressraum eingeblendet.<br />

Alle nicht statischen Datenbereiche der Bibliothek werden den entsprechenden Bereichen der Anwendung<br />

angegliedert. Sie sind für jeden Prozess der die Bibliothek nutzt, jeweils einmal im Adressraum<br />

des Prozesses zu finden. Deshalb können zwei Prozesse, welche gemeinsam eine Bibliothek nutzen, verschiedene<br />

Ausprägungen der gleichen, in der Bibliothek definierten, globalen Variable besitzen. Eine<br />

Kommunikation der Prozesse mit Hilfe dieser Variablen ist somit nicht möglich. Als Stackspeicher, wird<br />

von der Bibliothek der Stack des aufrufenden Prozesses genutzt. Der statische Teil einer dynamischen<br />

Bibliothek wird von allen Prozessen gemeinsam verwendet. Dafür wird dieser Bereich im Hauptspeicher<br />

von der Speicherverwaltung (memory management unit) vor einem Beschreiben geschützt. Der Maschinencode<br />

einer Bibliothek ist dadurch vor dem Überschreiben gesichert, kommt aber trotzdem maximal<br />

einmal im Hauptspeicher vor. Da die Bibliothek in den virtuellen Adressraum des aufrufenden Prozesses<br />

eingeblendet wird, sind beim Ausführen die Bibliotheksfunktionen genauso effektiv, wie von vornherein<br />

in die Anwendung eingebaute Funktionen. Sie besitzen aber automatisch auch die gleichen Rechte wie<br />

die Anwendung bzw. der Prozess selbst. Dynamische Bibliotheken können entweder fest oder dynamisch<br />

in eine Anwendung eingebunden werden.<br />

Beim festen Linken werden für die benötigten Funktionen (oder Daten) der Bibliothek Scheinobjekte<br />

(sogenannte stubs) erzeugt, damit für den Linker alle Referenzen erfüllt sind. Es sind also wie beim<br />

statischen Linken, alle Funktionsdefinitionen auch im Programm zu finden. Die Implementation ist hingegen<br />

nur in der Bibliothek enthalten. Beim Starten der Applikation wird die Bibliothek, wie zuvor<br />

beschrieben geladen und in den virtuellen Adressraum eingeblendet. Da das Linken beim Start des Programms<br />

erfolgt, also vor Ausführung des Programmcodes, kann die Anwendung auf mögliche Fehler<br />

beim Einbinden der Bibliotheken nicht reagieren. Fehlende Bibliotheken beispielsweise, haben immer<br />

einen Programmabbruch zur Folge. Wie beim statischen Linken, werden beim festen Linken, fehlende<br />

Funktionen und Probleme bei der Kompatibilität zwischen Anwendung und Bibliothek entdeckt und dem<br />

Programmierer beim Erstellen der Anwendung mitgeteilt.<br />

Die zweite Art der Bindung einer dynamischen Bibliothek ist das dynamische Binden. Dabei sind nach<br />

dem Kompilieren der Anwendung weder die Funktionsdefinitionen noch deren Implementation im Programm<br />

festgeschrieben. Das Laden der Bibliothek und das Einblenden in den virtuellen Adressraum geschieht<br />

analog zum festen Linken, allerdings wird beim dynamischen Linken der Zeitpunkt des Bindens<br />

vom Programmierer festgelegt. Dafür stellt das Betriebssystem Funktionen zum Laden und Entladen von<br />

Bibliotheken und zum Ermitteln der Einsprungadressen von Bibliotheksfunktionen oder Bibliotheksdaten<br />

bereit. Da das dynamische Linken vollkommen unabhängig vom Kompiler und vom Linker abläuft,


2. GRUNDLAGEN 11<br />

muss der Programmierer allein dafür sorgen, dass die implizit angenommene Funktionsdefinition mit<br />

der Bibliotheksfunktion übereinstimmt. Dadurch wird es aber auch möglich auf Fehler beim Binden zu<br />

reagieren. Es könnte z. B. auf eine alternative Bibliothek zurückgegriffen werden, wenn die eigentliche<br />

Bibliothek nicht vorhanden ist. Dies ist beim festen Linken nicht möglich.<br />

Abbildung 2.3: Festes Binden einer dynamischen Bibliothek<br />

2.3.3 Windows und dynamische Bibliotheken<br />

In Windows Betriebssystemen werden dynamische Bibliotheken DLL (dynamic link library) genannt.<br />

Sie besitzen alle die Funktion DllMain, welche als Einsprungpunkt beim Laden und Entladen der DLL<br />

dient. Ihr wird als zweiter Parameter der Grund für den Aufruf übergeben. Wird DllMain vom Programmierer<br />

der DLL nicht implementiert, so wird eine Standard DllMain-Funktion erzeugt (für weitere<br />

Informationen siehe [12]). Die von Windows für das dynamische Binden bereitgestellten Funktionen<br />

sind LoadLibrary, FreeLibrary und GetProcAddress. Zum Laden wird LoadLibrary verwendet, welche<br />

bei Erfolg einen Verweis vom Typ HINSTANCE auf die geladene DLL zurückgibt. Dieser Verweis wird<br />

von GetProcAddress verwendet um einen Zeiger auf in der Bibliothek enthaltene Funktionen oder Daten<br />

zu bekommen. Diese können dann mit dem Zeiger ausgeführt bzw. ausgelesen und verändert werden.


2. GRUNDLAGEN 12<br />

FreeLibrary blendet die DLL aus dem virtuellen Adressraum aus und veranlasst zudem das Löschen aus<br />

dem physischen Arbeitsspeicher, wenn sie von keinem anderen Prozess genutzt wird.<br />

2.3.4 Linux und dynamische Bibliotheken<br />

Dynamische Bibliotheken werden in Linux shared objects genannt. Sie besitzen die Einsprungpunkte<br />

_init und _fini für das Laden bzw. Entladen der DLL. Werden diese vom Programmierer nicht implementiert,<br />

werden Standardversionen dieser Funktionen genutzt. Das Laden einer dynamischen Bibliothek<br />

erfolgt über die Betriebssystemfunktion dlopen. Auch hier wird ein Zeiger zurückgegeben der dann<br />

von dlsym genutzt werden kann, um einen Zeiger auf eine Funktion oder ein Objekt der Bibliothek zu<br />

erhalten. Durch dlclose wird die Bibliothek wieder aus dem virtuellen Adressraum entfernt und ggf. aus<br />

dem Hauptspeicher gelöscht.<br />

2.4 Beispiele für Pluginsysteme<br />

In vielen kommerziellen, als auch freien Applikationen werden Pluginsysteme genutzt. Sie erweitern<br />

die bestehende Funktionalität, verändern das Aussehen oder führen komplett neue Funktionen in die<br />

Hostanwendung ein. Im Folgenden sollen einige Pluginsysteme vorgestellt und so Beispielhaft deren<br />

Nutzung in heutigen Anwendungen aufgezeigt werden.<br />

2.4.1 Adobe Photoshop Plugins<br />

Im Bereich der Bildbearbeitung ist das Pluginsystem von Photoshop am verbreitetsten. Es wird nicht nur<br />

von Photoshop selbst, sondern auch von anderen Anwendungen (z. B. Paintshop Pro) zur Erweiterung<br />

genutzt. Auch innerhalb der Produktfamilie von Adobe können häufig die gleichen Plugins untereinander<br />

verwendet werden.<br />

Das Pluginsystem wurde von Adobe mit der Version 2.5 der Bildbearbeitungssoftware Photoshop [1]<br />

eingeführt. Für Pluginentwickler wird ein SDK (Software Development Kit) bereitgestellt, womit sich<br />

Plugins in C (später auch teilweise in C++) schreiben lassen. Diese Sammlung von Programmen und<br />

Dokumentationen war bis 2002 frei zugänglich und kostenlos. Seit 2002 ist die Nutzung des SDK kostenpflichtig.<br />

Von der Firma Centaurix wurde mittlerweile das Photoshop SDK für Delphi portiert [5].<br />

Neben der Programmierung von Plugins mittels C/C++ bzw. Delphi, gibt es die Möglichkeit Plugins mit<br />

verschiedenen Hilfsprogrammen zu erstellen. Diese Programme nehmen dem Entwickler viele Aufgaben<br />

ab und verbergen den eigentlichen C/C++ Quellcode des Plugins. Sie werden zumeist für die Entwicklung<br />

freier Plugins genutzt. Einige Beispiele für diese Programme sind „Filter Factory“, „Filter Formula“


2. GRUNDLAGEN 13<br />

und „Filter Meister“.<br />

Für die Erweiterung von Photoshop sind verschiedene Arten von Plugins verfügbar:<br />

Image Filter Dies ist wohl die häufigste Art von Plugin. Sie können Bilder in verschiedenster Weise<br />

verändern. Ein Beispiel für ein solches Plugin ist der Filter „Wolken“, welcher das ausgewählte<br />

Bild, unter Zuhilfenahme der Vordergrund- und Hintergrundfarbe, mit einem Wolkenmuster füllt.<br />

Import Mit einem solchen Plugin können neue Quellen bestimmt werden, aus denen sich Bilder zur<br />

Bearbeitung mit Photoshop importieren lassen.<br />

Export Neben dem Speichern von Bildern auf Festplatten, können durch ein Export Plugin auch andere<br />

Ziele (z. B. Drucker) definiert werden.<br />

Format Die von Photoshop unterstützen Grafikformate können mit dieser Art Plugin erweitert werden.<br />

Color Picker Hiermit lässt sich ein eigener Farbwahldialog designen und in Photoshop nutzen.<br />

Selection Um Photoshop eigene Auswahl-, Pfad- oder Ebenenfunktionen hinzuzufügen, kann ein Pluginentwickler<br />

diesen Plugintyp nutzen.<br />

Automation So ziemlich alle Menuaktionen und Tooloperationen lassen sich mit diesem Plugintyp<br />

automatisieren. Es können unter Anderem auch weitere Plugins gestartet werden.<br />

Extension und Parser Die Schnittstellen dieser Plugintypen sind nicht öffentlich. Solche Plugins<br />

werden nur von Adobe erstellt.<br />

Alle erstellten Plugins werden in entsprechende Verzeichnisse von Photoshop kopiert, wo sie beim Start<br />

der Anwendung gesucht werden. Photoshop prüft dabei für jedes Plugin ob es eine sogenannte „plugin<br />

property list“ (PiPL) als Ressource enthält. Eine solche Liste stellt alle benötigten Informationen über<br />

das Plugin bereit. Diese Informationen werden von Photoshop auch zum Darstellen der Plugins im Menu<br />

genutzt. So kann z. B. für Filter der Untermenüpunkt festgelegt werden, unter welchem der Filter in<br />

Photoshop zu finden ist.<br />

Die Schnittstelle von Plugin und Hostanwendung ist sehr einfach gehalten. Von der Hostanwendung<br />

wird die Funktion main des Plugins zur Kommunikation genutzt. Die Art der Interaktion wird über ein<br />

Integerparameter bestimmt. Alle gemeinsam genutzten Daten werden über Zeiger auf die Datenbereiche<br />

mitgeteilt, welche als Parameter der Funktion main übergeben werden.<br />

2.4.2 Qt Plugins<br />

Trolltechs API Qt unterstützt ebenfalls die Nutzung von Plugins. Qt stellt dabei eine Sammlung von Klassen,<br />

Funktionen und Makros bereit, die der Erstellung von Plugins und Hostanwendungen dienen. Eine<br />

Qt nutzende Anwendungen kann die Klasse QPluginLoader verwenden, um so durch Plugins erweitert


2. GRUNDLAGEN 14<br />

werden zu können. Für ein Pluginsystem mit Qt müssen als Erstes die Schnittstellen definiert werden.<br />

Sie werden mit Hilfe von einem Makro Qt bekannt gemacht und können den Plugins als Basisklassen<br />

dienen. So erstellte Plugins können von einem QPluginLoader Objekt geladen und mit qobject_cast auf<br />

Schnittstellen getestet werden.<br />

Neben diesem Pluginsystems für Anwendungen, enthält die Qt Klassenbibliothek schon fertige Basisplugins,<br />

um Qt selbst zu erweitern. Für eigene Erweiterungen von Qt kann von diesen Plugins geerbt,<br />

eigene Funktionen hinzugefügt und dann mittels einem dafür vorgesehenen Makro das neue Plugin<br />

bei Qt registriert werden. Je nach gewähltem Basisplugin muss die neu geschriebene Erweiterung in<br />

ein spezielles Verzeichnis kopiert werden, damit Qt es nutzen kann. Sollen Applikationen weitergegeben<br />

werden, die eine so erweiterte Qt API nutzen, dann müssen die Plugins entweder statisch an das<br />

Programm gelinkt werden, oder mit der Applikation mitgeliefert und in entsprechende Verzeichnisse gesteckt<br />

werden. In Tabelle 2.1 werden alle vordefinierten Basisplugins und deren Standardverzeichnisse<br />

aufgelistet.<br />

Plugin<br />

QAccessibleBridgePlugin<br />

QAccessiblePlugin<br />

QDecorationPlugin<br />

QIconEnginePlugin<br />

QImageIOPlugin<br />

QInputContextPlugin<br />

QKbdDriverPlugin<br />

QMouseDriverPlugin<br />

QPictureFormatPlugin<br />

QScreenDriverPlugin<br />

QScriptExtensionPlugin<br />

QSqlDriverPlugin<br />

QStylePlugin<br />

QTextCodecPlugin<br />

Verzeichnis<br />

accessiblebridge<br />

accessible<br />

decorations<br />

iconengines<br />

imageformats<br />

inputmethods<br />

kbddrivers<br />

mousedrivers<br />

pictureformats<br />

gfxdrivers<br />

script<br />

sqldrivers<br />

styles<br />

codecs<br />

Tabelle 2.1: Qt Plugins


3. DIE PLUGIN API FÜR C++ 15<br />

3 Die Plugin API für C++<br />

Wie bereits zuvor erwähnt, gibt es sowohl für C++ also auch für .NET eine API. Im Folgenden werden<br />

die einzelnen Teile der API für die Programmiersprache C++ näher beleuchtet und deren Verwendung<br />

erklärt. Die Grundidee der C++ Plugin API besteht darin, dass eine Reihe von Schnittstellen definiert<br />

werden und jedes Plugin von den Schnittstellen erbt, die nach Außen sichtbar sein sollen, d. h. deren<br />

Funktionen das Plugin unterstützt und von der Hostanwendung genutzt werden können. Damit das<br />

Ansprechen verschiedener Plugins möglich wird, wurde eine Schnittstelle definiert, von der alle Plugins<br />

erben. Diese Schnittstelle ist iplugin_base. In der C++ Plugin API werden neben den Schnittstellen<br />

auch Klassen eingeführt, die zum Laden und Schreiben von Plugins nötig sind. Außerdem werden einige<br />

Typen deklariert, welche die Arbeit mit Plugins erleichtern sollen. Da es in C++ keine spezielle<br />

Schnittstellendeklaration gibt, werden hier einfache Klassen verwendet. Diese können aber wie Schnittstellen<br />

(interfaces) behandelt werden, weil C++ Mehrfachvererbung unterstützt, d. h. eine C++ Klasse<br />

aus mehreren Basisklassen bestehen.<br />

3.1 Typenreflektion mit dynamic_cast<br />

Abbildung 3.1: Speicherlayout eines Plugins


3. DIE PLUGIN API FÜR C++ 16<br />

Für alle Plugins, die mit Hilfe der hier vorgestellten C++ API erstellt wurden, gibt es eine gemeinsame<br />

Basisklasse. Dies ist die Schnittstelle iplugin_base. Da neben dieser Basisklasse ein Plugin von beliebigen<br />

anderen Klassen erben kann, wird ein Zeiger auf iplugin_base (piplugin_base) zum Ansprechen der<br />

Plugins genutzt. In Abbildung 3.1 ist beispielhaft das Speicherlayout eines C++ Plugins zu sehen. Um<br />

neben iplugin_base auch die anderen Schnittstellen (Basisklassen) ansprechen zu können, müssen zur<br />

Laufzeit der Typ und die Basisklassen für das Plugin bekannt sein. Allgemeiner Formuliert muss man<br />

zur Laufzeit für jedes Objekt die zugrunde liegende Klasse bzw. deren Eigenschaften ermitteln können.<br />

Dieses Konzept wird Typenreflektion oder auch RTTI (runtime type information - „Typinformation zur<br />

Laufzeit“) genannt. Die Sprache C++ unterstützt Typenreflektion nur für polymorphe Objekte, d. h. sie<br />

müssen eine Instanz einer virtuellen Klasse sein und so zumindest eine virtuelle Methode besitzen. Der<br />

Grund für diese Voraussetzung ist, dass der C++ Kompiler jeder virtuellen Klasse einen Zeiger auf die<br />

sogenannte virtual function pointer table (vtbl) anfügt. Eine solche Funktionstabelle wird zur Laufzeit<br />

für jede virtuelle Klasse geführt. Der Operator dynamic_cast nutzt diese Tabelle für die Typenreflektion<br />

und bietet so eine generische Methode, um zur Laufzeit mögliche Basisklassen eines Typs zu ermitteln.<br />

Dynamic_cast kann außerdem mit einem Verweis (Zeiger oder Referenz) auf eine Teilklasse eines polymorphen<br />

Objekts, auf das Vorhandensein weiterer Teilklassen (Basisklassen) dieses Objektes testen. Er<br />

könnte also z. B. mit einem Zeiger auf die Schnittstelle iplugin_base eines Plugins testen, ob dieses Plugin<br />

auch die Schnittstelle irenderable implementiert. Um das zu realisieren versucht dynamic_cast den<br />

Verweis eine Teilklasse in einen Verweis auf die andere Teilklasse des gleichen polymorphen Objekts zu<br />

wandeln. Für die Typumwandlung wird der dynamische Typ eines Verweises geprüft, und die Wandlung<br />

nur dann ausgeführt, wenn sie erlaubt ist. Dem Beispiel von weiter oben folgend würde dynamic_cast also<br />

erst den Typ des Plugins ermitteln, zu welchem die Teilklasse gehört, auf welche piplugin_base zeigt<br />

und dann testen ob dieser Plugintyp in die Schnittstelle irenderable umgewandelt werden kann. Ist der<br />

Verweis ein Zeiger wird bei einer unerlaubten Wandlung von Typen ein Nullzeiger zurückgegeben. Wird<br />

eine Referenz als Verweis genutzt, wirft dynamic_cast im Fehlerfall eine Ausnahme vom Typ bad_cast.<br />

[3] Quelltext A.1 zeigt die Verwendung von dynamic_cast.<br />

3.2 Schnittstellendefinitionen in PluginDefs<br />

Damit die Hostanwendung und die Plugins kommunizieren können, müssen die Schnittstellen zwischen<br />

Beiden definiert werden. Diese Definition muss an zentraler Stelle erfolgen und sowohl vom Plugin als<br />

auch von der Hostanwendung unabhängig sein. In der C++ Plugin API sind alle Schnittstellendefinitionen<br />

in der statische Bibliothek PluginDefs enthalten. Alle dort definierten Schnittstellen können bei der<br />

Entwicklung von Plugins verwendet werden. Da C++ wie bereits erwähnt keine spezielle Deklaration für


3. DIE PLUGIN API FÜR C++ 17<br />

Schnittstellen vorsieht, sind alle Schnittstellen virtuelle Klassen ,die von plugin_interface_class erben.<br />

Außer der Standardschnittstelle iplugin_base sind alle Schnittstellen nicht abstrakt, obwohl dies dem<br />

Charakter einer Schnittstelle am ehesten entsprechen würde. Es brächte aber einige Nachteile mit sich,<br />

wenn sie alle abstrakt wären, denn es ist in C++ nicht möglich eine Liste von Typen zu erzeugen. Eine<br />

solche Liste wird aber z. B. beim Laden eines Plugins benötigt, um die Schnittstellen anzugeben, die<br />

ein zu ladendes Plugin implementieren soll (siehe dafür Kapitel 3.3.1). Durch den Verzicht auf abstrakte<br />

Schnittstellenklassen können nun Instanzen der einzelnen Schnittstellen in eine Liste gesteckt werden.<br />

Eine solche Liste die Schnittstellen aufnehmen kann ist in der C++ Plugin API bereits definiert und heißt<br />

plugin_interface_list.<br />

3.2.1 Plugin_interface_class<br />

Die abstrakte Klasse plugin_interface_class ist die Basisklasse aller Plugin-Schnittstellen der API. Über<br />

sie können alle Schnittstellen angesprochen werden und wie bereits erwähnt in einer Liste zusammengefasst<br />

werden. Wie im Quelltext A.2 zu sehen wird in der Klasse die abstrakte Boolesche Funktion<br />

is_implemented_by definiert, welche von allen erbenden Schnittstellen implementiert werden muss. Die<br />

Funktion bestimmt für ein übergebenes Plugin, ob dieses die aktuelle Schnittstelle unterstützt. Um zu<br />

testen, ob ein Plugin eine Schnittstelle unterstützt, muss als Erstes eine Instanz der Schnittstelle erstellt<br />

werden und dann der Methode is_implemented_by dieser Schnittstelle, das Plugin als Parameter<br />

übergeben werden. Im Normalfall wird in der Funktion zum Testen eine Umwandlung mittels dynamic_cast<br />

von dem übergebenen Plugin zur Schnittstelle durchgeführt. Scheitert diese Umwandlung, wird<br />

von is_implemented_by „false“ zurückgegeben. Im Erfolgsfall liefert die Funktion ein „true“ zurück. In<br />

der Verwaltungsklasse plugin_loader wird is_implemented_by z. B. zum selektiven Laden der Plugins<br />

genutzt, die bestimmte Schnittstellen beinhalten.<br />

3.2.2 Iplugin_base<br />

Für den ersten Zugriff auf ein Plugin wird die Standardschnittstelle iplugin_base genutzt. Sie ist eine<br />

Schnittstelle, die von allen Plugins implementiert werden muss. In ihr ist die abstrakte Funktion<br />

get_plugin_name angegeben. Sie ermöglicht es dem Plugin einen Namen zu geben. Daneben wird die<br />

ebenfalls abstrakte Funktion create_new_instance definiert, welche einen Zeiger vom Typ piplugin_base<br />

auf eine neue Instanz eines Plugins zurückliefert. Genau wie die Methode get_plugin_name muss auch<br />

create_new_instance vom Entwickler eines Plugins implementiert werden. Zumeist wird dies in der Pluginfabrik<br />

passieren (siehe dafür Kapitel 3.4.2). Neben der Klasse iplugin_base wird mit piplugin_base<br />

ein Zeiger auf eine Instanz dieser Klasse definiert. Weiterhin kann iplugin_pointer_list dazu genutzt


3. DIE PLUGIN API FÜR C++ 18<br />

werden um Plugins in einer Liste zu speichern. Der Quelltext A.3 zeigt den Header der iplugin_base<br />

Schnittstelle.<br />

3.2.3 Irenderable<br />

Für alle Plugins die auf irgendeine Weise, Inhalte in der Hostanwendung grafisch ausgeben wollen, ist die<br />

Schnittstelle irenderable gedacht. Plugins, die von irenderable erben, können über die Funktion render<br />

angesprochen werden. Diese Funktion wird vom Plugin implementiert und sollte eine grafische Ausgabe<br />

bewirken. Sie kann dann immer aufgerufen werden ,wenn die Hostanwendung neu gezeichnet werden<br />

muss.<br />

Die Schnittstelle irenderable beinhaltet keine Einschränkung bezüglich der Art der grafischen Ausgabe.<br />

Die render Methode könnte jede beliebige Systembibliothek zum Zeichen verwenden. Für Hostanwendungen<br />

die verschiedene Systembibliotheken zur Ausgabe von Daten nutzen kann, ist es deshalb ratsam<br />

in einem Plugin mit grafischer Ausgabe, neben irenderable noch eine weitere Schnittstelle zu implementieren,<br />

welche die Art der Ausgabe genauer spezifiziert. Dadurch kann beim Laden eines Plugins<br />

festgelegt werden, was für Systembibliotheken zur Darstellung von Inhalten von der Hostanwendung<br />

unterstützt werden. Ein Plugin könnte z. B. zusätzlich die Schnittstelle id3d9_plugin implementieren.<br />

Beim Laden dieses Plugins würde das Pluginsystem dann feststellen können, dass die grafische Ausgabe<br />

mit Direct3D 9 durchgeführt wird.<br />

3.2.4 Id3d9_plugin<br />

Die Schnittstelle id3d9_plugin ist für Plugins gedacht, die mit Direct3D 9 auf einen Bereich der Hostanwendung<br />

zeichnen möchten. Die Hostanwendung ist also für die Initialisierung von Direct3D 9 zuständig,<br />

d. h. unter anderem für das Erstellen eines IDirect3DDevice9 Objektes, welches die Zeichenfläche<br />

für Direct3D 9 darstellt. Ein solches Objekt wird in Direct3D 9 von allen Funktionen benötigt, die etwas<br />

darstellen wollen. Deswegen muss es auch einem Plugin zur Verfügung stehen, welches grafische<br />

Objekte mittels Direct3D 9 ausgeben möchte. Für dieses Problem enthält id3d9_plugin die Funktion init_d3d9.<br />

Sie besitzt als Parameter einen Standardzeiger vom Typ void. Der Zeiger ist nicht näher typisiert,<br />

weil es sonst feste Abhängigkeiten zwischen der Plugin API und den DirectX Bibliotheken gäbe. Es ist<br />

vorgesehen, dass dieser Zeiger auf ein IDirect3DDevice9 Objekt zeigt, welches in der Hostanwendung<br />

erstellt wurde. Dieser Zeiger kann vom Plugin gespeichert werden und ihm so Zugriff auf das benötigte<br />

IDirect3DDevice9 Objekt bieten. Mit init_d3d9 kann das Plugin außerdem alle für seine grafischen<br />

Ausgaben notwendige Objekte erstellen. Um diese Objekte wieder freizugeben, kann release_d3d9 verwendet<br />

werden.


3. DIE PLUGIN API FÜR C++ 19<br />

Eine Hostanwendung die Plugins unterstützt, welche die id3d9_plugin Schnittstelle implementieren,<br />

muss sich darum kümmern, dass die Funktionen init_d3d9 und release_d3d9 an den notwendigen Stellen<br />

aufgerufen werden.<br />

3.2.5 IPluginEventCallback<br />

Bei einigen Anwendungen des Pluginsystems kann es notwendig sein, dass die Hostanwendung auf<br />

bestimmte Ereignisse in einem Plugin reagiert. Beispielsweise könnte ein Plugin der Hostanwendung<br />

signalisieren, dass Teile von einer Datenmenge fertig bearbeitet wurden und bereit sind von der Hostanwendung<br />

ausgegeben zu werden. Für solcher Art Plugins ist die Schnittstelle iplugin_event_callback gedacht,<br />

deren Aufbau im Quelltext A.4 zu sehen ist. Sie führt die zwei bereits implementierten Methoden<br />

set_event_callback und do_event ein, welche einem Plugin die Möglichkeit bieten, zur Ereignisbehandlung<br />

eine Funktion der Hostanwendung zu nutzen. Eine solche Funktion in der Hostanwendung wird<br />

in der Programmierung „callback“ Funktion genannt, weil sie im Plugin „zurückgerufen“ werden kann.<br />

Sie muss dem von cplugin_event_callback festgelegten Typ entsprechen, d. h. sie besitzt keinen Rückgabewert<br />

und hat als einzigen Parameter den Aufzählungstyp enum_plugin_event. Durch die Methode<br />

set_event_callback wird sie für die Behandlung von Ereignissen im Plugin festgelegt. Dies wird erreicht,<br />

indem set_event_callback den private Funktionszeiger cfpdo_event auf die als Parameter übergebene<br />

„callback“ Funktion setzt. Tritt im Plugin ein Ereignis ein, dass gemeldet werden soll, muss die private<br />

Schnittstellenmethode do_event vom Plugin aufgerufen werden. Diese stößt wiederum die Hostfunktion<br />

an, auf die von cfpdo_event gezeigt wird. Dadurch wird das Ereignis nach Außen bekannt zu geben, und<br />

kann in der „callback“ Funktion behandelt werden. Die Art des Ereignisses wird aus einer Aufzählung<br />

vom Typ enum_plugin_event bestimmt und als Parameter der „callback“ Funktion mitgeteilt.<br />

3.2.6 Ikeyboard_event_handler<br />

In den meisten Bereichen, in denen Plugins benutzt werden, kümmert sich die Hostanwendung um die<br />

Interaktion mit dem Benutzer. Tastatureingaben und andere Ereignisse werden in dafür vorgesehenen<br />

Funktionen behandelt. Mit Hilfe der Schnittstelle ikeyboard_event_handler können die Tastaturereignisse<br />

an ein Plugin weitergegeben werden, wodurch dieses dann auf das übergebene Ereignis reagieren<br />

kann. In der Schnittstelle werden drei Arten von Tastaturereignissen unterschieden. Wird eine Taste herunter<br />

gedrückt kann dies mit der Methode on_key_down dem Plugin mitgeteilt werden. Durch on_key_up<br />

erfährt das Plugin, dass eine Taste wieder losgelassen wurde. Wurde eine Taste gedrückt und wieder losgelassen,<br />

wird dies durch on_key_press dem Plugin übergeben. Allen drei Methoden wird die gedrückte<br />

Taste als Parameter übergeben. Dafür wird der Aufzählungstyp xp_key_code verwendet.


3. DIE PLUGIN API FÜR C++ 20<br />

3.2.7 Imouse_event_handler<br />

Wenn Plugins auf Mausereignisse reagieren sollen, kann imouse_event_handler implementiert werden.<br />

Genau wie bei der Schnittstelle ikeyboard_event_handler, werden hier die von der Hostanwendung<br />

aufgefangenen Ereignisse, an das Plugin weitergegeben. Die Methode on_mouse_button_down meldet<br />

dem Plugin, dass ein Mausknopf herunter gedrückt wurde. Dagegen kann ein losgelassener Knopf<br />

mit on_mouse_button_up bekannt gemacht werden. Durch on_mouse_move werden Mausbewegungen<br />

mitgeteilt. Um einen Mausklick weiterzugeben wird on_mouse_click genutzt und für Doppelklicks ist<br />

on_mouse_dbclick zuständig. Als Parameter wird ein Wert vom Aufzählungstyp xp_mouse_button und<br />

die Mauskoordinaten als int erwartet.<br />

3.3 Die Verwaltungsklasse plugin_loader<br />

In einer Hostanwendung gibt es in Bezug auf Plugins drei wichtige Aufgaben. Die Plugins müssen dynamisch<br />

geladen werden können, der Zugriff auf die geladenen Plugins muss schnell und einfach vonstatten<br />

gehen und nicht mehr benötigte Plugins müssen auch wieder entfernbar sein. Um dem Entwickler<br />

einer Hostanwendung diese Dinge abzunehmen, enthält die C++ Plugin API die Verwaltungsklasse plugin_loader,<br />

welche für jede der einzelnen Aufgaben passende Methoden bereitstellt. Die Hostanwendung<br />

muss nun nur noch ein Objekt aus der Klasse erzeugen und kann dann mit dessen Hilfe Plugins nutzen.<br />

Der Quelltext A.5 zeigt den Header der Verwaltungsklasse plugin_loader.<br />

Da das Laden und Entladen von Plugins das dynamische Binden bzw. entfernen von Bibliotheken beinhaltet,<br />

ist plugin_loader auf die in Kapitel 2.3.3 und 2.3.4 beschriebenen Systemfunktionen angewiesen.<br />

Damit die Plattformunabhängigkeit der Klasse gewährleistet bleibt, werden statt den systemspezifischen<br />

Funktionen, die in den Dateien „CrossPlattform.h“ und „CrossPlattform.cpp“, für Linux und<br />

Windows implementierten, plattformunabhängigen Funktionen xp_load_library, xp_get_proc_address<br />

und xp_free_library genutzt.<br />

Zum Laden dynamischer Bibliotheken wird xp_load_library verwendet. Diese gibt ein xp_dll_handle<br />

zurück, welches zur Identifizierung der geladenen Bibliothek dient. Ein solches xp_dll_handle wird<br />

z. B. von der Methode xp_get_proc_address benötigt. Diese Methode wird dazu genutzt, um in der<br />

zum übergebenen xp_dll_handle passenden dynamischen Bibliothek, die Einsprungadresse der Funktion<br />

get_plugin_factory_list zu finden. Auf den Nutzen dieser Funktion und weitere Einzelheiten zum Laden<br />

von Plugins wird in Kapitel 3.3.1 genauer eingegangen. Sind alle Plugins einer Bibliothek nicht mehr<br />

in Gebrauch, kann die Bibliothek mit xp_free_library entladen werden. Welche Bibliothek freigegeben<br />

werden soll, wird durch den Parameter vom Typ xp_dll_handle bestimmt.


3. DIE PLUGIN API FÜR C++ 21<br />

3.3.1 Laden neuer Plugins<br />

Wie bereits erwähnt, wird dem Entwickler durch die Klasse plugin_loader u. a. das Laden von Plugins<br />

vereinfacht. Dafür ist die überladene Methode load zuständig. Wie der Quelltext A.5 zeigt ist dazu immer<br />

der Name der Pluginbibliothek inklusive dem Pfad erforderlich. Optional kann außerdem eine Liste<br />

von Schnittstellen übergeben werden. In der Methode wird zuerst die angegebene dynamische Bibliothek<br />

durch die plattformunabhängige Funktion xp_load_library geladen. Das zurückgegebene xp_dll_handle<br />

wird in einer Struktur vom Typ plugin_list_item gespeichert. Diese enthält neben dem xp_dll_handle<br />

noch einen Zeiger auf ein geladenes Plugin. Der Zeiger ist wie üblich vom Typ piplugin_base. Nach<br />

dem Laden der Bibliothek wird ein Funktionszeiger definiert, welchen xp_get_proc_address anschließend<br />

auf die Einsprungadresse der „C“-Funktion get_plugin_factory_list setzt. Diese Funktion wird vom<br />

Entwickler einer Pluginbibliothek implementiert, und gibt eine Referenz auf eine Liste sogenannter Pluginfabriken<br />

zurück (siehe Kapitel 3.4). Nachdem xp_get_proc_address erfolgreich den Funktionszeiger<br />

gesetzt hat, wird dieser zum Ausführen der zugewiesenen „C“-Funktion genutzt. Dadurch erhält man<br />

die oben erwähnte Referenz und kann mit ihrer Hilfe die Plugins der Bibliothek erzeugen, welche die<br />

geforderten Schnittstellen unterstützen. Wurde der Methode load keine Liste von notwendigen Schnittstellen<br />

übergeben, werden alle Plugins aus der Bibliothek erzeugt. Zum Abschluss werden Zeiger auf<br />

diese Plugins in der Struktur vom Typ plugin_list_item gespeichert, und dann zusammen mit dem zugehörigen<br />

xp_dll_handle in die Liste m_plugin_list vom Typ plugin_list eingetragen, welche der Klasse<br />

plugin_loader angehört. Soll ein Plugin vor dem Programmende aus dieser Liste entfernt werden, kann<br />

man entweder die Methode delete_plugin der Klasse plugin_loader verwenden, oder direkt auf die Liste<br />

zugreifen und deren Methode zum Löschen verwenden.<br />

3.3.2 Pluginliste<br />

Von der Klasse plugin_loader geladene Plugins werden in einer Liste vom Typ plugin_list gespeichert.<br />

Diese Liste heißt m_plugin_list, ist öffentlich und kann so wenn nötig über das plugin_loader Objekt direkt<br />

angesprochen werden. Im Normalfall wird sie aber nur von den Methoden der Klasse plugin_loader<br />

genutzt. Auf den Zugriff und den Umgang mit Plugins wird im folgenden Kapitel 3.3.3 näher eingegangen.<br />

Ein Element der Liste plugin_list ist durch plugin_list_item beschrieben. Es enthält einen Zeiger auf<br />

eine Plugininstanz und einen Verweis (Handle) auf die dazugehörigen Bibliothek. Wie der Quelltext<br />

A.6 zeigt, werden intern diese Listeneinträge in einem vector gespeichert. Das Hinzufügen von Einträgen<br />

geschieht durch die Methode add_item. Ihr werden die neuen Einträge mittels einem als Parameter<br />

übergebenen plugin_list_item mitgeteilt. Die Klasse plugin_loader nutzt sie beim Laden der Plugins aus


3. DIE PLUGIN API FÜR C++ 22<br />

einer Bibliothek. Das Entfernen von bestehenden Elementen erfolgt durch delete_item. Dieser Methode<br />

wird der Index des zu löschenden Listeneintrags, als Parameter überliefert. Beim Löschen von Einträgen,<br />

kümmert sich die Klasse plugin_list auch darum, dass neben der Plugininstanz die zugehörige Bibliothek<br />

aus dem Speicherbereich der Hostanwendung ausgeblendet (entladen) wird, wenn kein anderes Plugin<br />

dieser Bibliothek in der Liste vorhanden ist. Durch Aufruf des Destruktors der Listenklasse, also beim<br />

Freigeben des Objektes, werden automatisch alle Elemente der Liste mit Hilfe von delete_item gelöscht.<br />

Um ein bestimmtes Plugin aus der Liste anzusprechen, nutzt plugin_loader die Methode get_plugin,<br />

welche einen Zeiger vom Typ piplugin_base zurückgibt. Durch plugin_count wird die Anzahl der in der<br />

Liste enthaltenen Plugins zurückgegeben.<br />

3.3.3 Zugriff auf Plugins<br />

Neben Methoden zum Laden und Entfernen von Plugins, bietet plugin_loader auch Möglichkeiten zum<br />

Zugriff auf die geladenen Plugins. Die Pluginliste m_plugin_list ist zwar wie bereits erwähnt öffentlich<br />

und somit auch direkt ansprechbar, allerdings gestaltet sich der Zugriff durch die vom plugin_loader<br />

bereitgestellten Funktionen etwas einfacher. Diese Funktionen sind zum Einem get_plugin und zum Anderen<br />

get_interface.<br />

Genau wie bei der gleichnamigen Methode der Klasse plugin_list, wird auch hier get_plugin der Index<br />

des gewünschten Plugins übergeben und ein Zeiger auf die iplugin_base Basisklasse des Plugins<br />

zurückgeliefert. Dieser Zeiger kann dann mit Hilfe von dynamic_cast in andere Schnittstellen umgewandelt<br />

werden. Eine andere Möglichkeit des Zugriffs bietet die inline Schablonenmethode get_interface.<br />

Ihr wird als Schblonenparameter eine gewünschte Schnittstelle übergeben. Der als Funktionsparameter<br />

angegebene Index, bestimmt das Plugin aus der Liste, für welches der Zugriff auf die angegebene<br />

Schnittstelle gewährt werden soll. Wie im Quelltext A.5 zu sehen ist, führt get_interface dann eine<br />

Typumwandlung mittels dynamic_cast durch, und liefert einen Zeiger zurück, der auf die geforderte<br />

Schnittstelle zeigt. Enthält das Plugin keine solche Schnittstelle, wird ein Nullzeiger zurückgegeben.<br />

Der Quelltext A.7 zeigt den Umgang mit beiden Methoden.<br />

3.4 Plugin Bibliotheken schreiben<br />

Das Schreiben von Plugins, die durch Klassen der hier vorgestellten API genutzt werden sollen, gestaltet<br />

sich kaum unterschiedlich zum Schreiben normaler Anwendungen. Da Plugins im Speicherbereich des<br />

aufrufenden Prozesses ausgeführt werden, besitzen sie die gleichen Rechte und Einschränkungen wie zu<br />

dem Prozess gehörende Funktionen. Ein wichtiger Unterschied besteht allerdings darin, dass ein Plugin


3. DIE PLUGIN API FÜR C++ 23<br />

selten alleine in der Hostanwendung agiert. Das bedeutet, dass je nachdem wie stark die Plugins in eine<br />

Anwendung eingreifen, sich auch indirekt der Einfluss der Plugins untereinander erhöht. Nutzt beispielsweise<br />

ein Plugin die Funktionen von OpenGL zur grafischen Ausgabe, so bleiben die dafür benötigten<br />

Änderungen an OpenGL (z. B. Lichteinstellungen, Farben o. Ä.) innherhalb der gesammten Hostanwendung<br />

erhalten. Ein weiteres, OpenGL nutzendes Plugin, könnte also dadurch beeinflusst werden und so<br />

seine Daten nicht wie gewünscht ausgeben. Ein Plugin sollte deshalb nur soviel wie unbedingt nötig an<br />

„Eigenschaften“ der Hostanwendung ändern und soweit wie möglich die unerwünschten, aber für die<br />

Funktion des Plugins notwendigen, Änderungen nach der Ausführung des Plugins wieder rückgängig<br />

machen.<br />

Damit Plugins von einer Instanz der Klasse plugin_loader geladen werden können, müssen sie vom<br />

Pluginentwickler in eine dynamische Bibliotheken geschrieben werden. Eine solche Bibliothek kann im<br />

Prinzip eine beliebige Zahl an Plugins enthalten. Für das Laden ist es nötig das alle enthaltenen Plugins<br />

an zentraler Stelle registriert sind. Dazu dient eine Verwaltungsklasse vom Typ plugin_factory_list_class,<br />

auf welche im folgenden Kapitel 3.4.1 genauer eingegangen wird. Damit eine Hostanwendung Zugriff<br />

auf diese Liste bekommt, muss eine Pluginbibliothek die Funktion get_plugin_factory_list exportieren.<br />

Diese Funktion wird als „C“-Funktion deklariert, damit der Compiler kein „name mangeling“ durchführt.<br />

Das „name mangeling“ ist eine Bezeichnung für die Umbenennung von Funktionen durch den<br />

C++ Kompiler. Der neue Name wird unter Einbeziehung des Funktionsnamen und der Funktionsparameter<br />

gebildet. Der Grund für die Umbenennung durch den Kompiler liegt darin, dass C++ Funktionen<br />

überladen werden können, d. h. dass es möglich ist, sie mit jeweils unterschiedlichen Parametern mehrfach<br />

zu implementieren. Dadurch ist aus dem Funktionsnamen allein nicht eindeutig zu schließen welche<br />

Implementation dieser Funktion gemeint ist. Da aber Symbole, also Funktionen und andere globale<br />

Objekte in einer kompilierten Datei, über den Namen angesprochen werden, muss der C++ Kompiler<br />

die Funktionsnamen wie oben beschrieben erweitern. Leider gibt es keinen Standard für diese Umbenennung.<br />

Der Symbolname einer C++ Funktion kann also je nach Kompiler variieren. Da Plugins mit<br />

unterschiedlichen C++ Kompilern geschrieben worden sein können, der Name der Funktion aber vorher<br />

bekannt und überall gleich sein soll, wird get_plugin_factory_list also als „C“-Funktion deklariert.<br />

In C gibt es keine Möglichkeit zum überladen von Funktionen und es findet darum auch kein „name<br />

mangeling“ statt.<br />

3.4.1 plugin_factory_list_class<br />

Wie zuvor erwähnt, exportiert jede Pluginbibliothek eine Funktion get_plugin_factory_list. Sie gibt eine<br />

Referenz auf eine Instanz der Klasse plugin_factory_list_class zurück. Diese stellt wiederum eine


3. DIE PLUGIN API FÜR C++ 24<br />

Liste dar, welche für jedes Plugin der Bibliothek eine sogenannte „Pluginfabrik“ (siehe Kapitel 3.4.2)<br />

enthält. Sie führt die Funktion create_plugin ein, welche ein ausgewähltes Plugin mit Hilfe der zugehörigen<br />

„Fabrik“ erstellt. Welches Plugin zu erstellen ist, wird über einen als Parameter übergebenen<br />

Index festgelegt. Neben dieser Funktion kann create_all_plugins genutzt werden, um mehrere Plugins<br />

der Bibliothek zu erstellen. Ihr werden zwei Listobjekte als Parameter übergeben. Die erste Liste ist<br />

bei der Übergabe leer und wird von der Funktion mit Zeigern auf die erstellten Plugins gefüllt. Diese<br />

Zeiger sind vom Typ piplugin_base, zeigen also auf die Standardschnittstelle iplugin_base des erstellten<br />

Plugins. Die zweite Liste, die von create_all_plugins verlangt wird, enthält Instanzen aller Schnittstellen,<br />

welche das zu erzeugende Plugin implementieren soll. Sie dient also als Filter, um nicht alle Plugins<br />

einer Bibliothek in eine Hostanwendung aufnehmen zu müssen. Wird eine leere Liste übergeben, werden<br />

dementsprechend alle Plugins der Bibliothek geladen. Sowohl create_all_plugins als auch create_plugin<br />

werden im Normalfall nicht direkt vom Entwickler aufgerufen. Die Klasse plugin_loader nutzt sie zum<br />

Laden der Plugins (siehe auch Kapitel 3.3.1).<br />

Eine typische Pluginbibliothek enthält genau eine globale Instanz der Klasse plugin_factory_list_class,<br />

von welcher mit der oben beschriebenen Funktion, eine Referenz an die Hostanwendung übergeben wird.<br />

Dieser Instanz müssen alle Plugins der Bibliothek bekannt gemacht werden. Dafür ist die Schablonenfunktion<br />

register_factory vorgesehen. Ihr wird mittels Schablonenparameter eine „Pluginfabrikklasse“<br />

übergeben. Aus dieser erzeugt die Funktion eine Instanz, die sie dann in der Liste m_class_factory_list<br />

speichert. Im Quelltext A.8 ist die Listenklasse und die Implementation der Schablonenfunktion register_factory<br />

zu sehen.<br />

3.4.2 Pluginfabriken<br />

Will man eine Pluginbibliothek schreiben, die mit den hier vorgestellten Klassen kompatibel ist, muss für<br />

jedes Plugin, welches der Bibliothek hinzugefügt wird, auch eine „Pluginfabrik“ vorhanden sein. Eine<br />

solche Klassenfabrik (engl. class factory) ist in der Lage, Instanzen des ihr zugeordneten Plugins zu erzeugen.<br />

Sie erben von den gleichen Schnittstellen wie das Plugin, was sie erzeugen wollen, implementieren<br />

aber die Schnittstellenmethoden nicht neu. Eine Ausnahme bildet die abstrakte Standardschnittstelle<br />

iplugin_base. Sie „zwingt“ den Pluginentwickler dazu, die Beiden für die Fabrik wesentlichen Methoden<br />

zu implementieren. Die Eine ist get_plugin_name und liefert den Namen des zu erstellenden Plugins<br />

zurück. Die Andere ist create_new_instance. Sie ist für das eigentliche erstellen des Plugins zuständig.<br />

In Pluginbibliotheken werden „Pluginfabriken“ in eine Liste eingetragen um einer Hostanwendung, welche<br />

die jeweilige Bibliothek aufruft, mitteilen zu können, welche Plugins in der Bibliothek enthalten<br />

sind (siehe Kapitel 3.4.1). Hierzu werden nicht die Plugins selbst gelistet, weil die „Fabriken„ sowohl


3. DIE PLUGIN API FÜR C++ 25<br />

weniger Speicherplatz benötigen, als auch meist schneller zu erzeugen sind als die zugehörigen Plugins.<br />

Trotzdem besitzen sie die zum Laden von Plugins nötigen Informationen über unterstütze Schnittstellen<br />

sowie den zur Identifikation der Plugins nützlichen Pluginnamen.<br />

Die Plugin API verlangt von „Pluginfabriken“, dass sie die gleichen Schnittstellen besitzen wie das von<br />

ihnen erzeugte Plugin selbst. Dies kann dadurch erreicht werden, dass das Plugin allein von der „Fabrik“<br />

erbt. Es müsste dann auch die beiden Funktionen der Standardschnittstelle iplugin_base nicht erneut implementieren,<br />

da dies bereits in der Basisklasse, der „Pluginfabrik“, geschehen ist. Der Pluginentwickler<br />

ist aber nicht dazu gezwungen so vorzugehen. Er könnte einem Plugin auch weitere Basisklassen zuweisen.<br />

Es ist aber zu beachten, dass eine Hostanwendung immer nur die Schnittstellen der „Pluginfabrik“<br />

abfragen kann, nicht die des Plugins.


4. DIE PLUGIN API FÜR .NET 26<br />

4 Die Plugin API für .NET<br />

Will man Pluginsysteme in .NET verwenden, kann die C++ API nicht genutzt werden. Aus diesem Grund<br />

wurden die Klassen der C++ API für eine .NET Version umgeschrieben. Dafür wurde die Programmiersprache<br />

C# verwendet. Die entstandene API kann aber auch von allen anderen „managed“ Sprachen<br />

benutzt werden. Für eine Zusammenarbeit von C++ und .NET Komponenten, muss der Entwickler eine<br />

„gemischte“ Anwendung schreiben, d. h. eine Anwendung mit einen „native“ Teil und einem „managed“<br />

Teil und auch allein die Kommunikation der beiden Teile bewältigen (siehe auch Kapitel 2.2.3).<br />

Die Klassennamen und Funktionen wurden weitgehend übernommen und auch der sonstige Aufbau des<br />

.NET Teils der Plugin API gleicht dem der C++ Plugin API sehr. Sie enthält ebenfalls eine Reihe von<br />

Schnittstellen, die von Plugins implementiert werden können. Daneben enthält sie die Verwaltungsklasse<br />

plugin_loader die wie in C++ für das Laden und Löschen der Plugins, sowie für den Zugriff auf die geladenen<br />

Plugins zuständig ist. Weil es in .NET möglich ist, aus einem Assembly alle vorhandenen Typen<br />

auszulesen, sind für das Erstellen von Pluginbibliotheken (Assemblies) keine „Pluginfabriken“ notwendig.<br />

Dadurch gestaltet sich der Aufbau der Bibliotheken etwas einfacher als in C++. Die Plugins können<br />

ohne zusätzlichen Aufwand direkt in ein Assembly geschrieben werden. Diese Art von Assembly wird<br />

in C# Klassenbibliothek genannt.<br />

4.1 Typenreflektion im .NET Framework<br />

Im .NET Framework wird die Typenreflektion durch das Assembly System.Reflection bewerkstelligt.<br />

Dieses besteht, wie Abbildung 4.1 zeigt, aus verschiedenen APIs. Für die Typenreflektion sind primär<br />

die Info APIs von Bedeutung. Die Info APIs erlauben den Zugriff auf das Typensystem von .NET. Mit<br />

ihrer Hilfe können dynamisch Objekte erstellt, Methoden aufgerufen und Objekteigenschaften verändert<br />

werden, ohne das der Typ beim Kompilieren bekannt ist. Außerdem können jegliche Informationen<br />

über ein Assembly (die sogenannten Metadaten) ausgelesen werden. Diese Metadaten stehen neben dem<br />

Zwischencode in jedem Assembly und enthalten Tabellen zu verwendeten Klassen, Felder usw. Enthält<br />

das Assembly z. B. fünf Klassen, so hat die Klassentabelle in den Metadaten fünf Zeilen. Die Tabellen<br />

können auf weitere Tabellen verweisen und so beispielsweise alle Methoden einer Klasse zeigen.


4. DIE PLUGIN API FÜR .NET 27<br />

Die Info APIs stellen verschiedene Klassen für den Zugriff auf die Metadaten bereit. Die Klassen Assembly<br />

und Module bieten Zugriff auf wiederverwendbare Codesammlungen und ausführbare Assemblies.<br />

Type gibt Informationen über einzelne Typen der CLR preis. Zusätzlich sind die Klassen ConstructorInfo,<br />

MethodInfo, FieldInfo, PropertyInfo, EventInfo und ParameterInfo vorhanden, welche weitere Informationen<br />

über das Typensystem liefern. In den meisten Fällen werden die Info APIs zum Auslesen der<br />

Metadaten von Assemblies und Typen benötigt.<br />

Für Informationen über ein Assembly wird die Assembly Klasse genutzt. Eine Instanz dieser Klasse kann<br />

sowohl aus Informationen bereits geladener Assemblies erstellt werden, als auch beliebige Assemblies<br />

von einem Datenträger oder aus einem Datenstrom laden und deren Metadaten zurückliefern. Für die Plugin<br />

API ist vor allem das Laden von Assemblies von einem Datenträger interessant. Dafür können die<br />

statische Memberfunktionen Load, LoadFrom und LoadFile der Assembly Klasse genutzt werden. Load<br />

benötigt einen aussagekräftigen Namen des zu ladenden Assembly, d. h. das Assembly muss der Anwendung<br />

bekannt sein. Um Assemblies von der Festplatte oder sogar aus einem Netzwerk zu Laden, muss<br />

auf LoadFrom oder LoadFile zurückgegriffen werden. Diese beiden Funktionen unterscheiden sich nur<br />

wenig. Beiden wird ein string übergeben, der den Namen des Assembly und optional den Pfad enthält.<br />

Das so spezifizierte Assembly wird dann geladen und das Assembly Objekt mit den Metadaten gefüllt.<br />

Der Hauptunterschied ist, dass LoadFrom verschiedene Verzeichnisse nach bereits geladenen Assemblies<br />

durchsucht, welche mit dem zu ladenden Assembly übereinstimmen. Wird ein Assembly gefunden,<br />

nutzt LoadFrom dieses, anstatt erneut ein Assembly zu laden. Trotzdem kann es bei beiden Funktionen<br />

vorkommen, dass das gleiche Assembly mehrfach geladen wird, auch wenn dies mit LoadFrom seltener<br />

passiert. Aus diesem Grund wird in der Plugin API auch LoadFrom genutzt. Für die Verwendung von<br />

Assemblies in der Plugin API ist es wichtig zu wissen welche Klassen (Plugins) enthalten sind. Mit der<br />

Methode GetTypes kann dies erreicht werden. Sie liefert ein Array von Type Objekten zurück, welche den<br />

im Assembly enthaltenen Typen entsprechen. Jedes dieser Type Objekte kann auf das Vorhandensein von<br />

Schnittstellen getestet werden. Dies wird auch beim Laden von Plugins durch die Plugin API genutzt.<br />

Hier wird für jedes Type Objekt im Array mit der Funktion GetInterfaces eine Liste der implementierten<br />

Schnittstellen angefordert und mit der Liste der erwünschten Schnittstellen verglichen. Um von bestimmten<br />

Typen des Assembly Instanzen zu bilden, kann die Funktion CreateInstance des Assembly Objekts<br />

genommen werden. Sie erfordert in der einfachsten Überladung nur einen ausagekräftigen Namen des<br />

Typs. Üblicherweise wird die Eigenschaft FullName des Type Objekts zur Namensübergabe verwendet.<br />

Auf diese Art können Plugins in Assemblies verpackt und von der Plugin API geladen werden.


4. DIE PLUGIN API FÜR .NET 28<br />

Abbildung 4.1: Überblick der System.Reflection APIs ([7])<br />

4.2 Schnittstellen in PluginDefs<br />

Das Assembly PluginDefs enthält alle Schnittstellendefinitionen der Plugin API für .NET. Sie dienen,<br />

genau wie in der C++ API, der Kommunikation zwischen Hostanwendung und Plugin. Im Unterschied zu<br />

C++, unterstützt C# das Konzept von Schnittstellen. In C# ist dafür das Schlüsselwort interface vorgesehen.<br />

Schnittstellen werden im Grunde genau wie abstrakte Klassen in C++ deklariert. Zusätzlich dürfen<br />

sie aber auch keine Membervariablen enthalten. Eine C# Schnittstelle besteht also allein aus Deklarationen<br />

von Methoden, Eigenschaften, „Indexers“, die es erlauben auf Elemente der implementierenden<br />

Klasse mittels Index zuzugreifen (z. B. klassen_element[1];) und Ereignissen. Da es in .NET möglich<br />

ist Typen in Listen zusammenzufassen und als Parameter zu übergeben, müssen die Schnittstellen auch<br />

nicht mehr instanzierbar sein. Darüberhinaus erben, aufgrund des wohldefinierten Typensystems von


4. DIE PLUGIN API FÜR .NET 29<br />

.NET, alle Schnittstellen von System.Object und nicht mehr von plugin_interface_class, wodurch auch<br />

die für .NET überflüssige Methode is_implemented_by weg fällt. Quelltext A.9 zeigt beispielhaft an<br />

iplugin_base, wie in C# Schnittstellen deklariert werden. Diesen von der Programmiersprache bedingten<br />

Unterschieden folgend wurden die Schnittstellen aus dem C++ Teil der API angepasst, so dass alle<br />

Schnittstellen der C++ API, auch im .NET Teil zu finden sind. Es soll im Folgenden nur auf einige der<br />

Schnittstellen eingegangen werden, weil sich die meisten nur wenig von ihrer C++ Variante unterscheiden.<br />

4.2.1 Die Standardschnittstelle iplugin_base<br />

Die Schnittstelle iplugin_base spielt in der Plugin API für C++ eine besondere Rolle. Sie wird von<br />

allen Plugins genutzt und dient so als Basis für jeglichen Umgang mit einem Plugin. In .NET ist dies<br />

nicht mehr der Fall. Durch das wohldefinierten Typensystem von .NET bedingt, erben alle Plugins von<br />

System.Object. Für eine Liste von Plugins, ist iplugin_base deshalb nicht mehr notwendig. Außerdem<br />

verliert die Methode create_new_instance in .NET an Bedeutung, da hier die Plugins direkt aus den<br />

Pluginklassen erstellt werden. Somit enthält iplugin_base nur noch eine Methode um den Namen des<br />

Plugins zu ermitteln. Da die Schnittstelle in .NET sehr verkleinert wurde und an Wichtigkeit verloren<br />

hat, muss sie auch nicht mehr von einem Plugin implementiert werden. Sie wurde aber trotzdem in die<br />

Plugin API für .NET übernommen, weil es in vielen Fällen nützlich ist, einem Plugin einen Namen<br />

zuweisen zu können, um es dadurch leichter zu identifizieren.<br />

4.2.2 Iplugin_event_callback<br />

Die Bezeichnung dieser Schnittstelle ist eigentlich nicht ganz korrekt. In .NET werden Rückruffunktionen<br />

als „delegates“ bezeichnet. Da sie aber in ihrer Wirkungsweise den aus C++ bekannten „callback“<br />

Funktionen gleichen und damit die .NET und die C++ Version der Plugin API möglichst einheitlich gestaltet<br />

sind, wurde der Name der C++ Schnittstelle übernommen. Da die Schnittstellen der .NET Plugin<br />

API keine Implementationen besitzen, enthält iplugin_event_callback lediglich eine Deklaration der Eigenschaft<br />

set_event_callback. Diese Eigenschaft kann nur beschrieben aber nicht gelesen werden, d. h.<br />

sie bestitzt nur eine set Methode. Sie ist vom Typ plugin_event_delegate und soll in ihrer Implementation<br />

im Plugin, ein privaten „delegate“ (Funktionszeiger) auf eine von der Hostanwendung bestimmte Funktion<br />

setzten. Das Plugin kann dann den Verweis auf die Funktion nutzen, um diese auszuführen. Eine<br />

so zugeordnete Funktion muss einen Parameter vom Aufzählungstyp enum_plugin_event haben. Dieser<br />

Typ beschreibt die Art des Ereignisses, welches das Plugin melden möchte.


4. DIE PLUGIN API FÜR .NET 30<br />

4.3 PluginLoader.NET<br />

Wie in C++, steht dem Entwickler einer Hostanwendung auch unter .NET die Verwaltungsklasse plugin_loader<br />

zu Verfügung. Sie ist im Assembly „PluginLoader“ definiert und enthält die gleichen Funktionen<br />

wie die Version der C++ Plugin API. Neue Plugin Assemblies können mit der überladenen Methode<br />

load geladen werden, wobei diese den Dateinamen des Assembly und optional eine Liste von Schnittstellen<br />

benötigt. Wird eine Liste übergeben, werden nur solche Plugins geladen, die alle Schnittstellen<br />

der Liste implementieren. Ist die Liste leer oder wird sie nicht übergeben, lädt die Verwaltungsklasse alle<br />

in der Bibliothek enthaltenen Plugins. Geladen wird ein Plugin durch die vom .NET Framework bereitgestellten<br />

Klasse System.Reflection.Assembly. Die Verwaltungsklasse plugin_loader nutzt eine Instanz<br />

dieser Klasse, um mit Hilfe deren Memberfunktion LoadFrom das Assembly zu laden, welches durch<br />

den der Methode übergebenen Dateinamen spezifiziert wird. Eine weitere für das Laden von Plugins<br />

wichtige Funktion ist GetTypes. Sie ist ebenfalls Bestandteil der Assemblyklasse und liefert alle in einem<br />

Assembly enthaltenen Typen zurück. Ein plugin_loader Objekt nutzt die so erhaltenen Typen, um<br />

mit Hilfe Funktion CreateInstance des Assemblyobjektes, Instanzen von ihnen zu erstellen. Abbildung<br />

A.10 zeigt die load Funktion der plugin_loader Klasse.<br />

Der Zugriff auf die Plugins erfolgt analog zu C++, mit den Methoden get_plugin und get_interface.<br />

Auch in .NET ist es möglich, einmal geladene Plugins, vor dem Programmende durch die Methode delete_plugin<br />

wieder zu entfernen. Im Unterschied zu C++, wird hier aber das Assembly nicht entladen,<br />

wenn kein in ihr enthaltenes Plugin mehr genutzt wird. Ein einmal dynamisch hinzugefügtes Assembly<br />

bleibt also bis zum Programmende der Hostanwendung geladen. Die Klasse plugin_loader nutzt eine<br />

Liste von Objekten zum speichern von Plugininstanzen. Diese ist wie in C++ vom Typ plugin_list, führt<br />

aber bisher keine eigenen Methoden ein.


5. BEISPIELANWENDUNGEN 31<br />

5 Beispielanwendungen<br />

Neben der eigentlichen Plugin API, wurden im Rahmen dieses <strong>Beleg</strong>s einige Beispielanwendungen erstellt.<br />

Diese sollen Einblicke in die Nutzung der API bringen und werden im Folgenden etwas genauer<br />

erklärt.<br />

5.1 DirectX Beispiel für C++<br />

Dieses Beispiel besteht aus einer Hostanwendung (SampleHost.exe) und zwei DLLs ( MeshDll.dll und<br />

InteractiveRoom.dll). Es nutzt die in der Direct X SDK (Software Development Kit) enthaltenen DXUT<br />

Hilfsklassen [11].<br />

Abbildung 5.1: Screenshot aus der DirectX Beispielanwendung


5. BEISPIELANWENDUNGEN 32<br />

5.1.1 Die Hostanwendung<br />

Die Hostanwendung wird in der für DXUT typischen Weise initialisiert. Als Erstes werden den Ereignissen<br />

DeviceCreated, DeviceReset, DeviceLost, DeviceDestroyed, FrameRender und FrameMove<br />

Funktionen zur Behandlung des Ereignisses zugewiesen. Außerdem wird die Funktion zur Nachrichtenbehandlung<br />

der Anwendung festgelegt. Das Hauptfenster, die notwendigen DirectX Objekte werden<br />

durch DXUTInit, DXUTCreateWindow und DXUTCreateDevice erstellt. Danach wird die Hauptschleife<br />

in der Funktion DXUTMainLoop betreten. Neben dem plugin_loader Objekt werden eine Kamera (CModelViewerCamera)<br />

und ein Objekt für die grafische Benutzeroberfläche (SampleHostGUIClass) global<br />

definiert. Im Konstruktor der SampleHostGUIClass wird die Benutzeroberfläche erstellt. Zur Interaktion<br />

hält sie Knöpfe zum Laden und Entladen der Plugins bereit. In einer Liste werden alle geladenen Plugins<br />

angezeigt. Dafür wird der in der Schnittstelle iplugin_base definierte Name des Plugins genutzt. Um von<br />

der Hostanwendung geladen werden zu können, müssen die Plugins die Schnittstellen irenderable und<br />

id3d9_plugin implementieren. Ist ein Plugin in der Liste ausgewählt, dann wird dessen Memberfunktion<br />

render von der Hostanwendung aufgerufen. Im Falle eines Plugins dass ikeyboard_event_handler<br />

implementiert, werden zusätzlich die Tastendrücke der „Hoch“-Taste und „Runter“-Taste an das Plugin<br />

übergeben. Dies wird vom „interaktives Auto“ Plugin in der Bibliothek InteractiveRoom.dll genutzt. Die<br />

Hostanwendung verwendet ein CModelViewerCamera Objekt, um eine vom Benutzer gesteuerte Kamera<br />

zu erzeugen. Der Nutzer der Anwendung kann so mit Hilfe der Maus die Position der Kamera verändern.<br />

Mit gedrückter Maustaste wird bei jeder Mausbewegung die Kamera Rotiert. Über das Mausrad kann ein<br />

Zoom ausgelöst werden.<br />

5.1.2 Die Dll MeshDll<br />

Die Pluginbibliothek MeshDll enthält drei Plugins. Alle drei Plugins implementieren die Schnittstellen<br />

iplugin_base, irenderable und id3d9_plugin. Für jedes ist eine Fabrikklasse vohanden, von der das Plugin<br />

erbt. Da die Schnittstelle iplugin_base abstrakt ist, wird sie bereits in der Fabrik implementiert. Die Plugins<br />

nutzen den von der Hostanwendung erzeugten LPDIRECT3DDEVICE9 Zeiger um in der Funktion<br />

render Objekte grafisch auszugeben. Das Plugin TubeMeshPlugin zeichnet mit Hilfe eines Vertexbuffers<br />

eine Röhre. Das Plugin TeapotMeshPlugin lädt ein Teekannen-Objekt aus einer Datei und stellt es rotierend<br />

da. Beim Plugin TigerMeshPlugin wird ebenfalls ein Objekt aus einer Datei geladen und rotierend<br />

dargestellt. Der so gerenderte Tiger wird aber zusätzlich noch mit einer Textur versehen.<br />

Die DLL enthält weiterhin das zum Laden der Plugins notwendige plugin_factory_list_class Objekt.<br />

Diesem werden in DllMain Mittels register_factory die Plugins bekannt gemacht.


5. BEISPIELANWENDUNGEN 33<br />

5.1.3 Die DLL InteractiveRoom<br />

In der DLL InteractiveRoom ist ein Plugin enthalten. Dieses soll die Funktion der Schnittstelle ikeyboard_event_handler<br />

verdeutlichen. Es zeichnet ein Auto auf das gegebene IDIRECT3DDEVICE9 Objekt,<br />

welches sich je nach Tastendruck vorwärts oder rückwärts im Kreis bewegt. Genau wie bei der<br />

Pluginbibliothek MeshDll, ist auch hier für jedes Plugin eine Fabrik vorhanden, mit welcher das Plugin<br />

erzeugt werden kann. Das Plugin IACarPlugin nutzt sowohl ikeyboard_event_handler, als auch die für<br />

die Hostanwendung nötigen irenderable und id3d9_plugin . Die Schnittstelle iplugin_base wird, wie bei<br />

jedem Plugin, natürlich auch implementiert.<br />

5.2 OpenGL Beispiel für C++<br />

Dieses Beispiel besteht aus einer Hostanwendung (OGLHost.exe) und einer DLL ( OGLDll.dll). Zum<br />

erstellen wurde die Grafikbibliothek OpenGL und die Zusatzbibliothek GLUT verwendet.<br />

Abbildung 5.2: Screenshot aus der OpenGL Beispielanwendung


5. BEISPIELANWENDUNGEN 34<br />

5.2.1 OpenGLHost<br />

Die Hostanwendung OpenGLHost nutzt die Zusatzbibliothek GLUT für das Erstellen des Hauptfensters<br />

und zum behandeln einiger Ereignisse. Dafür werden mit glutInit und glutInitDisplayMode die<br />

für OpenGL notwendigen Objekte erzeugt und Einstellungen gesetzt. Die Funktionen glutInitWindow-<br />

Position und glutInitWindowSize setzten die Eigenschaften des Hauptfensters, welches dann mit glut-<br />

CreateWindow erzeugt wird. Damit Ereignisse, die von einem mit GLUT erstellten Fenster aufgefangen<br />

werden, die Hostanwendung erreichen, muss sie Funktionen festlegen, die sich um die Behandlung der<br />

Ereignisse kümmern. Diese Funktionen werden bei GLUT mittels je nach Art des Ereignisses verschiedenen<br />

Methoden registriert. Soll die Hostanwendung z. B. auf eine Größenänderung des Hauptfensters<br />

reagieren, wird ein dafür bestimmte Funktion an glutReshapeFunc übergeben. Sie muss als Parameter<br />

zwei int Werte besitzen, die von GLUT beim Eintreten des Ereignisses und vor dem Aufruf der gewählten<br />

Funktion auf die neue Höhe und Breite des Fenster gesetzt werden. Die Beispielanwendung benutzt<br />

solche „Ereignishandler“, d. h. Funktionen die in GLUT registriert und dann beim Auftreten des Ereignisses<br />

aufgerufen werden, für Tastatureingaben, Veränderungen der Fenstergröße und Sichtbarkeit und<br />

für die Darstellung des Fensters, wenn es z. B. zuvor verdeckt war. Daneben wird noch eine Funktion<br />

definiert, welche immer dann aufgerufen werden soll, wenn die Anwendung nicht „beschäftigt“ ist, sie<br />

also keine Nachrichten bearbeiten muss. Diese Funktion wird durch glutIdleFunc festgelegt und kann<br />

u. a. für die Animation von Fensterinhalten genutzt werden, da sie von GLUT so häufig wie möglich<br />

aufgerufen wird und so flüssiges Darstellen von Bildabfolgen ermöglicht. Nachdem alle Einstellungen<br />

gesetzt wurden, wird die Hauptschleife des Programms durch glutMainLoop betreten.<br />

Im Gegensatz zum DirectX Beispiel wird in OpenGLHost die Pluginbibliothek automatisch geladen.<br />

Dies geschieht aber dennoch dynamisch durch ein globales plugin_loader Objekt. Der Nutzer kann in<br />

der laufenden Anwendung zwischen Vollbild und Fenstermodus wechseln. Durch „+“ und „-“ kann er<br />

aus den geladenen Plugins das Nächste bzw. Vorherige auswählen, und darstellen lassen. Neben diesen<br />

Tastaturereignissen gibt die Hostanwendung auch einen Tastendruck auf die Cursortasten an das gerade<br />

ausgewählte Plugin weiter.<br />

5.2.2 Die Pluginbibliothek OGLDLL<br />

Die Pluginbibliothek für OpenGL enthält zwei Plugins. Beide sind aus Beispielanwendungen gewonnen<br />

worden, welche der Demonstration der Möglichkeiten von OpenGL dienen und von der Website<br />

„Codesampler.com“ [6] stammen. Von den eigenständigen Programme mit den Titeln „Dot3 Per-Pixel<br />

Bump Mapping“ und „Point Sprites“ wurden dafür die Funktionen zur Erstellung des Hauptfensters<br />

und zur Behandlung von Nachrichten entfernt. Die Variablen und Funktionsnamen wurden angepasst,


5. BEISPIELANWENDUNGEN 35<br />

und es wurden die von den Pluginschnittstellen vorgegebenen Funktionen hinzugefügt. Die unterstützten<br />

Schnittstellen sind irenderable und ikeyboard_event_handler. Eine für die genutzte Grafikbibliothek<br />

spezifische Schnittstelle ist, im Gegensatz zu DirectX, bei OpenGL nicht notwendig, da hier für die<br />

Ausgabe von grafischen Inhalten nur Funktionen verwendet werden. Es muss so kein globales OpenGL<br />

Objekt an die Plugins übergeben werden. Es ist dabei aber zu beachten, dass die Initialisierung der für die<br />

Darstellung der Szene notwendigen Einstellungen von OpenGL (z. B. Licht) bereits im Konstruktor des<br />

jeweiligen Plugins erfolgt. Die Hostanwendung muss dementsprechend schon beim Laden des Plugins<br />

für OpenGL eingerichtet sein.<br />

Bei beiden Plugins ist es möglich, die Szene zu rotieren. Dies geschieht im Plugin und nicht wie beim<br />

Beispiel für DirectX durch eine Kamera in der Hostanwendung. Damit der Benutzer die Rotation steuern<br />

kann, werden den Plugins über die Schnittstelle ikeyboard_event_handler jeder Tastendruck auf eine der<br />

Cursortasten mitgeteilt. Das Plugin ändert dann je nach Tastendruck die Darstellung der Szene. Durch<br />

irenderable wird die Funktion render eingeführt, welche die Szene des jeweiligen Plugins auf das Fenster<br />

der Hostanwendung zeichnet.<br />

5.3 Beispiel für .NET<br />

Da die Plugin API neben C++ auch für .NET Anwendungen gedacht ist, soll dieses Beispiel den Umgang<br />

mit der API in .NET zeigen. Es beinhaltet eine Hostanwendung (HostAnwendung.exe) und eine DLL (<br />

PluginDLL.dll). Das Beispiel wurde in C# erstellt und ist nur für die Nutzung unter Windows gedacht.<br />

5.3.1 Hostanwendung<br />

Die zu diesem Beispiel gehörende .NET Hostanwendung ist eine einfache C# „Windows-Anwendung“.<br />

Das von Visual Studio automatisch generierte Formular wurden nach den Bedürfnissen angepasst und ein<br />

privater Member vom Typ plugin_loader hinzugefügt. Im Konstruktor des Formulars wird das Assembly<br />

„PluginDLL.dll“ dynamisch geladen, und diejenigen Plugins erstellt, welche sowohl irenderable als<br />

auch die Schnittstellen iplugin_event_callback und imouse_event_handler implementieren. Nach dem<br />

Laden werden die Funktionen zum Weiterleiten von Mausklicken (einfacher und doppelter Mausklick)<br />

und Mausbewegungen an ein Panel gebunden, so dass es alle diese Mausereignisse an die entsprechende<br />

Methode der Plugins weiterleitet. Den Plugins werden wiederum eine eigene Funktion über die Eigenschaft<br />

set_event_callback zugeteilt, welche vom Plugin aufgerufen werden soll, wenn ein Pluginereignis<br />

auftritt.<br />

Der Nutzer der Hostanwendung kann mit der Maus auf jeden Punkt des Panel klicken und muss versu-


5. BEISPIELANWENDUNGEN 36<br />

chen einen vom Plugin festgelegten Bereich zu treffen. Gelingt ihm dies, wird vom Plugin ein Erfolg<br />

gemeldet und die Hostanwendung gibt die Erfolgsmeldung über eine „Messagebox“ an den Nutzer weiter.<br />

Mit dem Knopf „Show Results“ werden alle geladenen Plugins aufgefordert zu Zeichen, d. h. es wird<br />

die Methode render der Schnittstelle irenderable aufgerufen.<br />

5.3.2 Das Assembly PluginDLL<br />

Die Pluginbibliothek PluginDLL ist eine C# Klassenbibliothek. Sie enthält ein Plugin mit dem Namen<br />

mouse_click_tracker_plugin, welches neben den unterstützen Pluginschnittstellen auch von der Klasse<br />

Form erbt. Das Plugin ist also ein Formular, welches außer einem Panel keine weiteren Steuerelemente<br />

enthält. Das Plugin implementiert die Schnittstellen iplugin_base, irenderable, imouse_event_handler<br />

und iplugin_event_callback. Beim Erstellen legt es einen rechteckigen Bereich fest, und prüft bei jedem<br />

Mausklick und jeder Mausbewegung mit gedrückter Maustaste, welche über die Schnittstelle imouse_event_handler<br />

von der Hostanwendung an das Plugin weitergeleitet und vom Plugin in einer Liste<br />

gespeichert wurden, ob die Koordinaten des Mauszeigers in diesem Bereich liegen. Ist dies der Fall, sendet<br />

es der Hostanwendung einen Erfolg (PE_SUCCESS) über die privaten Methode priv_event. Dieser<br />

Methode wird durch die Eigenschaft set_event_callback eine Funktion der Hostanwendung zugeordnet.<br />

Die Funktion render des Plugins, zeigt das Formular dem Benutzer an. Bei der Darstellung des Formulars<br />

werden die bei Mausereignissen gespeicherten Koordinaten durch einen Punkt repräsentiert. Auch<br />

der festgelegte Zielbereich wird mit dargestellt.<br />

Das Plugin zeigt neben der Verwendung der Plugin API unter .NET auch wie eine einfache Kommunikation<br />

zwischen Hostanwendung und Plugin ablaufen kann.


6. ZUSAMMENFASSUNG 37<br />

6 Zusammenfassung<br />

Das Ziel dieses <strong>Beleg</strong>s war die Entwicklung einer plattformunabhängigen Plugin API. Sie sollte das<br />

Schreiben und Laden von Plugins sowohl unter Windows als auch unter Linux vereinfachen. Dies wird<br />

durch die hier vorgestellten Klassen ermöglicht. Es wurde darauf geachtet, diese API sehr flexibel und<br />

erweiterbar zu gestalten. In der API sind die wichtigsten Schnittstellendefinitionen enthalten, es können<br />

aber jederzeit Schnittstellen beliebiger Art hinzugefügt werden. Da die Plugins in dynamische Bibliotheken<br />

geschrieben werden, ist eine Ausführung im Prozess der Hostanwendung gewährleistet und es entfällt<br />

jegliche Interprozesskommunikation, also eine Kommunikation zwischen zwei unterschiedlichen<br />

Prozessen. Um Plugins schnell und einfach in einer Anwendung nutzen zu können muss lediglich die<br />

plugin_loader Klasse eingebunden werden. Dadurch wird das dynamische Laden von Plugins ermöglicht.<br />

Dabei gibt es seitens der API so gut wie keine Einschränkung bezüglich der Art der Plugins. Es<br />

sind Plugins die vorhandene Objekte (Meshs) ändern genauso denkbar, wie komplette Applikationen,<br />

die lediglich die Interaktion mit dem Benutzer der Hostanwendung überlassen.


Literaturverzeichnis 38<br />

Literaturverzeichnis<br />

[1] Adobe Photoshop.<br />

http://www.adobe.com/de/products/photoshop/<br />

[2] ALLEN JONES, Rakesh R.: Die C++ Programmiersprache. Apress, 2006<br />

[3] AUPPERLE, Martin: Die Kunst der Programmierung mit C++. Bd. 2. Vieweg, 2002<br />

[4] AUTODESK. 3d Studio MAX.<br />

http://www.autodesk.de/3dsmax<br />

[5] Centaurix Interactive Designs.<br />

http://www.centaurix.com/pssdk.html<br />

[6] CodeSampler.com.<br />

http://www.codesampler.com/oglsrc.htm<br />

[7] DUFFY, Joe: Professional .NET Framework 2.0. Wiley Publishing, Inc., 2006<br />

[8] JACK DRAFAHL, Sue D.: Plug-Ins for Adobe Photoshop: A Guide for Photographers. Amherst<br />

Media, Inc., 2004<br />

[9] JASMIN BLANCHETTE, Mark S.: C++ GUI Programming with Qt 4. Prentice Hall, 2006<br />

[10] LLOPIS, Noel: C++ for Game Programmers. Charles River Media, 2003<br />

[11] MICROSOFT. DXUT Programming Guide.<br />

http://msdn2.microsoft.com/en-us/library/bb173316.aspx<br />

[12] MICROSOFT. MSDN.<br />

http://msdn.microsoft.com<br />

[13] MICROSOFT. .NET Framework.<br />

http://www.microsoft.com/germany/msdn/netframework<br />

[14] Mono.<br />

http://www.mono-project.com<br />

[15] MOZILLA-EUROPE. Firefox.<br />

http://www.mozilla-europe.org/de/products/firefox


Literaturverzeichnis 39<br />

[16] Microsoft Component Object Model.<br />

http://www.microsoft.com/com/default.mspx<br />

[17] MULLIN, Eileen: Inside the Adobe Photoshop 6 Studio. Thomson Course Technology, 2000<br />

[18] SIVA CHALLA, Artur L.: Essential Guide to Managed Extensions for C++. apress, 2002<br />

[19] SMITH, Colin: How to Do Everything with Photoshop CS. McGraw-Hill Professional, 2003<br />

[20] STROUSTRUP, Bjarne: Die C++ Programmiersprache. Bd. 4. Addison Wesley, 2000<br />

[21] WACHTLER, Klaus. Dynamische Bibliotheken unter Windows und Linux.<br />

http://www.wachtler.de/dynamischeBibliotheken/


Abbildungsverzeichnis 40<br />

Abbildungsverzeichnis<br />

2.1 CTS Typenhierarchie ([7]) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6<br />

2.2 Statisches Binden einer statischen Bibliothek . . . . . . . . . . . . . . . . . . . . . . . 9<br />

2.3 Festes Binden einer dynamischen Bibliothek . . . . . . . . . . . . . . . . . . . . . . . . 11<br />

3.1 Speicherlayout eines Plugins . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15<br />

4.1 Überblick der System.Reflection APIs ([7]) . . . . . . . . . . . . . . . . . . . . . . . . 28<br />

5.1 Screenshot aus der DirectX Beispielanwendung . . . . . . . . . . . . . . . . . . . . . . 31<br />

5.2 Screenshot aus der OpenGL Beispielanwendung . . . . . . . . . . . . . . . . . . . . . . 33<br />

A.1 Verwendung dynamic_cast . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41<br />

A.2 Header für plugin_interface_class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41<br />

A.3 Header für iplugin_base . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42<br />

A.4 Header für IPluginEventCallback . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42<br />

A.5 Header für Verwaltungsklasse plugin_loader . . . . . . . . . . . . . . . . . . . . . . . . 43<br />

A.6 Header für PluginList . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44<br />

A.7 Beispiele für Zugriff auf Plugins . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44<br />

A.8 Header für plugin_factory_list_class . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45<br />

A.9 Beispiel einer Schnittstelle in .NET . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45<br />

A.10 load Funktion der plugin_loader Klasse . . . . . . . . . . . . . . . . . . . . . . . . . . 45


ANHANG A. QUELLTEXTE 41<br />

A Quelltexte<br />

class CBase1 { ... }; //virtuelle Klasse CBase1<br />

class CBase2 { ... }; //virtuelle Klasse CBase2<br />

class CDerived: public CBase1, public CBase2 { ... }; //virtuelle Klasse CDerived<br />

//pCBase1 zeigt auf Teilklasse CBase1 in CDerived Objekt<br />

CBase1* pCBase1 = new CDerived();<br />

CDerived* pCDerived = new CDerived();<br />

//Umwandlung mit dynamic\_cast in Basisklasse CBase1<br />

CBase1* pCBase11 = dynamic\_cast(pCDerived);<br />

//Wenn pCBase11 == NULL dann schlug die Umwandlung fehl,<br />

//sonst zeigt pCBase11 auf Teilklasse CBase1 in CDerived Objekt<br />

//Umwandlung mit dynamic\_cast in andere Teilklasse CBase2<br />

CBase2* pCBase2 = dynamic\_cast(pCBase1);<br />

//Wenn pCBase2 == NULL dann schlug die Umwandlung fehl,<br />

//sonst zeigt pCBase2 auf Teilklasse CBase2 in CDerived Objekt<br />

Abbildung A.1: Verwendung dynamic_cast<br />

class plugin_interface_class<br />

{<br />

public:<br />

virtual ~plugin_interface_class();<br />

};<br />

virtual bool is_implemented_by(piplugin_base pplugin_class ) = 0;<br />

Abbildung A.2: Header für plugin_interface_class


ANHANG A. QUELLTEXTE 42<br />

class iplugin_base: public plugin_interface_class<br />

{<br />

public:<br />

virtual ~iplugin_base();<br />

virtual std::string get_plugin_name() = 0;<br />

virtual piplugin_base create_new_instance() = 0;<br />

};<br />

virtual bool is_implemented_by(piplugin_base pplugin_class );<br />

Abbildung A.3: Header für iplugin_base<br />

class iplugin_event_callback: public plugin_interface_class<br />

{<br />

public:<br />

virtual ~iplugin_event_callback();<br />

void set_event_callback(cplugin_event_callback);<br />

virtual bool is_implemented_by(piplugin_base pplugin_class );<br />

private:<br />

cplugin_event_callback cfpdo_event;<br />

void do_event(enum_plugin_event);<br />

};<br />

Abbildung A.4: Header für IPluginEventCallback


ANHANG A. QUELLTEXTE 43<br />

class plugin_loader<br />

{<br />

public:<br />

plugin_list m_plugin_list;<br />

plugin_loader(void);<br />

~plugin_loader(void);<br />

void load(xp_dll_filename plugin_filename);<br />

void load(xp_dll_filename plugin_filename,<br />

const plugin_interface_list& a_interface_list);<br />

int get_plugin_count();<br />

piplugin_base get_plugin(unsigned int index);<br />

template interface_typ* get_interface(unsigned int index)<br />

{<br />

return dynamic_cast(get_plugin(index));<br />

}<br />

};<br />

void delete_plugin(int plugin_index)<br />

{<br />

m_plugin_list.delete_item( plugin_index);<br />

};<br />

Abbildung A.5: Header für Verwaltungsklasse plugin_loader


ANHANG A. QUELLTEXTE 44<br />

struct plugin_list_item<br />

{<br />

xp_dll_handle dll_handle;<br />

piplugin_base pplugin;<br />

};<br />

class plugin_list<br />

{<br />

private:<br />

std::vector m_plugin_list_items;<br />

public:<br />

plugin_list();<br />

~plugin_list();<br />

void add_item(plugin_list_item a_plugin_list_item);<br />

void delete_item(int index);<br />

};<br />

unsigned int plugin_count();<br />

piplugin_base get_plugin(unsigned int index);<br />

Abbildung A.6: Header für PluginList<br />

//Zugriff "uber GetPlugin<br />

piplugin_base pInterface = plugin_loader_Intstanz.get_plugin(1);<br />

irenderable* pRenderInterface1 = dynamic\_cast(pInterface);<br />

if (pRenderInterface1 != NULL)<br />

{<br />

//benutze Interface<br />

}<br />

//Zugriff "uber GetInterface<br />

irenderable* pRenderInterface2 = plugin_loader_Intstanz.get_interface(1);<br />

if (pRenderInterface2 != NULL)<br />

{<br />

//benutze Interface<br />

}<br />

Abbildung A.7: Beispiele für Zugriff auf Plugins


ANHANG A. QUELLTEXTE 45<br />

class plugin_factory_list_class<br />

{<br />

public:<br />

iplugin_pointer_list m_class_factory_list;<br />

plugin_factory_list_class() {}<br />

virtual ~plugin_factory_list_class();<br />

piplugin_base create_plugin(unsigned int iindex);<br />

void create_all_plugins(iplugin_pointer_list& a_created_plugin_list,<br />

const plugin_interface_list& needed_interfaces);<br />

};<br />

template void register_factory()<br />

{<br />

m_class_factory_list.push_back(new class_factory);<br />

}<br />

Abbildung A.8: Header für plugin_factory_list_class<br />

public interface iplugin_base<br />

{<br />

string get_plugin_name { get; }<br />

}<br />

Abbildung A.9: Beispiel einer Schnittstelle in .NET<br />

public void load(string a_filename, Type[] a_needed_interfaces_list)<br />

{<br />

Assembly a = Assembly.LoadFrom(a_filename);<br />

Type[] enum_types = a.GetTypes();<br />

foreach (Type current_type in enum_types)<br />

{<br />

Type[] implemented_interfaces = current_type.GetInterfaces();<br />

}<br />

if (m_test_for_needed_interfaces(implemented_interfaces, a_needed_interfaces_list))<br />

m_loaded_plugins.Add(a.CreateInstance(current_type.FullName));<br />

}<br />

Abbildung A.10: load Funktion der plugin_loader Klasse


ANHANG B. VERZEICHNISSE AUF DER CD ZUM BELEG 46<br />

B Verzeichnisse auf der CD zum <strong>Beleg</strong>

Hurra! Ihre Datei wurde hochgeladen und ist bereit für die Veröffentlichung.

Erfolgreich gespeichert!

Leider ist etwas schief gelaufen!