Delphi: смещение поля записи
Я ищу способы получить смещение поля в записи Delphi. Эти два следующих метода работают, но я надеялся на более чистый способ. В принципе, мне бы понравилось работать третье шоу. Любые идеи?
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;
изменить:
Хорошо, ответ ниже работает отлично, спасибо! Для справки, здесь вывод ассемблера для различных опций:
---- 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: похоже, что это дубликат предыдущего вопроса: предыдущий аналогичный вопрос, как указано ниже RRUZ. Как показано здесь, другой метод заключается в объявлении глобальной переменной и использовании ее следующим образом. Как ни странно, компилятор все еще не может назначить правильное значение во время компиляции, как видно на выходе ассемблера, поэтому для эффективности и удобочитаемости лучше использовать метод 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 переработанный код со всеми известными способами для этого. Обратите внимание, что код ассемблера, сгенерированный для способов 3-го, 4-го и 5-го (класса), идентичен, независимо от того, являются ли они вложенными или нет. Выберите свой любимый способ, когда вы это сделаете!
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;
Ответы
Ответ 1
Я всегда использую этот подход:
Offset := Integer(@rec_a(nil^).c);
Не допускайте, чтобы использование nil^
отключилось, оно совершенно безопасно. И не беспокойтесь об усечении 64-битного указателя. Если у вас есть запись, размер которой составляет > 4 ГБ, тогда у вас больше проблем!
Ответ 2
Вы также можете использовать общий подход:
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;
И назовите его следующим образом:
ShowMessage( IntToString( GetFieldOffset( TypeInfo(rec_a),'c')));
Не так быстро, как другие альтернативы, но дает унифицированное общее решение.
Глядя на ваши варианты здесь для чистого решения, лучше всего объявить общую функцию:
function GetFieldOffset( const P : Pointer) : Integer; Inline;
// Example calls :
// GetFieldOffset( @PMyStruct(nil).MyParameter);
// GetFieldOffset( @TMyStruct(nil^).MyParameter);
begin
Result := Integer( P);
end;
Таким образом, даже если вызов выглядит неудобно, имя функции сообщает вам, что происходит.
Вложение вызова удаляет служебные данные вызова функции, поэтому он будет работать как средство для оформления кода.
Можно получить постоянные значения для базы данных и адреса поля:
const
cStruct : MyStruct = ();
cMyInteger3Offs : Pointer = @cStruct.MyInteger3;
cMyStructBase : Pointer = @cStruct;
Но это не сделает код более чистым.