如何在没有助手的情况下访问私有方法?
How to access private methods without helpers?
在 Delphi 10 Seattle 我可以使用以下代码来解决过于严格的可见性限制。
如何访问私有变量?
type
TBase = class(TObject)
private
FMemberVar: integer;
end;
以及如何访问普通或虚拟私有方法?
type
TBase2 = class(TObject)
private
procedure UsefullButHidden;
procedure VirtualHidden; virtual;
procedure PreviouslyProtected; override;
end;
Previously I would use a class helper to break open the base class.
type
TBaseHelper = class helper for TBase
function GetMemberVar: integer;
在 Delphi 10.1 Berlin 中,class 助手不再有权访问主题 class 或记录的私有成员。
是否有其他方式访问私有成员?
假设扩展的 RTTI 不可用,那么如果不诉诸被认为是黑客攻击的手段,您将无法从不同单元的代码访问私有成员。当然,如果有 RTTI 就可以使用。
据我了解,使用助手破解私有成员是一个无意的意外。其目的是私有成员仅在同一单元的代码中可见,而严格的私有成员仅在同一 class 中的代码中可见。此更改纠正了事故。
如果编译器无法为您破解 class,您将需要求助于其他方法。例如,您可以重新声明足够多的 TBase
class 来欺骗编译器告诉您某个成员住在哪里。
type
THackBase = class(TObject)
private
FMemberVar: integer;
end;
现在你可以写了
var
obj: TBase;
....
MemberVar := THackBase(obj).FMemberVar;
但这非常脆弱,一旦更改 TBase
的布局就会破裂。
这适用于数据成员,但对于非虚拟方法,您可能需要使用运行时反汇编技术来查找代码的位置。对于虚拟成员,此技术可用于查找 VMT 偏移量。
延伸阅读:
如果有为 class 私有成员生成的扩展 RTTI 信息 - 字段 and/or 方法,您可以使用它来访问它们。
当然,通过 RTTI 访问比通过 class 助手访问要慢得多。
访问方式:
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 classes 生成的默认 RTTI 信息如下
- 字段 -
private
、protected
、public
、published
- 方法 -
public
、published
- 属性 -
public
、published
不幸的是,这意味着无法通过 RTTI 访问核心 Delphi 库的私有方法。 涵盖了允许 classes 在没有扩展 RTTI 的情况下访问私有方法的 hack。
如果不需要ARM编译器支持,可以另寻解决方案here。
使用内联汇编器,您可以轻松访问私有字段或方法。
我认为在大多数情况下 更好,但是如果您需要 快速解决方案 巨大 class,这个方法可能更有用。
更新(6 月 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;
这是他调用私有方法的示例代码。
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.
在 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;
不幸的是,class 10.1 Berlin 的 class 助手无法访问 strict private/private 字段 。 RTTI 是一个选项,但如果性能至关重要,则可以认为它很慢。
这是一种在启动时使用 class 助手和 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 部分只执行一次。
注意:使用 RTTI 访问 protected/private 方法
RTL/VCL/FMX 尚未声明使用 RTTI 访问 protected/private 方法的可见性。必须使用本地指令 {$RTTI}.
进行设置
使用RTTI访问其他代码中的private/protected方法需要例如设置:
{$RTTI EXPLICIT METHODS([vcPublic, vcProtected, vcPrivate])}
如果您想要一种不影响性能的简洁方式,您仍然可以使用 with 语句从记录助手访问私有字段。
function TValueHelper.GetAsInteger: Integer;
begin
with Self do begin
Result := FData.FAsSLong;
end;
end;
我希望他们保持这个方法开放,因为我们有高性能要求的代码。
只需使用'with'语句访问私有字段!
请参阅下面的示例代码,一如既往地取自 this article I noticed today. (Thanks, Mr.DEKO!)
如上文所述,此黑客最初于 2019 年 8 月在 QualityPortal 上报道。 (需要登录)
重写前(使用)
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;
我很惊讶它是如此简单:-)
在 Delphi 10 Seattle 我可以使用以下代码来解决过于严格的可见性限制。
如何访问私有变量?
type
TBase = class(TObject)
private
FMemberVar: integer;
end;
以及如何访问普通或虚拟私有方法?
type
TBase2 = class(TObject)
private
procedure UsefullButHidden;
procedure VirtualHidden; virtual;
procedure PreviouslyProtected; override;
end;
Previously I would use a class helper to break open the base class.
type
TBaseHelper = class helper for TBase
function GetMemberVar: integer;
在 Delphi 10.1 Berlin 中,class 助手不再有权访问主题 class 或记录的私有成员。
是否有其他方式访问私有成员?
假设扩展的 RTTI 不可用,那么如果不诉诸被认为是黑客攻击的手段,您将无法从不同单元的代码访问私有成员。当然,如果有 RTTI 就可以使用。
据我了解,使用助手破解私有成员是一个无意的意外。其目的是私有成员仅在同一单元的代码中可见,而严格的私有成员仅在同一 class 中的代码中可见。此更改纠正了事故。
如果编译器无法为您破解 class,您将需要求助于其他方法。例如,您可以重新声明足够多的 TBase
class 来欺骗编译器告诉您某个成员住在哪里。
type
THackBase = class(TObject)
private
FMemberVar: integer;
end;
现在你可以写了
var
obj: TBase;
....
MemberVar := THackBase(obj).FMemberVar;
但这非常脆弱,一旦更改 TBase
的布局就会破裂。
这适用于数据成员,但对于非虚拟方法,您可能需要使用运行时反汇编技术来查找代码的位置。对于虚拟成员,此技术可用于查找 VMT 偏移量。
延伸阅读:
如果有为 class 私有成员生成的扩展 RTTI 信息 - 字段 and/or 方法,您可以使用它来访问它们。
当然,通过 RTTI 访问比通过 class 助手访问要慢得多。
访问方式:
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 classes 生成的默认 RTTI 信息如下
- 字段 -
private
、protected
、public
、published
- 方法 -
public
、published
- 属性 -
public
、published
不幸的是,这意味着无法通过 RTTI 访问核心 Delphi 库的私有方法。
如果不需要ARM编译器支持,可以另寻解决方案here。
使用内联汇编器,您可以轻松访问私有字段或方法。
我认为在大多数情况下
更新(6 月 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;
这是他调用私有方法的示例代码。
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.
在 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;
不幸的是,class 10.1 Berlin 的 class 助手无法访问 strict private/private 字段 。 RTTI 是一个选项,但如果性能至关重要,则可以认为它很慢。
这是一种在启动时使用 class 助手和 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 部分只执行一次。
注意:使用 RTTI 访问 protected/private 方法
RTL/VCL/FMX 尚未声明使用 RTTI 访问 protected/private 方法的可见性。必须使用本地指令 {$RTTI}.
进行设置使用RTTI访问其他代码中的private/protected方法需要例如设置:
{$RTTI EXPLICIT METHODS([vcPublic, vcProtected, vcPrivate])}
如果您想要一种不影响性能的简洁方式,您仍然可以使用 with 语句从记录助手访问私有字段。
function TValueHelper.GetAsInteger: Integer;
begin
with Self do begin
Result := FData.FAsSLong;
end;
end;
我希望他们保持这个方法开放,因为我们有高性能要求的代码。
只需使用'with'语句访问私有字段!
请参阅下面的示例代码,一如既往地取自 this article I noticed today. (Thanks, Mr.DEKO!)
如上文所述,此黑客最初于 2019 年 8 月在 QualityPortal 上报道。 (需要登录)
重写前(使用
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;
我很惊讶它是如此简单:-)