Plugin-DLL

Aus LOTUS Wiki DE
Version vom 23. Oktober 2023, 22:03 Uhr von DrBlackError (Diskussion | Beiträge) (1 Version importiert: Init)
(Unterschied) ← Nächstältere Version | Aktuelle Version (Unterschied) | Nächstjüngere Version → (Unterschied)
Wechseln zu: Navigation, Suche

Die Plugin-DLL enthält den eigentlichen ausführbaren Code des Plugins. Für die Kommunikation mit LOTUS müssen je nach Bedarf festgelegte Methoden exportiert werden. Im Folgenden soll deshalb erläutert werden, wie der Variablen-Zugriff genau von statten geht und welche Methoden zu exportieren sind. Grundsätzlich bestehen aber große Freiheiten beim Anlegen der DLL; beispielsweise ist es möglich, ein zusätzliches Fenster mit VCL-Komponenten darzustellen und zu verwenden.

Da – abgesehen vom AOwner – auf die Verwendung von Objekten/Klassen verzichtet wurde, ist es vermutlich möglich, die Plugin-DLL auch in einer anderen Programmiersprache als Object Pascal zu entwickeln. Getestet wurde von mir aber nur Object Pascal (Delphi). Dementsprechend ist das folgende Beispiel auch in dieser Programmiersprache verfasst.

Welche Methoden aber konkret auf welche Weise deklariert und exportiert werden müssen, um verschiedene Kommunikationsaufgaben zu übernehmen, wird anhand des folgenden Beispiels erläutert. ACHTUNG: Im Abschnitt exports muss dringend auf die korrekte Groß- und Kleinschreibung geachtet werden!

Beispielcode
library Test;
uses
  SysUtils,
  Dialogs,
  Classes,
  TestU in 'TestU.pas' {Form1};
{$R *.res}
procedure PluginStart(AOwner: TComponent); stdcall;
begin
  form1 := TForm1.Create( AOwner );
  form1.Show;
end;
procedure PluginFinalize; stdcall;
begin
  form1.Free;
end;
procedure ReceiveVarFloat(varindex: word; value: single); stdcall;
begin
  case varindex of
    0:
    begin
      form1.Label2.Caption := floattostrF( value, ffFixed, 5, 1 );
      form1.Gauge1.Progress := round( value );
    end;
  end;
end;
procedure ReceiveVarBool(varindex: word; value: boolean); stdcall;
begin
  case varindex of
    0:
    begin
      form1.Label2.Caption := BoolToStr(value);
    end;
  end;
end;
procedure ReceiveVarInt(varindex: word; value: integer); stdcall;
begin
  case varindex of
    0:
    begin
      form1.Label2.Caption := IntToStr(value);
      form1.Gauge1.Progress := value;
    end;
  end;
end;
procedure OnConnectingVehicle(name: shortstring); stdcall;
begin
  form1.Caption := name;
end;
procedure OnUnconnectingVehicle; stdcall;
begin
  form1.Caption := 'no bus loaded';
end;
function SetButton(eventindex: word): boolean;
begin
  case eventindex of
    0: result := Form1.button1_pressed;
  end;
end;
function SetFloat(eventindex: word): single;
begin
  case eventindex of
    0: result := (Form1.TrackBar1.Position - 15)/15;
  end;
end;
exports
  ReceiveVarInt,
  SetButton,
  SetFloat,
  PluginStart,
  OnConnectingVehicle,
  OnUnconnectingVehicle,
  PluginFinalize;
begin
end.


Typen- und Funktionsdefintionen für C, C++ und C#
typedef void* TComponent; // Nicht verwendbar
typedef byte Boolean;
typedef unsigned short Single, *PSingle;
typedef struct _ShortString {
 byte Length; // == min(sizeof(ShortString.String), 256)
 char String[]; // Nicht-nullterminiert! NUR strncpy o.Ä. anwenden, rennt sonst direkt in unbekannten Speicher!
 } ShortString, *PShortString;
typedef float Float, *PFloat;
__declspec(dllexport) void __stdcall PluginStart(TComponent Owner);
__declspec(dllexport) void __stdcall PluginFinalize(void);
__declspec(dllexport) void __stdcall ReceiveVarFloat(word varindex, single value);
__declspec(dllexport) void __stdcall ReceiveVarBool(word VarIndex, Boolean Value);
__declspec(dllexport) void __stdcall ReceiveVarInt(word VarIndex, int Value);
__declspec(dllexport) void __stdcall OnConnectingVehicle(ShortString Name);
__declspec(dllexport) void __stdcall OnUnconnectingVehicle(void);
__declspec(dllexport) Boolean __stdcall SetButton(single Eventindex);
__declspec(dllexport) PFloat __stdcall SetFloat(single EventIndex);

Vielen Dank an Feder für den Code!

Initialisierung und Finalisierung

Nach dem Laden der DLL wird PluginStart aufgerufen. In diesem Fall soll eine Form erstellt werden; für diesen Zweck bietet die Prozedur den AOwner als Parameter an.

Vor dem Entladen der DLL beim Schließen von LOTUS wird PluginFinalize" aufgerufen. In diesem Beispiel wird die Form aus dem Speicher gelöscht.

Unmittelbar nachdem ein Fahrzeug in den Fokus genommen wird – also nach dem Start der Simulation, nach dem Neuplatzieren eines Fahrzeuges oder beim Wechsel auf ein anderes Fahrzeug – und somit die DLL an das neu ausgewählte Fahrzeug andocken soll, wird die Prozedur OnConnectingVehicle ausgeführt. Diese übergibt als Parameter den Namen des neu ausgewählten Fahrzeuges. In diesem Beispiel wird der Name des Fahrzeuges als Titel auf die Form geschrieben.

Bevor ein Fahrzeug aus dem Fokus genommen werden soll (ebenfalls beim Wechsel, beim Löschen oder bei Rückkehr zum Hauptmenü) wird OnUnconnectingVehicle ausgeführt.

Lesezugriff

Der Lesezugriff erfolgt mit den Prozeduren ReceiveVarBool, ReceiveVarInt, ReceiveVarFloat und ReceiveVarString. Der erste Parameter ist jeweils varindex: word, der zweite ist value: boolean, integer, single oder PWideChar, je nach Prozedur.

Der Ablauf ist folgender: LOTUS durchläuft die jeweilige Variablenliste in der Plugin-Ini und ruft jeweils die entsprechende ReceiveVar...-Prozedur auf und übergibt ihr den Index der Variable in der *.ini-Liste und den aktuellen Wert. Im Normalfall wird dann in der Prozedur ein Case-Konstrukt verwendet, um die Werte der Variablen entsprechend unterschiedlich zu verarbeiten.

Im Beispiel wird in der Prozedur ReceiveVarFloat dafür gesorgt, dass der Wert der Variable, die in der Plugin-Ini im [ReadingVarsFloat]-Abschnitt unter var.0 eingetragen ist, in das Label2 der Form geschrieben wird und die Gauge1 entsprechend ausschlägt.

Schreibzugriff

Der Schreibzugriff erfolgt dahingehend, dass Tastatur-/Gamecontroller-Events ausgelöst werden (und damit der Druck von Tastenkombinationen oder Joystick-Knöpfen simuliert wird) und dass Float-Events ausgelöst werden, denen der neue Float-Wert beigefügt ist (was somit der Verarbeitung von Joystick-Achsen entspricht).

Anders gesagt: Die Plugin-Schnittstelle funktioniert dahingehend wie ein frei konfigurierbarer und beliebig komplexer Gamecontroller. Die Event-Namen sind dieselben wie bei der Konfiguration von Tastatur und Gamecontroller.

Für die Events gibt es die beiden Funktionen SetButton und SetFloat. Beide übergeben als Parameter den Index (aus der Plugin-Ini). Die Funktion kann dann je nach Index den gewünschten Wert zurückgeben. Für diesen Zweck wird wiederum üblicherweise ein Case-Konstrukt verwendet. In diesem Beispiel wird in SetButton das erste Tastatur-Event in der Liste in der *.ini-Datei von der Variable Form1.button1_pressed gesteuert. Außerdem wird das erste Float-Event in der Liste in der *.ini-Datei in SetFloat entsprechend der Stellung des Reglers TrackBar1 gesetzt.