Plugin-DLL: Unterschied zwischen den Versionen
imported>DrBlackError Keine Bearbeitungszusammenfassung |
Keine Bearbeitungszusammenfassung |
||
Zeile 1: | Zeile 1: | ||
[[Category:Lexikon]] | [[Category:Lexikon]] | ||
[[Category:Unvollständige Seiten]] | [[Category:Unvollständige Seiten]] | ||
Die Plugin-DLL enthält den eigentlichen ausführbaren Code des Plugins. | 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. | 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 <code>AOwner</code> – 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 <code>exports</code> muss dringend auf die korrekte Groß- und Kleinschreibung geachtet werden! | |||
<div class="toccolours mw-collapsible mw-collapsed" overflow:auto;"> | |||
<div style="font-weight:bold;line-height:1.6;">Beispielcode</div> | |||
<div class="mw-collapsible-content"> | |||
library Test; | |||
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 | 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. | |||
</div></div> | |||
<div class="toccolours mw-collapsible mw-collapsed" overflow:auto;"> | |||
<div style="font-weight:bold;line-height:1.6;">Typen- und Funktionsdefintionen für C, C++ und C#</div> | |||
<div class="mw-collapsible-content"> | |||
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); | |||
</div></div> | |||
typedef void* TComponent; // Nicht verwendbar | |||
typedef byte Boolean; | |||
typedef unsigned short Single, *PSingle; | |||
typedef struct _ShortString { | |||
} ShortString, *PShortString; | |||
typedef float Float, *PFloat; | |||
void __stdcall PluginStart(TComponent Owner); | |||
void __stdcall PluginFinalize(void); | |||
void __stdcall ReceiveVarFloat( | |||
void __stdcall ReceiveVarBool( | |||
void __stdcall ReceiveVarInt( | |||
void __stdcall OnConnectingVehicle(ShortString | |||
void __stdcall OnUnconnectingVehicle(void); | |||
Boolean __stdcall SetButton( | |||
PFloat __stdcall SetFloat( | |||
Vielen Dank an Feder für den Code! | Vielen Dank an Feder für den Code! | ||
== Initialisierung und Finalisierung == | |||
Nach dem Laden der DLL wird | Nach dem Laden der DLL wird <code>PluginStart</code> 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 <code>PluginFinalize</code>" 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 <code>OnConnectingVehicle</code> 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 <code>OnUnconnectingVehicle</code> ausgeführt. | |||
== Lesezugriff == | |||
Der Lesezugriff erfolgt mit den Prozeduren <code>ReceiveVarBool</code>, <code>ReceiveVarInt</code>, <code>ReceiveVarFloat</code> und <code>ReceiveVarString</code>. Der erste Parameter ist jeweils <code>varindex: word</code>, der zweite ist <code>value: boolean</code>, <code>integer</code>, <code>single</code> oder <code>PWideChar</code>, je nach Prozedur. | |||
Der Ablauf ist folgender: LOTUS durchläuft die jeweilige Variablenliste in der Plugin-Ini und ruft jeweils die entsprechende <code>ReceiveVar...</code>-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 <code>Case</code>-Konstrukt verwendet, um die Werte der Variablen entsprechend unterschiedlich zu verarbeiten. | |||
Im Beispiel wird in der Prozedur | Im Beispiel wird in der Prozedur <code>ReceiveVarFloat</code> dafür gesorgt, dass der Wert der Variable, die in der Plugin-Ini im <code>[ReadingVarsFloat]</code>-Abschnitt unter <code>var.0</code> 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). | 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. | 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 <code>SetButton</code> und <code>SetFloat</code>. 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 <code>Case</code>-Konstrukt verwendet. In diesem Beispiel wird in <code>SetButton</code> das erste Tastatur-Event in der Liste in der *.ini-Datei von der Variable <code>Form1.button1_pressed</code> gesteuert. Außerdem wird das erste Float-Event in der Liste in der *.ini-Datei in <code>SetFloat</code> entsprechend der Stellung des Reglers <code>TrackBar1</code> gesetzt. | |||
Für die Events gibt es die beiden Funktionen |
Aktuelle Version vom 23. Februar 2023, 15:15 Uhr
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!
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.
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[Bearbeiten | Quelltext bearbeiten]
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[Bearbeiten | Quelltext bearbeiten]
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[Bearbeiten | Quelltext bearbeiten]
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.