Delphi: вызов функции, имя которой хранится в строке
Можно ли вызвать функцию, имя которой хранится в строке в Delphi?
9 ответов
Пожалуйста, дайте более подробную информацию о том, что вы пытаетесь достичь.
Насколько я знаю:
- Невозможно вызвать случайную функцию, подобную этой.
- Для функций класса и объекта (MyObject.Function) это можно сделать с помощью RTTI, но это большая работа.
- Если вам просто нужно вызвать один конкретный тип функций (скажем, функция (целое, целое): строка), это намного проще.
В последнем случае объявите тип функции, затем получите указатель на функцию и приведите его так:
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.
Вы не указали свою версию Delphi, однако, если у вас есть Delphi 2010(+), вы можете сделать это, используя расширенный RTTI, я не эксперт по ним, но я попробовал этот пример для вас:
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;
Хорошие вещи, если у вас есть процедура или функция с параметрами, она будет работать без дополнительной работы.
Я удивлен, что никто не предложил таблицу отправки. Это именно то, для чего это.
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.
Реализация, которую я написал, использует дженерики, поэтому она будет работать только с Delphi 2009+. Для более старых версий это, вероятно, было бы проще всего реализовать с помощью TStringList и шаблона команды.
В Delphi 2010 вы можете использовать JSON и SuperObject для вызова метода с параметрами.
http://code.google.com/p/superobject/source/browse/
Если вам нужно, есть также парсер xml для преобразования xml в 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;
Если вы спрашиваете, есть ли что-то вроде JavaScript eval()
это возможно в Delphi, нет, это не (легко) достижимо, поскольку Delphi компилируется в нативный код.
Если вам нужно только поддерживать некоторые строки, вы всегда можете сделать много if
или case
... Что-то вроде:
if myString = 'myFunction' then
myFunction();
Хорошо, я очень опоздал на вечеринку, но вы можете определенно вызывать подпрограммы по имени с помощью этого кода (с некоторыми ограничениями)
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;
На всякий случай, если кому-то это нужно для Delphi 7 / 2010
Поместите каждую функцию в действие. Затем вы можете найти действие по имени и выполнить его
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;
Вы можете сделать что-то подобное, создав один или несколько классов с опубликованными свойствами, которые используют функции для реализации их функций чтения и записи. Затем свойства могут быть обнаружены с использованием RTTI-отражения и использованы для ссылки, что приводит к вызову базовых функций.
Кроме того, вы можете хранить указатели на функции в таблице или даже свойство Object в TStringList и эффективно индексировать их по имени строки.
Прямой вызов функции по имени в Delphi невозможен.
Следующее простое решение с использованием export и GetProcAddress также работает для старых версий Delphi:
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;