我可以用类助手调用静态私有类方法吗?

时间:2014-02-07 00:22:59

标签: delphi

特别是,我觉得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;

这种单线方法可以在短时间内重新实现,但我最好留下供应商自行决定的确切实施细节。

有没有办法将此方法“重新引入”public可见度?

2 个答案:

答案 0 :(得分: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;

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


  哎呀,大卫展示了一种方法来扩展这个想法以使用原始名称。这可能是工具箱中的一个多功能技巧。


仅提及有关此文件的说明:

Ordinary Class Methods

  

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

Class Static Methods

  

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

注意:记录只能使用静态类方法,与类不同。

Class and Record Helpers

  

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

...

  

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

     

您可以使用单一类型定义和关联多个帮助程序。但是,源代码中的任何特定位置只应用零或一个助手。最近范围中定义的帮助程序将适用。类或记录助手范围以正常的Delphi方式确定(例如,在单位的使用条款中从右到左)。


如上所述,记录只能有静态类方法。因此,如果您想在记录中“重新引入”私有类方法,这里有一个解决方案(基于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.

答案 1 :(得分: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. 的地址
  3. 反汇编该地址的代码,直至找到第一条call指令。
  4. 解码call指令以找到目标地址,并且可以找到IsLatin1
  5. 我并没有为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.