How to access private methods without helpers?

前端 未结 6 1925
孤独总比滥情好
孤独总比滥情好 2020-11-28 06:39

In Delphi 10 Seattle I could use the following code to work around overly strict visibility restrictions.

How do I get access to private variables?



        
相关标签:
6条回答
  • 2020-11-28 07:04

    If you want a clean way that does not impact performance, you still can access private fields from a record helper using the with statement.

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

    I hope they keep this method open, because we have code with high performance demands.

    0 讨论(0)
  • 2020-11-28 07:07

    If there is extended RTTI info generated for the class private members - fields and/or methods you can use it to gain access to them.

    Of course, accessing through RTTI is way slower than it was through class helpers.

    Accessing methods:

    var
      Base: TBase2;
      Method: TRttiMethod;
    
      Method := TRttiContext.Create.GetType(TBase2).GetMethod('UsefullButHidden');
      Method.Invoke(Base, []);
    

    Accessing variables:

    var
      Base: TBase;
      v: TValue;
    
      v := TRttiContext.Create.GetType(TBase).GetField('FMemberVar').GetValue(Base);
    

    Default RTTI information generated for RTL/VCL/FMX classes is following

    • Fields - private, protected, public, published
    • Methods - public, published
    • Properties - public, published

    Unfortunately, that means accessing private methods via RTTI for core Delphi libraries is not available. @LU RD's answer covers hack that allows private method access for classes without extended RTTI.

    Working with RTTI

    0 讨论(0)
  • 2020-11-28 07:08

    Just use 'with' statement to access private fields !

    See the sample code below, taken from this article I noticed today. (Thanks, Mr.DEKO as always !)

    This hack is originally reported on QualityPortal in August 2019 as described on above aritcle. (login required)


    before rewrite (using "asm" method)

    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}
    

    rewrite using 'with'

    function TPropertyEditorHelper.GetPropList: PInstPropList;
    begin
      with Self do
        Result := FPropList;
    end;
    

    I was amazed it's so simple :-)

    0 讨论(0)
  • 2020-11-28 07:14

    If you don't need ARM compiler support, you can find another solution here.

    With inline asembler, you can access private field or method, easily.

    I think David's answer is better in most case, but if you need a quick solution for a huge class, this method could be more useful.

    Update(June 17): I've just noticed, I forgot to share his sample code for accessing private fields from his post. sorry.

    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;
    

    Here is his sample code for 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.
    
    0 讨论(0)
  • 2020-11-28 07:25

    There is still a way to use class helpers for access of private methods in Delphi 10.1 Berlin:

    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;
    

    Unfortunately there is no way to access strict private/private fields by class helpers with Delphi 10.1 Berlin. RTTI is an option, but can be considered slow if performance is critical.

    Here is a way to define the offset to a field at startup using class helpers and 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;
    

    This will have the benefit that the slow RTTI part is only executed once.


    Note: Using RTTI for access of protected/private methods

    The RTL/VCL/FMX have not declared visibility for access of protected/private methods with RTTI. It must be set with the local directive {$RTTI}.

    Using RTTI for access of private/protected methods in other code requires for example setting :

    {$RTTI EXPLICIT METHODS([vcPublic, vcProtected, vcPrivate])}
    
    0 讨论(0)
  • 2020-11-28 07:28

    Assuming that extended RTTI is not available, then without resorting to what would be considered hacking, you cannot access private members from code in a different unit. Of course, if RTTI is available it can be used.

    It is my understanding that the ability to crack private members using helpers was an unintentional accident. The intention is that private members only be visible from code in the same unit, and strict private members only be visible from code in the same class. This change corrects the accident.

    Without the ability to have the compiler crack the class for you, you would need to resort to other ways to do so. For instance, you could re-declare enough of the TBase class to be able to trick the compiler into telling you where a member lived.

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

    Now you can write

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

    But this is horrendously brittle and will break as soon as the layout of TBase is changed.

    That will work for data members, but for non-virtual methods, you'd probably need to use runtime disassembly techniques to find the location of the code. For virtual members this technique can be used to find the VMT offset.

    Further reading:

    • http://hallvards.blogspot.nl/2004/06/hack-5-access-to-private-fields.html
    • https://bitbucket.org/NickHodges/delphi-unit-tests/wiki/Accessing%20Private%20Members
    0 讨论(0)
提交回复
热议问题