This article covers the advantages and disadvantages of Dynamic Link Libraries, how to write them, a discussion of the Entry point function, types of DLL linkage and more.
Support Files for this article are available at the end of this article.
Overview
Reusing code has been an area of interest for researchers and developers equally. With each new emerging technology we get to see a new form of it in action. Let it be COM (the binary standard) or the new buzzword .NET, all are equipped to provide code reusability to today’s developer.
In this article we’re going to talk about DLLs or the Dynamic Link Libraries. The areas that’ll be highlighted include the advantages/disadvantages, writing DLLs, a discussion of the Entry point function, types of DLL linkage and more. We’ll also discuss the common pitfalls and mistakes made by developers and common troubleshooting issues. I’ve also included a PowerPoint presentation which gives a brief overview of this article that you may find useful.
I won’t assume that you’ve ever written or used DLLs, but I do assume that you know basic Win32 application programming using the C/SDK and understand how to use the Visual Studio IDE. It’d be a plus if you’re familiar with basic operating system concepts like processes threads and fibers because you’ll get a better understanding of the concept being explained. But be sure that at the end of this article you’ll be able to harness the power offered by DLLs in the next project you design.
Introduction
As the name ‘Dynamic Link Library’ implies DLLs are libraries that contain code and data that can be used across multiple applications at the same time. For example the USER32.dll or the KERNEL32.dll are the DLLs that every windows program needs in order to work on the Win32 Subsystem.
Once written and packed in a DLL, a function can be used in any application in its binary form without actually referring to its source code. Same is the case with data (if you choose to!). But you may ask, “I can do so by just including the source files and using the function in my application.” Yes, indeed, this was the way programs were written in the DOS days, but the enhancements made to the underlying hardware when reflected by the operating system sitting at the top of it are magnified manifolds and one should be able to leverage it to reap maximum benefits. Moreover there might be situations where you don’t have the source code and you know a third-party DLL can be used. Talking of benefits, we shall just glance through the advantages and disadvantages of using DLLs.
• Low Memory Usage -- The code and data that we compile in a DLL is shared among the applications that use the DLL. Moreover depending on the way you load a DLL in your application, it’ll be loaded only when needed. Either way it uses less memory.
The code inside the DLL is resident in the read-only sections and loaded in memory pages marked as read-only. It allows the system to map the DLL into the address space of the processes that uses it. Yes, the DLL is loaded just once, and if a process requests using it, the operating system just maps the DLL to calling application’s address space.
• Reduced Swapping -- Imagine two processes using the same DLL and one of them finishes its job and exits. But the DLL won’t unload now because the DLL manages its lifetime by keeping a reference count of the processes that use it. It unloads itself as soon as the reference count drops to zero, a stage where any process is not using it.
Now assume that a new process begins and requests the system to load the same DLL that is now being used by an already running application. So what happens next? Does the system load the DLL again? No! Certainly not. The currently loaded DLL has all its code and data in memory and the system just maps this to the address space of the new process and it works just fine. So it saved a lot of work on the operating system’s part, which would have involved reading the DLL code and data from the disk file.
• Use as an Off the Shelf Component -- Once you’ve built a DLL after proper design, you may use it in any application you find suitable. Think of it as a component that you’re very sure about in terms of functionality. You just drop this in one of your projects, and it should work fine.
Imagine a DLL implementation that provides all the calculations which are required while developing a graphics rendering application. This DLL can now be used in any project that requires these calculations.
• Interoperable between Languages -- The DLL written in one language might be used in applications written in a different language. It’s common for developers to write optimized routines in assembler and package them as a DLL and later call them from a C++ or Visual Basic application.
On a developer's part, it’s unfortunate that most of the languages provide support for using the DLLs but only a few let one create them. But still it’s a benefit that one may use a DLL written by someone else. This doesn’t only allows one to focus on the business logic and be away from the hassle of writing some complex routines, but also freedom from the debugging and testing of this module. Imagine performing I/O in a Visual Basic application using a DLL that uses the inportb and outportb instructions from the native CRT library. DLLs make writing such interoperable code a breeze.
• Can Provide After Market Support -- What if you find after shipping a product that it needed some alterations. It’s a troublesome job to rebuild and redistribute the whole application to all the buyers. But it can be avoided if you design your application in well-defined modules packaged into DLLs. You could just make the DLL available as an upgrade to the whole package and the product should work fine. Read on the disadvantages which might fake this point, but I’ll say that it’s just the way you write your code.
• The DLL Hell -- It’s the biggest trouble that DLLs sometimes cause. You might have encountered situations where a program fails to load, displaying some error such as “The ordinal abc could not be located in the dynamic-link library xyz.dll.” Or you install a new application and some other programs starts to malfunction or even fails to load. These are just the symptoms of DLL hell on your machine. The most common source being a irresponsibly made install program that doesn't check versions before copying DLLs into the system directory. This may also happen if a newer DLL has replaced an older one and has major changes in the functionality. DLLs should be backward-compatible, but it's the proper design of the application that ensures backward compatibility and in most cases is hard to achieve.
The DLLs usually contain code, data and resources. Code is stored on read-only sections so that it can be shared among the requesting applications, but the case with data in the DLLs is different. Each requesting application gets its private copy of data segments unless one explicitly marks them as shared. There may be resource-only DLLs too that we’re going to discuss shortly.
The code section contains classes or standalone (unrelated) functions that you want to expose through the DLL. In case of classes you’ve all the functionality and data bound into one entity already, but with standalone related functions you may have some shared global data too. All of this is defined in one or more source files but finally packaged as one DLL. Classes and functions that are available to the applications using the DLL are called exports from the DLL. If your DLL uses functions from other DLLs, then those are called as imports to the DLL. A free useful utility that allows you view exported and imported functions from a DLL or EXE file is freely available from www.dependencywalker.com. Below is a snapshot of the same displaying exports from the WinScard.dll.
Figure 1: Function Exports of a Windows System DLL
Exporting Classes and Functions from the DLL
To make the code you’ve compiled in a DLL, you must explicitly export it so that other applications may use it. There are two ways to do so:
By creating a module definition (.DEF) file and use the .DEF file when building the DLL. This approach also facilitates exporting functions by ordinal rather than by name, which is the default. You’ll need to specify the /DEF while you use the linker to build the DLL.
By using the keyword __declspec (dllexport) in the function's definition. In case you wish to export classes, you’ll place this keyword after the class keyword. A complier specific name mangling technique is used to mangle member function names and other exports. If you specify C linkage by using the extern “C” keyword, then name mangling won’t be performed.
Let's say you’ve a function Foo(Type1 a,Type2 b) and you wish to export it from the DLL you’re writing, so you just append __declspec(dllexport) keyword before the function name or write a module definition file with the following contents:
LIBRARY FooLib EXPORTS Foo private @1
Here the last line tells the linker (you’ve to do so with a /DEF switch) to export the function.
A DLL can contain many functions but the Entry point function is special. As explained by the MSDN library, the DllMain function is an optional entry point into a DLL. If the function is used, it is called by the system when processes and threads are initialized and terminated, or upon calls to the LoadLibrary and FreeLibrary functions. DllMain name is just a placeholder for the library-defined function name. You must specify the actual name you use when you build your DLL. This function is a bit different from the other functions that the DLL contains in the sense that it allows us to do specific initialization or clan-up as per our requirements. Let’s have a look at the DllMain function in code:
HINSTANCE g_hInstance; BOOL WINAPI DllMain(HINSTANCE hInstDLL,DWORD fdwReason,LPVOID lpvReserved) { switch(fdwReason) { case DLL_PROCESS_ATTACH: g_hInstance = hInstDLL; break; case DLL_THREAD_ATTACH: break; case DLL_THREAD_DETACH: break; case DLL_PROCESS_DETACH: break; }
return TRUE; }
It provides us four places that we can use to do application specific cleanup or initialization. They are:
• DLL_PROCESS_ATTACH: This is the parameter sent in the fdwReason parameter if some process loaded the DLL for the first time. If we are not using some shared data among DLL instances, then each application using this DLL will have its private copy of DLL data. This means the programmer is free to do any initialization. But how can someone know what program loaded the DLL. It’s simple, just call the GetModuleFileName API with NULL as the instance handle. This will give you the name of the application in whose context this handler is executing. This really can make a big contribution if you intend to do application specific initialization. Just make sure to return a TRUE from this function if you want the DLL to load successfully or else it’ll fail, causing the application to fail or unload if it’s linked implicitly (we’re going to discuss that in the next section).
• DLL_THREAD_ATTACH: This is similar to the DLL_PROCESS_ATTACH case but is invoked when a thread tries to use a function from the DLL.
• DLL_THREAD_DETACH: This is the complement of the above DLL_THREAD_ATTACH case and is invoked when a thread detaches from the DLL or finishes using this DLL or explicitly calls FreeLibrary API. You may perform any cleanup of resources that you allocated in DLL_THREAD_ATTACH handler.
• DLL_PROCESS_DETACH: This is the complement of the DLL_PROCESS_ATTACH case and is invoked when a process detaches from the DLL or finishes using this DLL or explicitly calls FreeLibrary API. You may perform any cleanup of resources that you allocated in DLL_THREAD_ATTACH handler.
One may disable the DLL_THREAD_ATTACH and DLL_THREAD_DETACH cases while the DLL loads by calling DisableThreadLibraryCalls API. Another point to note that if some memory allocation is to be done in context of the DLL in the entry point function, one should use the TlsAlloc and TlsFree APIs to do so. TLS stands for Thread Local Storage.
It’s not actually with Dynamic link libraries but it’s worth explaining here so that you may see the benefits of dynamic linking. Special libraries called static libraries are used when you don’t want your final compiled application to have any dependencies. So to make it standalone, the compiler embeds all the code from the static library in the final executable and removes any dependencies it had. If you’ve used the Borland C++ compiler then you’d know because it used to put the code of all the functions that you’ve used in your program in the program code itself and you need no libraries to run the final executables. But the overhead is huge. Each executable will have its own copy of all the functions. With small libraries with a few Kilobyte of overhead, it must be fine but what about big libraries like MFC. So here comes dynamic linking to the rescue.
Dynamic Linking
Dynamic linking refers to linking at runtime rather than at compile time. Information is still embedded in the final executable but it’s the bare minimum for the loader (which loads the executable at runtime in the memory) to identify the DLL the program uses and load them with the application by mapping all the DLLs to the process’ address space. Dynamic linking has two forms depending on how the information is embedded in the final executable. They are Implicit Linking and Explicit Linking:
Implicit Linking: Implicit linking occurs at compile time when an application's code makes a reference to an exported DLL function. When the source code for the calling executable is compiled, the DLL function call translates to an external function reference in the object code. To resolve this external reference, the application must link with the import library (.LIB file) that is produced when the DLL is built.In the VC++ IDE one may specify these in the Project | Settings Dialog on the Link tab as shown below:
Figure 2: Specifying the Import Libraries
Here you can see WinScard.lib and a custom build SCardLib.lib specified as the import libraries for use at link time.
The import library only contains information about the exported symbols from the DLL and no actual code that helps the linker resolve any function calls made to the DLL. If the linker finds the function export information in a lib file ( import library), it assumes that the code for that function exists in a DLL, and to resolve references to that function the linker simply adds information to the final executable file that can be used by system loader when the process starts.
When the loader prepares a program for execution, that contains dynamically linked symbols, it uses the information embedded in the program's executable file at link time to locate the required DLLs. If it cannot locate the DLL, the system terminates the process and displays a dialog box that reports the error. Otherwise, the system loads the DLL (if not already loaded) and maps the DLL modules into the process' address space.
If any of the DLLs have an entry-point function (as discussed above), the operating system calls the function. The fdwReason parameter has the value DLL_PROCESS_ATTACH that indicates that the DLL is attaching to the process. If the entry-point function does not return TRUE, the system aborts loading the process and reports the error.
As a final step, the loader modifies the executable code of the process to point to DLL functions wherever a reference to them are made.
Like the rest of a program's code, DLL code is mapped into the address space of the process when the process starts up and it is loaded into memory only when needed.
Explicit Linking: Most of the applications that are developed use implicit linking because it is the easiest linking method to use. But due to design constraints, sometimes explicit linking is necessary. Here are some common situations when one needs to use explicit linking:
When the application does not know the name of a DLL and/or exports that it will have to load. For example, the application might need to obtain the name of the DLL and the exported functions from a configuration file.
A process using implicit linking is terminated by the operating system if the DLL is not found at process startup. A process using explicit linking is not terminated in this situation and can attempt to recover from the error. For example, the process could notify the user of the error and have the user specify another path to the DLL.
A process using implicit linking is also terminated if any of the DLLs it is linked to have a DllMain function that fails. A process using explicit linking is not terminated in this situation if it handles such a condition.
An application that implicitly links to many DLLs can be slow to start because Windows loads all of the DLLs when the application loads. To improve startup performance, an application can implicitly link to those DLLs needed immediately after loading and wait to explicitly link to the other DLLs when they are needed.
Explicit linking eliminates the need to link the application with an import library. If changes in the DLL cause the export ordinals to change, applications using explicit linking do not have to relink (assuming they are calling GetProcAddress with a name of a function and not with an ordinal value), whereas applications using implicit linking must relink to the new import library.
Usually a dynamic link library would contain many exports that other programs use. However, a DLL may choose not to export even a single function. Why would such a DLL ever be created? The answer is resources and their use in localization.
Let's say you're working on a Windows application that needs to be deployed in many languages. Normally you would write all the menus, static strings in the native language you work in, but what if the application should have a capability to be customizable in terms of the language of operation. One solution could be to make separate resources for each, but that’d take a lot of effort on the developer’s part and obviously not a good solution. Another solution could be to put all the strings and menus etc. that need to be localized are kept in a DLL and you just modify the resources to make it in a different language. And on the users demand choose the appropriate DLL and use those resources in an application. Here comes the use for resource only DLLs.
DLLs and Function Forwarding
You may choose to delegate handling of the functions exported from your DLL (but not implemented) to implementation in some other DLL. This is called function forwarding.
You can do so just by making an entry in your import definition (.def) file which mentions the original function and its ordinal number as:
This is a function exported by WinSCard.dll. While doing so you’ll need to forward all the functions which the original library exports. It means a lot of work. So you’d ask, “Why do I need something like that?” The answer is if you need to override the default behavior of a few functions of a DLL where you don’t have the source code to it …….. You may use this technique. DLL function hooking is another way which is more complicated, and here are we doing it the simple but hard way. So you just forward all the functions other than you need to override and just rename the original DLL to something like xyz.dll and provide an entry for each overridden and forwarded function. So a function Foo1 overriden and Foo2 forwarded your def file will have the entries like:
Foo1 private @1 Foo2 = xyz.Foo2 @2
And you’re done. Now after doing the processing in your defined Foo1 you may or may not choose to call the xyz.Foo1 function based on your design.
Starting with Windows 2000, you can ensure that your application uses the correct version of a DLL by creating a redirection file. The redirection file must be named as “appname.local”. For example; an application c:myappmyapp.exe calls LoadLibrary using the path c:program filescommon filessystemmydll.dll. If c:myappmyapp.exe.local and c:myappmydll.dll exist, LoadLibrary will load c:myappmydll.dll.
Otherwise, LoadLibrary will load c:program filescommon filessystemmydll.dll.
Writing your own DLLs
I’ll explain briefly how to write your own DLLs and use them in your applications using VC++ 6.0 and Visual Basic 6.0. The source code files for the examples can be downloaded from the last page of this article. So let’s get started!
There are 3 steps: Creating Source Files, Exporting Symbols, and Creating an Import Library.
1. Creating Source Files
• Fire up Microsoft Visual C++ IDE and create a Blank Workspace with the name DLLsTraining. • Add a new “Win32 Dynamic-Link Library” project with name “LoadTimeDLL” to the workspace “DLLsTraining”. • Choose an empty DLL project • Add a new file named “LoadTimeDLL.cpp” and “LoadTimeDLL.h” to LoadTimeDLL project. • Add code shown below to “LoadTimeDLL.cpp” and “LoadTimeDLL.h”
Read the comments so as to find out why are you writing this.
LoadTimeDLL.cpp
#include <windows.h> // Define the symbol LOAD_TIME_DLL_EXPORTS before including // "LoadTimeDll.h". This makes the __declspec(dllexport) visible to the // implementing .cpp file. When using this header in other modules, don’t define // this symbol. Then those modules will see __declspec(dllimport). This makes // those modules to import the function form the DLLs.
#define LOAD_TIME_DLL_EXPORTS
#include "LoadTimeDLL.h"
void SayLoadTimeDLL() { MessageBox(NULL, TEXT(“Information"), TEXT("I am a load time DLL"),MB_OK); }
LoadTimeDLL.h
//Add the include guards to protect from cyclic an redundant inclusions #ifndef _LOAD_TIME_DLL_H #define _LOAD_TIME_DLL_H
#ifdef LOAD_TIME_DLL_EXPORTS // This is syntax that has to be followed to export a function that can be used // from other module, which loaded this dll. “__declspec(dllexport)” tells the // compiler to export the definition of this function. extern __declspec(dllexport) void SayLoadTimeDLL() ; #else // This is syntax that has to be visible for the module that uses this function. // “__declspec(dllimport)” tells the compiler to bring the definition of this // function from a DLL. extern __declspec(dllimport) void SayLoadTimeDLL() ; #endif
#endif
Now Compile the project LoadTimeDLL. The out put should look like: -------------Configuration: LoadTimeDLL - Win32 Debug------------ Compiling... LoadTimeDLL.cpp Linking...
Creating library Debug/LoadTimeDLL.lib and object Debug/LoadTimeDLL.exp
LoadTimeDLL.dll - 0 error(s), 0 warning(s)
If the generation of “LoadTimeDLL.lib” is absent, it means no functions were exported and DLL cannot be loaded or used.
(To test, change the name of the SayLoadtimeDLL to SayLoadtimeDll. Observe case.)
What you’ve done is creating a library that you can implicitly link to.
Now perform the following steps to create a library that you’ll link to explicitly:
• Add a new “Win32 Dynamic-Link Library” project with name “RunTimeDLL” to the workspace “DLLsTraining”. • Choose an empty DLL project • Add a new file named “RunTimeDLL.cpp”, “RunTimeDLL.h” and “RunTimeDLL.def” to RunTimeDLL project. • Add code shown below to “RunTimeDLL.cpp” and “RunTimeDLL.h”
RunTimeDLL.cpp
#include <windows.h>
#include "RunTimeDLL.h"
// Definition of the function which will be exported to other modules.
void SayRunTimeDLL() { MessageBox( NULL, TEXT("I am a run time DLL"), TEXT(“Information"), MB_OK ); }
RunTimeDLL.h
// Add the include guards to protect from cyclic an redundant inclusions
#ifndef _RUN_TIME_DLL_H #define _RUN_TIME_DLL_H
// Here the function will be exported using .def file. So no need to export using the // declspec. This is the way to export the function from an dll that can be loaded at // runtime and asked for the addresses of the function.
void SayRunTimeDLL() ;
#endif
RunTimeDLL.def
LIBRARY "RunTimeDLL"
EXPORTS SayRuntimeDLL
Now Compile the project RunTimeDLL. The output should look like:
Creating library Debug/RunTimeDLL.lib and objectDebug/RunTimeDLL.exp
RunTimeDLL.dll - 0 error(s), 0 warning(s)
If the generation of “RunTimeDLL.lib” is absent, it means no functions were exported and DLL cannot be loaded used.
(To test change the name of the SayRunTimeDLL to SayRunTimeDll in .def file. Observe case.)
2. Exporting Symbols
As I explained earlier that by using the __declspec (dllexport) keyword you made that dll export the particular symbol. Alternatively as in the RunTimeDLL when you create a .def file you state the function names to be exported.
3. Creating an Import Library
When you build the projects in the IDE.The .lib file produced with the DLL is the import library that your applications may link to if they need to use the function that you wrote in the DLL.
Here are a few points that any developer would need to keep in mind:
• When the system starts a program that uses load-time dynamic linking, it uses the information in the file to locate the names of the required DLL(s).
Following search sequence will be used to locate DLLs:
The directory from which the application loaded.
The current directory.
The Windows system directory. Use the GetSystemDirectory function to get the path of this directory.
The 16-bit Windows system directory. There is no function that obtains the path of this directory, but it is searched. The name of this directory is System.
The Windows directory. Use the GetWindowsDirectory function to get the path of this directory.
The directories that are listed in the PATH environment variable.
• The DLL is mapped into the virtual address space of the process during its initialization and is loaded into physical memory only when needed.
• DLLs are loaded using LoadLibrary or LoadLibraryEx APIs.
• Standard search sequence (mentioned above) will be used to locate DLLs unless you decide to redirect the DLL(Win2K only).
• Two DLLs that have the same base file name and extension but are found in different directories are not considered to be the same DLL.
• The system calls the entry-point function in the context of the thread that called LoadLibrary or LoadLibraryEx.
• If the system cannot find the DLL or if the entry-point function returns FALSE, LoadLibrary or LoadLibraryEx returns NULL.
• The process can use GetProcAddress to get the address of an exported function in the DLL using a DLL module handle returned by either LoadLibrary, LoadLibraryEx, or GetModuleHandle
• When the DLL module is no longer needed, the process can call FreeLibrary or FreeLibraryAndExitThread.
• Run-time dynamic linking enables the process to continue running even if a DLL is not available.
• Run-time dynamic linking can cause problems if the DLL uses the DllMain function to perform initialization for each thread of a process, because the entry-point is not called for threads that existed before LoadLibrary or LoadLibraryEx is called.
• DLLs can contain global data or local data.
• The default scope of DLL variables is the same as that of variables declared in the application.
• When a DLL allocates memory using any of the memory allocation functions (GlobalAlloc, LocalAlloc, HeapAlloc, and VirtualAlloc), the memory is allocated in the virtual address space of the calling process and is accessible only to the threads of that process.
• The thread local storage (TLS) functions enable a DLL to allocate an index for storing and retrieving a different value for each thread of a multithreaded process.
• Warning: VC++ compiler supports a syntax that enables you to declare thread-local variables: _declspec(thread). If you use this syntax in a DLL, you will not be able to load the DLL explicitly using LoadLibrary or LoadLibraryEx. If your DLL will be loaded explicitly, you must use the thread local storage functions instead of _declspec(thread).
• GetModuleFileName Retrieves the full path and file name for the file containing the specified module.
• GetModuleHandle Retrieves a module handle for the specified module.
• GetModuleHandleEx Retrieves a module handle for the specified module.
• GetProcAddress Retrieves the address of an exported function or variable from the specified DLL.
• LoadLibrary Maps the specified executable module into the address space of the calling process.
• LoadLibraryEx Maps the specified executable module into the address space of the calling process.
The DLL is not in the path that can be located by system
The entry point setting of the DLL has changed
DLL entry point function is returning FALSE
Failed to get the procedure address from DLL:
The exporting of the function may not be correct.
Use .def file to export the functions.
System Crashing while accessing global data:
If your system is multi threaded, ensure that the data is protected across the threads.
How to export a class?
Exactly similar to that of function except the __declspec should come between the keyword class and the name of the class. Example: class __declspec(dllexport) CexportedClass The module that is using this call should see a declaration like class __declspec(dllimport) CexportedClass
Weird “User breakpoint called from code at 0x77f7629c”
In project settings-->C/C++->Code Generation category choose “Multithreaded DLL” under “Use runtime library” heading.
The Sample Code
You can download the code in the examples above so that you can try hands on what you’ve just read. Download the code here.
The PowerPoint Presentation
The PowerPoint presentation with this article is a quick refresher on the topic of DLLs and I hope you’ll find that useful if you’re training others about using DLLs and similar topics. Download the PPT here.