33

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 Berlin では、クラス ヘルパーはサブジェクト クラスまたはレコードのプライベート メンバーにアクセスできなくなりました。

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

4

6 に答える 6

24

クラスのプライベート メンバー (フィールドおよび/またはメソッド) に対して生成された拡張 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 情報は次のとおりです。

  • フィールド- private、、、protected_publicpublished
  • メソッド - publicpublished
  • プロパティ - publicpublished

残念ながら、コア Delphi ライブラリのプライベート メソッドに RTTI 経由でアクセスすることはできません。@LU RD の回答は、拡張 RTTI なしでクラスのプライベート メソッド アクセスを許可するハックをカバーしています。

RTTI の操作

于 2016-04-19T12:07:01.250 に答える
22

Delphi 10.1 Berlin では、プライベート メソッド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 Berlin では、クラス ヘルパーによって厳密なプライベート/プライベート フィールドにアクセスする方法はありません。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])}
于 2016-06-11T08:55:30.597 に答える
11

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

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

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

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

今、あなたは書くことができます

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

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

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

参考文献:

于 2016-04-19T11:12:52.803 に答える