Delphi:记录字段的偏移量

17

我正在寻找一种获得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

编辑2:看起来这是一个之前已有的问题的重复:之前相似的问题 正如RRUZ所指出的那样。 如在那里所示,另一种方法是声明一个全局变量并按如下方式使用它。 奇怪的是编译器仍然无法在编译时分配正确的值,正如汇编输出中所示,因此为了效率和可读性,最好使用空值方法。

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

编辑3:好的,用所有已知的方法修改了代码。请注意,生成的汇编代码对于第三种、第四种和第五种(类方法)方式是相同的,无论它们是否被内联。当你开始做这些事情时,请选择你喜欢的方式!

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;

在BASM代码中不需要使用“offset”关键字,只需使用“mov eax,rec_a.c”。 - kludg
1
可能是获取结构变量位置的重复问题。 - RRUZ
@serg 即使是冗余代码也可以增加一些安全性——消除你没有预料到的歧义或在你打错字时产生语法错误,而更加松散的编码将会被编译成错误的结果。 - Arioch 'The
2个回答

20

我始终采用这种方法:

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

不要因为使用nil^而感到困惑,它是完全安全的。不用担心64位指针截断。如果您有一个大小大于4GB的记录,则会有更大的问题!


太棒了!有了我的汇编语言背景,我从来没有想过那个...真遗憾...你试过把它设为const吗? - Arioch 'The
我尝试了一下,[const rec_a_field_c_offset=longint(@rec_a(nil^).c);] 返回一个错误,说结果不是常量 :/ - Marladu
1
David,将类函数声明为内联会跳过函数调用并插入偏移值。它在声明意义上不是const,但在实际意义上是const。 - LU RD
使用NIL的方法在Delphi .NET编译时会变得非常麻烦。 - Alex T
@Alex 我倾向于忽略那些已经停止更新的编译器。 - David Heffernan
显示剩余8条评论

3
您也可以使用通用方法:
```html

您也可以使用通用方法:

```
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;

但是这样并不能让代码看起来更加清晰。

这种方法也放弃了编译时类型检查和IntelliSense支持。 - Uli Gerhardt
@UliGerhardt,是的,有时使用rtti会出现这种情况。我寻找了一种解决方案,在编译时可以解析“c”,但没有找到简单的方法。 - LU RD
你是否也曾走了弯路?我在这个问题上花费了比应该更多的时间,但仍然找不到一种将记录字段偏移量放入常量中的方法。此外,需要注意的是,本答案中展示的方法仅适用于较新版本的Delphi,旧版本没有RTTI单元。 - Marladu
在Delphi-2010中新增了这个新的RTTI功能。我也进行了一些有关将偏移量定义为常量的研究,但是似乎无论是RTTI还是高级记录都不可行。请参见我的更新以获取摘要。 - LU RD
只是一个快速的提示,我不打算将其添加到顶部,但有一种已知的方法可以在非变量常量中获取偏移量,但由于许多原因,它非常糟糕,那就是使用sizeof(子记录),例如:'type rec_a_sub1=record a,b:longint end; const ofs_rec_a_c=sizeof(rec_a_sub1);'。它非常难以维护,即使在最注重性能的内部循环中也不值得使用,只为了节省1个汇编语句。 - Marladu
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接