【问题标题】:How to access a private field from a class helper in Delphi 10.1 Berlin?如何从 Delphi 10.1 Berlin 中的类助手访问私有字段?
【发布时间】:2016-05-20 16:11:03
【问题描述】:

我想使用 Gabriel Corneanu 的 jpegex,它是 jpeg.TJPEGImage 的类助手。 阅读thisthis 我了解到,除了Delphi Seattle,您不能再像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;

【问题讨论】:

  • 你知道答案是否定的。没有魔法。
  • documentation 明确表示从 10.1 Berlin 开始这是不可能的。 你不能再这样做了的哪一部分不清楚?
  • 你是说加布里埃尔·科尼努吗?
  • @Ken White:David 的评论“为什么不修改 VMT?”似乎表明这并非完全不可能。
  • FWIW,您始终可以使用 RTTI。不是那么方便,但有可能。我个人认为你也不应该使用 RTTI,并尝试一种完全不同的方法,不需要你访问私有数据。

标签: delphi class-helpers delphi-10.1-berlin


【解决方案1】:

今天我找到了一个巧妙的方法来解决这个错误,使用 with 语句。

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

除此之外,Embarcadero 还出色地建造了围墙以保护私密部位,这可能就是他们将其命名为 10.1 Berlin 的原因。

【讨论】:

    【解决方案2】:

    小心!这是一个令人讨厌的 hack,当被黑类的内部字段结构发生变化时可能会失败。

    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: Access to private fields

    【讨论】:

    • 太棒了。只能猜测为什么它“必须与 TJPEGData 中的相对位置相同” - 这是一种将您的手放在内存地址上的方法?我不得不把它放在接口部分并添加 TJPEGImageHack = class(TGraphic) private FImage: TJPEGData; FBitmap:TBitmap; FScaledWidth:整数; FScaledHeight:整数; FTempPal:HPalette;结尾; FScaledWidth 和 FScaledHeight 没有被使用,但我认为它们对于“相对位置”是必要的
    • @stackmik,因为它不访问私有字段,而是将其他可访问的字段放在相同的 absolute 内存地址。
    • 如果我能够做到的话,我会这么说……是的,这就是我的总体想法。对不起上面的乱码,我不知道编辑限制为 5 分钟。 Uwe 的 hack 可能很脏,但它做得很干净......
    • 添加了一些额外的细节,希望你不要介意 Uwe。
    【解决方案3】:

    通过使用类助手和 RTTI 的组合,可以使用类助手获得与以前的 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;
    

    正如你所看到的,它需要一些额外的输入,但与修补整个单元相比,它已经足够简单了。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-01-07
      • 2016-12-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多