As your mama taught you, you should protect your private parts, and leave other peoples private parts alone. The same goes for programming, of course. Encapsulation and information hiding is two of the main pillars of object oriented programming, so we better not be messing with those, right? Right. But once in a blue moon, we just have the urge to behave badly and reach into the private fields of another class, defined in another unit.
When possible, the previously described hack of accessing a private field through a published property's RTTI is preferred to the technique presented in this article. However, sometimes that hack is not very practical or even possible. Using RTTI is relatively slow (although the RTTI access to retrieve field offsets could be cached) and the private field you need access to may not be exposed as a property. In addition, if you need access to all or most of a class's internal private fields, the solution described here may be preferable.
As mentioned earlier, Delphi's concept of private (in contrast with D8/D2006-2007's strict private) still allows other code in the same unit full access, while excluding access to external units. So all classes and routines in the same unit are intimate friends of each other.
Have you noticed how promiscuous some of the VCL classes are with each other? For instance, the abstract base class of the component streaming system, TFiler, defines a number of private fields. The actual worker classes TReader and TWriter (both descending from TFiler) are both defined in the same unit, and blatantly access the private fields defined in TFiler (for instance the all-important FStream field).
If you should wish to write your own application-level streaming system (with the ability to stream and auto-create non-TComponent classes) that still participates in the TPersistent DefineProperties system, you would have to extend TReader and TWriter. It looks like these classes have been designed to be extended, considering the virtual protected methods they introduce. Your own TMyReader and TMyWriter classes would not have access to the TFiler private fields, nor to the private fields defined in TReader and TWriter. This could(*) severely limit what your descendant classes could do.
Ok, enough talk already - on to the meat. To get access to the private fields of TFiler, declare a shadow class that exactly matches the private declaration from Classes.pas:
type
TFilerHack = class(TObject)
private
FStream: TStream;
end;
Given a TFiler instance, you can now simply cast to TFilerHack to get access to the fields:
procedure Foo(Filer: TFiler);
var
Stream: TStream;
begin
Stream := TFilerHack(Filer).FStream;
end;
Notice that I still use the private visibility directive for the field - this still gives access to all code in the same unit. Normally, this kind of cracker classes should be declared in the implementation section of the unit. This hack is of course extremely version sensitive to changes in the cracked class. Compared to other solutions of using magic field offsets it has the benefit of having the compiler computing the offset for you and from being protected against changes in the base class (TObject in this case). Still, if you ever use this hack, make sure you {$IFDEF} or {$IF} protect the code against changes in compiler and RTL version (or 3rd party component set version).
(*) A few years ago (using D5), I actually implemented an application-level streaming system by writing my own TReader and TWriter classes and using hacks similar to the ones described in this article. Since then TFiler, TReader and TWriter have been improved with regards to extensibility - providing protected read-only properties to get to some of the private fields, for instance (kudos to Borland CodeGear on that one). It may well be that a functional application-level streaming system could be built without using this hack in D7, but I wouldn't count on it. I still use TFiler as an illustrative example.