Delphi Clinic C++Builder Gate Training & Consultancy Delphi Notes Weblog Dr.Bob's Webshop
Dr.Bob's Delphi Notes Dr.Bob's Delphi Clinics Dr.Bob's Delphi Courseware Manuals
 Dr.Bob Examines... #121
See Also: Dr.Bob's Delphi Papers and Columns

Delphi 2010 Delayed Dynamic Link Libraries
Traditionally, Dynamic Link Libraries (DLLs) can be loaded in two different ways: implicit or explicit. In this article, I’ll create a simple DLL, and show how this DLL can be loaded implicitly as well as explicitly. I’ll then move to a new feature introduced with Delphi 2010: the delayed loading of DLLs, which offers the best of both worlds and more, as we’ll see.

Example DLL
The example DLL should be as simple as possible, yet introducing another nice feature that perhaps not all of the readers may know: the concept of function overloading in DLLs, and how to export overloaded functions correctly. The example I’m going to use her is a DLL with two “Add” methods, one for adding integers, and one for adding doubles. Since the function names are the same, we must decorate them with the overload keyword. In order to export them, we must make sure each function is exported with a unique name, so in case of the Add for doubles, I’ll export it by name “AddFloat”.
The source code for the library eBob42 is as follows:

  library eBob42;

  function Add(X,Y: Integer): Integer; overload; stdcall;
  begin
    Result := X + Y
  end;

  function Add(X,Y: Double): Double; overload; stdcall;
  begin
    Result := X + Y
  end;

  exports
    Add(X,Y: Integer) name 'Add',
    Add (X,Y: Double) name 'AddFloat';

  end.

When compiling this source code, we’ll end up with a eBob42.DLL that we can import and use in different ways: implicit, explicit or with the new delayed technique, offered by Delphi 2010.

Implicit
The implicit import of functions from DLLs is the easiest way to use a DLL. All you have to do is repeat the function definition, including the calling convention (plus overload when needed), and add the “external” keyword plus the name of the DLL where the function can be found. For an overloaded function, we should also include the name keyword again, followed by the correct name under which the function was exported.
All in all, not very complex, and for the eBob42.DLL, the Implicit import unit could be implemented as follows:

  unit eBob42Implicit;
  interface

  const
    DLL = 'eBob42.DLL';

    function Add(X,Y: Integer): Integer; overload; stdcall
      external DLL;

    function Add(X,Y: Double): Double; overload; stdcall
      external DLL name 'AddFloat';

  implementation

  end.

The biggest disadvantage of using the implicit import of DLLs technique is the fact that you’ll get an error message when trying to load an application that requires a DLL, and that DLL cannot be found. In that situation, the application will be unable to start, so the error message is a fatal one, and without the DLL, the application itself is useless.

Explicit
The main alternative for implicit import is the explicit loading and import of functions from a DLL. This takes more code, but it allows us to give a nice error message when the DLL cannot be loaded and/or when a function from the DLL cannot be found, without keeping the application itself from running. So even without the DLL being present, the application can be used (albeit without the functionality from the DLL).
As an example of an explicit import unit, where we explicitly need to load the DLL using LoadLibrary and get a handle to the functions using GetProcAddress, is as follows:

  unit eBob42Explicit;
  interface

  const
    DLL = 'eBob42.DLL';

  var
    Add: function(X,Y: Integer): Integer; stdcall = nil;
    AddFloat: function(X,Y: Double): Double; stdcall = nil;

  implementation
  uses
    Windows;

  var
    eBob42Handle: Cardinal;
  initialization
    eBob42Handle := LoadLibrary(DLL);
    if eBob42Handle <= 32 then
      MessageBox(0,Error: could not load ' + DLL, 'Oops!', MB_OK)
    else
    begin
      Add := GetProcAddress(eBob42Handle, 'Add');
      AddFloat := GetProcAddress(eBob42Handle, 'AddFloat')
    end
  finalization
    FreeLibrary(eBob42Handle)
  end.

Obviously, the unit eBob42Explicit is a lot bigger and complex than the simple unit eBob42Implicit. And each additional function from the DLL will make this difference bigger, because eBob42Implicit only needs to list the function (with the external keyword), while eBob42Explicit needs to declare a function pointer and assign a value to that pointer using GetProcAddress.
The biggest advantage of explicit importing is the fact that the application will be able to load and start even if the DLL that we’re trying to use cannot be found (or loaded). We’ll see an error message when the LoadLibrary or GetProcAddress fails, but the application itself will still run.
The disadvantage is that the code for the explicit import unit is a lot more complex, and when we call the imported functions through the function pointers, we should check if the function pointers are actually assigned (otherwise we might still get a run-time error or access violation).
Although neither of these approaches appears perfect, Delphi 2010 now supports a third method which combines the strength and best of both worlds, and then some. The technique is known as delayed loading.

Delay Load
Delphi 2010 introduces a new keyword: delayed. In fact, it’s so new that the online help, the wiki and even the syntax highlighter don’t know about it, yet. The only source of information that I could find was the blog post of Allen Bauer of the Delphi R&D Team itself.
The basic idea of the solution is the fact that the DLL will not be loaded right away (which is the case for implicit linking), but only when needed. So potentially “delayed”, and hence the name “delay loading”.
The syntax of using the delayed approach is actually quite similar to the implicit import unit, with the exception that we now add the delayed keyword to the function definition (and since neither code insight and syntax highlighting seem to know about this new keyword, you’ll have to trust on the compiler to tell you when it’s right: after the name of the DLL, without semi-colon between the name of the DLL and the delayed keyword itself).

  unit eBob42Delayed;
  interface

  const
    DLL = 'eBob42.DLL';

    function Add(X,Y: Integer): Integer; overload; stdcall
      external DLL delayed;

    function Add(X,Y: Double): Double; overload; stdcall
      external DLL name 'AddFloat' delayed;

  implementation

  end.

When compiling unit eBob42Delayed, you get two warnings about the delayed keyword, telling you that the symbol DELAYED is specific to a platform. Yeah right, that doesn’t matter to much to me to be honest. What matters is that we now have the ease of implicit importing with the robustness of explicit importing. The best of both worlds: unit eBob42Delayed is as short as unit eBob42Implicit, and yet the application will start normally even if the DLL cannot be found.
There is one thing left to test: imagine what would happen if we use the eBob42Delayed unit, and start the application without the DLL being present (or found), and then call the function Add? The good news is that the application can be started just fine, and will remain up-and-running. The bad news is that the user will see a not very user-friendly error message, namely:

I can imagine that for the average user this error message will not be fully clear, so the user may not know what the actual problem is. Of course, we can catch this EExternalException in a try-except block, but the problem is that we do not know if the error is caused by a missing DLL, or perhaps by the function which was not found in the DLL (for example if an incorrect version of the DLL was loaded with the correct name, but without the required function exported).

DelayedLoadHook
Based on a blog post from – again – Allen Bauer, we could read that there is actually a way to handle the specific errors that can occur when (delay) loading a DLL or obtaining a function pointer using GetProcAddress. The delay loading itself is done in an old (but well-tested) delayhpl.c file from the C++RTL, which offers the option to “hook” to the notification messages from this process by defining a DelayedLoadHook function that we can install using the SetDliNotifyHook function. The DelayedLoadHook function should be defined as follows (according to line 2392 of system.pas):

  DelayedLoadHook = function (dliNotify: dliNotification;
    pdli: PDelayLoadInfo): Pointer; stdcall;

The records dliNotification and PDelayLoadInfo are also interesting, and contain the information we need to determine the nature of the notification (and possibly error). Again a little snippet from system.pas:

  dliNotification = (
    dliNoteStartProcessing,        { used to bypass or note helper only     }
    dliNotePreLoadLibrary,         { called just before LoadLibrary, can    }
                                   {  override w/ new HMODULE return val    }
    dliNotePreGetProcAddress,      { called just before GetProcAddress, can }
                                   {  override w/ new Proc address return   }
                                   {  value                                 }
    dliFailLoadLibrary,            { failed to load library, fix it by      }
                                   {  returning a valid HMODULE             }
    dliFailGetProcAddress,         { failed to get proc address, fix it by  }
                                   {  returning a valid Proc address        }
    dliNoteEndProcessing           { called after all processing is done,   }
                                   {  no bypass possible at this point      }
                                   {  except by raise, or RaiseException    }
  );

Based on the value of dliFailLoadLibrary, we can raise an exception to explain to the user in detail that a DLL could not be loaded. And based on the value dliFailGetPRocAddress, we can tell the user that the DLL could be loaded, but the specific function could not be found in this DLL. In order to determine the name of the DLL and when needed the name of the function, we should examine the DelayLoadInfo record, which is defined as follows:

  DelayLoadInfo = record
    cb:          LongWord;       { size of structure                 }
    pidd:        PImgDelayDescr; { raw form of data (everything is
                                   there)                            }
    ppfn:        Pointer;        { points to address of function to
                                   load                              }
    szDll:       PAnsiChar;      { name of dll                       }
    dlp:         TDelayLoadProc; { name or ordinal of procedure      }
    hmodCur:     HMODULE;        { the hInstance of the library we
                                   have loaded                       }
    pfnCur:      Pointer;        { the actual function that will be
                                   called                            }
    dwLastError: LongWord;       { error received (if an error
                                   notification)                     }
  end;

For the name of the DLL itself, we can use the field szDll, and for the name of the function (or the value of the export index from the function in the DLL) we have to look a little bit further (or deeper) to the dlp structure of type TDelayLoadProc. The type DelayLoadProc in turn is defined in a variant record as follows:

  DelayLoadProc = record
    fImportByName:      LongBool;
    case Byte of
      0: (szProcName:   PAnsiChar);
      1: (dwOrdinal:    LongWord);
  end;

If the variant record field fImportByName is true, then we should look at the szProcName field, otherwise the value of the dwOrdinal field must be used (in case the function was imported by index number instead of by name).
With this information at our disposal, we can write a procedure DelayedHandlerHook (see listing 5) with arguments dliNotification and PDelayLoadInfo, and inside this procedure we can raise an exception with detailed information if the specific error situations have occurred.

DelayedHandler
For my own convenience, I’ve placed the procedure DelayedHandlerHook inside its own unit DelayedHandler, making sure that the DelayedHandlerHook is installed by calling the SetDliFailureHook function in the initialization section of the unit, and uninstalling it again in the finalization section again. As a result, you only need to add this unit to the uses clause of any project that uses the “delayed” external references, and needs to be able to raise specific exceptions for the situations where the DLL could not be found (or loaded) or when a specific function (or index) could not be found in the DLL.
The two specific exceptions are of type ELoadLibrary and EGetProcAddress and also defined in Listing 5:

  unit DelayedHandler;
  interface
  uses
    SysUtils;

  type
    ELoadLibrary = class(Exception);
    EGetProcAddress = class(Exception);

  implementation

  function DelayedHandlerHook(dliNotify: dliNotification;
    pdli: PDelayLoadInfo): Pointer; stdcall;
  begin
    if dliNotify = dliFailLoadLibrary then
      raise ELoadLibrary.Create('Could not load ' +  pdli.szDll)
    else
    if dliNotify = dliFailGetProcAddress then
      if pdli.dlp.fImportByName then
        raise EGetProcAddress.Create('Could not load ' +
          pdli.dlp.szProcName + ' from ' + pdli.szDll)
      else
        raise EGetProcAddress.Create('Could not load index ' +
          IntToStr(pdli.dlp.dwOrdinal) + ' from ' + pdli.szDll)
  end;

  initialization
    SetDliFailureHook(DelayedHandlerHook);
  finalization
    SetDliFailureHook(nil);
  end.

This time, when the DLL could not be found or loaded, we’ll get a far more descriptive exception which can be displayed in a ShowMessage box as follows:

My final tests show that each time we call the (delay loaded) Add function, the application tries to load the DLL (if not already loaded). This means that if the DLL could not be found the first time this function was called, then during the second attempt, it will again try to load the DLL. And if we’ve found and for example installed the DLL in the meantime, then this means the second call will succeed!
This is another, perhaps unexpected, advantage of the delayed loading approach compared to the explicit importing approach. Unless we extend the explicit importing approach to also try to load the DLL when we make a function call, the delay loading will be more robust and able to connect to a DLL even after the application has started.
As final test, let’s see what happens if the DLL is present but we want to import and call a function with an incorrect name. As a test, we should modify the eBob42Delayed unit for the Add Float function as follows:

  function Add(X,Y: Double): Double; overload; stdcall
    external DLL name 'AddDouble' delayed;
                // was ‘AddFloat’

This will lead to a nice exception to inform us that the function AddDouble could not be found in the eBob42.DLL.

Note that the exception is displayed using a call to ShowMessage, by our client code, inside a try-except block. But you can use this technique also in web server application (where you don’t want to use a ShowMessage) by handling the exception inside a try-except block in another way.

Summary
Where the implict import of DLLs is easy but not always very convenient or robust (when the DLL is not present, the application won’t start), and the explicit import of DLLs is robust but more complex (where you should also always check before calling a function pointer that this function pointer is actually assigned), there the delayed loading technique of DLLs offers the best of both worlds. The easy of declaration (with the additional delayed keyword) with the easy and robustness of use, plus the ability to “connect” to the DLL at a later time, even after the application was started and the DLL wasn’t found the first time. In combination with the unit DelayedHandler, for specific exception raising in case the DLL could not be found or loaded, or a specific function was not found, we now have the ultimate way of importing and using DLLs from now on (although this functionality is only supported by Delphi 2010).

References
Allen Bauer, Procrastinators Unite… Eventually!
Allen Bauer, Exceptional Procrastination


This webpage © 2010 by Bob Swart (aka Dr.Bob - www.drbob42.com).All Rights Reserved.