如何从 Delphi 10.1 Berlin 中的 class 助手访问私有字段?
How to access a private field from a class helper in Delphi 10.1 Berlin?
我想使用 Gabriel Corneanu 的 jpegex,class jpeg.TJPEGImage 的帮手。
阅读 this and 我了解到,在 Delphi 西雅图以外,您无法再像 jpegex 那样访问私有字段(下例中的 FData)。像 David Heffernan 提议的那样研究 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;
小心!这是一个令人讨厌的 hack,当被 hacked class 的内部字段结构发生变化时可能会失败。
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" class 的父 class 与原始 class 的父 class 相同时,此方法才有效。因此,在这种情况下,TJPEGData 继承自 TSharedImage,"hack" class 也是如此。这些位置也需要匹配,因此如果列表中的 FData 之前有一个字段,那么一个等效字段应该位于 "hack" class,即使它没有被使用。
有关其工作原理的完整说明可在此处找到:
通过结合使用 class 助手和 RTTI,可以使用 class 助手获得与以前 Delphi 版本相同的性能。
诀窍是使用 RTTI 在启动时解析私有字段的偏移量,并将其作为 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;
如您所见,它需要一些额外的输入,但与修补整个单元相比,它非常简单。
今天我发现使用 with 语句绕过这个错误的巧妙方法。
function TValueHelper.GetAsInteger: Integer;
begin
with Self do begin
Result := FData.FAsSLong;
end;
end;
除此之外,Embarcadero 在建造围墙以保护私处方面做得很好,这可能就是他们将其命名为 10.1 Berlin 的原因。
我想使用 Gabriel Corneanu 的 jpegex,class jpeg.TJPEGImage 的帮手。
阅读 this and
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;
小心!这是一个令人讨厌的 hack,当被 hacked class 的内部字段结构发生变化时可能会失败。
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" class 的父 class 与原始 class 的父 class 相同时,此方法才有效。因此,在这种情况下,TJPEGData 继承自 TSharedImage,"hack" class 也是如此。这些位置也需要匹配,因此如果列表中的 FData 之前有一个字段,那么一个等效字段应该位于 "hack" class,即使它没有被使用。
有关其工作原理的完整说明可在此处找到:
通过结合使用 class 助手和 RTTI,可以使用 class 助手获得与以前 Delphi 版本相同的性能。
诀窍是使用 RTTI 在启动时解析私有字段的偏移量,并将其作为 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;
如您所见,它需要一些额外的输入,但与修补整个单元相比,它非常简单。
今天我发现使用 with 语句绕过这个错误的巧妙方法。
function TValueHelper.GetAsInteger: Integer;
begin
with Self do begin
Result := FData.FAsSLong;
end;
end;
除此之外,Embarcadero 在建造围墙以保护私处方面做得很好,这可能就是他们将其命名为 10.1 Berlin 的原因。