2013-06-04 26 views
9

Sto sperimentando la possibilità di richiamare dinamicamente procedure o funzioni che si trovano in una tabella di funzioni. L'applicazione specifica è una DLL che esporta un puntatore a una tabella di funzioni insieme alle informazioni sul numero di argomenti e tipi. L'applicazione host ha quindi la capacità di interrogare la DLL e chiamare le funzioni. Se fossero metodi oggetto, potrei usare Rtti per invocarli, ma sono normali procedure e funzioni. La DLL deve esportare normali puntatori a funzione non oggetti perché la DLL può essere scritto in qualsiasi lingua, tra cui C, Delphi, eccCome richiamare dinamicamente una procedura o una funzione denominata in Delphi

Per esempio, ho un record di dichiarata e compilato in una DLL:

TAPI = record 
     add : function (var a, b : double) : double; 
     mult : function (var a, b : double) : double; 
end; 
PAPI = ^TAPI; 

ho recuperare il puntatore a questo record, ha dichiarato come:

apiPtr : PAPI; 

Si supponga che ho anche l'accesso ai nomi delle procedure, il numero di argomenti e tipi di argomenti per ogni voce nel record.

Si supponga di voler chiamare la funzione di aggiunta. Il puntatore a funzione per aggiungere sarà:

@apiPtr^.add // I assume this will give me a pointer to the add function 

Presumo non c'è altro modo diverso da quello di utilizzare alcuni asm per spingere gli argomenti necessari sullo stack e recuperare il risultato?

Prima domanda, qual è la migliore convenzione di chiamata per dichiarare la procedura come, cdecl? Sembra più semplice per assemblare la pila prima della chiamata.

Seconda domanda, ci sono esempi online che effettivamente lo fanno? Mi sono imbattuto in http://www.swissdelphicenter.ch/torry/showcode.php?id=1745 (DynamicDllCall) che è vicino a quello che voglio, ma ho semplificato come sotto, ora restituisce un puntatore (EAX) al risultato:

function DynamicDllCall(proc : pointer; const Parameters: array of Pointer): pointer; 
var x, n: Integer; 
    p: Pointer; 
begin 
n := High(Parameters); 
if n > -1 then begin 
    x := n; 
    repeat 
    p := Parameters[x]; 
    asm 
     PUSH p 
    end; 
    Dec(x); 
    until x = -1; 
end; 
asm 
    CALL proc 
    MOV p, EAX <- must be changed to "FST result" if return value is double 
end; 
result := p; 

fine;

ma non riesco a farlo funzionare, restituisce un valore per i primi parametri anziché il risultato. Forse ho sbagliato la convenzione di chiamata o forse ho frainteso come recuperare il risultato in EAX.

io chiamo DynamicDllCall come segue:

var proc : pointer; 
    parameters: array of Pointer; 
    x, y, z : double; 
    p : pointer; 
begin 
    x:= 2.3; y := 6.7; 
    SetLength(parameters, 2); 
    parameters[0] := @x; parameters[1] := @y; 
    proc := @apiPtr^.add; 
    p := DynamicDllCall(proc, Parameters); 
    z := double (p^); 

Qualsiasi consiglio ben accetto. Apprezzo che alcuni possano pensare che questo non è il modo in cui si dovrebbe fare per farlo, ma sono comunque curioso di sapere se è almeno possibile.

Aggiornamento 1 Posso confermare che la funzione di aggiunta sta ottenendo i valori corretti per fare l'aggiunta.

Update 2 Se cambio la firma di aggiungere:

add : function (var a, b, c : double) : double; 

e assegnare il risultato a c all'interno aggiungere, quindi posso recuperare la risposta corretta nella matrice dei parametri (supponendo aggiungo uno più elemento ad esso, 3 invece di 2). Il problema quindi è che non capisco come vengono restituiti i valori dalle funzioni. Qualcuno può spiegare come funzioni restituiscono valori e come recuperarli al meglio?

Aggiornamento 3 Ho la mia risposta. Avrei dovuto indovinare. Delphi restituisce diversi tipi tramite diversi registri. ad es. gli interi ritornano tramite EAX, il doppio invece restituisce tramite ST (0). Per copiare ST (0) nella variabile risultato, devo usare "risultato FST" piuttosto che "MOV p, EAX". Almeno ora so che è possibile in linea di principio farlo. Se sia una cosa sensata da fare è un'altra questione che ora devo pensare.

+0

C'era una bella introduzione a Delphi ASM all'indirizzo www.delphi3000.com (articoli/articolo_3766.asp), ma l'intero sito non è più disponibile ... –

+1

Forse la documentazione su [Procedure e procedure di assemblaggio] (http : //docwiki.embarcadero.com/RADStudio/XE4/en/Assembly_Procedures_and_Functions) aiuterebbe. Vedi l'argomento 'Risultati delle funzioni'. –

+0

Non c'è molto là fuori e i documenti ufficiali non sono terribili utili.Comunque ho messo insieme abbastanza informazioni da: http://stackoverflow.com/questions/15786404/fld-instruction-x64-bit e http://www.guidogybels.eu/docs/Using%20Assembler%20in% 20Delphi.pdf – rhody

risposta

1

Questo è un problema difficile da risolvere. Un modo per accedere dinamicamente ai metodi in una DLL in fase di runtime consiste nell'utilizzare una libreria di interfacce di funzioni esterne come libffi, dyncall o DynaCall(). Tuttavia, nessuno di questi è stato trasferito nell'ambiente Delphi.

Se l'applicazione deve interfacciare un insieme di metodi in una DLL insieme con le informazioni Rtti fornite dalla DLL ed esporle a un linguaggio di scripting come Python, un'opzione è scrivere codice Delphi che ispeziona la DLL e scrive fuori uno script compatibile ctypes che può essere caricato in un interprete Python integrato in fase di runtime. Finché si definisce in anticipo un insieme limitato ma sufficiente di tipi che i metodi DLL possono gestire, questa è una soluzione pratica.

9

Questo è un problema XY: Vuoi fare X, e, per qualsiasi motivo, si è deciso Y è la soluzione, ma hai problemi a fare Y lavoro. Nel suo caso, X è funzioni esterne chiamata tramite puntatori e Y è spingere manualmente i parametri nello stack. Ma per realizzare X, non è proprio necessario fare Y.

L'espressione @apiPtr^.add non fornisce un puntatore alla funzione. Fornirà un puntatore al campo add del record TAPI. (Poiché add è il primo membro del record, l'indirizzo di tale campo sarà uguale all'indirizzo apiPtr; nel codice Assert(CompareMem(@apiPtr, @apiPtr^.add, SizeOf(Pointer)).) Il campo addcontiene un puntatore alla funzione, quindi se questo è ciò che si desidera , usa solo apiPtr^.add (e nota che lo ^ è opzionale in Delphi).

La migliore convenzione di chiamata da utilizzare è stdcall. Qualsiasi linguaggio che supporti l'esportazione di funzioni DLL supporterà tale convenzione di chiamata.

Non è necessario assemblatore o altre manipolazioni difficili per richiamare le proprie funzioni. Conosci già il tipo di funzione perché lo hai usato per dichiarare add. Per chiamare la funzione puntata da quel campo, è sufficiente utilizzare la stessa sintassi per chiamare una funzione ordinaria:

z := apiPtr.add(x, y); 

Il compilatore conosce il tipo dichiarato del campo add, in modo che provvederà lo stack per voi.

+0

Hai ragione che potrei usare apiPtr.add (x, y) e questo è quello che stavo usando. Ma significa che ho bisogno di dettagli sulla funzione add in fase di compilazione. Voglio chiamare add in base alle informazioni di runtime. Le mie DLL possono esportare metodi aggiuntivi per fornire le informazioni rtti su ciascuna funzione. Questa informazione dovrebbe consentirmi di costruire una chiamata al volo in fase di runtime. Perché? La mia particolare applicazione è quella di consentire che tali metodi siano automaticamente resi disponibili a un motore di scripting in fase di runtime. Il motore di scripting non dovrebbe preoccuparsi di quali funzioni sono disponibili nella DLL, basta passarle all'utente. – rhody

Problemi correlati