Von B. Kainka, Lernpaket MSR mit dem PC
Mit Delphi kann eine Windows-Funktionsbibliothek (Dynamic Linc Library, DLL) für den Einsatz in unterschiedlichen Programmiersprachen programmiert werden. Insbesondere bei der Arbeit mit Visual Basic ist dies ein einfacher und problemloser Zugang zur seriellen Schnittstelle.
Der Quelltext einer DLL unterscheidet sich kaum von dem eines normalen Programms. Erzeugt man in Delphi ein neues Projekt und wählt dazu den Typ DLL, dann erscheint bereits der Rahmen mit dem Schlüsselwort library am Anfang. Nun können wie gewohnt mit uses die verwendeten Units angegeben werden. Dann folgen die globalen Variablen.
Es folgen die einzelnen Prozeduren und Funktionen. Für die Übergabe der Parameter aus anderen Programmiersprachen ist der Zusatz stdcall wichtig. Er bewirkt unter anderem, dass Parameter nicht in Registern, sondern auf den Stack übergeben werden. Wenn mehr als ein Parameter an eine Prozedur oder Funktion übergeben werden soll, bewirkt diese Einstellung, dass Parameter in der Reihenfolge von rechts nach links übergeben werden.
Aufmerksamkeit erfordert die Übergabe von Zeichenketten. Hier muss grundsätzlich der Typ PChar (Zeiger auf einen nullterminierten String) verwendet werden. Dies ist wichtig für OpenCOM, SendString und ReadString. Diese Anpassungen sollen hier für die Prozeduren TimeOuts und OpenCOM gezeigt werden.
procedure TIMEOUTS (TOut: Integer); stdcall;
var TimeOut:TCOMMTIMEOUTS;
begin
TimeOut.ReadIntervalTimeout:=1;
TimeOut.ReadTotalTimeoutMultiplier:=1;
TimeOut.ReadTotalTimeoutConstant:=TOut;
TimeOut.WriteTotalTimeoutMultiplier:=10;
TimeOut.WriteTotalTimeoutConstant:=TOut;
SetCommTimeouts(PortHandle,TimeOut);
end;
function OPENCOM (OpenString: pchar): Integer;
var PortStr, Parameter :String;
DCB: TDCB;
begin
...
Listing 7 Die Parameterübergabe
Alle bisherigen Prozeduren aus der Unit RSCOM.PAS können mit der Aufrufkonvention stdcall in die DLL übernommen werden. Grundsätzlich ist in einer DLL für Windows32 die Groß/Kleinschreibung wichtig. Damit es bei der Übergabe nicht zu Fehlern kommt, werden hier alle Funktionen und Prozeduren mit Großbuchstaben bezeichnet. Alle Funktionen und Prozeduren, die nach außen exportiert werden sollen, müssen am Ende des Quelltextes mit exports in einer Index-Liste angegeben werden.
exports
OPENCOM index 1,
TIMEOUTS index 2,
BUFFERSIZE index 3,
CLOSECOM index 4,
SENDBYTE index 5,
READBYTE index 6,
SENDSTRING index 7,
READSTRING index 8,
CLEARBUFFER index 9,
INBUFFER index 10,
OUTBUFFER index 11,
DTR index 12,
RTS index 13,
TXD index 14,
CTS index 15,
DSR index 16,
RI index 17,
DCD index 18,
INPUTS index 19,
TIMEINIT index 20,
TIMEREAD index 21,
DELAY index 22,
REALTIME index 23,
NORMALTIME index 24;
Listing 8 Die exportierten Funktionen
Besondere Aufmerksamkeit erfordern der Start und die Beendigung der DLL. Hier muss sichergestellt werden, dass die Schnittstellen auch dann geschlossen werden, wenn ein Programm, das die DLL aufgerufen hat, unvorschriftsmäßig beendet wird.
Delphi stellt in einer DLL die Variable DLLProc als einen Zeiger auf eine Prozedur zur Verfügung, die von einem DLL-Eintrittspunkt aufgerufen wird. Diese Variable ist bereits in der Unit SYSINIT.DCU deklariert, die automatisch in jedes DLL-Projekt eingebunden wird. Man braucht diesen Pointer nur mit der Adresse einer Prozedur zu füllen, die beim DLL-Eintritt aufgerufen werden soll, also z. B. beim ersten Aufruf der DLL aus einem VB-Programm. Eine Prozedur, die DLLProc zugewiesen wird, muss einen Parameter des Typs Integer erhalten.
procedure LibraryProc(Reason: Integer);
Beim Aufruf der Prozedur enthält der Parameter Reason einen Wert zwischen 0 und 3, der den genauen Grund für den Aufruf enthält. In der Procedur LibraryProc wird eine eventuell noch geöffnete Schnittstelle geschlossen. Ein erfolgreicher Aufruf von OPENCOM weist der globalen Variablen PortHandle ein gültiges Handle zu. Wenn nun der Entry-Point erneut aufgerufen wird, bevor die Schnittstelle geschlossen werden konnte, findet die Prozedur LibraryProc das Handle vor und schließt sie. Damit die Prozedur tatsächlich ausgeführt wird, muss im Hauptteil der DLL zwischen begin und end die Adresse der Prozedur, also @LibraryProc der Variablen DLLProc zugewiesen werden.
procedure LibExit;
begin
if PortHandle> 0 then Closecom;
ExitProc := SaveExit;
end;
procedure LibraryProc(Reason: Integer);
begin
if (Reason = DLL_PROCESS_DETACH) then
if (PortHandle> 0) then Closecom;
end;
begin
SaveExit := ExitProc;
ExitProc := @LibExit;
DLLProc := @LibraryProc;
end.
Listing 9 Start und Ende
Genauso wichtig ist das korrekte Verlassen der DLL. So wie DLLProc beim Start der DLL verwendet wird, ist ExitProc ein Zeiger auf eine Prozedur, die beim Entfernen der DLL ausgeführt werden soll. Es ist hier die Prozedur LibExit, in der ebenfalls die Schnittstelle geschlossen wird. Ein alter Wert des Zeigers ExitProg wird in SaveExit zwischengespeichert und beim Verlassen wiederhergestellt.