Могу ли я вызвать статический метод закрытого класса с помощью помощника класса?

В частности, я чувствую потребность в 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 ответа

Решение

Смотрите обновление ниже

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

Есть ли какой-нибудь способ "вновь" представить этот метод для всеобщего обозрения?

Да. Вводя нестатический вызов функции через новую функцию класса. Хитрость заключается в том, чтобы использовать вспомогательную способность для доступа ко всем членам через 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 (например, справа налево в предложении пользователя модуля).


Как вы отметили выше, записи могут иметь только статические методы класса. Так что, если вы хотите "заново" ввести в запись метод частного класса, вот решение (основанное на методике Дэвида):

Предположим, у нас есть:

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.
Другие вопросы по тегам