As many of you know, Embarcadero made a change to the Delphi compiler in 10.1 Berlin to close an existing bug that allowed access to private data of any class via a class helper. Developers who were leveraging this hack would need a migration strategy.
Table of Contents
Private is… Private
For quite some time, there was a bug in the Delphi compiler that ended up allowing class helper methods to access private fields of the class they helped, regardless of the unit in which the class was declared. This “hack” basically broke OOP encapsulation rules. To enforce visibility semantics, class and record helpers in most recent versions of Object Pascal compilers (starting with 10.1 Berlin) cannot access private members of the classes or records that they extend. Notice that protected members, on the other hand, are accessible to class helpers, exactly like they are to derived classes.
(By the way, this issue was covered last month also by Craig at http://chapmanworld.com/2016/05/03/the-encapsulation-bug-that-was-a-feature/)
A number of developers found this hack quite handy for accessing private fields and methods of the class library or component they use and we got complaints during the beta and after the product release about this change. We spent a lot of time evaluating different options and solutions… but could not find any better option. The request of “leave this bug open, it is a feature we rely upon” does make sense, but ultimately allowing access to private fields in libraries implies going against the main pillar of object-oriented programming and the last 20 years of computer languages. Not only that, in practical terms it means having to maintain some sort of compatibility of the private portion of core library classes in the future, avoid changing any behavior. The reason these private features are private is exactly to allow developer freedom to change them in the future!
We also got requests to leave this as a temporarily switch, but that would have only delayed the decision (and the code update) to the next release, while leaving the bug open. Another point I heard a lot was about other programming languages allowing the same type of private access. C++ has a “friend class” construct, but the difference there is you grant friendship to another class, while that other class cannot just claim friendship and access private data. Imagine Facebook would allow any person to claim friendship (not be granted friendship) and access your information at will.
Other languages like Java and C# allow access to private fields via reflection… which is exactly what Delphi also allows, and has allowed for many years now since extended RTTI was introduced. Now the question becomes: if this is possible anyway, why don’t let developers do it the easy way? The issue is developers should not be encouraged to access private data and they should clearly express their intention in code, clearly indicating what they are doing. To me, using RTTI satisfies these requirements better.
Again, this is not a decision that was taken lightly. We even invited a former R&D engineer, who originally added class helpers to the language, to share his opinion with us and in the beta forums. That resulted in some very interesting conversations.
But is Private Always Needed?
An important point that was highlighted by this change in the compiler, is that in some cases libraries keep access to some information private without a full evaluation of the scenarios in which developers might need access to those fields or methods. During the beta, developers pointed out fields or methods they really needed to access to, and the team either moved some fields to protected, added additional access properties (still keeping the data private), or introduced more flexibility in other ways. These are specific titles of QP issues that have been addressed during the beta (but there have been many more in this and the previous releases):
Private access issue: TCustomForm.FTextHeight
Private access issue: TBitmap.FAlphaFormat
Private access issue: VCL TBitmap.FImage / TBitmapImage.FDib
TListView: It should be possible to define own calls for TListColumn
Mark CreateRoot method in ShellCtrls virtual and elevate visibility Root fields
FMX TTreeViewItem needs protected ChildrenOffset
TRTTIParameter.Parent not pointing to correct TRttiMethod instance (affects Spring4D)
Change visibility specifiers in all style hook classes
Private access issue: TStyleManager.FSystemStyle
We are more than willing to keep evaluating more cases in which exposing a private member makes sense, and the same you should probably ask to other library vendors.
But What About my Code?
Most of Delphi developers have been able to move their code to 10.1 Berlin easily, as they were not leveraging this hack or did it only occasionally and were able to bypass the problem. There are others who are experiencing bigger problems. What are the options?
One option is to consider if changes already done to the RTL or VCL libraries have already made the private access redundant. At times this is the case (and more often that some people think). If this is not the case, you might also want to consider if that is really needed. If it does, apply one of the workarounds below and file a quality portal report asking Embarcadero to open access that specific information.
The cleaner solution for private access is generally the use of RTTI. Something like
might do the trick. Beside the extra code (which can be simplified and embedded in a class helper!) the drawback is the significantly slower performance. However, you can also keep the RTTI context and the TRttiField object around for optimization. This is a small working VCL-based code snippet (even if not a terribly useful one):
ShowMessage (rttiCtx.GetType(TForm).GetField(‘FClientWidth’).GetValue (self).ToString);
If you need a fast, direct solution a real hack is to declare a data structure matching the class and doing a hard type cast among the two classes. Or just get the field position in the class structure and do a pointer-based access. These approaches are clearly not recommended, but (in corner cases) they offer top performance.
For some alternative coding styles and some discussions, you can see among other sources:
Compatibility versus Evolution
Even if this is a very specific scenario, having to do more with a bug than with a feature, the issue we faced while deciding for a direction to take in this case is one between maintaining perfect compatibility with all existing code and evolving the tool. In many cases the two can coexist (and Delphi has one of the best track records with 20 years of backwards compatibility), but in same scenarios you have to privilege one over the other. In this case the call was to fix a really critical bug. I know some of you would disagree…