I saw in this question: Empty string becomes null when passed from Delphi to C# as a function argument that Delphi's empty string value in reality is just a null-pointer - which I understand the reasoning behind.
I do have an issue though as I am developing a Web API in Delphi and I am having trouble implementing a PATCH endpoint and I wondered if anyone has had the same issue as me.
If i have a simple resource Person which looks like this.
{
"firstName": "John",
"lastName": "Doe",
"age": 44
}
and simply want to change his lastName property using a PATCH document - I would sent a request that looks like this:
{
"lastName": "Smith"
}
Now - in my api, using Delphis System.JSON library I would just check if the request has the firstName and age properties before setting them in the request handler which sets the properties in an intermediate object PersonDTO, but later I have to map these values to the actual Person instance - and here comes my issue:
When mapping between multiple objects I cannot tell if a string is empty because it was never set (and should be treated as null) or was explicitly set to '' to remove a property from my resource - How do I circumvent this?
if personDTO.FirstName <> '' then
personObject.FirstName := personDTO.FirstName;
Edit: I have considered setting the strings to #0 in the DTO's constructor to distinguish between null and '' but this is a large (1M line) code base, so I would prefer to find a robust generic way of handling these scenarios
Delphi does not differentiate between an empty string and an unassigned string. They are implemented the exact same way - as a nil pointer. So, you will have to use a different type that does differentiate, such as a Variant. Otherwise, you will have to carry a separate boolean/enum flag alongside the string to indicate its intended state. Or, wrap the string value inside of a record/class type that you can set a pointer at when assigned and leave nil when unassigned.
in Delphi String is an array, but it's an array a little longer than the actual count of characters. For exemple in Delphi string always end up with the #0 at high(myStr) + 1. this is need when casting the string to pchar. if in your flow you don't plan to cast the string to pchar then you can write a special char in this "invisible" characters to distinguish between null and empty (actually i never tested this solution)
The answer is in your question itself. You need to know what has been supplied. This means that you either need to use what was actually provided to the API rather than serialising into an object (which has to include all the members of the object), or you need to serialise into an object whose members will support you knowing whether they have been set or not.
If you are serialising into an intermediate object for the API then when you come to update your actual application object you can use an assign method that only sets the members of the application object that were set in the API. Implementing these checks in the intermediate object for your API means that you won't have to change any code in the main application.
Code that suggests how you might do this:
unit Unit1;
interface
uses Classes;
type
TAPIIVariableStates = (APIVarSet, APIVarIsNull);
TAPIVariableState = Set of TAPIIVariableStates;
TAPIString =class(TObject)
protected
_szString: String;
_MemberState: TAPIVariableState;
function _GetHasBeenSet(): Boolean; virtual;
function _GetIsNull(): Boolean; virtual;
function _GetString(): String; virtual;
procedure _SetString(szNewValue: String); virtual;
public
procedure AfterConstruction(); override;
procedure Clear(); virtual;
procedure SetToNull(); virtual;
property Value: String read _GetString write _SetString;
property HasBeenSet: Boolean read _GetHasBeenSet;
property IsNull: Boolean read _GetIsNull;
end;
TAPIPerson = class(TPersistent)
protected
FFirstName: TAPIString;
FLastName: TAPIString;
FComments: TAPIString;
procedure AssignTo(Target: TPersistent); override;
function _GetComments(): String; virtual;
function _GetFirstName(): String; virtual;
function _GetLastName(): String; virtual;
procedure _SetComments(szNewValue: String); virtual;
procedure _SetFirstName(szNewValue: String); virtual;
procedure _SetLastName(szNewValue: String); virtual;
public
destructor Destroy; override;
procedure AfterConstruction(); override;
property FirstName: String read _GetFirstName write _SetFirstName;
property LastName: String read _GetLastName write _SetLastName;
property Comments: String read _GetComments write _SetComments;
end;
TApplicationPerson = class(TPersistent)
protected
FFirstName: String;
FLastName: String;
FComments: String;
public
property FirstName: String read FFirstName write FFirstName;
property LastName: String read FLastName write FLastName;
property Comments: String read FComments write FComments;
end;
implementation
uses SysUtils;
destructor TAPIPerson.Destroy();
begin
FreeAndNil(Self.FFirstName);
FreeAndNil(Self.FLastName);
FreeAndNil(Self.FComments);
inherited;
end;
procedure TAPIPerson.AfterConstruction();
begin
inherited;
Self.FFirstName:=TAPIString.Create();
Self.FLastName:=TAPIString.Create();
Self.FComments:=TAPIString.Create();
end;
procedure TAPIPerson.AssignTo(Target: TPersistent);
begin
if(Target is TApplicationPerson) then
begin
if(Self.FFirstName.HasBeenSet) then
TApplicationPerson(Target).FirstName:=Self.FirstName;
if(Self.FLastName.HasBeenSet) then
TApplicationPerson(Target).LastName:=Self.LastName;
if(Self.FComments.HasBeenSet) then
TApplicationPerson(Target).Comments:=Self.Comments;
end
else
inherited;
end;
function TAPIPerson._GetComments(): String;
begin
Result:=Self.FComments.Value;
end;
function TAPIPerson._GetFirstName(): String;
begin
Result:=Self.FFirstName.Value;
end;
function TAPIPerson._GetLastName(): String;
begin
Result:=Self.FLastName.Value;
end;
procedure TAPIPerson._SetComments(szNewValue: String);
begin
Self.FComments.Value:=szNewValue;
end;
procedure TAPIPerson._SetFirstName(szNewValue: String);
begin
Self.FFirstName.Value:=szNewValue;
end;
procedure TAPIPerson._SetLastName(szNewValue: String);
begin
Self.FLastName.Value:=szNewValue;
end;
procedure TAPIString.AfterConstruction();
begin
inherited;
Self._MemberState:=[APIVarIsNull];
end;
procedure TAPIString.Clear();
begin
Self._szString:='';
Self._MemberState:=[APIVarIsNull];
end;
function TAPIString._GetHasBeenSet(): Boolean;
begin
Result:=(APIVarSet in Self._MemberState);
end;
function TAPIString._GetIsNull(): Boolean;
begin
Result:=(APIVarIsNull in Self._MemberState);
end;
function TAPIString._GetString(): String;
begin
Result:=Self._szString;
end;
procedure TAPIString._SetString(szNewValue: String);
begin
Self._szString:=szNewValue;
Include(Self._MemberState, APIVarSet);
(* optionally treat an emoty strung and null as the same thing
if(Length(Self._szString)=0) then
Include(Self._MemberState, APIVarIsNull)
else
Exclude(Self._MemberState, APIVarIsNull); *)
end;
procedure TAPIString.SetToNull();
begin
Self._szString:='';
Self._MemberState:=[APIVarSet, APIVarIsNull];
end;
end.
Using AssignTo in the TAPIPerson means that if your TApplicationPerson object derives from TPersistent (and has a properly implemented Assign method) then you can just use <ApplicationPersonObject>.Assign(<APIPersonObject>) to update just those fields which have changed. Otherwise you need a public method in the TAPIPerson that will update the TApplicationPerson appropriately.
Related
I have a class of the following shape
TParser = class
private
FFlag : boolean;
FIntermediateValue : double;
procedure F1(var aPartOfInput : string);
procedure F2(var aSmallerPartOfInput : string) ;
public
function Parse(const anInput : string): double;
end;
function TParser.Parse(const anInput : string) : double;
var
aPartOfInput : string;
begin
{ Do some checks on the input and set FFlag to true/false.
Set aPartOfInput to a piece of the input}
f1(aPartOfInput);
Result := FIntermediateValue;
end;
procedure TParser.F1(var aPartOfInput : string);
begin
{ Slice some more off the input.
Set the FIntermediateValue }
f2(aPartOfInput)
end;
procedure TParser.F2(var aSmallerPartOfInput : string);
begin
{ Depending on the input and FFlag, update FIntermediateValue }
end;
We currently create/destroy a squillion (technical term) of these through the system.
I am looking at creating a single instance and invoking it from each of these places.
This single instance will be invoked from multiple threads.
Does the use of the private fields mean that this is not thread safe?
If this is not thread safe, what are my options for making it thread safe?
EDIT - Options
Thanks all for the comments.
A separate instance for each thread
Sounds good, but because of the way that the code is structured, the instance would need to be 'global' to the thread, there is no way (as far as I can see) to inject the instance down the object graph to where is it needed (at the moment, new instances of the parser are created where they are needed)
threadvar
TheParser : TParser;
seems to be a way to give me this. Create the instance when we start the thread and free it on leaving. Are there any problems with this?
Get rid of the instance variables
If we re-design the parser so that it is a function rather than an instance of a class and all the values are passed around, would this make it thread safe?
interface
function Parse(const anInput : string): double;
implementation
function F2(var aSmallerPartOfInput : string; theFlag : boolean; theIntermediateValue : double) : double ;
begin
{ update theIntermediateValue based on flag and input }
Result := theIntermediateValue;
end;
function F1(var aPartOfInput : string; theFlag : boolean) : double;
var
anIntermediateValue : double;
begin
{ Slice some more off the input
Set the FIntermediateValue }
Result := f2(aPartOfInput, theFlag, anIntermediateValue)
end;
function Parse(const anInput : string) : double;
var
aPartOfInput : string;
aFlag : boolean;
begin
{ set aFlag
set aPartOfInput to a piece of the input }
Result := f1(aPartOfInput, aFlag);
end;
No, they aren't.
You must synchronize the access, if you want to use the same object instance within different threads.
On Windows just use a critical section (TRTLCriticalSection) and on posix os' use mutex (pthread_mutex_t)
You always can choose to design your class idempotent, so you can have a instance of it in each thread and just use it in there.
Hello I m working about string split-merge like this:
Button1.click...etc.
var
s:String;
ars:array[1..10] of String;
i:integer;
begin
ars[1]:='0';
ars[2]:='012';
ars[3]:='23';
ars[4]:='458';
ars[5]:='022'; // These values are example of.
ars[6]:='001';
ars[7]:='0125';
ars[8]:='250';
ars[9]:='859';
ars[10]:='9';
for i:=1 to 10 do
begin
s:=s+ars[i];
end;
//Finally I get string like this example: '01123641054257867420..etc..'
end;
How can I split the final string back? I dont know the Length of the ars array value. As you see some of it 4 bytes, 3 bytes, 2 bytes.. I merge all of it and then I want to split it again.. How can I do this? Is there any idea or Algorithms? I searched in Google but I did not anything.. Thank you..
#Jens Borrisholt My codes are:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{ TMyStrings }
{$R *.dfm}
type TMyStrings = class(TStringList)
protected
function GetTextStr: string; override;
end;
function TMyStrings.GetTextStr: string;
var
Element: String;
begin
Result := '';
for Element in Self do
Result := Result + Element;
end;
end.
Where is the problem?
There is no built-in functionality to do that, since you throw away information (the length of each string). So that information have to be stored somewhere.
You could use a TStringList descendant :
Interface
TMyStrings = class(TStringList)
protected
function GetTextStr: string; override;
end;
Implementation
{ TMyStrings }
function TMyStrings.GetTextStr: string;
var
Element: String;
begin
Result := '';
for Element in Self do
Result := Result + Element;
end;
And how to use it :
procedure TForm5.FormCreate(Sender: TObject);
var
MyStrings : TMyStrings;
begin
MyStrings := TMyStrings.Create;
MyStrings.Add('0');
MyStrings.Add('012');
MyStrings.Add('23');
MyStrings.Add('458');
MyStrings.Add('022'); // These values are example of.
MyStrings.Add('001');
MyStrings.Add('0125');
MyStrings.Add('250');
MyStrings.Add('859');
MyStrings.Add('9');
Caption := MyStrings.Text;
FreeAndNil(MyStrings);
end;
With this in hand you can get your list as a joined string, and you still have the original information about each string.
I am implementing a pool of objects in Delphi. I need to synchronize the threads to get the objects from the pool.
Thread Code:
uClientQueryPool.CLIENT_POOL_GUARD.Acquire();
QueryClient := QUERY_POOL.GetClient();
uClientQueryPool.CLIENT_POOL_GUARD.Release;
Pool Code:
var
CLIENT_POOL_GUARD: TCriticalSection;
type
TClientQueryPool = class
public
function GetClient(): TQueryClient;
end;
The CLIENT_POOL_GUARD is a unit variable. The pool is working well, but can I use "uClientQueryPool.CLIENT_POOL_GUARD.Acquire();" and "uClientQueryPool.CLIENT_POOL_GUARD.Release;" inside the GetClient method?
Like this:
function TClientQueryPool.GetClient: TQueryClient;
begin
CLIENT_POOL_GUARD.Acquire();
...
CLIENT_POOL_GUARD.Release;
end;
Moving the lock inside the get/pop/whatever method is just fine, as is making the CriticalSection instance a private member of the pool class. Use the same CS in the release() call that pushes the objects back onto the pool.
Been doing this for decades, usually with TObjectQueue as the pool queue, a CS to protect it and a semaphore to count the pool contents and something for requesting threads to block on if the pool empties temporarily.
Don't know where that 'double acquire' thread came from. Either the lock is inside the pool class, or outside. I really can't imagine why anyone would code up both!
Example classes:
First, thread-safe P-C queue, for holding the pooled objects:
unit tinySemaphoreQueue;
interface
uses
Windows, Messages, SysUtils, Classes,syncObjs,contnrs;
type
pObject=^Tobject;
TsemaphoreMailbox=class(TobjectQueue)
private
countSema:Thandle;
protected
access:TcriticalSection;
public
property semaHandle:Thandle read countSema;
constructor create; virtual;
procedure push(aObject:Tobject); virtual;
function pop(pResObject:pObject;timeout:DWORD):boolean; virtual;
end;
implementation
{ TsemaphoreMailbox }
constructor TsemaphoreMailbox.create;
begin
inherited Create;
access:=TcriticalSection.create;
countSema:=createSemaphore(nil,0,maxInt,nil);
end;
function TsemaphoreMailbox.pop(pResObject: pObject;
timeout: DWORD): boolean;
begin // wait for a unit from the semaphore
result:=(WAIT_OBJECT_0=waitForSingleObject(countSema,timeout));
if result then // if a unit was supplied before the timeout,
begin
access.acquire;
try
pResObject^:=inherited pop; // get an object from the queue
finally
access.release;
end;
end;
end;
procedure TsemaphoreMailbox.push(aObject: Tobject);
begin
access.acquire;
try
inherited push(aObject); // shove the object onto the queue
finally
access.release;
end;
releaseSemaphore(countSema,1,nil); // release one unit to semaphore
end;
end.
then object pool:
unit tinyObjectPool;
interface
uses
Windows, Messages, SysUtils, Classes,syncObjs,contnrs,
tinySemaphoreQueue;
type
TobjectPool=class;
TpooledObject=class(TObject)
private
FmyPool:TObjectPool;
protected
Fparameter:TObject;
public
procedure release;
constructor create(parameter:TObject); virtual;
end;
TpooledObjectClass=class of TpooledObject;
TobjectPool=class(TsemaphoreMailbox)
private
Fparameter:TObject;
function getPoolLevel: integer;
public
property poolLevel:integer read getPoolLevel;
constructor create(poolDepth:integer;
pooledObjectClass:TpooledObjectClass;parameter:TObject); reintroduce; virtual;
end;
implementation
{ TobjectPool }
constructor TobjectPool.create(poolDepth: integer;
pooledObjectClass: TpooledObjectClass;parameter:TObject);
var objectCount:integer;
thisObject:TpooledObject;
begin
inherited create;
Fparameter:=parameter; // a user parameter passed to all objects
for objectCount:=0 to poolDepth-1 do // fill up the pool with objects
begin
thisObject:=pooledObjectClass.create(parameter);
thisObject.FmyPool:=self;
inherited push(thisObject);
end;
end;
function TobjectPool.getPoolLevel: integer;
begin
access.acquire;
result:=inherited count;
access.release;
end;
{ TpooledObject }
constructor TpooledObject.create(parameter: TObject);
begin
inherited create;
Fparameter:=parameter;
end;
procedure TpooledObject.release;
begin
FmyPool.push(self);
end;
end.
Yes you can. Note, though that although you can pull an object from the pool in a thread-safe manner, it may not be thread-safe to use it if the object itself isn't thread-safe. For instance, in the example below, the pool is thread safe and even makes threads wait if all objects in the pool are in use, but once an object is in use, using it still is not thread safe, because it uses global data.
uses
SyncObjs;
var
GlobalData: Integer = 0;
type
TDataObject = class
Used: Boolean;
procedure UpdateData;
end;
type
TPool = class
FLock: TCriticalSection;
FSemaphore: TSemaphore;
FDataObjects: array[0..9] of TDataObject;
constructor Create;
destructor Destroy; override;
function GetDataObject: TDataObject;
procedure ReleaseDataObject(AObject: TDataObject);
end;
var
Pool: TPool;
type
TDataThread = class(TThread)
constructor Create;
procedure Execute; override;
end;
{ TPool }
constructor TPool.Create;
var
i: Integer;
begin
inherited Create;
FLock := TCriticalSection.Create;
FSemaphore := TSemaphore.Create(nil, Length(FDataObjects), Length(FDataObjects), '', False);
for i := Low(FDataObjects) to High(FDataObjects) do
FDataObjects[i] := TDataObject.Create;
end;
destructor TPool.Destroy;
var
i: Integer;
begin
for i := Low(FDataObjects) to High(FDataObjects) do
FDataObjects[i].Free;
FSemaphore.Free;
FLock.Free;
end;
function TPool.GetDataObject: TDataObject;
var
i: Integer;
begin
Result := nil;
FLock.Acquire;
try
FSemaphore.Acquire;
for i := Low(FDataObjects) to High(FDataObjects) do
if not FDataObjects[i].Used then
begin
Result := FDataObjects[i];
Result.Used := True;
Exit;
end;
Assert(Result <> nil, 'Pool did not return an object');
finally
FLock.Release;
end;
end;
procedure TPool.ReleaseDataObject(AObject: TDataObject);
begin
if not AObject.Used then
raise Exception.Create('Data object cannot be released, because it is not in use.');
AObject.Used := False;
FSemaphore.Release;
end;
{ TDataObject }
procedure TDataObject.UpdateData;
begin
Inc(GlobalData);
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
TDataThread.Create;
end;
{ TDataThread }
constructor TDataThread.Create;
begin
inherited Create(True);
FreeOnTerminate := True;
Resume;
end;
procedure TDataThread.Execute;
var
DataObject: TDataObject;
begin
DataObject := Pool.GetDataObject;
DataObject.UpdateData; // <-- Not thread-safe!
Pool.ReleaseDataObject(DataObject);
end;
initialization
Pool := TPool.Create;
finalization
Pool.Free;
end.
1) I'd remove Acquire/Release code from threads code - it is fragile. In one thread you forget to call it - and ba-bang! Security measures, as a rule of thumb, should be centralized and enforced by server, not distributed in fuzzy way in clients.
2) Acquire/Release calls should be guarded from errors, else any stray exception would forever lock all the threads.
function TClientQueryPool.GetClient: TQueryClient;
begin
CS.Acquire;
try
// actually getting object, preferably just calling
// internal non-public thread-unsafe method for it
finally
CS.Release;
end;
end;
3) Critical section itself should better be a Pool's internal, non-public member. That way you would be allowed in future, when you forget of implementation details, easy refactoring, like:
3.1) implementing several pools
3.2) moving pool code to another unit
3.3) ensuring any stray erroneous code outside pool would not be able to crash the application be randomly acquiring or releasing the CS
4) Double calling of acquire/release over TCriticalSection object puts all your bets over implications from a single note in TCriticalSection documentation, pointed to by The_Fox.
"Each call to Release should be balance by an earlier call to Acquire"
http://docwiki.embarcadero.com/Libraries/en/System.SyncObjs.TCriticalSection.Release
And over the hope that all other Pascal implementations today and tomorrow would not miss it.
That is fragile practice. And multi-threading code is famous for creating Heisenbugs, when there are problems at clients sites, but you can not reproduce and find it in house.
If in future your company would expand to different platform or different language implementation, that puts a potential land mine. And the kind of mine, that would be hard to find by testing in house. Multithreading code is the place where you'd better be over-defeinsive and just do not allow ANY uncertainty to happen.
i use this component for processing drag and drop files
http://melander.dk/delphi/dragdrop
unit DragThread;
interface
uses
Classes,DragDrop, DropTarget,DragDropFile,Dialogs,SysUtils;
type
TDragThread = class(TThread)
private
{ Private declarations }
ArraysLength : Integer;
DragComponent : TDropFileTarget;
DragArray,HashsArray : Array of string;
Procedure FDArray;
//Procedure FDHArray;
protected
procedure Execute; override;
Public
Constructor Create(Com: TDropFileTarget);
Destructor Destroy; Override;
end;
implementation
{ TDragThread }
Constructor TDragThread.Create(Com: TDropFileTarget);
begin
inherited Create(True);
DragComponent := Com;
end;
Destructor TDragThread.Destroy;
begin
//DragComponent.Free;
end;
Procedure TDragThread.FDArray;
var
A : Integer;
begin
SetLength(DragArray,DragComponent.Files.Count);
SetLength(HashsArray,DragComponent.Files.Count);
ShowMessage(IntToStr(DragComponent.Files.Count)); // just working in the first time !!
for A := 0 to DragComponent.Files.Count -1 do begin
DragArray[A] := DragComponent.Files[A];
//ShowMessage(DragComponent.Files[A]);
end;
ArraysLength := DragComponent.Files.Count-1;
//ShowMessage(DragComponent.Files[0]);
end;
procedure TDragThread.Execute;
begin
{ Place thread code here }
FDArray;
end;
end.
the strange thing that the Drop process working just one time then the DragComponent.Files.Count gives 0 for ever .!!
that's how i call it
procedure TForm1.DropFileDrop(Sender: TObject; ShiftState: TShiftState;
APoint: TPoint; var Effect: Integer);
var
DropThread : TDragThread;
begin
DropThread := TDragThread.Create(DropFile);
DropThread.Resume;
end;
i want to know why this happened and thanks in advance :) .
Don't operate VCL components from other threads.
There's no guarantee that the component's drop-event information will continue to be valid once the drop event has completed.
Copy all the information you need out of the component when you construct the thread (i.e., fully populate DragArray) and then use that cached data when executing the thread. Don't store a reference in DragComponent or you might be tempted to use it from the thread's Execute method, which you really shouldn't do.
Is it possible to call a function whose name is stored in a string in Delphi?
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.
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.
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
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;
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();
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
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;
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.
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;
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;