Как получить доступ к приватному полю из помощника класса в 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", даже если оно не используется.
Полное описание того, как это работает, можно найти здесь:
Сегодня я нашел способ обойти эту ошибку, используя оператор 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;
Как вы можете видеть, это требует некоторого дополнительного набора текста, но по сравнению с исправлением целого модуля, это достаточно просто.