2010-11-15 14 views
18

E 'possibile chiamare una funzione il cui nome è memorizzato in una stringa in Delphi?Delphi: Chiamare una funzione il cui nome è memorizzato in una stringa

+5

Se lo è, è ancora una cattiva idea (se non in casi molto rari). Cosa devi fare? – delnan

+1

@delnan Perché questa è una cattiva idea? Sto cercando, ad esempio, di implementare alcuni pulsanti creati dinamicamente come comandi di menu. Le funzioni da chiamare per ciascuna vengono estratte da un DB e inserite in un array. Quindi chiamo la procedura il cui nome è in un elemento dell'array, quando si fa clic su un particolare pulsante. Penso che sia carino. Perché è una cattiva idea? In realtà sto provando questo su Lazarus Pascal (simile a Delphi) – itsols

+0

@itsols Oltre a vari problemi tecnici (che * sono * importanti, ma richiedono spazio per spiegare e non * sempre * si applicano), l'obiezione principale è che confonde i nomi delle variabili con i dati dell'applicazione. Ned Batchelder ha scritto su confusione simile, nel contesto di una lingua in cui è in realtà facile da fare: [Tenere i dati fuori dai nomi delle variabili] (http://nedbatchelder.com/blog/201112/keep_data_out_of_your_variable_names.html). Usando le funzioni di prima classe più una struttura di dati di mappatura, o un'istruzione 'case', puoi ottenere la stessa cosa senza confondere i due. – delnan

risposta

21

Si prega di fornire maggiori dettagli su che cosa stai cercando di ottenere.

Per quanto ne so:

  • Non è possibile chiamare una funzione random del genere.
  • Per le funzioni classe e oggetto (MyObject.Function) questo può essere fatto con RTTI, ma è molto lavoro.
  • Se hai solo bisogno di chiamare un particolare tipo di funzioni (ad esempio, funzione (numero intero, intero): stringa), è molto più semplice.

Per l'ultimo, dichiarare un tipo di funzione, quindi ottenere un puntatore a funzione e gettato in questo modo:

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. 
+0

Deve essere possibile. Se VB6 può farlo, certamente Delphi deve permetterlo. – itsols

7

Se stai chiedendo se c'è qualcosa come il eval() JavaScript è possibile in Delphi, no questo non è (facilmente) realizzabile poiché Delphi compila il codice nativo.

Se è necessario solo per supportare alcune stringhe si può sempre fare molte if o un case ... Qualcosa di simile:

if myString = 'myFunction' then 
    myFunction(); 
+0

L'ho fatto in VB6 e ** è ** una lingua nativa. Quindi credo che sia possibile anche con Delphi (e nel mio caso Lazarus Pascal) – itsols

5

Si può fare qualcosa di simile per la lavorazione di una o più classi con proprietà pubblicati che utilizzare le funzioni per implementare le funzionalità di lettura e scrittura. Le proprietà possono quindi essere scoperte utilizzando la riflessione RTTI e referenziate, causando il richiamo delle funzioni sottostanti.

In alternativa, è possibile memorizzare i puntatori alle funzioni in una tabella, o anche la proprietà oggetto di TStringList ed efficace li indice nome di stringa.

Diritto chiamata di una funzione per nome non è possibile in Delphi.

+1

E ', per le funzioni di classe. RTTI può essere memorizzato anche per loro, anche con parametri e dettagli della convenzione di chiamata. Ma è PITA. – himself

8

Mi sorprende che nessuno ha suggerito un dispatch table. Questo è esattamente ciò che è per.

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. 

L'implementazione che ho scritto utilizza i generici in modo che funzionasse solo con Delphi 2009+. Per le versioni precedenti sarebbe probabilmente più semplice implementare utilizzando TStringList e command pattern

+0

+1 per l'idea, in particolare per Delphi 2009 e precedenti. Con Delphi 2010+, RTTI ha già tutte le informazioni sottostanti, quindi la risposta di Mohammed e Henri è più semplice. –

+0

Pagina non trovata. – ZzZombo

+0

Seriamente? SO spostato/eliminato una domanda senza aggiornare i collegamenti dal proprio sito web? –

11

Non hai specificato la tua versione Delphi, tuttavia se hai Delphi 2010 (+) puoi farlo usando il RTTI avanzato, non lo sono esperto di loro, ma ho provato questo campione per voi:

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; 

Le buone cose, se si dispone di procedura o funzione con parametri che funzionerà senza più lavoro.

+1

Si noti che questo codice perde almeno un'istanza 'TProcClass' per ogni metodo chiamato in questo modo. –

8

con Delphi 2010 è la utilizza JSON e SuperObject per richiamare il metodo con parametters.

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

Se avete bisogno, c'è anche un parser XML per trasformare xml a 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; 
+0

È oltre il punto ma ha ancora a che fare con SuperObject. So che sei l'uomo giusto al posto giusto :-). Puoi prestare un po 'di attenzione a [questa domanda] (http://stackoverflow.com/q/7841617/744588) da qualche parte su SO? – menjaraz

5

Inserire ciascuna funzione in un'azione. Quindi è possibile trovare l'Azione per nome ed eseguirlo

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; 
+0

+1; concetto interessante –

+0

@RobMcDonell +1 per questa idea. Ho fatto una cosa simile nel vecchio VB6 e sto cercando di portarlo a Lazarus (che sono molto nuovo). Penso che la tua linea 'TAction (MainForm.Components [I]). Execute;' sia il fulcro dell'intera cosa. Volete indirizzarmi a un doc adatto sul comando? Anche una semplice spiegazione potrebbe fare. Grazie! – itsols

4

OK, io sono molto in ritardo alla festa, ma si può sicuramente chiamare le routine per nome con questo codice (Ci sono alcune limitazioni pensiero)

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; 

Solo nel caso qualcuno ha bisogno di questo per Delphi 7/2010

+0

Scusami - Sono abbastanza nuovo su questa piattaforma. Come puoi assegnare un valore come quello che hai fatto nella prima linea?Che tipo di dati è TExec? – itsols

+1

@itsols: Certo ho modificato la mia risposta, in pratica metti la riga che dice 'TExec = procedura di Object;' solo * dopo * la parola chiave 'type' e questo è – TheDude

+0

+1 per quello ... È chiaro ora :) Grazie! – itsols

Problemi correlati