How to access a private field from a class helper in Delphi 10.1 Berlin?

前端 未结 3 645
我寻月下人不归
我寻月下人不归 2020-12-09 20:07

I would like to use Gabriel Corneanu\'s jpegex, a class helper for jpeg.TJPEGImage. Reading this and this I\'ve learned that beyond Delphi Seattle you cannot access private

相关标签:
3条回答
  • 2020-12-09 20:12

    Beware! This is a nasty hack and can fail when the internal field structure of the hacked class changes.

    type
      TJPEGDataHack = class(TSharedImage)
        FData: TCustomMemoryStream; // must be at the same relative location as in TJPEGData!
      end;
    
      // TJPEGDataHelper
    function TJPEGDataHelper.Data: TCustomMemoryStream;
    begin
      Result := TJPEGDataHack(self).FData;
    end;
    

    This will only work if the parent class of the "hack" class is the same as the parent class of the original class. So, in this case, TJPEGData inherits from TSharedImage and so does the "hack" class. The positions also need to match up so if there was a field before FData in the list then an equivalent field should sit in the "hack" class, even if it's not used.

    A full description of how it works can be found here:

    Hack #5: Access to private fields

    0 讨论(0)
  • 2020-12-09 20:17

    By using a combination of a class helper and RTTI, it is possible to have the same performance as previous Delphi versions using class helpers.

    The trick is to resolve the offset of the private field at startup using RTTI, and store that inside the helper as a class var.

    type 
      TBase = class(TObject)
      private  // Or strict private
        FMemberVar: integer;
      end;
    
    type
      TBaseHelper = class helper for TBase // Can be declared in a different unit
      private
        class var MemberVarOffset: Integer;
        function GetMemberVar: Integer;
        procedure SetMemberVar(value: Integer);
      public
        class constructor Create;  // Executed automatically 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;
    

    As you can see it requires a bit of extra typing, but compared to patching a whole unit, it is simple enough.

    0 讨论(0)
  • 2020-12-09 20:30

    Today I found a neat way around this bug using the with statement.

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

    Besides that Embarcadero did a nice job building walls to protect the private parts and that's probably why they named it 10.1 Berlin.

    0 讨论(0)
提交回复
热议问题