How to access a private field from a class helper

2019-01-24 07:16发布

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;

3条回答
做自己的国王
2楼-- · 2019-01-24 07:22

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.

查看更多
Bombasti
3楼-- · 2019-01-24 07:28

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.

查看更多
祖国的老花朵
4楼-- · 2019-01-24 07:44

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

查看更多
登录 后发表回答