Delphi: I call you by your name – dynamische Prozeduraufrufe
Bisher hab ich eigentlich nur Artikel über C# geschrieben. Und das obwohl ich arbeitstechnisch die meiste Zeit mit Delphi programmiere. Und da ich kürzlich dabei über ein Problem gestoßen bin bzw. eine sehr schöne Lösung zu einem Problem gefunden habe, denke ich, dass es mal an der Zeit ist auch darüber etwas zu schreiben.
Dynamische Prozeduraufrufe. Unter C# ein gängiges Prozedere, das auch ganz leicht mit Reflection zu lösen ist. Unter Delphi gibt es aber kein Reflection. Möglich ist so etwas trotzdem.
Wann benötigt man überhaupt die Möglichkeit, eine Methode an Hand ihres Namens aufzurufen? Ein einfaches Beispiel wäre eine Netzwerk-Verbindung über die Befehle zur Steuerung des Programms angenommen werden können. Über das Netzwerk erhält man zum Beispiel den Namen der auszuführenden Methode. Man muss dann lediglich noch die Methode implementieren, aber keine Logik mehr einbauen, wann diese Methode aufgerufen werden soll.
Erst möchte ich aber ein bisschen weiter ausholen um zu zeigen, wie ich auf die Lösung gekommen bin.
Schauen wir uns zu erst einmal einen Aufruf einer Prozedur in einer DLL an (ich gehe dabei nicht näher darauf ein, weil es hier nicht um dieses Thema geht)
type
TMyMethod = procedure(param1: integer);
...
procedure CallMethod();
var
lib: THandle;
mymethod: TMyMethod;
begin
lib := LoadLibrary('library.dll');
@mymethod := GetProcAddress(lib, 'MyMethod');
// jetzt kann man die Procedure aufrufen
mymethod(1234);
end;
So. Was war jetzt meine Idee? Mit GetProcAddress holt man sich einen Zeiger auf die Methode innerhalb der angegebenen Library. Das heißt, eine Methode ist letztendlich nichts anderes als ein Zeiger auf einen Einsprungspunkt, der ausgeführt werden soll. Damit der Compiler weiß, wie die Methode genau aussieht, muss sie vorher als Type definiert werden.
Wenn ich jetzt davon ausgehe, dass diese Konstellation – also Zeiger auf Einsprungspunkt – immer zutrifft, auch wenn nicht eine externe Library aufruft, dann muss man das doch auch irgendwie innerhalb einer Klasse machen können.
Tatsächlich hab ich auch etwas entsprechendes gefunden:
function MethodAddress(name: string): pointer;
Diese Function ist in einer Basisklasse der VCL definiert und daher in allen Objekten vorhanden. Nur was macht man jetzt mit diesem Pointer, den man da zurück bekommt? Da hilft einem die Hilfe von Delphi weiter. Denn es gibt einen Record (TMethod), den man damit füllen kann.
Der Record TMethod enthält zwei Felder
- Data -> Hier wird ein Zeiger auf das Objekt hinterlegt, in dem die gefragte Methode zu finden ist
- Code -> Das ist dann der Zeiger auf die besagte Methode
Diesen Record kann man dann in jeden beliebigen Procedure- bzw. Function-type casten und letztendlich entsprechend aufrufen.
So sieht das ganz letztendlich aus:
unit RemoteProcedures;
interface
uses
classes;
type
TProcedure = procedure(parameter: string) of object;
TRemoteProcedures = class
published
procedure MyMethod(parameter: string);
public
class procedure Execute(procedureName: string; parameter: string);
end;
var
_remoteProcedures: TRemoteProcedures;
implementation
{ TRemoteProcedures }
class procedure TRemoteProcedures.Execute(procedureName: string; parameter: string);
var
routine: TMethod;
proc: TProcedure;
begin
if not Assigned(_remoteProcedures) then
_remoteProcedures := TRemoteProcedures.Create;
routine.Data := Pointer(_remoteProcedures);
routine.Code := _remoteProcedures.MethodAddress(procedureName);
if not assigned(routine.Code) then
exit;
TProcedure(routine)(parameter);
end;
procedure TRemoteProcedures.MyMethod(parameter: string);
begin
// do something
end;
end.
Aufgerufen wird das ganze dann ganz einfach:
TRemoteProcedures.Execute('MyMethod', 'parameter');