Tuesday, August 25, 2009

Procrastinators Unite… Eventually!

We’re all taught at an early age to “Never put off until tomorrow that which can be done today.” In general, that is wise advice. However there are some cases where you do want to wait until the last possible moment to do (or not do) something. In fact, that is one of the overall tenets of  Agile Programming; delay decisions until the last possible moment because you always know more about a problem tomorrow than you do today and can make a better, more informed decision. But, I digress. I’m not here to talk about philosophies of life, or to introduce another “agile methodology” or even about a new weight loss plan based on bacon, lard and cheese puffs.

How many of you have written this same bit of boilerplate code or something similar over and over again?

if OSVersion >= 5.0 then
Module = LoadLibrary('kernel32.dll');
if Module <> 0 then
Proc := GetProcAddress(Module, 'APICall');
if Assigned(Proc) then
Proc(Param1, Param2);
end else
{ gracefully fallback }


What if you could just do this?

if OSVersion >= 5.0 then
APICall(Param1, Param2);
{ gracefully fallback }


The astute among you would immediately see that with previous Delphi releases, the second form, while certainly preferable, at some point the call to “APICall” would eventually have to effectively go through some bit of code similar to the first bit of code. Normally, you would declare an external API reference like this:

procedure APICall(Param1, Param2: Integer); stdcall; external 'kernel32.dll' name 'APICall';

That will cause the linker to emit a external reference into the executable binary that is resolved at load time. Therein lies the problem. That is the situation that the first bit of code above was designed to avoid. If APICall didn’t existing in ‘kernel32.dll’ at load time, the whole application would fail to load. Game over. Thanks for playing. Now, what if you could write code similar the second block of code above and still declare your external API calls in a manner similar to above?

Starting with Delphi 2010, you can do exactly the scenario I describe. To make the second code block work even if “APICall” isn’t available on the version of the OS on which your application is currently running, we’ve introduced a new directive to be used only in the above context, delayed;

procedure APICall(Param1, Param2: Integer); stdcall; external 'kernel32.dll' name 'APICall' delayed;

Simply put, by adding the delayed directive, that instructs the linker to generate the external API reference differently in the executable binary. Now Delphi’s RTL will take care of all that ugly “late-binding” boilerplate code for you. Rather than generating the import in the normal “Imports” section of the executable’s PE file, it is generated into the “Delayed Imports” section following the published PE spec. This also requires that the RTL now has a generic function that does the proper lookups and binds the API whenever it is called the first time. Subsequent calls are just as fast as a normal import.

This is different than similar functionality available in ILink32 from C++Builder wherein you can only specify an entire dll in which all references are delay loaded. Also, in C++ you cannot specify kernel32.dll or even ntdll.dll to be delay loaded, since the very startup of any application or dll requires them to already be loaded. In Delphi you can, since it is on an API-by-API basis. The intent of this feature was to make managing all the new APIs from Windows Vista and now Windows 7 much easier without having to continuously and manually write all the delay loaded boilerplate code. Throughout VCL, we can simply make runtime decisions on which APIs to call without always going through those manually coded “thunks.” They are now simply handled by the compiler/linker and the source code barely reveals the fact that something is late-bound.

In a future release, we are considering adding both the API-by-API capability to C++Builder and a way to specify an entire dll to be late-bound in Delphi.