我正在寻找获取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:看起来像是上一个问题的重复:previous similar question,如下面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:修改后的代码,知道完成此任务的方法。请注意,为第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;
答案 0 :(得分:18)
我总是使用这种方法:
Offset := Integer(@rec_a(nil^).c);
不要让nil^
的使用让你失望,这是非常安全的。并且不用担心64位指针截断。如果你有一个大小> 4GB的记录,那么你就会遇到更大的问题!
答案 1 :(得分: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;
但这不会使代码看起来更清晰。