In versions of Delphi before 10.1 Berlin, it was possible to access private members and private methods of a class simply by defining a class helper for it and accessing these private items in its methods.
But that it was so easy to access private members undermined the basic OO principles of information hiding and encapsulation. That is why this loophole (which could be considered a bug) was closed in Delphi 10.1 Berlin. But there was a lot of howling about this, because it was so easy and so convenient to have access to everything, even to other classes' "private parts".
There has been a lot of discussing going on about this. I will not repeat the arguments made there, but will state that I agree with the closing of the loophole. Accessing private members or methods is a hack, and a hack should never be easy. This hack was far too easy. Instead of using the hack, people should rather have tried to find other ways to achieve their goals.
But there is one argument of the pro-helper people that actually made sense: sometimes a class is not designed well, and you must resort to hacks.
Three hacks
They have all been mentioned before, in several blogs and StackOverflow answers, but I want to recap them here, in one post. All of these still require helper classes, as only there, you can still access private methods or members.
Accessing private methods
Say we have a class, let's call it THasPrivateMethods
, that has at least one private
method, let's say procedure PrivateMethod
, that you want to call. The following three
ways to call the private method have all been verified in Delphi 10.2.2 Tokyo. They should also
work in previous versions.
Using assembler
The first way to get at private methods is to define a helper class with a new method. Here, I'll
call the helper THelper
and the first method CallingPrivateMethodUsingAsm
.
This method must be an assembler method:
type THelper = class helper for THasPrivateMethods public procedure CallingPrivateMethodUsingAsm; end;
The implementation is extremely simple:
{$STACKFRAMES OFF} procedure THelper.CallingPrivateMethodUsingAsm; asm JMP THasPrivateMethods.PrivateMethod end;
In other words, in assembler, helper methods can still access private methods.
Using a method pointer
For those who are not very good with assembler, there is another, more tedious way, using a method pointer:
procedure CallingPrivateMethodUsingTMethod; inline;
The implementation takes the address of the private method (which can apparently still be queried), puts it in a method pointer and then calls the method pointer:
type TPrivateMethod = procedure of object; ... procedure THelper.CallingPrivateMethodUsingTMethod; var M: TMethod; begin M.Code := @THasPrivateMethods.PrivateMethod; M.Data := Self; TPrivateMethod(M); end;
Using with
Inside a method of a class helper, you can't call a private method directly anymore, but you can
use a simple trick (probably forgotten when they closed the loophole), using "with Self
do
". I am no fan of "with
", but in this special case, it works as expected:
procedure THelper.CallingPrivateMethodUsingWith; begin with Self do PrivateMethod; end;
Yes, it is that easy. But you must use "with Self do
" to call the private method. Now,
if you inline this method, there is absolutely no overhead. It will work as if you had
called the private method in your own routine:
procedure NeedsAccessToPrivateMethod; var H: THasPrivateMethods; begin H := THasPrivateMethods.Create; try // ... H.CallingPrivateMethodUsingWith; // ... finally H.Free; end; end;
Again, if CallingPrivateMethodUsingWith
is inlined, the resulting code is the same
as if you had written:
try // ... H.PrivateMethod; // ... finally
If you can read assembler, you can verify this in the CPU view of the IDE.
I will discuss accessing private fields in a later post.