DLL & Linking Tutorial
A DLL (.dll
) or dynamic link library (or shared library for *nix connoisseurs - .so
for Linux and .dylib
for MAC) is a library containing code and data that can be used by multiple programs at the same time. Dlls reduce the size of the executable and the memory usage of the operating system in general, since all applications just need to refer to a single place in memory to use a function in the library (instead of each application having its own copy of it).
Dlls help with separating concerns and reasoning about your code. It also serves for separate compilation, if we change the dll then we need to compile only the dll; the application code remains unaffected and unaware of the change (in this context it resembles the PIMPL idiom). It's worth noting that dll's are a little slower compared to static libraries (which are included in the executable/process), but that speed difference can be considered for almost all intents and purposes negligible.
Here's a comparison table between a regular application and a dll:
Application | dll |
can have multiple instances of itself | single instanced |
has its own local memory, stack, file descriptors, message queue and address space | has none |
can be run independently | can only exist as a component of another application |
There are 2 ways to link a dll to an executable:
- Implicit Linking aka Static Loading aka Link Time Dynamic Linking
- Explicit Linking aka Dynamic Loading aka Run Time Dynamic Linking
In both cases the .dll must be visible in the “environment” of the process at the time of the execution of its program. In other words the dll can either be in the same directory as the program, or in predetermined folders that it can be searched for, typically C:\Windows\system32, C:\Windows\system as well as the PATH
environment variable. Otherwise the application will fail at load time and produce an exception message.
Also the dll must specify its exported symbols with __declspec( dllexport )
otherwise they will not be found by the executable. Best to do this like so:
extern "C" __declspec( dllexport ) functionDeclaration;
To cut a long story short, the main difference between those 2 methods is that with implicit linking the dll is linked at link time, whereas with explicit linking it is loaded at run time whenever you make the loadLibrary
call. For more information read on.
It should be pointed out that both the dll and the program must share the same platform configuration, Portable Executable format, bitness, endianess etc.
Implicit Linking
With implicit linking the dll is loaded when the process is loaded and it sticks to the process until its termination.
The application:
- must include the necessary dll's source code headers (that declare the target functions)
- import the
.lib
which is produced along with.dll
to link external symbols to the program. This is often the point of confusion for many. This.lib
here is not a static library (static linking which is a similar but distinct concept and not part of this tutorial) as you may have thought even though they share the same file extension. It is known as an “import library”. What it does is it produces some glue code, telling the linker to add the functions to the program's import table and informs the program of the DLL storage space that will be needed later. So all it does is write some information into the executable that will allow the loader to automatically load the required libraries when the application runs. - must be able to “see” and access the
.dll
as always
If you're wondering why we must also include this .lib
in the Project's includes, it is because header file inclusion (as is customarily done) is not enough. Remember in C++ #include
is simply telling that a function or class or something exists (is “declared”), it doesn't give its implementation (except for static functions and templates).
If you don't want to include the header files you can alternatively specify dllimport
on those functions inside your application code. You can see in main.cpp
where I make reference to this in the comments. You can use dllimport
like so:
extern "C" __declspec( dllimport ) functionDeclaration;
// no need for dll header #include declarations!
In this project we will only demonstrate implicit linking. But for completion sake explicit linking will be referenced. Interested readers please continue.
Explicit Linking
Here the application doesn't include the dll's headers or use its import library, rather the OS loads the dll's functions on demand at runtime using LoadLibrary
. To do this it reserves memory for the dll to be placed in the address space of the process and later frees it when it's not needed. This can occur multiple times during a process's lifetime. Most dlls are loaded this way as system dlls during OS startup and they are resident in C:\Windows\system32.
What you have to do in this case in your program code is:
- specify function pointers to the target
dll
's functions you'll be using eg.typedef void (__stdcall *DllFunc)();
HMODULE hModule = loadLibrary( "path\\to\\dllModule.dll" )
gets a handle to the dll module. Make surehModule
is notnullptr
. Remember a Windows process has a list of module handles.FARPROC functionName = getProcAddress( hModule, "FunctionName" )
gets the address of the dll function. Error check that functionName is notnullptr
.functionName( args... )
calls the function.FreeLibrary( hModule )
frees the dll module when you're done.
If you are inquisitive you can use dependency walker application to identify all the dlls and their exported functions (alternatively you can use dumpbin).
Note: I used the terms application and process above interchangeably. In some contexts there could be a difference as an application can in fact consist of a collection of processes.
I used Windows and Visual Studio 2017 to build the project.
Github
Github repository link.
Acknowledgements
Microsoft docs