Могу ли я вызвать статический метод закрытого класса с помощью помощника класса?
В частности, я чувствую потребность в 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
Итак, вы можете сделать следующее:
- Взять адрес
TCharacter.IsControl
, - Разберите код по этому адресу, пока не найдете первый
call
инструкция. - Расшифруйте это
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.