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

梦想与她 提交于 2019-11-28 10:05:55
Uwe Raabe

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

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.

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.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!