OMSI Plugin Framework III

Aus OMSIWiki
Wechseln zu:Navigation, Suche

Die Plugin-Schnittstelle

Allgemeines

In diesem Kapitel werde ich die Arbeitsweise der Plugin-Schnittstelle des OMSI noch etwas tiefer beleuchten, vor allem mehr im Zusammenhang mit der Programmiersprache C++.

Das allgemeine Prinzip wird ja bereits in diesem Artikel erklärt. Auch wenn das Plugin in C++ programmiert wird, ändert sich an den grundlegenden Dingen nichts. Lediglich der im Artikel beschriebene Weg, ein Formular zu öffnen, ist absolut Delphi-typisch und in C++ so nicht möglich. Der Parameter, der beim Aufruf der Funktion Start mitgegeben wird, ist nicht nur einfach ein 'Delphi'-Parameter, er ist auch noch von der verwendeten Delphi-Version abhängig. Deshalb wird dieser Parameter im OMSI Plugin Framework nicht benutzt, auch wenn darüber ein Zugriff auf das Hauptfenster-Handle in C++ möglich wäre.

Lebenszyklus eines Plugins

Es gibt zwei Möglichkeiten, DLLs in ein Programm einzubinden. Die erste Möglichkeit ist, die DLL schon während der Programmierung sozusagen fest mit dem Programm zu verbinden. Diese DLLs müssen beim Programmstart vorhanden sein, sonst schlägt das Starten des Programmes fehl. Diese Möglichkeit benutzt man im z.B. für die System-DLLs. In der Natur eines Plugins liegt es aber, dass es ganz einfach gar nicht da sein kann. Also scheidet die eben erwähnte Möglichkeit zum Einbinden einer DLL aus. Deshalb macht der OMSI von der zweiten Möglichkeit Gebrauch. Ein Programm kann über den Aufruf einer Systemfunktion LoadLibrary zu jeder beliebigen Zeit DLLs nachladen. LoadLibrary sucht aber nur (im Wesentlichen) an drei Stellen automatisch nach einer DLL: in dem Ordner, in dem sich auch das Programm selbst befindet, im Windows-Ordner und im Windows\system(32)-Ordner. Wenn sie dort nicht fündig wird, liefert die Funktion einen Fehler zurück. Also muss der OMSI wissen, wo sich die DLL befindet. Dafür wurde der Ordner <OMSI_Installationsordner>\plugins festgelegt.

Lebenszyklus eines Plugins

Plugin-DLLs werden bereits kurz nach dem Start des OMSI geladen und ihre Start-Funktion aufgerufen. Wenn nach dem Start des OMSI das erste mal der Auswahldialog für die Karte, gespeichertes Spiel usw. erscheint, ist das Plugin schon aktiviert, aber im Ruhezustand. Zu diesem Zeitpunkt existiert noch keine Karte und kein Bus, da der Spieler ja die Karte erst noch auswählen muss. Auch nachdem der Spieler eine Karte ausgwählt und der OMSI Diese geladen hat, schläft das Plugin immer noch weiter. Es erwacht erst, sobald der OMSI den Bus des Spielers geladen hat. Solange ein Spieler-Bus existiert werden nun die AccessVariable- und AccessTrigger-Funktionen durch den OMSI aufgerufen.

Kurz vor Beendigung des OMSI ruft Dieser noch die Funktion Finalize auf. Damit endet der Lebenszyklus des Plugins.

Die Funktion Start

Gegenüberstellung Funktionsdeklaration Pascal / C++

{ Pascal }
procedure Start(AOwner: TComponent); stdcall;

// C++
void __stdcall Start(void* aOwner);

Die Funktion Start wird (wie im vorherigen Abschnitt beschrieben) einmalig vor Beginn des eigentlichen Spiels durch den OMSI aufgerufen und ist daher nicht zeitkritisch. In die Funktion Start gehören alle grundlegenden Initialisierungen, die Dein Plugin benötigt. Wenn man mal davon absieht, dass sich die Ladezeit des OMSI verlängert, hast Du hier Zeit, die Dinge zu tun, die Du als Vorbereitung für die eigentliche Arbeit im Plugin benötigst.

Wie im Abschnitt Allgemeines bereits erläutert, solltest Du es vermeiden, den Parameter dieser Funktion zu verwenden. Falls Du sehr daran interessiert bist, an das Handle des Hauptfensters zu gelangen, sei hier ganz grob der Weg beschrieben, wie das möglich wäre: Du müsstest Dir zunächst die technische Dokumentation der Komponente TComponent (bzw. TForm, denn eine TForm ist dieser Parameter tatsächlich) passend zu der Delphi-Version besorgen, mit der der OMSI programmiert wurde. Anhand der Dokumentation kannst du dann die Klasse in C++ nachbauen. Wenn Du das geschafft hast, kannst Du nun den Parameter verwenden. Das Ganze ist - wie gesagt - durchaus möglich, aber mehr ein Hack als vernünftige Programmierung.

Die Funktion AccessVariable

Gegenüberstellung Funktionsdeklaration Pascal / C++

{ Pascal }
procedure AccessVariable(varindex: word; var value: single; var write: boolean); stdcall;

// C++
void __stdcall AccessVariable(unsigned short varindex, float* value, bool* write);

Solange der Spieler über einen Bus verfügt, werden in jedem Frame alle in der .opl-Datei deklarierten Variablen nacheinander genau in der Reihenfolge der Deklaration über diese Funktion an Dein Plugin zur Bearbeitung übergeben.

Der erste Parameter ist dabei der Index der Variablen in der .opl-Datei. Die Zuordnung, welche Variable was ist, musst Du in Deinem Plugin selbst wieder herstellen. Der zweite Parameter ist ein Zeiger auf einen float-Wert. Das ist der aktuelle Wert der Variablen. Der Wert wird deshalb als Zeigertyp übergeben, damit Du den Wert im Plugin auch ändern kannst. Der dritte Parameter schließlich ist ein Zeiger auf einen bool-Wert. Darüber gibst Du dem OMSI Rückmeldung, ob Du den Inhalt der Variablen verändert hast. Wenn Du die Variable nur lesend benutzt, brauchst Du nichts weiter tun. Der Wert, auf den dieser Zeiger zeigt, ist immer mit false vorbelegt.

<unbewiesene_aussage>

Beim Ändern des Wertes einer Variablen musst Du auch bedenken, dass ein Script, welches ebenfalls dieselbe Variable ändert, Deinen Wert wieder überschreibt.

</unbewiesene_aussage>

Dadurch, das diese Funktion für jede Variable in jedem Frame aufgerufen wird, ergibt sich, das diese Funktion äußerst zeitkritisch ist. Über die Ausführungsdauer der Funktion in Deinem Plugin beeinflusst Du direkt die Framerate des OMSI (siehe Regel Nummer 1).

Die Funktion AccessTrigger

Gegenüberstellung Funktionsdeklaration Pascal / C++

{ Pascal }
procedure AccessTrigger(triggerindex: word; var active: boolean); stdcall;

// C++
void __stdcall AccessTrigger(unsigned short triggerindex, bool* active);

Ähnlich wie bei der Funktion AccessVariable wird auch AccessTrigger in jedem Frame für jeden in der .opl-Datei deklarierten Trigger genau in der Reihenfolge der Deklaration aufgerufen.

Der erste Parameter ist dabei der Index des Triggers in der .opl-Datei. Die Zuordnung, welcher Trigger wer ist, musst Du in Deinem Plugin selbst wieder herstellen. Der zweite Parameter ist ein Zeiger auf einen bool-Wert. Darüber gibst Du dem OMSI Rückmeldung, ob er den Trigger auslösen soll oder nicht. Wenn Du den Trigger nicht auslösen möchtest, brauchst Du nichts weiter tun. Der Wert, auf den dieser Zeiger zeigt, ist immer mit false vorbelegt.

Genau wie AccessVariable ist auch diese Funktion zeitkritisch, da sie für jeden Trigger in jedem Frame aufgerufen.

Auch wenn Du in Deinem Plugin den Trigger nicht auslöst, kann er trotzdem von einem Script ausgelöst werden.

Die Funktion Finalize

Gegenüberstellung Funktionsdeklaration Pascal / C++

{ Pascal }
procedure Finalize; stdcall;

// C++
void __stdcall Finalize();

Die Funktion Finalize wird einmalig beim Beenden des OMSI aufgerufen und ist nicht zeitkritisch. In diese Funktion gehören alle Aufräumarbeiten, die in Deinem Plugin nötig sind.


[zum Kapitel 2] [zum Inhaltsverzeichnis] [zum Kapitel 4]