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');