33

在 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;

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

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, public,published
  • 方法 - public,published
  • 属性 - public,published

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

使用 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 部分只执行一次。


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

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

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

{$RTTI EXPLICIT METHODS([vcPublic, vcProtected, vcPrivate])}
于 2016-06-11T08:55:30.597 回答
15

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

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

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

于 2017-03-21T19:54:29.230 回答
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 回答
5

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

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

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

更新(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;

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

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.
于 2016-04-21T09:33:58.353 回答
3

只需使用'with'语句来访问私有字段!

请参阅下面的示例代码,取自我今天注意到的这篇文章。(一如既往地感谢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;

我很惊讶它是如此简单:-)

于 2019-11-28T10:12:20.910 回答