About dynamic linking

A dynamic link library (DLL) is simply a resource for your application. This resource can contain bitmap and strings and all other kinds of resources but it can also contain executable code. This last resource is what DLLs are most often used for. Very simply put, a DLL then is a file with a .dll extension which contains executable code in the form of procedures and functions refered to as subroutines. This module is completely independent of any application although it may be dependent on other DLLs. The act of the DLL making these subroutines available to all applications and other DLLs is called exporting. For example a DLL that contains a subroutine called SomeProc is said to export the SomeProc subroutine. An application, or DLL, that uses this subroutine is said to import the subroutine from the DLL. All this is natively supported by the Windows Operating System. In fact, Windows itself is build using DLLs. All of the major parts of the operating system are contained inside DLLs. Some of these are user32.dll, kernel32.dll and gdi32.dll but there are many many more.

When using a DLL from your application the operating system has to do a number of things. When your applications is loaded the OS inspects a special section of your executable which is called the import section. This section alphabetically list all subroutines that your application uses and in which DLLs those subroutines are located. This section is created by the Delphi linker when you build your application. The OS enumerates through all the entries and attempts to load all the DLLs that are listed. If this succeeds your application can start running and use the imported subroutines. However, if the OS fails in loading even one of these DLLs, or cannot find one of the subroutines in the specified DLL, then it displays a dialog notifying the user of this fact and aborts your application before it even started running.

So how does Delphi know which subroutines to put into the import section and in which DLLs those subroutines are located? This is done through interface units. If you have ever looked at the Delphi source code files in the Source\RTL\Win folder then you have already seen an interface unit. These units contain declarations for all constant and record types that are used in combination with the subroutines exported by some DLL as well as declarations for these subroutines themselves. These units are most often translated from C/C++ header files which are identical to Pascal interface units but used with C/C++ compilers. Let's look at a small part of the Windows.pas interface unit. In the interface section you will see this

function InterlockedIncrement(var Addend: Integer): Integer; stdcall;
{$EXTERNALSYM InterlockedIncrement}
function InterlockedDecrement(var Addend: Integer): Integer; stdcall;
{$EXTERNALSYM InterlockedDecrement}
function InterlockedExchange(var Target: Integer; Value: Integer): Integer; stdcall;
{$EXTERNALSYM InterlockedExchange}

You see decalarations of three functions, what they accomplish is beyond the scope of this article. There is nothing out of the ordinary about these declarations except for two things. First, the declaration is followed by the keyword stdcall. Basically that indicates to the Delphi compiler how function arguments are passed from the caller to the callee and how the result is returned. You can look up the details about it in the Delphi helpfile. Second, for each declaration there is an accompanying $EXTERNALSYM comment. This comment is not important for Delphi programmers and is used for compatibility with C++ Builder. This comment, which is effectively a directive, is also described in the Delphi helpfile and in a bit more detail in the Pascal Header Translation Specification document at Techvoyage.

Further up, in the implementation section you'll see this

function InterlockedDecrement; external kernel32 name 'InterlockedDecrement';
function InterlockedExchange; external kernel32 name 'InterlockedExchange';
function InterlockedIncrement; external kernel32 name 'InterlockedIncrement';

Now this is odd. You probably would have expected to see the implementation of the three functions but instead there is something that looks more like a declaration. The functions are implemented inside a DLL to which we most often do not have the source code. So instead of an implementation these declarations tell the linker where the implementation can be found. The external keyword is followed by a string constant, in this case "kernel32" which defines the DLL from which the function is exported and the name keyword defines the name by which the subroutine is exported from the specified DLL. Usually the name is identical to the name used in the declaration but that's not a requirement. If you would use the subroutines listed above then the Delphi linker will see these declarations and add them to the import section of your executable file. So now the circle is complete, the application lists which subroutines it needs and where they are located and the OS makes sure that these DLLs are loaded when your application starts running. In addition to loading the DLL the OS loader does some other stuff such as fixing the function addresses so that your application can call these functions no matter where they are loaded into memory. The details of this are beyond the scope of this article. See Jeffrey Richters' excellent book, Advanced Windows 3rd edition for more information (the fourth edition came available while I was writing this, the title has been changed to Programming Applications for Microsoft Windows 4th edition).

The scenario described above is reffered to as implicit dynamic linking. This is by far the easiest way of linking to a DLL. However, there are some problems with this method. If you read the above carefully then you known that when the OS cannot find one of the DLLs or any of the subroutines that your application will be terminated before it even starts executing. Normally this won't happen because your installation program will of course make sure that all needed DLLs are present. But, there are situations in which the DLLs might not be present on the target system. Or, more commonly, the DLLs contain subroutines that can only be used on a specific OS. For instance the ToolHelp32 API is only available on Windows 9x and Windows 2000. So if your application uses ToolHelp32 and implicitly links to ToolHelp32.dll then your application cannot run on Windows NT 4. To get around these problems Windows offers something called explicit linking.

When using explicit linking the OS does not attempt to load the required DLLs when it loads your application. Instead you are responsible for loading these DLLs yourself. Windows makes three functions available for you to accomplish this. We'll look at them later. When you use explicit linking your interface unit has to be changed as well. Instead of having the function declarations shown above you will declare function variables. For the three functions in the example above this would look like this

var
  InterlockedIncrement: function (var Addend: Integer): Integer; stdcall;
  InterlockedDecrement: function (var Addend: Integer): Integer; stdcall;
  InterlockedExchange: function (var Target: Integer; Value: Integer): Integer; stdcall;

These declarations will be in the interface section and there is no need for a implementation declaration as when you use implicit linking. In case your not familiar with function variables, they are simply variables which you can use to call a function. The idea is that you assign the address of some function to the variable and then use the variable to call it, something like this:

var
  IntLockInc: function (var Addend: Integer): Integer; stdcall;
  Add: Integer;
begin
  IntLockInc := @InterlockedIncrement;
  Add := 0;
  IntLockInc(Add);
end;

The code above assumes that the InterlockedIncrement function is declared somewhere else and assigns it to a variable called IntLockInc and then calls the function through this variable. As you can see a function variables usage is identical to normal functions once you have assigned a function address to it. This is where the three functions mentioned earlier come into play. To call a function located in some DLL through a function variable using explicit linking you'll have to follow these steps:

  1. Locate and load the DLL using the LoadLibrary or LoadLibraryEx Windows API function
  2. Use the GetProcAddress API function to retrieve the address of the desired function and assign it to a function variable
  3. Use the function variable to call the function
  4. When done using the DLL unload it by using the FreeLibrary API function

For a detailed description of these functions look them up in the Win32 API helpfile. Here's an example of how to do all this for the InterlockedIncrement function:

procedure TForm1.Button1Click(Sender: TObject);
var
  InterlockedIncrement: function (var Addend: Integer): Integer; stdcall;
  Lib: HINST;
  I: Integer;
begin

  // attempt to load the kernel32.dll library
  Lib := LoadLibrary(PChar('kernel32.dll'));
  if Lib <> 0 then
  begin

    // get the address of the InterlockedIncrement function
    @InterlockedIncrement := GetProcAddress(Lib, PChar('InterlockedIncrement'));
    if Assigned(InterlockedIncrement) then
    begin
      I := 0;
      InterlockedIncrement(I);
      ShowMessage(IntToStr(I));
    end;

    // done using kernel32.dll so unload it
    FreeLibrary(Lib);
  end;
end;

If you execute this code then you will see a message box displaying the value 1 to prove that the variable I was incremented using the InterlockedIncrement function. Looking at the code above shows that using explicit linking requires a bit more work then using implicit linking and is somewhat more error prone as well. The benefits are well worth it though. For instance assume that the kernel32 DLL did not exist on the target system (it does since it is required to run Windows but go with me here). In that case LoadLibrary would return 0. You could use that knowledge to abort whatever you were trying to do and notify the user that that particular functionality is not available, without your application terminating. Your user would still be able to use the other parts of your application which do not require kernel32. The same goes for the situation where kernel32.dll is present on the target system but it does not export an InterlockedIncrement function. In that case GetProcAddress would return nil and you could gracefully recover. If you would have used implicit linking your application would never even have started execution.

This article described the difference between implicit and explicit linking. Hopefully I did that in an understandable manner. If not then I highly recommend that you find another resource to help you make this distinction clear because it is one of those fundamental things that every Windows programmer should master. My recommendations would be Jeffrey Richters, Advanced Windows. Note that this book is aimed at C programmers.