Как получить доступ к приватному полю из помощника класса в 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", даже если оно не используется.

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

Взлом № 5: Доступ к приватным полям

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

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

Кроме того, Embarcadero проделал отличную работу по строительству стен для защиты частных частей, и, вероятно, именно поэтому они назвали его 10.1 Berlin.

Используя комбинацию помощника по классу и 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;

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

Другие вопросы по тегам