17

我正在寻找获取 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

编辑2:看起来这是上一个问题的副本:RRUZ 在下面提到的上一个类似问题。如图所示,另一种方法是声明一个全局变量并按如下方式使用它。奇怪的是,编译器仍然无法在编译时分配正确的值,如汇编器输出所示,因此为了效率和可读性,最好使用 nil 方法。

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

编辑3:好的修改代码与所有知道的方法来实现这一点。请注意,为第 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;
4

2 回答 2

19

我总是使用这种方法:

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

不要让使用nil^让您失望,它非常安全。并且不用担心 64 位指针截断。如果您的记录大小> 4GB,那么您的问题就更大了!

于 2013-01-22T15:34:26.357 回答
3

您还可以使用通用方法:

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;

但这不会使代码看起来更干净。

于 2013-01-24T01:18:41.397 回答