Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Delphi: Offset of record field

I'm looking for ways to obtain the offset of a field in a Delphi record. These 2 following methods work but i was hoping for a cleaner way. Basically i would have liked the third showmessage to work. Any 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;

edit: Alright, the below answer works fine, thanks! For reference, here's the assembler output for the various options:

---- 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: looks like this is a duplicate of a previous question: previous similar question as noted below by RRUZ. As shown there, another method is to declare a global variable and use it as follows. Strangely enough the compiler still isnt able to assign the proper value at compile time as is seen in the assembler output, so for both efficiency and readability it`s better to use the nil method.

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

edit3: Ok revised code with all know ways to accomplish this. Note that the assembler code generated for the 3rd, 4th, and 5th (class method) ways is identical, whether they are inlined or not. Choose your favorite way when you get to do this stuff!

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;
like image 664
Marladu Avatar asked Jan 22 '13 15:01

Marladu


2 Answers

I always use this approach:

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

Don't let the use of nil^ put you off, it's perfectly safe. And don't worry about 64 bit pointer truncation. If you have a record whose size is >4GB then you have bigger problems!

like image 57
David Heffernan Avatar answered Oct 07 '22 00:10

David Heffernan


You could also use a generic approach:

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;

And call it like this:

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

Not as fast as your other alternatives, but gives a unified generic solution.


Looking at your options here for a clean solution, it seems the best is to declare a generic function:

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

So even if the call looks awkward, the function name tells you what's going on. Inlining the call removes the function call overhead, so it will work as a code beautifier.

It is possible to get constant values for a record base and field address:

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

But this will not make code look cleaner.

like image 41
LU RD Avatar answered Oct 06 '22 23:10

LU RD