Solved

Calling functions/procedures runtime

Posted on 1998-12-28
21
253 Views
Last Modified: 2010-04-04
OK, this is a bit complicated...

I am not going to explain exactly why I need this but rather what I need.

I have a function in one of my unit files which is publicly accessable:
function MYFUNC(param1, param2, param3: String): String;

In the "old" days when I still programmed in Clipper I would have been able to do the following:

//I set x equal to a string as follows
x = "MYFUNC('String1','String2','String3')"

//Now I can "execute" x.
SavedVal := &X

What the above would do is run MYFUNC('String1','String2','String3') saving the result into SavedVal.

This is exactly what I need to do in Delphi.

I have the function name as a string as well as the three parameters for this function. I concatenate these to build up the function call with it's parameter giving me:

TheString := 'MYFUNC(''String1'',''String2'',''String3'')'

Is there a way to have Delphi evaluate TheString at runtime returning the result from the function?

Example
 NewString := IWouldLikeToRunThis(TheString);

Using case statement and IF clauses is what I'm trying to avoid. In other word I don't need anything that looks like the code below as an answer.

IF FunctionName = 'MYFUNC' then MyFunc(Param1,Param2,Param3)
ELSE IF FunctionName = 'ANOTHERFUNC then AnotherFunc(Param1)
ELSE IF...

Thank you in advance
0
Comment
Question by:Gerhard100198
  • 7
  • 5
  • 4
  • +2
21 Comments
 
LVL 12

Accepted Solution

by:
rwilson032697 earned 200 total points
ID: 1353492
In a word - no.

There are some caveats though.

1. There are scripting components etc for delphi but I don't think they will be able to generically access a function in the way you describe... A wee search on DSP will turn up a couple.

2. You could use function pointers and differentiate based on parameters (which you are already doing as you are building up the parameter string.

3. You could place all these functions in a DLL and bind them by name at runtime (this is similar to the function pointers but gives a more generic approach to accessing a named procedure)

4. You could use either of 2 or 3 above but make each function take a open array as a single parameter. Then every function can be called in the same way with one parameter that you construct on the fly much in the same way as you do in clipper (this would be my pick and my proposed answer):

EG:

function DoSomething(Args : const array of const);

begin
// Do your stuff here
end;

I realise this does screw with your interfaces but its the closest approach I can suggest to fit your exact requirements.

Cheers,

Raymond.
0
 
LVL 1

Author Comment

by:Gerhard100198
ID: 1353493
rwilson,

What you're suggesting sounds very interesting but it's really new to me so I need a bit more info please.

Please note that I will have various functions, all accepting a different number of parameters. All parameters passed to the functions will be of type string. I understand what you're suggesting with passing the parameters as an array. If I'm correct you suggest this route for the simple reason that this would mean that I could do these function calls all in the same way irrespective of the function since the parameters are passed as an array in all cases.

1) Could you please explain in more detail about function pointers. How would I use this? Maybe an example?

2) I've also thought of working through a DLL but have never worked with functions in DLL before. Again, any examples on how to go about this?

3) If I make use of the DLL route, would it be possible to add more DLL with new functions at a later stage to my project without having to recompile the main EXE?

4) Is is possible to compile units making use of FRM files into DLL files?

5) How about making use of packets instead of DLL files. I've read that there are certain advantages such as the fact that the program will not fall over when a packet can not be found but if a DLL can not be found the program will refuse to run.

Thanks again.
0
 
LVL 10

Expert Comment

by:viktornet
ID: 1353494
You could use POS() to see if there is a parameter in the function to suggest to do a certain thing... that way you can parse your function and see what should be executed...

-Viktor
--Ivanov
0
 
LVL 12

Expert Comment

by:rwilson032697
ID: 1353495
Gerhard,

Well, if they are all strings it gets even easier!

Procedure MyFunc(Args : const array of string);

begin
  Showmessage('arg1 = '+args[0]+', arg2 = '+arg[1]); // I can never remember if these are 0 or 1 based, sigh.
end;

Call it like this (irrespective of how many args you have):

MyFunc(['fred', 'mary']);

or like this:

type
  ArgsType = array[1..10] of string;
var
  args : ArgsType;

begin
  args[1] := 'Fred';
  args[2] := 'mary';
  MyFunc(args);
end.


Right to your points! Lets see here:

1. Function pointers:
type
  PtrToMyFunction = procedure(Arg1, Arg2 : String);

Var
  MyFunctionPtr : PtrToMyFunction;

procedure AFunction(Arg1, Arg2 : String);

begin
// do something
end;

begin
  MyFunctionPtr := @AFunction; // Sometimes you don't need the @
  MyFunctionPtr('a', 'b'); // Call it
end.

2. DLLs. You can set a function in a DLL to proc pointer like this:

DLLHandle := LoadLibrary('MyFuncs.DLL');
MyFunctionPtr := GetProcAddress(DLLHandle, 'DoSomething');

the dll would look like this:

library MyFuncs;

procedure DoSomething(arg1, arg2 : String);
begin
// do it here
end;

exports DoSomething;

end.

3. Yes. Though the application would need some way of determining what the DLL provided. This could be accomplished by extra functions in the DLL which describe the procedures it contains.

4. Yes. Just include them in the DLL library project in the normal manner.

5. I am not familiar with 'packets' as used in Delphi. If you mean packages these are usually used to contain components. Loading DLLs at runtime can be error trapped (the handle returned is < 0 if there was an error so you can exit gracefully etc

Cheers,

Raymond.

0
 
LVL 20

Expert Comment

by:Madshi
ID: 1353496
Hi Raymond,

have looked at what you've written. Just one thing:
PLEASE, PLEASE don't write
  MyFunctionPtr := @AFunction;
You really SHOULD leave the "@" away. You need it only if you have to pass the function's address to a win32 function.
If you leave it away, Delphi makes sure that the parameters and the calling convention is right.

E.g.
type TTestProc = procedure (str: string) : boolean;
var  testProc  : TTestProc;
procedure test(int: integer; ch: char); stdcall;

You could write "testProc:=@test" and Delphi would accept this. THAT IS QUITE BAD!!! If you write "testProc:=test", Delphi will give you an error, since the parameters and the calling convention do not fit...

The rest of what you've written is fine...  :-)

Gerhard,
just another idea. You could use a list of a type like this:
  type TGerhardFunction = record
                            name: string;
                            func: function (const astr: array of string) : string;
                          end;
  var  gerhardsFuncs    : array [0..99] of TGerhardFunction;

Then of course you would have to initialize this array like this:
  gerhardsFuncs[0].name:='func1'; gerhardsFuncs[0].func:=func1;
  gerhardsFuncs[1].name:='func2'; gerhardsFuncs[1].func:=func2;
  ...

Now you could write a combined function like this:

CallGerhardFunc(name: string; const params: array of string) : string;
var i1 : integer;
begin
  result:='';
  for i1:=low(gerhardsFuncs) to high(gerhardsFuncs) do
    if CompareText(gerhardsFuncs[i1].name,name)=0 then begin
      result:=gerhardsFuncs[i1](params);
      break;
    end;
end;

But this would have one problem: The linker cannot link out the functions that are not called. All the 100 functions will always be linked into the final exe.

Regards, Madshi.
0
 
LVL 1

Author Comment

by:Gerhard100198
ID: 1353497
rwilson,

Thank a heap, the points are yours. It turned out to be much easier than i anticipated thanks to you.

One more question though, is it possible the change the properties of components in my program. In other words, I have a form (Form1) which is the main form of my project. On this form I have a button (Button1). Is it possible to do Form1.Button1.Caption := 'What ever' from within a function in a DLL?
0
 
LVL 1

Author Comment

by:Gerhard100198
ID: 1353498
When I run the following code I get an "Invalid pointer operation" message directly after the ShowMessage line has executed. Any idea what I'm doing wrong?

unit AccessDLL;

interface

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

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

var
  Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.Button1Click(Sender: TObject);
type
  TMyFunctionPtr = function(arg1,arg2: String): String;
var
  DLLHandle: THandle;
  MyFunctionPtr: TMyFunctionPtr;
  y: String;
begin
  DLLHandle := LoadLibrary('DLL.dll');
  MyFunctionPtr := GetProcAddress(DLLHandle,'DoSomething');
  y := MyFunctionPtr('One','Two');
  ShowMessage(y);

end;

end.

=====================
Here is the DLL code

library DLL;

{ Important note about DLL memory management: ShareMem must be the
  first unit in your library's USES clause AND your project's (select
  Project-View Source) USES clause if your DLL exports any procedures or
  functions that pass strings as parameters or function results. This
  applies to all strings passed to and from your DLL--even those that
  are nested in records and classes. ShareMem is the interface unit to
  the BORLNDMM.DLL shared memory manager, which must be deployed along
  with your DLL. To avoid using BORLNDMM.DLL, pass string information
  using PChar or ShortString parameters. }

uses
  ShareMem,
  SysUtils,
  Classes,
  Dialogs;

function DoSomething(arg1, arg2: String): String;
begin
//      ShowMessage(arg1+arg2);
  Result := arg1+arg2;
//  Form1.Button1.Caption := arg1+arg2;
end;

exports DoSomething;

end.
0
 
LVL 1

Expert Comment

by:slautin
ID: 1353499
LoadLibrary, GetProcAddress - it's OK.
But what you'll doing with different by type and count
parameters? Array [n...m] - isn't fine.
In same cases I transfer paremeters within self coding
variables stak.
In overall cases i must use ASM for preparing parameters stak :(.
0
 
LVL 10

Expert Comment

by:viktornet
ID: 1353500
Try using PCHAR's.... Here is what it should look like...

//In the PAS file....
procedure TForm1.Button1Click(Sender : TObject);
type
  TMyFunctionPtr = function(arg1, arg2 : String) : String;
var
  DLLHandle : THandle;
  MyFunctionPtr : TMyFunctionPtr;
  y : String;
  p : PChar;
begin
  DLLHandle := LoadLibrary('DLL.dll');
  MyFunctionPtr := GetProcAddress(DLLHandle,'DoSomething');
  p := MyFunctionPtr('One','Two');
  y := StrPas(p);
  ShowMessage(y);
  FreeLibrary('DLL.dll'); //NEVER FORGET TO FREE THE LIBRARY...
end;

//In the DLL File
function DoSomething(arg1, arg2: String): PChar;
var
  buf : PChar;
  res : string;
begin
  res := arg1 + arg2;
  GetMem(buf, Length(res));
  StrPCopy(buf, res);
  Result := buf;
  FreeMem(buf);
end;

I have not tested this but it should work, maybe with a little modification...

-Viktor
--Ivanov
0
 
LVL 12

Expert Comment

by:rwilson032697
ID: 1353501
Madshi: re: @: Thats why I said sometimes. I have found that there are occasions when everything is identical but delphi will not permit you to assign the procedure/function to a pointer variable without using the '@'

Viktor: Yes this would work, but would break the requirements Gerhard has set down.

Gerhard: Yes you should be able to assign a button caption in the DLL. You will need to pass the form or button to the DLL. There may possible be problems with the DLL having separate application objects and resource caches, though compiling with run-time packages should resolve these.

The invalid pointer op is either because something is messing up the transfer of huge strings from the main app to the DLL and back or there is a calling convention nasty. You look like you are doing the right things with sharemem etc. try using the pascal calling convetions, like this:
function DoSomething(arg1, arg2: String): String; pascal;

Cheers,

Raymond.
0
How to improve team productivity

Quip adds documents, spreadsheets, and tasklists to your Slack experience
- Elevate ideas to Quip docs
- Share Quip docs in Slack
- Get notified of changes to your docs
- Available on iOS/Android/Desktop/Web
- Online/Offline

 
LVL 20

Expert Comment

by:Madshi
ID: 1353502
Raymond,

can you please give me an example where Delphi doesn't accept a function assignment, though both parameters AND calling convention are identical? I really don't believe that! I've never had something like this!
Perhaps you mixed up a "function" and a "function of object"?

Regards, Madshi.
0
 
LVL 10

Expert Comment

by:viktornet
ID: 1353503
I think passing a pointer to the string is a lot easier than passing the whole string, because as Raymond said there might be some problems with the string passing...

btw- Raymond, I don't see where Gerhard sets any requirements about not using PChars... It is almost the same...

you could also do this,...

type
       TMyFunctionPtr = function(arg1, arg2 : String) : String;
     var
       DLLHandle : THandle;
       MyFunctionPtr : TMyFunctionPtr;
       y : String;
     begin
       DLLHandle := LoadLibrary('DLL.dll');
       MyFunctionPtr := GetProcAddress(DLLHandle,'DoSomething');
       y := StrPas(MyFunctionPtr('One','Two'));
       ShowMessage(y);
       FreeLibrary('DLL.dll'); //NEVER FORGET TO FREE THE LIBRARY...
     end;

This could do it also..
0
 
LVL 12

Expert Comment

by:rwilson032697
ID: 1353504
Madshi,

I dont have an example to hand sorry. No I was not mixing up function and function of object.

Its just that, sometimes, you appear to need to use it in circumstances where I was sure you did not have to...

Cheers,

Raymond.
0
 
LVL 12

Expert Comment

by:rwilson032697
ID: 1353505
Viktor, yes - I suppose you could use a const array of PChar... But if you can pass the strings over directly its much cleaner.

Cheers,

Raymond.
0
 
LVL 10

Expert Comment

by:viktornet
ID: 1353506
Yeah, I think so too... If you find a way to do that, or probably find the bug in Gerhard's code, please let me know...

Happy New Year!

-Viktor
--Ivanov
0
 
LVL 1

Author Comment

by:Gerhard100198
ID: 1353507
Thank you to all of you that helped and sent comments, especially rwilson for your time.

BTW I'm now looking into runtime packages as an alternative to DLL use. See the question I posted in this regard.

Thanks again

Gerhard
0
 
LVL 20

Expert Comment

by:Madshi
ID: 1353508
Raymond, if you come across such a function assignment problem some days later again, would you please tell it to me...  (perhaps post a 0 point question or eMail me to "Mathias.Rauen@gmx.de"). I'm really interested in that...   :-)

Regards, Madshi.
0
 
LVL 12

Expert Comment

by:rwilson032697
ID: 1353509
Sure Mathias - don't hold your breath though - its something that I don't use very often...

Raymond.
0
 
LVL 20

Expert Comment

by:Madshi
ID: 1353510
O-o-o-.....          ...       ...   Ok. I'll continue with breathing...  :-)
0
 
LVL 12

Expert Comment

by:rwilson032697
ID: 1353511
Gerhard, Viktor: I've set up a simple app to show passing huge strings across DLL boundaries: (PS: I used a project group for the first time when setting this up - cool!)

Cheers,

Raymond.

Heres the DLl source:

library test;

{ Important note about DLL memory management: ShareMem must be the
  first unit in your library's USES clause AND your project's (select
  Project-View Source) USES clause if your DLL exports any procedures or
  functions that pass strings as parameters or function results. This
  applies to all strings passed to and from your DLL--even those that
  are nested in records and classes. ShareMem is the interface unit to
  the BORLNDMM.DLL shared memory manager, which must be deployed along
  with your DLL. To avoid using BORLNDMM.DLL, pass string information
  using PChar or ShortString parameters. }

uses
  ShareMem,
  SysUtils,
  Classes,
  Dialogs;

procedure doit(s : string); stdcall;

begin
  ShowMessage('The message = '+s);
end;

exports
  doit;

begin
end.


Heres the project source

program DLLString;

uses
  Sharemem,
  Forms,
  DllStringUnit in 'DllStringUnit.pas' {Form1};

{$R *.RES}

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


Heres the form source:

unit DllStringUnit;

interface

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

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

var
  Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.Button1Click(Sender: TObject);
var
h : THandle;
doitptr : procedure(s : string); stdcall;

begin
h := loadlibrary('test.dll');
doitptr := GetProcAddress(h, 'doit');
doitptr('Fred');
end;

end.


object Form1: TForm1
  Left = 215
  Top = 107
  Width = 696
  Height = 480
  Caption = 'Form1'
  Color = clBtnFace
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'MS Sans Serif'
  Font.Style = []
  OldCreateOrder = False
  PixelsPerInch = 96
  TextHeight = 13
  object Button1: TButton
    Left = 96
    Top = 64
    Width = 75
    Height = 25
    Caption = 'Button1'
    TabOrder = 0
    OnClick = Button1Click
  end
end
 
0
 
LVL 10

Expert Comment

by:viktornet
ID: 1353512
Thanks Raymond :-)

So ShareMem.pas was the trick, wasn't it? :)
0

Featured Post

6 Surprising Benefits of Threat Intelligence

All sorts of threat intelligence is available on the web. Intelligence you can learn from, and use to anticipate and prepare for future attacks.

Join & Write a Comment

Creating an auto free TStringList The TStringList is a basic and frequently used object in Delphi. On many occasions, you may want to create a temporary list, process some items in the list and be done with the list. In such cases, you have to…
Hello everybody This Article will show you how to validate number with TEdit control, What's the TEdit control? TEdit is a standard Windows edit control on a form, it allows to user to write, read and copy/paste single line of text. Usua…
In this seventh video of the Xpdf series, we discuss and demonstrate the PDFfonts utility, which lists all the fonts used in a PDF file. It does this via a command line interface, making it suitable for use in programs, scripts, batch files — any pl…
You have products, that come in variants and want to set different prices for them? Watch this micro tutorial that describes how to configure prices for Magento super attributes. Assigning simple products to configurable: We assigned simple products…

747 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

10 Experts available now in Live!

Get 1:1 Help Now