Plugin-DLL

Aus LOTUS Wiki DE
Version vom 21. Januar 2025, 22:56 Uhr von DrBlackError (Diskussion | Beiträge) (Updated from LOTUS Lexicon on 2025-01-21)
(Unterschied) ← Nächstältere Version | Aktuelle Version (Unterschied) | Nächstjüngere Version → (Unterschied)
Wechseln zu: Navigation, Suche

Vorlage:ArticleMetadata

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!**


Code

 1. [[1]]library Test;
 2. [[2]]
 3. [[3]]uses
 4. [[4]] SysUtils,
 5. [[5]] Dialogs,
 6. [[6]] Classes,
 7. [[7]] TestU in 'TestU.pas' {Form1};
 8. [[8]]
 9. [[9]]{$R *.res}
 10. [[10]]
 11. [[11]]procedure PluginStart(AOwner: TComponent); stdcall;
 12. [[12]]begin
 13. [[13]] form1 := TForm1.Create( AOwner );
 14. [[14]] form1.Show;
 15. [[15]]end;
 16. [[16]]
 17. [[17]]procedure PluginFinalize; stdcall;
 18. [[18]]begin
 19. [[19]] form1.Free;
 20. [[20]]end;
 21. [[21]]
 22. [[22]]procedure ReceiveVarFloat(varindex: word; value: single); stdcall;
 23. [[23]]begin
 24. [[24]] case varindex of
 25. [[25]] 0:
 26. [[26]] begin
 27. [[27]] form1.Label2.Caption := floattostrF( value, ffFixed, 5, 1 );
 28. [[28]] form1.Gauge1.Progress := round( value );
 29. [[29]] end;
 30. [[30]] end;
 31. [[31]]end;
 32. [[32]]
 33. [[33]]procedure ReceiveVarBool(varindex: word; value: boolean); stdcall;
 34. [[34]]begin
 35. [[35]] case varindex of
 36. [[36]] 0:
 37. [[37]] begin
 38. [[38]] form1.Label2.Caption := BoolToStr(value);
 39. [[39]] end;
 40. [[40]] end;
 41. [[41]]end;
 42. [[42]]
 43. [[43]]procedure ReceiveVarInt(varindex: word; value: integer); stdcall;
 44. [[44]]begin
 45. [[45]] case varindex of
 46. [[46]] 0:
 47. [[47]] begin
 48. [[48]] form1.Label2.Caption := IntToStr(value);
 49. [[49]] form1.Gauge1.Progress := value;
 50. [[50]] end;
 51. [[51]] end;
 52. [[52]]end;
 53. [[53]]
 54. [[54]]procedure OnConnectingVehicle(name: shortstring); stdcall;
 55. [[55]]begin
 56. [[56]] form1.Caption := name;
 57. [[57]]end;
 58. [[58]]
 59. [[59]]procedure OnUnconnectingVehicle; stdcall;
 60. [[60]]begin
 61. [[61]] form1.Caption := 'no bus loaded';
 62. [[62]]end;
 63. [[63]]
 64. [[64]]function SetButton(eventindex: word): boolean;
 65. [[65]]begin
 66. [[66]] case eventindex of
 67. [[67]] 0: result := Form1.button1_pressed;
 68. [[68]] end;
 69. [[69]]end;
 70. [[70]]
 71. [[71]]function SetFloat(eventindex: word): single;
 72. [[72]]begin
 73. [[73]] case eventindex of
 74. [[74]] 0: result := (Form1.TrackBar1.Position - 15)/15;
 75. [[75]] end;
 76. [[76]]end;
 77. [[77]]
 78. [[78]]exports
 79. [[79]] ReceiveVarInt,
 80. [[80]] SetButton,
 81. [[81]] SetFloat,
 82. [[82]] PluginStart,
 83. [[83]] OnConnectingVehicle,
 84. [[84]] OnUnconnectingVehicle,
 85. [[85]] PluginFinalize;
 86. [[86]]begin
 87. [[87]]end.

Alles anzeigen

Typen- und Funktionsdefinitionen für C, C++ und C#

Code

 1. [[88]]typedef void* TComponent;
 2. [[89]]typedef byte Boolean;
 3. [[90]]typedef float single, Single, *PSingle;
 4. [[91]]typedef struct _ShortString {
 5. [[92]] byte Length; // == min(sizeof(ShortString.String), 256)
 6. [[93]] char String[]; // Nicht-nullterminiert! NUR strncpy o.Ä. anwenden, rennt sonst direkt in unbekannten Speicher!
 7. [[94]]} ShortString, *PShortString;
 8. [[95]]typedef float Float, *PFloat;
 9. [[96]]typedef unsigned short word;
 10. [[97]]
 11. [[98]]__declspec(dllexport) void __stdcall PluginStart(TComponent Owner);
 12. [[99]]__declspec(dllexport) void __stdcall PluginFinalize(void);
 13. [[100]]__declspec(dllexport) void __stdcall ReceiveVarFloat(word varindex, single value);
 14. [[101]]__declspec(dllexport) void __stdcall ReceiveVarBool(word VarIndex, Boolean Value);
 15. [[102]]__declspec(dllexport) void __stdcall ReceiveVarInt(word VarIndex, int Value);
 16. [[103]]__declspec(dllexport) void __stdcall OnConnectingVehicle(ShortString Name);
 17. [[104]]__declspec(dllexport) void __stdcall OnUnconnectingVehicle(void);
 18. [[105]]__declspec(dllexport) Boolean __stdcall SetButton(single Eventindex);
 19. [[106]]__declspec(dllexport) PFloat __stdcall SetFloat(single EventIndex);

Alles anzeigen

Vielen Dank an Feder für den Code!

1 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.

2 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.

3 Schreibzugriff

Der Schreibzugriff erfolgt dahingehend, dass Tastatur-/Gamecontroller-Events ausgelöst werden (und damit der Druck von [[107]] 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.