web-dev-qa-db-ja.com

ヘルパーなしでプライベートメソッドにアクセスする方法

Delphi 10シアトルでは、次のコードを使用して、過度に厳しい可視性の制限を回避できます。

プライベート変数にアクセスするにはどうすればよいですか?

type 
  TBase = class(TObject)
  private
    FMemberVar: integer;
  end;

そして、どうすればプレーンまたは仮想プライベートメソッドにアクセスできますか?

type
  TBase2 = class(TObject) 
  private
    procedure UsefullButHidden;  
    procedure VirtualHidden; virtual;
    procedure PreviouslyProtected; override;
  end;

以前は、クラスヘルパーを使用して基本クラスを壊していました。

type
  TBaseHelper = class helper for TBase
    function GetMemberVar: integer;

Delphi 10.1ベルリンでは、クラスヘルパーはサブジェクトクラスまたはレコードのプライベートメンバーにアクセスできなくなりました。

プライベートメンバーにアクセスする別の方法はありますか?

クラスプライベートメンバー用に生成された拡張RTTI情報がある場合-フィールドまたはメソッド、あるいはその両方を使用して、それらにアクセスできます。

もちろん、RTTIを介したアクセスは、クラスヘルパーを介した場合よりもはるかに低速です。

アクセス方法:

var
  Base: TBase2;
  Method: TRttiMethod;

  Method := TRttiContext.Create.GetType(TBase2).GetMethod('UsefullButHidden');
  Method.Invoke(Base, []);

変数へのアクセス:

var
  Base: TBase;
  v: TValue;

  v := TRttiContext.Create.GetType(TBase).GetField('FMemberVar').GetValue(Base);

RTL/VCL/FMXクラス用に生成されたデフォルトのRTTI情報は次のとおりです

  • フィールド-privateprotectedpublicpublished
  • メソッド-publicpublished
  • プロパティ-publicpublished

残念ながら、それはコアDelphiライブラリのRTTIを介してプライベートメソッドにアクセスできないことを意味します。 @ LU RDの答え は、拡張RTTIなしでクラスのプライベートメソッドアクセスを可能にするハックをカバーしています。

RTTIの使用

21

Delphi 10.1ベルリンのプライベートメソッドへのアクセスにclass helpersを使用する方法はまだあります。

type
  TBase2 = class(TObject) 
  private
    procedure UsefullButHidden;  
    procedure VirtualHidden; virtual;
    procedure PreviouslyProtected; override;
  end;

  TBase2Helper = class helper for TBase2
    procedure OpenAccess;
  end;

  procedure TBase2Helper.OpenAccess;
  var
    P : procedure of object;
  begin
    TMethod(P).Code := @TBase2.UsefullButHidden;
    TMethod(P).Data := Self;
    P; // Call UsefullButHidden;
    // etc
  end;

残念ながら、Delphi 10.1ベルリンでは、クラスヘルパーがstrict private/private fieldsにアクセスする方法はありません。 RTTIはオプションですが、パフォーマンスが重要な場合は低速と見なすことができます。

クラスヘルパーとRTTIを使用して、起動時にフィールドへのオフセットを定義する方法を次に示します。

type 
  TBase = class(TObject)
  private  // Or strict private
    FMemberVar: integer;
  end;

type
  TBaseHelper = class helper for TBase
  private
    class var MemberVarOffset: Integer;
    function GetMemberVar: Integer;
    procedure SetMemberVar(value: Integer);
  public
    class constructor Create;  // Executed at program start
    property MemberVar : Integer read GetMemberVar write SetMemberVar;
  end;

class constructor TBaseHelper.Create;
var
  ctx: TRTTIContext;
begin
  MemberVarOffset := ctx.GetType(TBase).GetField('FMemberVar').Offset;
end;

function TBaseHelper.GetMemberVar: Integer;
begin
  Result := PInteger(Pointer(NativeInt(Self) + MemberVarOffset))^;
end;

procedure TBaseHelper.SetMemberVar(value: Integer);
begin
  PInteger(Pointer(NativeInt(Self) + MemberVarOffset))^ := value;
end;

これには、遅いRTTI部分が1回だけ実行されるという利点があります。


注:保護されたメソッド/プライベートメソッドへのアクセスにRTTIを使用する

RTL/VCL/FMXは、RTTIによる保護された/プライベートメソッドへのアクセスの可視性を宣言していません。ローカルディレクティブ {$ RTTI} で設定する必要があります。

[〜#〜] rtti [〜#〜]を使用して他のコードでプライベート/保護されたメソッドにアクセスするには、たとえば次の設定が必要です。

{$RTTI EXPLICIT METHODS([vcPublic, vcProtected, vcPrivate])}
19
LU RD

パフォーマンスに影響を与えないクリーンな方法が必要な場合でも、withステートメントを使用してレコードヘルパーからプライベートフィールドにアクセスできます。

function TValueHelper.GetAsInteger: Integer;
begin
  with Self do begin
    Result := FData.FAsSLong;
  end;
end;

パフォーマンスの要求が高いコードがあるので、このメソッドを開いたままにしておくことを望みます。

12
Toon Krijthe

拡張RTTIが利用できない場合、ハッキングと見なされるものに頼らないと、別のユニットのコードからプライベートメンバーにアクセスできません。もちろん、RTTIが利用可能な場合は、それを使用できます。

私の理解では、ヘルパーを使用してプライベートメンバーをクラックする機能は、意図しない事故でした。意図は、プライベートメンバーは同じユニット内のコードからのみ表示され、厳密なプライベートメンバーは同じクラス内のコードからのみ表示されることです。この変更により事故が修正されます。

コンパイラーにクラスをクラックさせる機能がなければ、他の方法でそれを行う必要があります。たとえば、TBaseクラスを十分に再宣言して、コンパイラーをだましてメンバーがどこに住んでいたかを通知することができます。

type
  THackBase = class(TObject)
  private
    FMemberVar: integer;
  end;

今書くことができます

var
  obj: TBase;
....
MemberVar := THackBase(obj).FMemberVar;  

しかし、これはひどくもろく、TBaseのレイアウトが変更されるとすぐに壊れます。

これはデータメンバーでは機能しますが、非仮想メソッドの場合は、ランタイムの逆アセンブリ手法を使用してコードの場所を見つける必要があるでしょう。仮想メンバーの場合、この手法を使用してVMTオフセットを見つけることができます。

参考文献:

10
David Heffernan

ARMコンパイラサポートが必要ない場合は、別の解決策を こちら で見つけることができます。

インラインアセンブラを使用すると、プライベートフィールドまたはメソッドに簡単にアクセスできます。

ほとんどの場合、 Davidの答え の方が良いと思いますが、クイックソリューションが必要な場合はhugeクラスでは、このメソッドはより便利かもしれません。

pdate(June 17):気づきました。[プライベートフィールドへのアクセスのサンプルコードを共有するのを忘れていました post 。ごめんなさい。

unit UnitA;

type
  THoge = class
  private
    FPrivateValue: Integer;
    procedure PrivateMethod;
  end;
end.

unit UnitB;

type
  THogeHelper = class helper for THoge
  public
    function GetValue: Integer;
    procedure CallMethod;
  end;

function THogeHelper.GetValue: Integer;
asm
  MOV EAX,Self.FPrivateValue
end;

procedure THogeHelper.CallMethod;
asm
  CALL THoge.PrivateMethod
end;

これがcalling private methodのサンプルコードです。

type
  THoge = class
  private
    procedure PrivateMethod (Arg1, Arg2, Arg3 : Integer);
  end;

// Method 1
// Get only method pointer (if such there is a need to assign a method pointer to somewhere)
type
  THogePrivateProc = procedure (Self: THoge; Arg1, Arg2, Arg3: Integer);
  THogePrivateMethod = procedure (Arg1, Arg2, Arg3: Integer) of object;

function THogeHelper.GetMethodAddr: Pointer;
asm
  {$ifdef CPUX86}
  LEA EAX, THoge.PrivateMethod
  {$else}
  LEA RAX, THoge.PrivateMethod
  {$endif}
end;

var
  hoge: THoge;
  proc: THogePrivateProc;
  method: THogePrivateMethod;
begin
  // You can either in here of the way,
  proc := hoge.GetMethodAddr;
  proc (hoge, 1, 2, 3);
  // Even here of how good
  TMethod (method) .Code := hoge.GetMethodAddr;
  TMethod (method) .Data := hoge;
  method (1, 2, 3) ;
end;

// Method 2
// To jump (here is simple if you just simply call)
procedure THogeHelper.CallMethod (Arg1, Arg2, Arg3 : Integer);
asm
  JMP THoge.PrivateMethod
end;

unit UnitA;

type
  THoge = class
  private
    FPrivateValue: Integer;
    procedure PrivateMethod;
  end;
end.
4
benok

'with'ステートメントを使用してプライベートフィールドにアクセスするだけです!

この記事 から取得した以下のサンプルコードを参照してください。今日気づきました。 (ありがとう、いつも通り Mr.DEKO !)

このハックは、上記の記事で説明したように、2019年8月に QualityPortal で最初に報告されました。 (ログインが必要です)


書き換え前( "asm"メソッドを使用

function TPropertyEditorHelper.GetPropList: PInstPropList;
{$IF CompilerVersion < 31.0}
begin
  Result := Self.FPropList;
end;
{$ELSE}
// http://d.hatena.ne.jp/tales/20160420/1461081751
asm
  MOV EAX, Self.FPropList;
end;
{$IFEND}

「with」を使用して書き換えます

function TPropertyEditorHelper.GetPropList: PInstPropList;
begin
  with Self do
    Result := FPropList;
end;

私はそれがとても簡単であることに驚きました:-)

0
benok