Как получить доступ к закрытому полю из помощника класса в Delphi 10.1 Berlin?

Я хотел бы использовать Габриэля Корнеану jpegex, помощник класса для jpeg.TJPEGImage. Чтение этой и этой я узнал, что за пределами Delphi Seattle вы больше не можете получить доступ к частным полям, как это делает jpegex (FData в примере ниже). Ковыряние с VMT, как предложил Дэвид Хеффернан, далеко за пределами меня. Есть ли более простой способ сделать это?

   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 ответов


остерегайтесь! Это неприятный Хак и может потерпеть неудачу, когда внутренняя структура поля взломанного класса изменяется.

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;

Это будет работать только в том случае, если родительский класс класса "hack" совпадает с родительским классом исходного класса. Таким образом, в этом случае tjpegdata наследуется от TSharedImage, а также от класса "hack". Позиции также должны совпадать, поэтому, если в списке было поле до FData, эквивалентное поле должно находиться в классе "hack", даже если это не использовать.

полное описание как это работает можно посмотреть здесь:

Hack #5: доступ к частным полям


сегодня я нашел аккуратный способ обойти эту ошибку, используя оператор with.

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

используя комбинацию помощника класса и RTTI, можно иметь ту же производительность, что и предыдущие версии Delphi, используя помощники классов.

трюк состоит в том, чтобы разрешить смещение частного поля при запуске с помощью RTTI и сохранить его внутри помощника как класс 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;

Как вы можете видеть, это требует немного дополнительного ввода, но по сравнению с ямочным единое целое, достаточно просто.