Better smart-linking through class construction

Posted by on in Blogs

Singletons, global services, and initialization. Most Delphi folks (myself included) don't really think about the impact of these things on how much code is being linked into their application. How many of you have your own stable of utility functions scattered throughout some number of units? You rely on these things everyday and would tend to feel naked without them. Even when you bang out that little test or utility app, those units are the first thing that goes into your uses clause. Have you ever stopped to think what impact they may be having the size of your little app? Long time Turbo Pascal and Delphi programmers have always taken for granted the smart-linker and its ability to ensure that only the code that was "touched" is what is actually linked into your final application. However, there are a lot of things we unwittingly do that can undermine the linker's "smartness." Try this little experiment; create a new console application, add all your favorite utility units to the uses clause and compile,  making sure building with packages is off. Now look at the size of your resulting executable. OMG! It does nothing, yet it is > xxxxK! Let's look at something we're working on to help you (and us) out.

As was mentioned at Delphi Live! several weeks ago, we're currently working on adding what we're calling "Mouse/Touch Gesture support" to VCL. This will eventually evolve into full multi-touch as the OS services and hardware becomes available. Initially we've been building our own "gesturing engine" and adding it throughout the VCL. From the outset, we knew that baking this support into the core of VCL was going to be key to its acceptance. At the same time we also know that there are many of you that don't need or want all that extra code slathered throughout core VCL. We also wanted to make sure this new gesturing support was "pluggable" at many levels. I'm not going to cover details about how this whole subsystem works, but simply want to use it as an example of something else we're looking at adding to the Delphi language.

Traditionally, if you had some set of classes that required some kind of global initialization or a class that was a singleton, you would place this startup code into the unit initialization section. Unfortunately, this has the side effect of dragging into the resulting binary all code and data that is directly and indirectly touched by the initialization code. Simply "using" the unit will drag in the code. Sometimes that is what you want, but what if all you wanted to do was use some other class or call some disconnected utility function within that unit? You have to pay the size and startup performance costs associated with initializing classes and data that you never even use in your own code.

Over the years, there are some "tricks" we've all played to try to get around this. The most common is in the case of singletons, where we "hide" the singleton instance behind a global function that will initialize the instance when it is first called. The problem is that everyplace you use the singleton, you incur a function call. Other tricks include injecting into each public method of an object, code that calls a helper method that does the global initialization. This also has the side-effect of incurring this call for each method. The interesting thing is that the compiler already has mechanisms in place to figure whether or not you're "touching" a given class or not. Why not just trigger on that and call an initialization method, if and only if that class is referenced someplace in code your. This really means whether or not the reference graph can trace all the way back to the main program block.

Enter class constructors. We're talking native Delphi here. Delphi for .NET and Delphi Prism have had class constructors all along since that is a concept that is baked into the .NET Common Language Runtime (CLR). However, there has never been the equivalent concept in Delphi for Win32. We're looking at fixing that. What if the following simple singleton pattern were possible?

unit SingletonUnit;

interface

type
TSingleton = class
private
class var FSingle: TSingleton;
class constructor Create;
public
procedure DoSomething;
class property Instance: TSingleton read FSingle;
end;

implementation

class constructor TSingleton.Create;
begin
FSingle := TSingleton.Create;
end;

procedure TSingleton.DoSomething;
begin
end;

end.

Now you can simply access the singleton by TSingleton.Instance.DoSomething; Because of the class constructor, the compiler will ensure that it is called long before you actually call "DoSomething" on the Instance class property. The astute among you have probably already spotted the hole in the above case. There's a memory leak now. How is the FSingle field destroyed? This is where the non-garbage-collected world of native Delphi has to do a little more work. The class destructor is how this will be handled.


  private
class var FSingle: TSingleton;
class constructor Create;
class destructor Destroy;
public
...
class destructor TSingleton.Destroy;
begin
FSingle.Free;
end;

Now let's look a little at how this all works. Class constructors (or type initializers) differ from instance constructors in that it is execute before any methods can be called or instances of the class constructed. The only rule is that it needs to run before anything else. How long before is largely irrelevant. Unlike class constructors in .NET, you cannot explicitly call them in Delphi Win32.  For Delphi Win32, class constructors are called in a manner similar to unit initialization. The difference is that the compiler treats them as a "weak reference." The class constructor is only explicitly called if other code references the class. Class constructors are called prior to the unit initialization getting called for the unit in which the class with the class constructor resides. If one class constructor references another class with a class constructor in the same unit, the compiler attempts to order the constructor calls such that deepest reference is called first. Beware of cycles! If you have several classes with class constructors within the same unit and they all refer to one-another, the ordering may be indeterminate and you may end up with strange results or even crashes. So be careful before you decide to add them to that huge morass of spaghetti code you inherited from your predecessor. For class destructors, they are called in the exact reverse order of the class constructors and are called after a given unit's finalization section is executed.


Back to the gesture engine. How does this all relate? When you drop a TGestureManager component onto a form, the designer/code manager adds an instance field of type TGesureManager to the designed form class. There is also a reference to the unit in which that component is declared added to the uses clause. The mere addition of the unit to the uses clause isn't what causes the gesture engine to be initialized, it is the component reference on the form itself that does this. If you removed the component from the form and the reference is deleted and left the uses clause reference, all that would be linked in would be a stub initialization/finalization from the given unit.


I'm already seeing several places throughout the Delphi RTL and VCL that would benefit from this. What if you wanted to use function from SysUtils, but did not want to incur the extra weight of full exception support? We may add a class constructor to the base Exception class that hook up the proper functions in System. There are probably many more places in the RTL and VCL that could be refactored in this manner. Of course we need to be cognizant of the fact that going too far may introduce strange ordering differences and could make some things behave badly or radically different. While this isn't some kind of magical "incredible shrinking machine," it is another tool that can be easily leveraged as we are adding and improving the VCL libraries. We will be able to add significant functionality without grossly impacting the minimum core set of code.

About
Gold User, Rank: 84, Points: 11
Comments are not available for public users. Please login first to view / add comments.