Windows Programming/Dynamic Link Libraries
Dynamic link libraries
[edit | edit source]Dynamic Link Libraries (DLLs) were introduced with the first releases of the Microsoft Windows operating system, and today are a fundamental structural component of the OS. They are found not only on the OS core, but as part of many of the frameworks created by Microsoft like the MFC, ATL, .NET, etc, even C and the C++ runtime libraries are distributed as DLLs.
DLLs allow certain code fragments to be compiled into a single library, and to be linked to by multiple programs. This means that only one copy of the library needs to exist, and multiple programs can share the functions and the data between them. The Windows system makes itself accessible, in several of its user-space features, by providing DLLs that programmers can use.
The difference between a DLL and a static library is that when you compile your programs, the DLL is not compiled into your executable, but instead remains a separate module. This feature helps to keep executable size low, and also allows for a DLL to be loaded into memory only when it is needed. Moreover, DLL code is shared over multiple different processes. Nearly every Windows executable shares kernel32.dll, and many share msvcrt.dll, the Visual C runtime. As a self contained entity a DLL also permits kick and target updates to the system and to applications. By simply replacing a DLL with a newer version that contains fixes or improvements, it is easy to extend the alteration to multiple dependent programs instantly.
The exact method of building a DLL file is dependent on the compiler you are using. However, the way in which DLLs are programmed is universal. We will talk about how to program DLL files in this chapter.
DLL hell
[edit | edit source]The common problem referred generically as "DLL hell" has always been a bane to Windows programmers and it really doesn't seem to have a solution in the horizon. The problem was stated in the 90s and that was when the term was coined. The issue is on the permissibility of the OS to let incorrect DLLs version to be loaded upon the request from an application, that would invariably lead to a crash. Today an application will simply refuse to run.
While a stable DLL will not open a hell, changing oder upgrading a DLL may make bugs in applications visible that rely to older or undocumented behaviour of the old DLL. In general, DLL loads are resolved by file name. For Windows XP and onwards, so-called application manifests (XML resource with ID 24) come into play to resolve DLL loads to a finer grade. Moreover, for 64-bit Windows, file system virtualization exists to resolve bitness equivalence. For unknown reason, Microsoft decided to keep DLL file names equal between 32 and 64 bit. There is simply no kernel64.dll, kernel32.dll exists twice, one for 32 bit and one for 64 bit.
__declspec
[edit | edit source]The __declspec keyword is a strange new keyword that is not part of the ANSI C standard, but that most compilers will understand anyway. __declspec allows a variety of non-standard options to be specified, that will affect the way a program runs. Specifically, there are two __declspec identifiers that we want to discuss:
- __declspec(dllexport)
- __declspec(dllimport)
When writing a DLL, we need to use the dllexport keyword to denote functions that are going to be available to other programs. Functions without this keyword will only be available for use from inside the library itself. Here is an example:
__declspec(dllexport) int MyFunc1(int foo)
The __declspec identifier for a function needs to be specified both in the function prototype and the function declaration, when building a DLL.
To "import" a DLL function into a regular program, the program must link to the DLL, and the program must prototype the function to be imported, using the dllimport keyword, as such:
__declspec(dllimport) int MyFunc1(int foo);
Now the program can use the function as normal, even though the function exists in an external library. The compiler works with Windows to handle all the details for you.
Many people find it useful to define a single header file for their DLL, instead of maintaining one header file for building a DLL, and one header file for importing a DLL. Here is a macro that is common in DLL creation:
#ifdef BUILDING_DLL #define DLL_FUNCTION __declspec(dllexport) #else #define DLL_FUNCTION __declspec(dllimport) #endif
Now, to build the DLL, we need to define the BUILDING_DLL macro, and when we are importing the DLL, we don't need to use that macro. Functions then can be prototyped as such:
DLL_FUNCTION int MyFunc1(void); DLL_FUNCTION int MyFunc2(void); .......
(just a note: Microsoft did not intend for this __declspec syntax to be used. Instead the intention was that the public api of a DLL would be declared in an "exports" file. However the above syntax, despite requiring the macro to switch it back and forth, was much more convenient and is pretty much used by all software today).
DllMain
[edit | edit source]When Windows links a DLL to a program, Windows calls the library's DllMain function. This means that every DLL needs to have a DllMain function. The DllMain function needs to be defined as such:
BOOL APIENTRY DllMain (HINSTANCE hInstance, DWORD reason, LPVOID reserved)
The keywords "BOOL", "APIENTRY", "HINSTANCE", etc., are all defined in <windows.h> so you must include that file even if you don't use any Win32 API functions in your library.
APIENTRY or WINAPI are keywords that denote the calling convention for the Windows API. They are both defined as __stdcall. Remember that the calling convention is part of a function's signature. The variable "hInstance" is the HINSTANCE handle for the library, and you can keep this and use it, or you can trash it. reason will be one of four different values:
- DLL_PROCESS_ATTACH
- a new program has just linked to the library for the first time.
- DLL_PROCESS_DETACH
- a program has unlinked the library.
- DLL_THREAD_ATTACH
- a thread from a program has linked to the library.
- DLL_THREAD_DETACH
- a thread from a program has just unlinked the library.
The DllMain function doesn't need to do anything special for these cases, although some libraries will find it useful to allocate storage for each new thread or process that is being used with the library.
The DllMain function must return TRUE if the library loaded successfully, or FALSE if the library had an error and could not load. Applications that cannot successfully open a library should fail gracefully.
Here is a general template for a DllMain function:
BOOL APIENTRY DllMain (HINSTANCE hInst, DWORD reason, LPVOID lpReserved) { switch (reason) { case DLL_PROCESS_ATTACH: break; case DLL_PROCESS_DETACH: break; case DLL_THREAD_ATTACH: break; case DLL_THREAD_DETACH: break; } return TRUE; }
However, if you aren't interested in any of the reasons, you can remove the entire switch statement from your program and return TRUE.
Linking a DLL
[edit | edit source]DLL libraries can be linked to an executable in two ways: Statically and Dynamically.
Static Linking to a DLL
[edit | edit source]When static linking to a DLL, the linker will do all the work, and it will be transparent to the programmer that the functions are located in an external library. That is, it will be transparent if the library writer has properly used the _DECLSPEC modifier in the library's header file.
When compiling a DLL, the compiler will generate two files: the DLL library file, and a static-linking stub .LIB file. The .LIB file acts like a mini static library, that tells the linker to statically link the associated DLL file. When using a DLL in a project, you can either provide the linker with the .LIB stub file, or some linkers allow you to specify the DLL directly (and the linker will then try to find the .LIB file, or may even attempt to create the .LIB file automatically).
Loading DLLs Dynamically
[edit | edit source]The real power behind DLL files is that they can be loaded into your program dynamically at execution time. This means that while your program is running, it can search for and load in new components, without needing to be recompiled. This is an essential mechanism for programs that allow plugins and extensions to be loaded at execution time. To Dynamically load the DLL file, you can call the LoadLibrary function to get a handle to that library, and then pass that handle to one of several other functions to retrieve data from the DLL. The prototype for LoadLibrary is:
HMODULE WINAPI LoadLibrary(LPCTSTR lpFileName);
HMODULE is a HANDLE to a program module. lpFileName is the file name of the DLL you want to load. Keep in mind that when loading a module, the system will check in your PATH first. If you want the system to check in other specified directories first, use the SetDllDirectory function first.
Once a DLL is loaded, and you have a handle to the module, you can do various things:
- Use GetProcAddress to return a function pointer to a function in that library.
- Use LoadResource to retrieve a resource from the DLL.
Once you are finished with a DLL file, and you want to remove it from memory, you can call the FreeLibrary function with the DLL's module handle.