Runtime functions/procedures

I need to call a function/procedure at runtime. What I know is the name of the function. I don't want to use a case or if condition to do that. I'm using Delphi 4.
gironaAsked:
Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

bruintjeCommented:
Hi Girona,

Could you be a bit clearer? Can't figure out what you mean exactly.
You have a function you need to call at runtime, is it under certain conditions or has it to be called by an event?

:O)


0
gironaAuthor Commented:
I have an editbox and when I write the name of a function in it I want to execute it.
0
MadshiCommented:
I guess you mean this:

var str : string = 'functionBlaBla';
begin
  call(str);
end;

Right?

That doesn't work this way, sorry...   :-(

But there are some workarounds. The best one is probably to use a dll, because then you can use GetProcAddress(dllHandle,functionStr).

Perhaps you should tell us more about for which purpose you need that. Then we'll find the best workaround for you...

Regards, Madshi.
0
Cloud Class® Course: SQL Server Core 2016

This course will introduce you to SQL Server Core 2016, as well as teach you about SSMS, data tools, installation, server configuration, using Management Studio, and writing and executing queries.

gironaAuthor Commented:
I have an editbox and when I write the name of a function in
0
gironaAuthor Commented:
I can't use dll's because do many processes involving other forms and components and it would be a mess right now to move everything into a dll.
0
MadshiCommented:
Ok, then you should do something like this:

type TGironasProc = procedure (int: integer);  // or other parameters...

var procArr : array [0..9] of record
                                name: string;
                                proc: TGironasProc;
                              end;

function CallProc(name: string; param: integer) : boolean;
begin
  result:=false;
  for i1:=0 to 9 do
    if name=procArr[i1].name then begin
      result:=true;
      procArr[i1].proc(param);
      exit;
    end;
end;

initialization
  procArr[0].name:='example1'; procArr[0].proc:=example1;
  procArr[1].name:=...
  ...
end.

Of course this method has some disadvantages:
(1) The parameters of the functions must be the same.
(2) You must fill the array.
(3) All the functions of the array are always linked in every program where you're using this unit. The linker can't remove them, because he doesn't know if he may do it...

Regards, Madshi.
0
bruintjeCommented:
Then I think I've to agree with Madshi, the way of typing in the name of the function doesn't work. Using a list(box) and a case statement are the solutions I would choose for. You have to do the conversion anyway, from the string in the box to the call of the function in the unit.

:O)
0
rickpetCommented:
Girona...

Actually you can do it using RTTI info...MethodAddress will return the address of "Published" Methods.

Create a new Component...that will hold all your "Published" procedures and functions....see following example...

Hope this works for you

Rick

unit Unit1;

interface
(*Note ListBox1 has the items filled with the following strings:
  MyProcedure1, MyProcedure2, MyProcedure3*)
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls;

type

  TMethodComponent = Class(Tcomponent)
    procedure MyProcedure1;
    procedure MyProcedure2;
    procedure MyProcedure3;
  end;

  TForm1 = class(TForm)
    ListBox1: TListBox;
    procedure FormCreate(Sender: TObject);
    procedure ListBox1Click(Sender: TObject);
  private
    { Private declarations }
    FMyMethod : TNotifyEvent;
    FMethodComponent: TMethodComponent;
  public
    { Public declarations }
    procedure MyMethod;
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

procedure TMethodComponent.MyProcedure1;
begin
  ShowMessage('My Procedure 1');
end;

procedure TMethodComponent.MyProcedure2;
begin
  ShowMessage('My Procedure 2');
end;

procedure TMethodComponent.MyProcedure3;
begin
  ShowMessage('My Procedure 3');
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  FMethodComponent := TMethodComponent.Create(Self);
end;

procedure TForm1.MyMethod;
begin
  if Assigned(FMyMethod) then
    FMyMethod(Self);
end;

procedure TForm1.ListBox1Click(Sender: TObject);
var
  Meth: TMethod;
begin
  Meth.Data := FMethodComponent;
  Meth.Code := FMethodComponent.MethodAddress(ListBox1.Items[ListBox1.ItemIndex]);
  FMyMethod := TNotifyEvent(Meth);
  MyMethod;
end;

end.



0
rickpetCommented:
Girona...

You can pass function results as follows...If you need to also have procedures...I would suggest that you convert your procedures to functions and pass Null values or empty strings..

Rick

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls;

type

  TMyFunction = function: variant of Object;

  TMethodComponent = Class(Tcomponent)
    function MyFunction1: variant;
    function MyFunction2: variant;
    function MyFunction3: variant;
  end;


  TForm1 = class(TForm)
    ListBox1: TListBox;
    Label1: TLabel;
    procedure FormCreate(Sender: TObject);
    procedure ListBox1Click(Sender: TObject);
  private
    { Private declarations }
    FMyMethod : TMyFunction;
    FMyVariant: Variant;
    FMethodComponent: TMethodComponent;
  public
    { Public declarations }
    procedure MyMethod;
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

function TMethodComponent.MyFunction1: variant;
begin
  ShowMessage('My Function 1');
  result := 100.99
end;

function TMethodComponent.MyFunction2: variant;
begin
  ShowMessage('My Function 2');
  result := 'My String';
end;

function TMethodComponent.MyFunction3: variant;
begin
  ShowMessage('My Function 3');
  result := 99
end;



procedure TForm1.FormCreate(Sender: TObject);
begin
  FMethodComponent := TMethodComponent.Create(Self);
end;

procedure TForm1.MyMethod;
begin
  if Assigned(FMyMethod) then
    FMyVariant := FMyMethod;
end;

procedure TForm1.ListBox1Click(Sender: TObject);
var
  Meth: TMethod;
begin
  Meth.Data := FMethodComponent;
  Meth.Code := FMethodComponent.MethodAddress(ListBox1.Items[ListBox1.ItemIndex]);
  FMyMethod := TMyFunction(Meth);
  MyMethod;
  Label1.Caption := FMyVariant;
end;

0
simonetCommented:
You can use GetProcAddress using hInstance as the handle of the file where the functions are, rather than opening a DLL and getting a handle to it.

Alex
0
MadshiCommented:
Hi Alex,

does that really work? I never tried it, but it sounds like a good idea...    :-)    Does that work for all functions?
However, different parameters are still a problem, but I think that doesn't matter that much.

Regards, Madshi.
0
MadshiCommented:
Hi Alex,

does that really work? I never tried it, but it sounds like a good idea...    :-)    Does that work for all functions?
However, different parameters are still a problem, but I think that doesn't matter that much.

Regards, Madshi.
0
gironaAuthor Commented:
I want an easier solution, instead of yours I could create a form or do the if solution, but I need a way to call it really easy.
Hi Alex, could you be more specific? please send an example, thanks.
0
rickpetCommented:
Simplier?

Are you trying not to program?

Rick
0
simonetCommented:
Hello, Girona...

sorry for taking too long to answer. Gotta work all day long and get ready for Carnival!

You have to export the functions. Make sure all of them use the same number of parameters and parameter types and all in the same sequence. Do it even if you have to make some "dumb" parameters (you know what I mean, right?).

I cannot send you code for that right because there's nothing but Netscape and MS Office in the machie I am an right now.

When I get home you'll do some testing and post the results here.

Yours,

Alex
0
bruintjeCommented:
Happy partying! Alex :O)
0
gironaAuthor Commented:
Thanks Alex,
    I'll be waiting any notice from you. I think till then I'll use an 'if' sentence.
0
simonetCommented:
Ok, girona. I tried the code according to my suggestion and it worked perfectly. I ran it under Delphi 3.

First of all, create a new application. In form1, put a TListBox with the following items:

ThisIsProcedure1
ThisIsProcedure2
ThisIsProcedure3
ProcedureWithParam

Create a new button.

Here's the source code for the project.

########## Listing for Project1.dpr

program Project1;

uses
  Forms,
  Unit1 in 'Unit1.pas' {Form1};

{$R *.RES}

exports
  ThisIsProcedure1,
  ThisIsProcedure2,
  ThisIsProcedure3,
  ProcedureWithParam;


begin
  Application.Initialize;
  Application.CreateForm(TForm1, Form1);
  Application.Run;
end.


########## Listing for unit1.pas

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls;

type
  TForm1 = class(TForm)
    ListBox1: TListBox;
    Edit1: TEdit;
    Button1: TButton;
    Label1: TLabel;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

type
  TWildProcedure = procedure (AnyInt : integer);


// All the procedures must follow the prototype defined by TWildProcedure.
// Otherwise they will need another "procedure type".
// Even if the procedure doesn't need any parameter, a "bumb" parameter
// must be defined.
// Not doing so will screw the stack up or not load the procedure.
procedure ThisIsProcedure1(Dumb : integer);
procedure ThisIsProcedure2(Dumb : integer);
procedure ThisIsProcedure3(Dumb : integer);
procedure ProcedureWithParam(Value : integer);


implementation

{$R *.DFM}

procedure ThisIsProcedure1(Dumb : integer);
begin
  Showmessage('procedure #1 executed!');
end;


procedure ThisIsProcedure2(Dumb : integer);
begin
  Showmessage('procedure #2 executed!');
end;

procedure ThisIsProcedure3(Dumb : integer);
begin
  Showmessage('procedure #3 executed!');
end;

procedure ProcedureWithParam(Value : integer);
begin
  ShowMessage('Sent value was '+ inttostr(Value));
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  MyProc : TWildProcedure;
begin
  @MyProc := GetProcAddress(hInstance, pchar(listbox1.items[listbox1.itemindex]));
  if (@MyProc<>nil) then
     MyProc(30);
end;

end.

################## that's it!

Assign the Onclick event of the TButton to Button1OnClick. Run the project. See how it works great?!

See, no confused calls, everything is simple. The trick here is to export the functions in the EXE (yes, an EXE can export functions and work like a DLL!). Another part of the trick is that all functions/procedures must have the exact same number and types of arguments/parameters (see the remarks on the source code).

Just another thing: the GetProcAddress stuff can be in any unit, not necessarily in the one that defines "ThisIsProcedureXXX". THe only requirement is that the other unit sees the definition for TWildProcedure.

If you have questions regarding the code, let me know.

Yours,

Alex



0

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
gironaAuthor Commented:
That works fine, and has good comments. Thanks.
0
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
Delphi

From novice to tech pro — start learning today.