Delphi:记录字段的偏移量

Delphi:记录字段的偏移量,delphi,record,offset,Delphi,Record,Offset,我正在寻找在Delphi记录中获取字段偏移量的方法。以下两种方法都有效,但我希望有一种更干净的方法。基本上,我希望第三个showmessage能够发挥作用。有什么想法吗 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)

我正在寻找在Delphi记录中获取字段偏移量的方法。以下两种方法都有效,但我希望有一种更干净的方法。基本上,我希望第三个showmessage能够发挥作用。有什么想法吗

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:好的,修改代码,并提供实现此目的的所有已知方法。请注意,为第三、第四和第五(类方法)方法生成的汇编代码是相同的,无论它们是否是内联的。当你开始做这些事情时,选择你最喜欢的方式

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;

我总是使用这种方法:

Offset := Integer(@rec_a(nil^).c);
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;

不要让
nil^
的使用让你感到不快,它非常安全。不要担心64位指针截断。如果您有一个大于4GB的记录,那么您就有更大的问题

您也可以使用通用方法:

Offset := Integer(@rec_a(nil^).c);
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;

没有其他备选方案那么快,但提供了统一的通用解决方案


在这里,您可以选择一个干净的解决方案,最好是声明一个泛型函数:

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;

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

偏移量
关键字在BASM代码中不需要;只是
mov eax,rec_a.c
。可能重复的@serg甚至冗余代码可能会增加soem安全性-在你没有预料到的地方消除歧义,或者在你输入错误的地方产生语法错误,更宽松的编码可能会编译成错误的结果太棒了!!!以我的ASM背景,我从没想过。。。羞愧。。。你试过使它成为常量吗?@Arioch我认为你不能为字段offsetWell生成常量表达式,至少这可以在类构造函数而不是实例构造函数中完成:-)我试过了,并且[const rec_a_field_c_offset=longint(@rec_a(nil^).c);]返回一个错误,说结果不是常量:/David,将类函数声明为内联将跳过函数调用并插入偏移量值。它不是声明意义上的常量,而是实际意义上的常量。这种方法还放弃了编译时类型检查和智能感知支持。@UliGerhardt,是的,有时使用rtti就是这样。我在寻找一个解决方案,在编译时可以解析“c”,但没有找到简单的方法。你找到一个困难的方法吗?我把这件事搞得太糟了,我仍然无法找到一种方法将记录字段偏移量设置为常量。同样值得注意的是,该答案中显示的方法仅适用于较新版本的delphi,较旧版本没有单元rtti。delphi-2010中添加了这种新的rtti可能性。我也做了一些关于将偏移量定义为常数的研究,但rtti和advanced records似乎都不可行。查看我的更新以获取摘要。感谢您的努力!遗憾的是,仍然无法干净地填充常量,为了将其作为int,我需要添加“var cMyInteger3Offs_int:longint absolute cMyInteger3Offs;”或在初始化部分初始化变量。在我完成开发后,与支持我的人核实后,看起来我将使用“内联nil class function”方法,因为他发现这是最有可能记住和理解的方法。谢谢大家对这个主题的投入!