Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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 fields anymore like jpegex does (FData in the example below). Poking around with the VMT like David Heffernan proposed is far beyond me. Is there any easier way to get this done?

   type
  // helper to access TJPEGData fields
  TJPEGDataHelper = class helper for TJPEGData
    function  Data: TCustomMemoryStream; inline;
    procedure SetData(D: TCustomMemoryStream);
    procedure SetSize(W,H: integer);
  end;

// TJPEGDataHelper
function TJPEGDataHelper.Data: TCustomMemoryStream;
begin
  Result := self.FData;
end;
like image 348
stackmik Avatar asked May 20 '16 16:05

stackmik


3 Answers

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.

like image 159
Toon Krijthe Avatar answered Nov 02 '22 03:11

Toon Krijthe


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

like image 24
Uwe Raabe Avatar answered Nov 02 '22 04:11

Uwe Raabe


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.

like image 10
LU RD Avatar answered Nov 02 '22 02:11

LU RD