2013-01-22 6 views
16

Sto cercando modi per ottenere l'offset di un campo in un record Delphi. Questi 2 metodi seguenti funzionano ma speravo in un modo più pulito. In sostanza avrei voluto che il terzo showmessage funzionasse. Qualche idea?Delphi: Offset del campo record

edit: Bene, la risposta qui sotto funziona bene, grazie! Per avere un riferimento, ecco l'output assembler per le varie opzioni:

---- result:=longint(@abc.c)-longint(abc); ---- 
lea edx,[eax+$08] 
sub edx,eax 
mov eax,edx 

---- mov eax,offset rec_a.c ---- 
mov eax,$00000008 

---- result:=longint(@rec_a(nil^).c); ---- 
xor eax,eax 
add eax,$08 

EDIT2: Sembra che questo è un duplicato di una domanda precedente: previous similar question come indicato di seguito per RRUZ. Come mostrato qui, un altro metodo è dichiarare una variabile globale e usarla come segue. Stranamente il compilatore non è ancora in grado di assegnare il valore corretto al momento della compilazione, come si vede nell'output dell'assembler, quindi per efficienza e leggibilità è meglio usare il metodo nil.

---- var ---- 
---- rec_a_ofs:rec_a; ---- 
---- ... ---- 
---- result:=longint(@rec_a_ofs.c)-longint(@rec_a_ofs); ---- 
mov eax,$0045f5d8 
sub eax,$0045f5d0 

edit3: codice revisionato Ok con tutti i metodi per farlo. Si noti che il codice assembler generato per il 3 °, 4 ° e 5 ° metodo (metodo di classe) è identico, sia che siano in linea o meno. Scegli la tua strada preferita quando arrivi a fare questa roba!

type 
prec_a=^rec_a; 
rec_a=record 
    a:longint; 
    b:byte; 
    c:pointer; 

    class function offset_c:longint;static;inline; 
end; 

//const 
// rec_a_field_c_offset=longint(@rec_a(nil^).c); // no known way to make this work 

{$warnings off} 
function get_ofs1:longint;inline; 
var 
abc:^rec_a; 
begin 
result:=longint(@abc.c)-longint(abc); 
end; 
{$warnings on} 

function get_ofs2:longint; 
asm 
mov eax,offset rec_a.c 
end; 

function get_ofs3:longint;inline; 
begin 
result:=longint(@rec_a(nil^).c); 
end; 

function get_ofs4:longint;inline; 
begin 
result:=longint(@prec_a(nil).c); 
end; 

class function rec_a.offset_c:longint; 
begin 
result:=longint(@prec_a(nil).c); 
end; 

var 
rec_a_ofs:rec_a; 

function get_ofs6:longint;inline; 
begin 
result:=longint(@rec_a_ofs.c)-longint(@rec_a_ofs); 
end; 

procedure TForm1.Button1Click(Sender: TObject); 
begin 
showmessage(inttostr(get_ofs1)); 
showmessage(inttostr(get_ofs2)); 
showmessage(inttostr(get_ofs3)); 
showmessage(inttostr(get_ofs4)); 
showmessage(inttostr(rec_a.offset_c)); 
showmessage(inttostr(get_ofs6)); 
// showmessage(inttostr(rec_a_field_c_offset)); 
end; 
+0

La parola chiave 'offset' non è necessaria nel codice BASM; solo "mov eax, rec_a.c'. – kludg

+1

possibile duplicato di [Ottieni posizione di una struct var] (http: // stackoverflow.it/questions/13168959/get-position-of-a-struct-var) – RRUZ

+0

@serg codice ridondante può aggiungere sicurezza a Soem - uccidere l'ambiguità dove non te l'aspettavi o fare errori di sintassi dove hai scritto male e codifica più rilassata sarebbe compilabile con risultati errati –

risposta

17

Io uso sempre questo approccio:

Offset := Integer(@rec_a(nil^).c); 

Non lasciate che l'uso di nil^ scoraggiare, è perfettamente sicuro. E non preoccuparti del troncamento del puntatore a 64 bit. Se hai un record con una dimensione> 4 GB hai problemi più grandi!

+0

Grazie per la risposta buona e veloce David. – Marladu

+0

Fantastico !!! con il mio background ASM non ho mai pensato a quello ... vergogna ... Hai provato a renderlo const? –

+0

@Arioch Non penso che tu possa creare un'espressione const per un offset di campo –

2

Si potrebbe anche usare un approccio generico:

uses 
    System.SysUtils,TypInfo,RTTI; 

function GetFieldOffset(ARecordTypeInfo : PTypeInfo; 
         const ARecordFieldName : String) : Integer; 
var 
    MyContext: TRttiContext; 
    MyField: TRttiField; 
begin 
    if (ARecordTypeInfo.Kind <> tkRecord) then 
    raise Exception.Create('Not a record type'); 
    for MyField in MyContext.GetType(ARecordTypeInfo).GetFields do 
    if MyField.Name = ARecordFieldName then 
    begin 
     Exit(MyField.Offset); 
    end; 
    raise Exception.Create('No such field name:'+ARecordFieldName); 
end; 

E chiamare in questo modo:

ShowMessage(IntToString(GetFieldOffset(TypeInfo(rec_a),'c'))); 

non così rapidamente come le vostre altre alternative, ma dà una soluzione generica unificata.


Guardando le opzioni qui per una soluzione pulita, sembra che la migliore è quella di dichiarare una funzione generica:

function GetFieldOffset(const P : Pointer) : Integer; Inline; 
// Example calls : 
// GetFieldOffset(@PMyStruct(nil).MyParameter); 
// GetFieldOffset(@TMyStruct(nil^).MyParameter); 
begin 
    Result := Integer(P); 
end; 

Quindi, anche se la chiamata appare goffo, il nome della funzione dice cosa sta succedendo sopra. Inlining la chiamata rimuove l'overhead di chiamata di funzione, quindi funzionerà come un beautifier di codice.

E 'possibile ottenere valori costanti per un indirizzo di base di registrazione e campo:

const 
    cStruct : MyStruct =(); 
    cMyInteger3Offs : Pointer = @cStruct.MyInteger3; 
    cMyStructBase : Pointer = @cStruct; 

Ma questo non renderà più pulito il codice look.

+0

Questo approccio consente anche il controllo del tipo in fase di compilazione e il supporto IntelliSense. –

+0

@UliGerhardt, sì, a volte succede usando rtti. Ho cercato una soluzione in cui 'c' potesse essere risolto in fase di compilazione, ma non ha trovato un modo semplice. –

+0

Hai trovato un modo difficile? Ho incasinato così tanto più di quanto avrei dovuto e ancora non avrei mai trovato il modo di mettere un offset di un campo di record in una costante. Inoltre, il metodo mostrato in questa risposta è che funziona solo con le versioni più recenti di delphi, le versioni precedenti non hanno l'unità rtti. – Marladu