21

Is it possible to call a function whose name is stored in a string in Delphi?

Woutb21
  • 247
  • 1
  • 2
  • 7
  • 6
    If it is, it's still a bad idea (except in very rare circumstances). What do you need to do? –  Nov 15 '10 at 16:31
  • 1
    @delnan Why is this a bad idea? I'm trying, for example, to implement some dynamically created buttons as menu commands. The functions to call for each are pulled from a DB and put into an array. Then I call the procedure whose name is in an array element, when a particular button is clicked. I think this is nice. Why is it a bad idea? I'm actually trying this on Lazarus Pascal (similar to Delphi) – itsols Oct 29 '12 at 14:13
  • @itsols Apart from various technical issues (which *are* important, but take space to explain and don't *always* apply), the main objection is that it confuses variable names with application data. Ned Batchelder has written on similar confusion, in the context of a language where it's actually easy to do: [Keep data out of your variable names](http://nedbatchelder.com/blog/201112/keep_data_out_of_your_variable_names.html). Using either first-class functions plus a mapping data structure, or a `case` statement, you can achieve the same thing without confusing the two. –  Oct 29 '12 at 16:09
  • @dlnan Your statement regarding confusion makes sense to me to a certain degree. However, I believe that this is why we have variable **prefixes** in most platforms today. btn, lst, txt to mention a few. further, I believe that we **are** keeping data and variables separate by using my technique. But the moment you put a case statement in, aren't we hardcoding the values in the program? A little confused there. If we're to use your method, if I were to change a menu item, I'd have to change my code. Isn't this right? – itsols Oct 29 '12 at 17:13
  • I would say people who think this is a bad idea are those unfamiliar with more modern languages, especially scripting languages (which would be a lot of the current Delphi users). This type of thing is done all the time in scripting languages. I've never seen any problems with this pattern when it's actually needed and as someone who uses Delphi daily, these are the type of features I miss. I'd say it's a stretch to say it's a bad idea, unless misused. – dallin Oct 28 '16 at 02:31

10 Answers10

29

Please give more details on what are you trying to achieve.

As far as I know:

  • It is not possible to call a random function like that.
  • For class and object functions (MyObject.Function) this can be done with RTTI, but it's a lot of work.
  • If you just need to call one particular type of functions (say, function(integer, integer): string), it's a lot easier.

For the last one, declare a function type, then get a function pointer and cast it like this:

type
  TMyFuncType = function(a: integer; b: integer): string of object;

  TMyClass = class
  published
    function Func1(a: integer; b: integer): string;
    function Func2(a: integer; b: integer): string;
    function Func3(a: integer; b: integer): string;
  public
    function Call(MethodName: string; a, b: integer): string;
  end;

function TMyClass.Call(MethodName: string; a, b: integer): string;
var m: TMethod;
begin
  m.Code := Self.MethodAddress(MethodName); //find method code
  m.Data := pointer(Self); //store pointer to object instance
  Result := TMyFuncType(m)(a, b);
end;

{...}

//use it like this
var MyClass: TMyClass;
begin
  MyClass := TMyClass.Create;
  MyClass.Call('Func1', 3, 5);
  MyClass.Call('Func2', 6, 4);
  MyClass.Destroy;
end.
himself
  • 4,806
  • 2
  • 27
  • 43
15

You didn't specify your Delphi version, However if you have Delphi 2010(+) you can do it using the enhanced RTTI, I'm not expert on them, but I tried this sample for you:

  TProcClass = class
    public
      procedure SayHi;
      function GetSum(X,Y:Integer): Integer;
  end;

uses
  Rtti;

{ TProcClass }

procedure TProcClass.SayHi;
begin
  ShowMessage('Hi');
end;

function TProcClass.GetSum(X, Y: Integer): Integer;
begin
  ShowMessage(IntToStr(X + Y));
end;

procedure ExecMethod(MethodName:string; const Args: array of TValue);
var
 R : TRttiContext;
 T : TRttiType;
 M : TRttiMethod;
begin
  T := R.GetType(TProcClass);
  for M in t.GetMethods do
    if (m.Parent = t) and (m.Name = MethodName)then
      M.Invoke(TProcClass.Create,Args)
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  ExecMethod('SayHi',[]);
  ExecMethod('GetSum',[10,20]);
end;

The good things, if you have procedure or function with parameters it will work without more work.

Mohammed Nasman
  • 10,992
  • 7
  • 43
  • 68
12

I'm surprised no one has suggested a dispatch table. This is exactly what it's for.

program RPS;

uses
  SysUtils,
  Generics.Collections;

type
  TDispatchTable = class(TDictionary<string, TProc>);

procedure Rock;
begin
end;

procedure Paper;
begin
end;

procedure Scissors;
begin
end;

var
  DispatchTable: TDispatchTable;

begin
  DispatchTable := TDispatchTable.Create;
  try
    DispatchTable.Add('Rock', Rock);
    DispatchTable.Add('Paper', Paper);
    DispatchTable.Add('Scissors', Scissors);

    DispatchTable['Rock'].Invoke; // or DispatchTable['Rock']();
  finally
    DispatchTable.Free;
  end;
end.

The implementation I wrote uses generics so it would only work with Delphi 2009+. For older versions it would probably be easiest to implement using TStringList and the command pattern

Kenneth Cochran
  • 11,954
  • 3
  • 52
  • 117
9

With Delphi 2010 you can uses JSON and SuperObject to invoke method with parametters.

http://code.google.com/p/superobject/source/browse/#svn/trunk

If you need, there is also an xml parser to transform xml to json.

  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
    procedure TestMethod(const value: string);
  end;

var
  Form1: TForm1;

implementation
uses superobject;

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
begin
  SOInvoke(Self, 'TestMethod', SO('{value: "hello"}'));
end;

procedure TForm1.TestMethod(const value: string);
begin
  Caption := value;
end;
Henri Gourvest
  • 989
  • 8
  • 12
  • It's beside the point but still has to do with SuperObject. I know you are the right man at the right place :-). Can you pay a little attention to [this question](http://stackoverflow.com/q/7841617/744588) somewhere on SO? – menjaraz Dec 06 '11 at 12:56
8

If you are asking if there is something like the JavaScript eval() is possible in Delphi, no this is not (easily) achievable since Delphi compiles to native code.

If you need only to support some strings you can always do many if or a case... Something like:

if myString = 'myFunction' then
    myFunction();
AlexV
  • 22,658
  • 18
  • 85
  • 122
  • I've done this in VB6 and and it **is** a native-compiled language. So I believe it must be possible with Delphi as well (and in my case Lazarus Pascal) – itsols Oct 29 '12 at 13:58
7

OK, I'm very late to the party, but you can definitely call routines by name with this code (There are some limitations thought)

type
    TExec = procedure of Object;
    // rest of section...

procedure TMainForm.ExecuteMethod(MethodName : String);
var
   Exec    : TExec;
   Routine : TMethod;
begin
     Routine.Data := Pointer(Form1);
     Routine.Code := Form1.MethodAddress(MethodName);
     if Not Assigned(Routine.Code) then
        Exit;

     Exec         := TExec(Routine);
     Exec;
end;

Just in case someone needs this for Delphi 7 / 2010

TheDude
  • 3,045
  • 4
  • 46
  • 95
6

Put each function in an Action. Then you can find the Action by name and Execute it

function ExecuteActionByName(const S: String);
var
  I: Integer;
begin
  for I := 0 to MainForm.ComponentCount-1 do
    if (MainForm.Components[I] is TAction)
    and SameText(TAction(MainForm.Components[I]).Name,S) then
    begin
      TAction(MainForm.Components[I]).Execute;
      Break;
    end;
end;
Rob McDonell
  • 1,309
  • 9
  • 15
  • @RobMcDonell +1 for this idea. I have done a similar thing in the old VB6 and I'm trying to port this to Lazarus (which I'm very new to). I think your line `TAction(MainForm.Components[I]).Execute;` is the hub of the whole thing. Would you please direct me to a suitable doc on the command? Even a simple explanation would do. Thanks! – itsols Oct 29 '12 at 14:07
  • Just want to point that this is the correct answer to the OP actual problem. You can also enable/disable actions and these will be reflected in the controls that use them (so you get enabled / disabled menu items, popups, buttons, etc.) – Leonardo Herrera Mar 29 '19 at 15:47
5

You can do something like this by crafting one or more classes with published properties that use functions to implement their read and write functionality. The properties can then be discovered using RTTI reflection and referenced, causing the underlying functions to get called.

Alternatively, you can store function pointers in a table, or even the Object property of TStringList and effectively index them by string name.

Straight calling of a function by name is not possible in Delphi.

Larry Lustig
  • 49,320
  • 14
  • 110
  • 160
  • 1
    It is, for class functions. RTTI can be stored for them too, even with parameter and calling convention details. But it's PITA. – himself Nov 15 '10 at 16:44
2

The following simple solution using exports and GetProcAddress also works for old Delphi versions:

type
    TMyProc = procedure(const value: Integer);

    procedure Test(const value: Integer);

    exports Test;

implementation

procedure Test(const value: string);
begin
    ShowMessage('It works! '  + value);
end;

procedure TForm1.Button1Click(Sender: TObject);
var
    p: TMyProc;
begin
    p := GetProcAddress(HInstance, 'Test'); 
    if Assigned(p) then P('Yes');
end;
Daniel
  • 21
  • 1
  • 1
    Also exhibits a possible mistake where the compiler cannot help. Correct the forward declaration of "Test" to match the actual function, leave "TMyProc" as is. Call "p" and have an access violation. – Sertac Akyuz Apr 05 '18 at 18:37
0
function ExecuteMethod(AClass : TClass; AMethodName : String; const AArgs: Array of TValue) : TValue;
var
  RttiContext : TRttiContext;
  RttiMethod  : TRttiMethod;
  RttiType    : TRttiType;
  RttiObject  : TObject;
begin
  RttiObject := AClass.Create;
  try
    RttiContext := TRttiContext.Create;
    RttiType    := RttiContext.GetType(AClass);
    RttiMethod  := RttiType.GetMethod(AMethodName);
    Result      := RttiMethod.Invoke(RttiObject,AArgs);
  finally
    RttiObject.Free;
  end;
end;
MrClarc
  • 43
  • 1
  • 7