Delphi: desplazamiento del campo de registro
record offset (2)
Estoy buscando formas de obtener el desplazamiento de un campo en un registro Delphi. Estos 2 métodos siguientes funcionan pero esperaba una manera más limpia. Básicamente me hubiera gustado que el tercer showmessage funcione. ¿Algunas ideas?
type
rec_a=record
a:longint;
b:byte;
c:pointer;
end;
{$warnings off}
function get_ofs1:longint;
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;
procedure TForm1.Button1Click(Sender: TObject);
begin
showmessage(inttostr(get_ofs1));
showmessage(inttostr(get_ofs2));
// showmessage(inttostr(longint(addr(rec_a.c)))); // is there a way to make this one work?
end;
editar: Muy bien, la respuesta a continuación funciona bien, ¡gracias! Como referencia, aquí está la salida del ensamblador para las diversas opciones:
---- 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: parece que este es un duplicado de una pregunta anterior : pregunta similar anterior como se indica a continuación por RRUZ. Como se muestra allí, otro método es declarar una variable global y usarla de la siguiente manera. Curiosamente, el compilador todavía no puede asignar el valor adecuado en tiempo de compilación como se ve en la salida del ensamblador, por lo tanto, para la eficiencia y la legibilidad, es mejor usar el método nil.
---- var ----
---- rec_a_ofs:rec_a; ----
---- ... ----
---- result:=longint(@rec_a_ofs.c)-longint(@rec_a_ofs); ----
mov eax,$0045f5d8
sub eax,$0045f5d0
edit3: Ok código revisado con todas las formas de hacerlo. Tenga en cuenta que el código de ensamblador generado para los modos 3ro, 4to y 5to (método de clase) es idéntico, ya sea que estén en línea o no. ¡Elige tu forma favorita cuando hagas estas cosas!
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;
También podría usar un enfoque genérico:
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;
Y llámalo así:
ShowMessage( IntToString( GetFieldOffset( TypeInfo(rec_a),''c'')));
No tan rápido como sus otras alternativas, pero ofrece una solución genérica unificada.
Mirando sus opciones aquí para una solución limpia, parece que lo mejor es declarar una función genérica:
function GetFieldOffset( const P : Pointer) : Integer; Inline;
// Example calls :
// GetFieldOffset( @PMyStruct(nil).MyParameter);
// GetFieldOffset( @TMyStruct(nil^).MyParameter);
begin
Result := Integer( P);
end;
Entonces, incluso si la llamada parece incómoda, el nombre de la función le dice lo que está sucediendo. Al inicializar la llamada, se elimina la sobrecarga de la llamada de función, por lo que funcionará como un embellecedor de código.
Es posible obtener valores constantes para una base de registro y una dirección de campo:
const
cStruct : MyStruct = ();
cMyInteger3Offs : Pointer = @cStruct.MyInteger3;
cMyStructBase : Pointer = @cStruct;
Pero esto no hará que el código se vea más limpio.
Yo siempre uso este enfoque:
Offset := Integer(@rec_a(nil^).c);
No dejes que el uso de nil^
te desanime, es perfectamente seguro. Y no se preocupe por el truncamiento del puntero de 64 bits. Si tiene un registro cuyo tamaño es> 4GB, ¡entonces tiene problemas mayores!