Domanda

Questa domanda si basa su a precedente, ma questo è solo FYI.

Sono riuscito a farlo funzionare, tuttavia, ho trovato qualcosa che non mi è chiaro, quindi se qualcuno può spiegare il seguente comportamento, sarebbe fantastico.

Ho la seguente classe:

type
  TMyObj = class
  published
    procedure testex(const s: string; const i: integer);
  end;

procedure TMyObj.testex(const s: string; const i: integer);
begin
  ShowMessage(s + IntToStr(i));
end;

e le seguenti due procedure:

procedure CallObjMethWorking(AMethod: TMethod; const AStrValue: string; const AIntValue: Integer);
begin
  asm
    PUSH DWORD PTR AIntValue;
    PUSH DWORD PTR AStrValue;
    CALL AMethod.Code;
  end;
end;

procedure CallObjMethNOTWorking(AInstance, ACode: Pointer; const AStrValue: string; const AIntValue: Integer);
begin
  asm
    MOV EAX, AInstance;
    PUSH DWORD PTR AIntValue;
    PUSH DWORD PTR AStrValue;
    CALL ACode;
  end;
end;

Per testare la versione funzionante, è necessario chiamare quanto segue:

procedure ...;
var
  LObj: TMyObj;
  LMethod: TMethod;
  LStrVal: string;
  LIntVal: Integer;
begin
  LObj := TMyObj.Create;
  try
    LMethod.Data := Pointer( LObj );
    LMethod.Code := LObj.MethodAddress('testex');

    LStrVal := 'The year is:' + sLineBreak;
    LIntVal := 2012;

    CallObjMethWorking(LMethod, LStrVal, LIntVal);
  finally
    LObj.Free;
  end; // tryf
end;

e per testare il NON Versione funzionante:

procedure ...;
var
  LObj: TMyObj;
  LCode: Pointer;
  LData: Pointer;
  LStrVal: string;
  LIntVal: Integer;
begin
  LObj := TMyObj.Create;
  try
    LData := Pointer( LObj );
    LCode := LObj.MethodAddress('testex');

    LStrVal := 'The year is:' + sLineBreak;
    LIntVal := 2012;

    CallObjMethNOTWorking(LData, LCode, LStrVal, LIntVal);
  finally
    LObj.Free;
  end; // tryf
end;

E infine la domanda: perché non lo è Callobjmethnotworking Lavorare, mentre Callobjmethworking è? Immagino che ci sia qualcosa di speciale nel modo in cui il compilatore tratta TMethod ... ma poiché la mia conoscenza dell'assemblea è limitata, non riesco a capirlo.

Apprezzerei molto se qualcuno potesse spiegarmi questo, grazie!

È stato utile?

Soluzione

La convenzione di chiamata predefinita a Delphi Win32 è "Register". Il primo parametro viene passato in EAX, il secondo in EDX e il terzo in ECX. Lo stack viene utilizzato solo se ci sono più di tre parametri o se vengono superati i tipi di valore maggiori di 4 byte, ma non è così nel tuo esempio.

La tua prima procedura di callobjmethworking funziona perché il compilatore ha già Posizionato Astrvalue in EDX e Aintvalue in ECX quando si chiamava callobjmethworking. Tuttavia, dal momento che non stai ripulendo le tue due istruzioni di spinta, le cose cattive sono destinate a verificarsi quando la procedura ritorna.

Il tuo codice dovrebbe apparire così. La direttiva STDCall è facoltativa in questo caso, ma potrebbe essere una buona idea usarla per cose come questa per assicurarsi che i tuoi parametri non siano persi perché usi i registri per altri scopi prima di andare in giro per chiamare effettivamente il metodo:

procedure CallObjMeth(AInstance, ACode: Pointer; const AStrValue: string; const AIntValue: Integer); stdcall;
asm
  MOV EAX, AInstance;
  MOV EDX, DWORD PTR AStrValue;
  MOV ECX DWORD PTR AIntValue;
  CALL ACode;
end;

Altri suggerimenti

Henrick Hellström ha ragione con il suo Rispondere, e noto che la tua domanda è taggata con Delphi 2010 e quindi riguarda solo Win32. Tuttavia, potresti essere interessato a vedere come sarebbe la situazione se andassi avanti a Win64 (Delphi> = XE2), quindi ho aggiunto una versione di Win64 di esempio al codice di Henrick:

procedure CallObjMeth(AInstance, ACode: Pointer; const AStrValue: string; const AIntValue: Integer); stdcall;
asm
{$IFDEF CPU386}
  MOV EAX, AInstance;
  MOV EDX, DWORD PTR AStrValue;
  MOV ECX, DWORD PTR AIntValue;
  {$IFDEF MACOS}
   //On MacOSX32 ESP = #######Ch here       
   SUB ESP, 0Ch  
  {$ENDIF}     
  CALL ACode;
  {$IFDEF MACOS}
   ADD ESP, 0Ch // restoring stack
  {$ENDIF}     
{$ENDIF}
{$IFDEF CPUX64}{$IFDEF WIN64} // <- see comments
  .NOFRAME //Disable stack frame generation
  //MOV RCX, AInstance {RCX} //<- not necessary because AInstance already is in RCX
  MOV R10, ACode {RDX}
  MOV RDX, AStrValue {R8}
  MOV R8D, AIntValue {R9D}
  SUB RSP, 28h    //Set up stack shadow space and align stack: 4*8 bytes for 4 params + 8 bytes bytes for alignment
  {$IFNDEF DO_NOT_TEST_STACK_ALIGNMENT}
  MOVDQA XMM5, [RSP]  //Ensure that RSP is aligned to DQWORD boundary -> exception otherwise
  {$ENDIF}
  CALL R10 //ACode
  ADD RSP, 28h  //Restore stack
{$ENDIF}{$ENDIF}
end;

Esistono diverse note esplicative da prendere:

1) ASM dichiarazione: In Delphi XE2 X64 non c'è miscelazione di codice Pascal e ASM, quindi l'unico modo per scrivere il codice di assemblaggio è in una routine che è composta da un singolo asm..end Blocco, no begin..end. Nota che il begin..end Intorno al tuo codice ASM a 32 bit ha anche un effetto. In particolare, stai forzando la generazione di una cornice dello stack e lascia che il compilatore effettui copie locali dei parametri della funzione. (Se ricorri all'utilizzo dell'assemblaggio in primo luogo, potresti non vorre che il compilatore lo facesse.)

2) Chiamare Convenzione: Su Win64, c'è solo una singola convenzione di chiamata. Cose come register e stdcall sono effettivamente insignificanti; E 'tutto lo stesso, WIN64 di Microsoft WIN64 Convention. È essenzialmente questo: i parametri vengono passati RCX, RDX, R8 e R9 registri (e/o XMM0-XMM4, restituire i valori in RAX/XMM0. I valori più grandi di 64 bit vengono passati tramite riferimento.

Le funzioni chiamate possono usare: RAX, RCX, RDX, R8-R11, ST(0)-ST(7), XMM0-XMM5, YMM0-YMM5, YMM6H-YMM15H, e deve preservare RBX, RSI, RDI, RBP, R12-R15, XMM6-XMM15. Se del caso, le funzioni chiamate devono emettere CLD/EMMS/VZEROUPPER Istruzioni per ripristinare la CPU allo stato previsto.

3) Allineamento e spazio ombraÈ importante sottolineare che ogni funzione ha il suo spazio ombra sullo stack, che è almeno 4 anni di spazio dello stack di Qword Param, anche se non ci sono parametri e indipendentemente dal fatto che la funzione chiamata lo tocchi effettivamente. Inoltre, nel sito di ciascuna funzione di funzione (a ciascuno CALL dichiarazione), RSP dovrebbe essere allineato a 16 byte (lo stesso per ESP su macOSX32, btw.). Questo spesso porta a cose come: sub rsp, ##; call $$; add rsp, ## costrutti in cui ## sarebbe la somma dei parametri (Qword) con cui la funzione deve essere chiamata, oltre a 8 byte opzionali per l'allineamento di RSP. Si noti che l'allineamento di RSP al CALL si traduce il sito RSP = ###8h All'ingresso della funzione (perché CALL mette l'indirizzo di ritorno sullo stack), quindi supponendo che nessuno si prenda RSP Prima di farlo, puoi aspettarti che sia quello.

Nell'esempio fornito, SSE2 MOVDQA l'istruzione viene utilizzata per testare l'allineamento di RSP. (XMM5 viene utilizzato come registro di destinazione perché può essere liberamente modificato ma non può contenere dati dei parametri della funzione).

4) ipotesiIl codice qui presuppone che il compilatore non inserisca il codice per modificare RSP. Potrebbero esserci situazioni in cui ciò potrebbe non essere vero, quindi fai attenzione a fare questo presupposto.

5) Gestione delle eccezioni La gestione delle eccezioni in Win64 è un po 'complicata e dovrebbe essere eseguita correttamente dal compilatore (il codice di esempio sopra non lo fa). Per consentire al compilatore di farlo, idealmente il codice dovrebbe utilizzare le nuove direttive BASM/istruzioni pseudo- .PARAMS, .PUSHNV e .SAVENV come delineato da Allen Bauer qui. Data la situazione giusta (sbagliata), altrimenti potrebbero accadere cose cattive.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top