我可以翻译为: 我能用类辅助器调用静态私有类方法吗?

4

特别是在TCharacter.IsLatin1中,我感觉需要进行翻译,该函数为private

type
  TCharacterHelper = class helper for TCharacter
  public
    class function IsLatin1(C: Char): Boolean; static; inline;
  end;

class function TCharacterHelper.IsLatin1(C: Char): Boolean;
begin
  Result := Ord(C) <= $FF;
end;

这个一行代码的方法可以很快重新实现,但最好还是让供应商自行决定具体的实现细节。

有没有办法将这个方法“重新引入”到公共可见性中?

2个回答

8
有没有一种方法可以使这个方法重新可见于公众?
有。通过引入一个新的类函数来调用非静态函数。 这里的技巧是利用助手能够通过Self访问所有成员。请参见Access a strict protected property of a Delphi class?How do I use class helpers to access strict private members of a class?。 这是通过从新的类函数调用私有助手非静态函数来完成的,其中Self可以被解析。
Type

  TCharacterHelper = class helper for TCharacter
  private
    class function IsLatin1Cracker(aChar: Char): Boolean; inline;
  public
    // Introduce a new public static class function
    class function IsLatinOne(aChar: Char): Boolean; static; inline;
  end;

class function TCharacterHelper.IsLatinOne(aChar: Char): Boolean;
begin
  Result := IsLatin1Cracker(aChar);
end;

class function TCharacterHelper.IsLatin1Cracker(aChar: Char): Boolean;
begin
  Result := Self.IsLatin1(aChar);  // Here Self can access base class
end;

虽然您不能使用原始方法名称,但仍可以通过这种方式调用原始类函数。


抱歉,David展示了一种扩展这个想法并使用原始名称的方法。这可以成为工具箱中多功能的技巧。

只是参考文档对此的说法:

普通类方法

您可以使用 Self 来调用构造函数和其他类方法,或者访问类属性和类字段。

类静态方法

与普通类方法不同,类静态方法根本没有 Self 参数。

注意:仅记录可以具有静态类方法,而类不行。

类和记录助手

您可以在任何可以合法使用扩展类或记录的地方使用助手。编译器的解析范围变为原始类型加上助手。

...

可见性范围规则和memberList语法与普通类和记录类型相同。

您可以为单个类型定义和关联多个helper。但是,在源代码中的任何特定位置只有零个或一个helper适用。将应用最近范围内定义的helper。类或记录helper范围按照正常的Delphi方式确定(例如,在单位的uses子句中从右到左)。


正如您上面提到的,记录只能具有静态类方法。因此,如果您想在记录中“重新引入”私有类方法,则可以采用以下解决方案(基于David的技术):
假设我们有:
Type
  TTestRec = record
  private
    class Function IsLatin1(C: Char): Boolean; static; inline;
  end;

并在一个新的单元中添加一个帮助程序:

unit HelperUnitForTTestRec;

interface

Type
  TTestRecHelper = record helper for TTestRec
  public
    class function IsLatin1(c:Char): Boolean; static; //inline; !! Inlining not possible
  end;

implementation

Type
  TTestRecCracker = record helper for TTestRec
  private
    function IsLatinOne(C:Char): Boolean; inline;
  public
    class function IsLatin1Cracker(c:Char): Boolean; static; inline;
  end;

function TTestRecCracker.IsLatinOne(c: Char): Boolean;
begin
  Result := Self.IsLatin1(C);  // <-- Here is Self resolved
end;

class function TTestRecCracker.IsLatin1Cracker(c: Char): Boolean;
var
  tmp: TTestRec;
begin
  Result := tmp.IsLatinOne(C); // <-- Must use a call to ordinary method
end;

class function TTestRecHelper.IsLatin1(c: Char): Boolean;
begin
  Result := IsLatin1Cracker(C);
end;
    
end.

@DavidHeffernan,这真是棘手!我不确定编译器架构师们是否预料到了这一点,但事实证明这是可能的,这证明了语言是模块化的,这是一件好事。 - LU RD
你有找到任何涵盖帮助程序确切可见性规则的文档吗?所有这些看起来对我来说都有点模糊不清。 - David Heffernan
@DavidHeffernan,抱歉,没有比文档中写的更多了(已添加到答案中)。 - LU RD
@DavidHeffernan,记录的类方法必须是静态的,因此我也为这种情况添加了解决方案。 - LU RD
1
所以实例方法就是它在那里发挥作用。我认为你现在已经掌握了所有内容! - David Heffernan
显示剩余2条评论

6

请见下面的更新

众所周知,帮助程序可以破解私有可见性。因此,类助手可以看到私有成员。然而,这种行为并不扩展到静态成员,因此TCharacter.IsLatin1在声明它的单元之外是无法访问的(通过公平手段)。

那么,不公平手段呢?好吧,TCharacter的一些公共方法确实调用了IsLatin1。尽管IsLatin1被声明为inline,但这些方法似乎是编译为调用语句,而不是内联代码。也许这是因为它们调用发生在同一个单元或同一个类型中,但内联引擎无法内联。

无论如何,我想说的是,在运行时,您可以反汇编其中一个这样的调用。举个例子,考虑IsControl

class function TCharacter.IsControl(C: Char): Boolean;
begin
  if IsLatin1(C) then
    Result := InternalGetLatin1Category(C) = TUnicodeCategory.ucControl
  else
    Result := InternalGetUnicodeCategory(UCS4Char(C)) = TUnicodeCategory.ucControl;
end;

它的第一个操作是调用IsLatin1。编译后的代码如下:
System.Character.pas.517: 
00411135 C3               ret 
00411136 8BC0             mov eax,eax
TCharacter.IsControl:
00411138 53               push ebx
00411139 8BD8             mov ebx,eax
System.Character.pas.533: 
0041113B 8BC3             mov eax,ebx
0041113D E852FFFFFF       call TCharacter.IsLatin1
00411142 84C0             test al,al
00411144 740F             jz $00411155
所以,你可以这样做:
  1. 获取TCharacter.IsControl的地址。
  2. 反汇编该地址处的代码,直到找到第一个call指令。
  3. 解码call指令以查找目标地址,这就是IsLatin1所在的位置。
我并不是在为IsLatin1进行宣传。它是一个非常简单的函数,不会发生变化,因此重新实现它肯定更好。但是对于更复杂的情况,可以使用这种方法。
我也不声称具有原创性。我从madExcept源代码中学到了这种技术。
好吧,@LU RD聪明地找到了证明我错误的方法。恭喜他。虽然我关于static方法的说法是准确的,但@LU RD使用了一个非常熟练的技巧,引入了一个非静态类方法,并通过这种方式破解了私有成员。
我想进一步展示如何使用两个辅助程序来使用原始名称公开功能:
unit CharacterCracker;

interface

uses
  System.Character;

type
  TCharacterHelper = class helper for TCharacter
  public
    class function IsLatin1(C: Char): Boolean; static; inline;
  end;

implementation

type
  TCharacterCracker = class helper for TCharacter
  public
    class function IsLatin1Cracker(C: Char): Boolean; inline;
  end;

class function TCharacterCracker.IsLatin1Cracker(C: Char): Boolean;
begin
  Result := TCharacter.IsLatin1(C); // resolves to the original method
end;

class function TCharacterHelper.IsLatin1(C: Char): Boolean;
begin
  Result := TCharacter.IsLatin1Cracker(C);
end;

end.

你可以使用这个单元,而在单元外唯一激活的帮助者是在接口部分声明的那个。这意味着你可以写出如下代码:

{$APPTYPE CONSOLE}

uses
  System.Character,
  CharacterCracker in 'CharacterCracker.pas';

var
  c: Char;

begin
  c := #42;
  Writeln(TCharacter.IsLatin1(c));
  c := #666;
  Writeln(TCharacter.IsLatin1(c));
  Readln;
end.

我尚未测试(因为我尚未安装),但我相信在Delphi Berlin中已不再可能,因为类助手不再具有访问私有成员的权限。 - dipold

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接