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

3

11

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;
Reber answered 20/5, 2016 at 16:11 Comment(9)
You know that the answer is no. There is no magic.Vidrine
The documentation clearly says this is not possible starting with 10.1 Berlin. What part of you can't do that anymore isn't clear?Milamilady
Did you mean Gabriel Corneanu?Arenicolous
@Ken White: David's remark "Why not modify the VMT?" seemed to indicate that it was not altogether impossible.Reber
@Free Consulting: Of course. Sorry. Corrected.Reber
Poking around in the VMT will not help here anyway, because the function you'd need to access is not virtual. As such only direct access to the data is an option.Tavarez
FWIW, you can always use RTTI. Not so convenient, but a possiblity. I personally think you should not use RTTI either, and try a totally different approach that does not require you to access private data.Bailsman
@Rudy Velthuis: I need this specificly for jpegex. A different approach would mean a major redesign of the code which I am not able to do. I emailed Gabriel Corneanu, though, he ist the original author and maybe he ist willing to put some more effort into jpegex. In the meantime I'll stick to Uwe's solution, which might be quick and dirty, but is a working piece of code. Maybe you feel like demonstrating how this can be achieved through RTTI?Reber
How to access private section via RTTIJaguar
B
15

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

Bigwig answered 20/5, 2016 at 16:36 Comment(9)
Awesome. Can only guess why it "must be at the same relative location as in TJPEGData" - this is a way to put your hand on memory adresses? I had to place this in the interface section and add TJPEGImageHack = class(TGraphic) private FImage: TJPEGData; FBitmap: TBitmap; FScaledWidth: Integer; FScaledHeight: Integer; FTempPal: HPalette; end; FScaledWidth and FScaledHeight are not being used, but I take it they are necessary for the "relative location"Reber
@stackmik, because it does not accesses private field but rather placing other accessible one at the same absolute memory address.Arenicolous
That's what I would have said if I had been able to... Yeah, that was my general idea. Sorry for the scrambled code above, I didn't know that editing ist limited to 5 min. Uwe's hack might be dirty, but it does a clean job...Reber
Recommended lecture : Hack #5: Access to private fieldsBoyne
Wednesday, June 02, 2004! Uwe apparently has a good memory.Reber
Added a bit of extra details, hope you don't mind Uwe.Strangle
Just discovered that Marco Cantú describes this hack (basically), too, in his 2015 Object Pascal Handbook, p. 227.Reber
It seems this loophole was closed in Delphi 10.1. It certainly doesn't work in 10.3.3.Trellas
In Angus Johnson's Image32 ©2019-2024 it still does work even in Delphi 12.1 (TJpegImageHack = class(TJpegImage) - with TJpegImageHack(jpeg).Bitmap do).Reber
M
31

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.

Misdate answered 21/3, 2017 at 19:47 Comment(0)
B
15

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

Bigwig answered 20/5, 2016 at 16:36 Comment(9)
Awesome. Can only guess why it "must be at the same relative location as in TJPEGData" - this is a way to put your hand on memory adresses? I had to place this in the interface section and add TJPEGImageHack = class(TGraphic) private FImage: TJPEGData; FBitmap: TBitmap; FScaledWidth: Integer; FScaledHeight: Integer; FTempPal: HPalette; end; FScaledWidth and FScaledHeight are not being used, but I take it they are necessary for the "relative location"Reber
@stackmik, because it does not accesses private field but rather placing other accessible one at the same absolute memory address.Arenicolous
That's what I would have said if I had been able to... Yeah, that was my general idea. Sorry for the scrambled code above, I didn't know that editing ist limited to 5 min. Uwe's hack might be dirty, but it does a clean job...Reber
Recommended lecture : Hack #5: Access to private fieldsBoyne
Wednesday, June 02, 2004! Uwe apparently has a good memory.Reber
Added a bit of extra details, hope you don't mind Uwe.Strangle
Just discovered that Marco Cantú describes this hack (basically), too, in his 2015 Object Pascal Handbook, p. 227.Reber
It seems this loophole was closed in Delphi 10.1. It certainly doesn't work in 10.3.3.Trellas
In Angus Johnson's Image32 ©2019-2024 it still does work even in Delphi 12.1 (TJpegImageHack = class(TJpegImage) - with TJpegImageHack(jpeg).Bitmap do).Reber
K
12

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.

Kosciusko answered 16/6, 2016 at 14:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.