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