如何在没有助手的情况下访问私有方法?

Joh*_*ica 29 delphi private delphi-10.1-berlin

在Delphi 10 Seattle中,我可以使用以下代码来解决过于严格的可见性限制.

如何访问私有变量?

type 
  TBase = class(TObject)
  private
    FMemberVar: integer;
  end;
Run Code Online (Sandbox Code Playgroud)

我如何访问普通或虚拟私有方法?

type
  TBase2 = class(TObject) 
  private
    procedure UsefullButHidden;  
    procedure VirtualHidden; virtual;
    procedure PreviouslyProtected; override;
  end;
Run Code Online (Sandbox Code Playgroud)

以前我会使用类助手来打开基类.

type
  TBaseHelper = class helper for TBase
    function GetMemberVar: integer;
Run Code Online (Sandbox Code Playgroud)

在Delphi 10.1 Berlin中,类助手不再能够访问主题类或记录的私有成员.

是否有其他方式可以访问私人会员?

Dal*_*kar 21

如果为类私有成员(字段和/或方法)生成了扩展的RTTI信息,则可以使用它来获取对它们的访问权限.

当然,通过RTTI访问比通过类帮助程序更慢.

访问方法:

var
  Base: TBase2;
  Method: TRttiMethod;

  Method := TRttiContext.Create.GetType(TBase2).GetMethod('UsefullButHidden');
  Method.Invoke(Base, []);
Run Code Online (Sandbox Code Playgroud)

访问变量:

var
  Base: TBase;
  v: TValue;

  v := TRttiContext.Create.GetType(TBase).GetField('FMemberVar').GetValue(Base);
Run Code Online (Sandbox Code Playgroud)

为RTL/VCL/FMX类生成的默认RTTI信息如下

  • 字段- ,private,,protectedpublicpublished
  • 方法 - public,published
  • 属性 - public,published

不幸的是,这意味着无法通过RTTI访问核心Delphi库的私有方法.@LU RD的答案涵盖了允许对没有扩展RTTI的类进行私有方法访问的hack.

使用RTTI


LU *_* RD 19

在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;
Run Code Online (Sandbox Code Playgroud)

遗憾的是,无法通过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;
Run Code Online (Sandbox Code Playgroud)

这将有利于慢速RTTI部分仅执行一次.


注意:使用RTTI访问受保护/私有方法

RTL/VCL/FMX尚未声明使用RTTI访问受保护/私有方法的可见性.必须使用本地指令{$ RTTI}进行设置.

使用RTTI访问其他代码中的私有/受保护方法需要进行以下设置:

{$RTTI EXPLICIT METHODS([vcPublic, vcProtected, vcPrivate])}
Run Code Online (Sandbox Code Playgroud)

  • @RudyVelthuis,很难理解为什么Emba认为应该限制类帮助者完全访问该类.类助手是每个定义类本身.当然,他们可以做他们想做的事情,但是当他们可以将有限的资源用于有用的东西而不是我时,可以选择这个功能.找到这个漏洞的荣誉应归Uwe Schuster所有. (7认同)
  • @RudyVelthuis,OOP是分层的,而班级助手是横向的.帮助纠正中没有任何内容,帮助者具有另一个范围而不是类本身. (3认同)

Too*_*the 13

如果您想要一种不影响性能的干净方式,您仍然可以使用with语句从记录助手访问私有字段.

function TValueHelper.GetAsInteger: Integer;
begin
  with Self do begin
    Result := FData.FAsSLong;
  end;
end;
Run Code Online (Sandbox Code Playgroud)

我希望他们保持这种方法开放,因为我们的代码具有高性能要求.


Dav*_*nan 10

假设扩展的RTTI不可用,那么在不诉诸于被视为黑客攻击的情况下,您无法从不同单元中的代码访问私有成员.当然,如果RTTI可用,则可以使用它.

我的理解是,使用帮助者破解私人成员的能力是一次无意的事故.目的是私有成员只能从同一单元中的代码中看到,而严格的私有成员只能从同一个类中的代码中看到.这一变化纠正了事故.

如果没有让编译器为你破解课程的能力,你需要采用其他方法来实现这一目标.例如,您可以重新声明足够的TBase类,以便能够欺骗编译器告诉您成员所在的位置.

type
  THackBase = class(TObject)
  private
    FMemberVar: integer;
  end;
Run Code Online (Sandbox Code Playgroud)

现在你可以写了

var
  obj: TBase;
....
MemberVar := THackBase(obj).FMemberVar;  
Run Code Online (Sandbox Code Playgroud)

但这很脆弱,一旦布局TBase改变就会破裂.

这适用于数据成员,但对于非虚方法,您可能需要使用运行时反汇编技术来查找代码的位置.对于虚拟成员,此技术可用于查找VMT偏移量.

进一步阅读:

  • 我认为emba在关闭这个选项时给每个人带来了很大的伤害,推动人们进入**而不是**升级到最新版本,在我看来反作用. (10认同)
  • 我们只是作为最后的手段.老实说.没有实践经验,为什么你觉得你最了解. (3认同)
  • @RudyVelthuis 在这个答案中总有一种方法可以通过 hack 来访问它们。它可能是一个错误,但它用于关闭类助手漏洞的目的是什么?这是一个干净的解决方案。此更改阻止我们升级。柏林的新功能不足以证明实施此解决方法的工作是合理的。我们为什么用它?我们有代码挂钩来修复/更改 VCL 源代码中的内容。一是优化TWriter,二是用我们自己的高DPI代码替换TCustomForm中的ReadState代码。 (2认同)

ben*_*nok 5

如果您不需要 ARM 编译器支持,您可以在此处找到另一种解决方案。

使用内联汇编器,您可以轻松访问私有字段或方法。

我认为在大多数情况下David 的答案更好,但是如果您需要一个大型课程的快速解决方案,这种方法可能更有用。

更新(6 月 17 日):我刚刚注意到,我忘了分享他从他的帖子中访问私有字段的示例代码。对不起。

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;
Run Code Online (Sandbox Code Playgroud)

这是他调用私有方法的示例代码。

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.
Run Code Online (Sandbox Code Playgroud)

  • 是的,这将在 10.1 中起作用,但这只是一个疏忽,因此可能会在下一个版本中“修复”(损坏)。 (2认同)