17

Delphiレコードのフィールドのオフセットを取得する方法を探しています。これらの2つの方法は機能しますが、私はよりクリーンな方法を望んでいました。基本的に、私は3番目の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:これを達成するためのすべての既知の方法でコードを改訂しました。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 に答える