Delphi: смещение поля записи - программирование

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;
4b9b3361

Ответ 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;

Но это не сделает код более чистым.