Wednesday, May 26, 2004

Hack #4: Access to protected methods

As we saw earlier, it is possible to gain write access to a read-only property, or rather to its backing field (am talking about native code Delphi here, Win32 and Kylix, - the situation is quite a bit stricter and cleaner in .NET land). You can promote protected properties to public or published to gain access to them, but there is no corresponding promotion syntax for fields or methods. Often you absolutely need to call a protected method of some class - most often method in a VCL component or a 3rd party utility class. This is usually the sign of a badly design class. The author didn't think that application level code would need access to the method. A mock-up example would be:

unit Utility;

interface

type
TUtility = class
protected
procedure Useful;
public
constructor Create;
procedure Boring;
end;

We want to call the protected Useful method, but all we got access to is the public Boring method. Typical, right? ;) If you are lucky, you have control over the type of the instance you are using, and can write your own descendent that promotes the method the clean way, like this:

type
TMyUtility = class(TUtility)
public
procedure Useful;
end;

procedure TMyUtility.Useful;
begin
inherited Useful;
end;

Now you can use TMyUtility instead of TUtility and you'll have the Useful method available as a public method. A final twist is to use the same class name, like this:

unit MyUtility;

interface

uses
Utility;

type
TUtility = class(Utility.TUtility)
public
procedure Useful;
end;

Now you only need to add the MyUtility reference in the uses clause after the Utility reference. However, sometimes you cannot control the type of the instance used - it is created inside a body of 3rd party code, for instance.


As many experienced Delphi programmers will know, you can easily gain access to the protected members of a class instance, simply by casting the instance to a descendent class type declared in the same unit as the cast-expression. For instance, to call the protected Useful method of an instance of the TUtility class you can just:

type
TUtilityEx = class(TUtility)
end;

procedure Foo(Instance: TUtility);
begin
// Fails to compile, cannot call protected method
Instance.Useful;
// compiles fine, calls protected method
TUtilityEx (Instance).Useful;
end;

The way the Delphi visibility directives work, all code in the same unit as the declared class will gain full public access to all members. It is a kind of implicit friend concept as known from C++.


Thanks to the ingeniousness of Borland's compiler developers this common hack even works in .NET with Delphi 8 - as long as the caller and method is in the same assembly - it does not work across assemblies, because protected maps to the family-or-assembly method attribute in the CLR.


One very prominent component vendor even routinely hides away truly useful (and sometimes essential) methods of their component and helper classes into the protected section, seemingly only in an attempt to hide complexity from naïve users. Then in all their own units that need access to the methods, they turn around and perform this local-descendent-cast hack and their own classes. Shudder! This hack should only be used to get access to other people's code, and not used between your own units!


Interestingly, the compiler only uses the local hack-class to get access to the protected method. Although the TUtilityEx class is mentioned in the source code, nothing from that class (the VMT, RTTI, classname info etc.) is needed or referenced in the compiled code, so the smart linker removes it from the executable code. Otherwise this hack would be very expensive with regards to code-size.


Thanks again, Danny & co!

11 comments:

Chris Dickerson said...

Is there a reason properties aren't used to expose protected methods and fields instead of this class hack?

Hallvard Vassbotn said...

Not sure what you mean here, Chris?

How do you expose "procedure Useful;" as a property?

As I show in the blog, if you can afford to use a instnace of a child class instead of the base class, you can promote the method by declaring a new public method with the same name, that simply forwards the call using the inherited syntax.

Only when you cannot change the type of the instance (it has already been created by OPC (Other People's Code)), you have to resort to the casting hack.

Chris Dickerson said...
This comment has been removed by a blog administrator.
Chris Dickerson said...

Hallvard, I think I was implying a wish more than anything.

Plus I confused field access and method access. I could have sworn C# allowed method access from a property accessor. Apparently not :-x

I like to rewrite a language, especially when I'm tired.

David Berneda said...

>>seemingly only in an attempt to hide complexity
>>from naïve users

Not only that... the big point here is to avoid myself design-mistakes, kind of last chance opportunity to "repair" (ie: move to public with different signatures) in future product versions (so the red-face longs just minutes, not years :-)

The long discussion about adding "internal" clause to TP, to break cross-unit restriction....

Hallvard Vassbotn said...

Thanks for coming forward, David...! :)

btw, it was not TeeChart and your company I was thinking about when I wrote that...

David Berneda said...

:-) Anyway, I think most 3rd parties are in the same situation and we also do casts and I'm in part ashamed as other design solutions are possible but either will take more programming hours or break source compability with previous versions.
When we ported, one by one :-) the 100k lines of TChart code from OPascal to C#, one of the benefits that we rapidly realize was the "internal" cross-unit concept to avoid casting hacks. (Cross-assembly would be even better, hopefully in a future .Net version)

Porting to D8 I got tired replacing cast-hacks to subtypes+exposed due to runtime errors instead of at compile time... something that could be simply done by dccil automatically, I think...

It's just we need new scope levels between "private" and "public" than just "protected" in Delphi parlance...

Petr Vones said...

Although this hack works (and it is even recommended "solution") I'd rather advocate for more proper design. Obviously most of component vendors (including Borland) has never tried to use their components from developer's perspective in real world applications. If so, they would find what should be moved to protected or public section quite easily. Sometimes it seems as developers are considered "dumb users" who will never need anything else than what is delivered and everything must be easy to use. But the truth is that software still remain complex domain even with their easy-to-use-component-for-dumb-developers. Unfortunately it isn't trivial task to convince component vendors to change their bad design since there are hacks like that :-)

Hallvard Vassbotn said...

> advocate for more proper design

hear hear!

I very much agree with you, Petr. Maybe there needs to be a new visibility directive 'strict public', that is only available to programmers programming in advanced mode...? I'm only half-kidding.... :)

Alister Christie said...

Remember that you can also use the strict keyword. strict private only allows access to methods/fields within the class, with strict protected you can only use methods/fields in descendants. Strangely there is no strict public/published ;-).
Also you can use the sealed keyword to prevent inheritance.

Hallvard Vassbotn said...

Alister,

Indeed!

I cover the relatively new strict, sealed and abstract classes and final methods here:
http://hallvards.blogspot.com/2006/09/dn4dp2-protecting-your-privates.html



Copyright © 2004-2007 by Hallvard Vassbotn