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情報は次のとおりです
private
、protected
、public
、published
public
、published
public
、published
残念ながら、それはコアDelphiライブラリのRTTIを介してプライベートメソッドにアクセスできないことを意味します。 @ LU RDの答え は、拡張RTTIなしでクラスのプライベートメソッドアクセスを可能にするハックをカバーしています。
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])}
パフォーマンスに影響を与えないクリーンな方法が必要な場合でも、withステートメントを使用してレコードヘルパーからプライベートフィールドにアクセスできます。
function TValueHelper.GetAsInteger: Integer;
begin
with Self do begin
Result := FData.FAsSLong;
end;
end;
パフォーマンスの要求が高いコードがあるので、このメソッドを開いたままにしておくことを望みます。
拡張RTTIが利用できない場合、ハッキングと見なされるものに頼らないと、別のユニットのコードからプライベートメンバーにアクセスできません。もちろん、RTTIが利用可能な場合は、それを使用できます。
私の理解では、ヘルパーを使用してプライベートメンバーをクラックする機能は、意図しない事故でした。意図は、プライベートメンバーは同じユニット内のコードからのみ表示され、厳密なプライベートメンバーは同じクラス内のコードからのみ表示されることです。この変更により事故が修正されます。
コンパイラーにクラスをクラックさせる機能がなければ、他の方法でそれを行う必要があります。たとえば、TBase
クラスを十分に再宣言して、コンパイラーをだましてメンバーがどこに住んでいたかを通知することができます。
type
THackBase = class(TObject)
private
FMemberVar: integer;
end;
今書くことができます
var
obj: TBase;
....
MemberVar := THackBase(obj).FMemberVar;
しかし、これはひどくもろく、TBase
のレイアウトが変更されるとすぐに壊れます。
これはデータメンバーでは機能しますが、非仮想メソッドの場合は、ランタイムの逆アセンブリ手法を使用してコードの場所を見つける必要があるでしょう。仮想メンバーの場合、この手法を使用してVMTオフセットを見つけることができます。
参考文献:
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.
'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;
私はそれがとても簡単であることに驚きました:-)