See update below
As is widely known, helpers do crack private visibility. So, private members are visible from a class helper. However, this behaviour does not extend to static members, so TCharacter.IsLatin1
is inaccessible (by fair means) outside the unit in which it is declared.
What about unfair means? Well, some public methods of TCharacter
do call IsLatin1
. And even though IsLatin1
is declared inline
, it seems that these methods are compiled with call statements rather than the code inlined. Perhaps that's because they calls occur in the same unit, or the same type, and the inline engine is not capable of inlining.
Anyway, where I am going with this is that you could, at runtime, disassemble one of these calls. For sake of argument, let's consider 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;
Its first act is to call IsLatin1
. The compiled code looks like this:
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
So, you could do the following:
- Take the address of
TCharacter.IsControl
.
- Disassemble the code at that address until you found the first
call
instruction.
- Decode that
call
instruction to find the target address, and that's where IsLatin1
can be found.
I'm not remotely advocating this for IsLatin1
. It's such a simple function, and not subject to change, that it's surely better to re-implement it. But for more complex situations, this method can be used.
And I'm also not claiming originality. I learnt this technique from the madExcept source code.
OK, @LU RD resourcefully found a way to prove me wrong. Congratulations for that. What I said about static
methods is accurate, however, @LU RD used a very adept trick of introducing a non-static class method and by that way crack the private members.
I'd like to take his answer a bit further by showing how to use two helpers to expose the functionality using the original name:
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.
You can use this unit, and the only helper that is active outside the unit is the one declared in the interface section. Which means you can write code like this:
{$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.