web-dev-qa-db-ja.com

Delphi:名前が文字列に格納されている関数を呼び出す

Delphiで名前が文字列に格納されている関数を呼び出すことはできますか?

21
Woutb21

何を達成しようとしているのか、詳しく教えてください。

私の知る限りでは:

  • そのようなランダムな関数を呼び出すことはできません。
  • クラス関数とオブジェクト関数(​​MyObject.Function)の場合、これはRTTIで実行できますが、多くの作業が必要です。
  • 特定の種類の関数(たとえば、function(integer、integer):string)を呼び出す必要がある場合は、はるかに簡単です。

最後の1つは、関数型を宣言してから、関数ポインターを取得し、次のようにキャストします。

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.
26
himself

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;

良いことは、パラメータを使用したプロシージャまたは関数がある場合、それ以上の作業なしで機能することです。

15
Mohammed Nasman

ディスパッチテーブル を誰も提案していないことに驚いています。これがまさにその目的です。

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.

私が書いた実装はジェネリックを使用しているため、Delphi2009以降でのみ機能します。古いバージョンの場合、TStringListと コマンドパターン を使用して実装するのがおそらく最も簡単です。

11
Kenneth Cochran

Delphi 2010では、JSONとSuperObjectを使用して、パラメータを使用してメソッドを呼び出すことができます。

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

必要に応じて、xmlをjsonに変換するためのxmlパーサーもあります。

  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;
9
Henri Gourvest

JavaScript eval()のようなものがDelphiで可能かどうかを尋ねている場合、Delphiはネイティブコードにコンパイルされるため、これは(簡単に)達成できません。

一部の文字列のみをサポートする必要がある場合は、いつでも多くのifまたはcase...を実行できます。

if myString = 'myFunction' then
    myFunction();
8
AlexV

各関数をアクションに入れます。次に、名前でアクションを見つけて実行できます

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;
6
Rob McDonell

OK、私は非常にパーティーに遅れていますが、このコードを使用して名前でルーチンを呼び出すことができます(いくつかの制限が考えられます)

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;

誰かがDelphi7/2010でこれを必要とする場合に備えて

6
TheDude

関数を使用して読み取りおよび書き込み機能を実装する公開プロパティを使用して1つ以上のクラスを作成することにより、このようなことを行うことができます。次に、RTTIリフレクションを使用してプロパティを検出して参照し、基になる関数を呼び出すことができます。

または、関数ポインタをテーブルに格納したり、TStringListのObjectプロパティに格納して、文字列名で効果的にインデックスを付けたりすることもできます。

Delphiでは、名前で関数を直接呼び出すことはできません。

5
Larry Lustig

exportsおよび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;
2
Daniel
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;
0
MrClarc