Dlls instead of static libraries offers many advantages. Mainly, each Dll module can be replaced relatively safely without the need to compile the whole program, as long as the Dll’s interface does not change. However, comfort has a price tag; Relying on the correct Dll with the correct interface to just be there when you need it on every computer is naive, and error prone. This article suggests that dynamic loading errors can be solved quite easily by using the delay load Dlls mechanism wisely. It suggests a way to transform any code that uses Dll’s to be error free using the delay load mechanism, while keeping it’s elegance.
Note: In the following code samples, linker options will be written as pragmas in order to present exactly how the program is to be linked. These pragmas should preferably be linker options in the project’s development environment.
Version 1.0: Error Prone Loading
The following program links dynamically with shlwapi.dll. The Shell Lightweight Utility Api (shlwapi) is included in Internet Explorer 4.0 or later.
Observe the following program:
#include <Windows.h> #include <SHLWAPI.H> #pragma comment(lib, "shlwapi.lib") int WINAPI WinMain(HINSTANCE hinstExe, HINSTANCE, LPSTR pszCmdLine, int nCmdShow) { BOOL x,y = 0; x = PathIsRootA("c:\\"); x = PathIsRelativeA("c:\\temp"); return(0); }
V1.0 above uses Dlls in the most “natural” manner – as written in the msdn. Even though, When the compiled binary will run on an operating system without IE 4.0 or later (Windows 95 or NT without upgrades), the process would terminate instantly because the shlwapi.Dll will not be found on the machine. Version 1.0 has no way to defend itself against this error, because it occurs before it is even loaded.
Note: Although the shlwapi is now common on virtually any Windows station, it will be used as a common example in this article. Keep in mind that similar problems can occur in many other situations involving dependencies upon Dlls existence on the host computer – whether they are part of the operating system or proprietary.
Version 1.01: Run Time Loading
One approach to identify loading errors in runtime is to dynamically load the Dll manually. As in dynamic loading, the program doesn’t have to include shlwapi.h or link with the stub shlwapi.lib. However, Using LoadLibrary for loading and GetProcAddress to get each function pointer could get real ugly, and may lead to many mistakes when concerning larger pieces of code and more complex functions.
The simple example above turns out to be tedious when using dynamic loading:
#include <Windows.h> int WINAPI WinMain(HINSTANCE hinstExe, HINSTANCE, LPSTR pszCmdLine, int nCmdShow) { BOOL x,y = 0; HMODULE hShlWapi = LoadLibrary("shlwapi.dll"); if (NULL != hShlWapi) { typedef BOOL (WINAPI *PATH_IS_ROOT_A)(LPCTSTR pPath); typedef BOOL (WINAPI *PATH_IS_RELATIVE_A)(LPCTSTR pPath); PATH_IS_ROOT_A pfPathIsRootA = (PATH_IS_ROOT_A)GetProcAddress(hShlWapi, "PathIsRootA"); PATH_IS_RELATIVE_A pfPathIsRelativeA = (PATH_IS_RELATIVE_A)GetProcAddress(hShlWapi, "PathIsRelativeA"); if (pfPathIsRootA && pfPathIsRelativeA) { x = pfPathIsRootA("c:\\"); y = pfPathIsRelativeA("c:\\temp"); } } return(0); }
Obviously, Version 1.01 is not how we want our code to look like. In comparison to V1.00, the code is complicated, whereas it is only supposed to simply call the two pre-defined functions. In V1.01, we do not use the header file SHLWAPI.h and a result we lose many benefits of using headers for using the Dlls we want:
- Compiler level parameter checking is useless since we declare the function types ourselves, Declaring the function pointers ourselves is very risky – one simple mistake in parameter definition for a function pointer typedef could lead to trashing the stack and lead the program toward an undefined behavior.
- Source editors’ intellisense is useless for knowing what parametes we have to pass on to the functions.
- With all the function pointers and typedefs, it gets difficult to see the gist, and larger pieces of code could get almost unreadable and unmaintainable. Code writing got more complicated and the code got longer., not to mention code review and debugging.
After examining the naive approach and the run-time loading approach, the next sections will demonstrate elegant and more flexible solutions, using the delay load Dlls mechanism.
Version 1.02: Simple Delay Load Dll
Ever since the early ages of VC6.0 in 1997, Microsoft has included the Delay Loaded Dlls mechanism in the dev environment. By using the Delay Load dll mechanism, it is possible to perfectly control the Dll load without loading the Dll and declaring function pointers explicitly. The delay load mechanism insures that the required Dll will be loaded dynamically only when it is first needed.
Any binary that uses the Delay Load Dlls has to be linked with Delayimp.lib which implements the dynamic load itself. Delayimp includes, among the rest, a function called __delayLoadHelper which is responsible for calling LoadLibrary if the delay loaded Dll is not loaded when its functions are called, and for calling GetProcAddress to replace calls to delay loaded functions with the addresses of the actual functions in the loaded Dlls when they are first called.
The DelayLoad linker switch orders the linker to replace all calls to the delay loaded Dll with calls to __delayLoadHelper. When __delayLoadHelper fails to help load a Dll or load a function from a delay loaded Dll, it throws a structured exception with the reason for failure in the seh exception pointers.
Version 1.02 resembles Version 1.00 except the linker options and the ability to detect and handle any loading problems with a structured exception that __delayLoadHelper throws upon failure:
#include <Windows.h> #include <SHLWAPI.h> #pragma comment(lib, "Delayimp.lib") #pragma comment(lib, "shlwapi.lib") // Tell the linker that my DLL should be delay loaded #pragma comment(linker, "/DelayLoad:shlwapi.dll") int WINAPI WinMain(HINSTANCE hinstExe, HINSTANCE, LPSTR pszCmdLine, int nCmdShow) { BOOL x,y = 0; // Wrap all calls to delay-load DLL functions inside an SEH // try-except block __try { x = PathIsRootA("c:\\"); y = PathIsRelativeA("c:\\temp"); } __except (EXCEPTION_EXECUTE_HANDLER) { // Prepare to exit elegantly } return(0); }
The delay load Dll mechanism also supplies the reason for failure in the seh exception pointers – whether the Dll was not found, or the function wasn’t found in the Dll, and which Dll and which function. An exception filter may be declared to filter SEH exceptions, and handle the delay helper exceptions correctly. The ExceptionInformation inside the exception record points to a DelayLoadInfo struct, which contains all we have to know about the exception – the Dll name, details about the function and more. In order to extract the actual exception reason, we can use the VcppException macro defined in delayimp.h.
The error identifying code below may seem a bit complicated, but it only has to appear once in your whole program:
#include <Windows.h> #include <SHLWAPI.h> #include <DelayImp.h> #pragma comment(lib, "Delayimp.lib") #pragma comment(lib, "shlwapi.lib") // Tell the linker that my DLL should be delay loaded #pragma comment(linker, "/DelayLoad:shlwapi.dll") int WINAPI WinMain(HINSTANCE hinstExe, HINSTANCE, LPSTR pszCmdLine, int nCmdShow) { BOOL x,y = 0; // Wrap all calls to delay-load DLL functions inside an SEH // try-except block __try { x = PathIsRootA("c:\\"); y = PathIsRelativeA("c:\\temp"); } __except (DelayLoadDllExceptionFilter(GetExceptionInformation())) { // Prepare to exit elegantly } return(0); } LONG WINAPI DelayLoadDllExceptionFilter(PEXCEPTION_POINTERS pExcPointers) { LONG lDisposition = EXCEPTION_EXECUTE_HANDLER; PDelayLoadInfo pDelayLoadInfo = PDelayLoadInfo(pExcPointers->ExceptionRecord->ExceptionInformation[0]); switch (pExcPointers->ExceptionRecord->ExceptionCode) { case VcppException(ERROR_SEVERITY_ERROR, ERROR_MOD_NOT_FOUND): printf("Dll %s was not found\n", pDelayLoadInfo->szDll); break; case VcppException(ERROR_SEVERITY_ERROR, ERROR_PROC_NOT_FOUND): if (pdli->dlp.fImportByName) { printf("Function %s was not found in %s\n", pDelayLoadInfo->dlp.szProcName, pDelayLoadInfo->szDll); } else { printf("Function ordinal %d was not found in %s\n", pDelayLoadInfo->dlp.dwOrdinal, pDelayLoadInfo->szDll); } break; default: // Exception is not related to delay loading lDisposition = EXCEPTION_CONTINUE_SEARCH; break; } return(lDisposition); }
Version 1.02 above looks much better than with run-time loading and handles all the possible errors, but it trusts DelayImp.lib with all the loading process. Having full control over the delay loading process is possible, as demonstrated in the following final section.
Version 1.03: Delay Load Dll with special recovery measures
Sometimes the show must go on and the program is required to recover from errors and give its best effort to keep working even when some Dll decides not to load for some reason, or when some imported function is missing in the imported dll. The Delay load implementation supplies a hooking mechanism for us to toy with several error situations before __delayLoadHelper raises an exception. __delayLoadHelper calls __pfnDliFailureHook on failure and __pfnDliNotifyHook on other notifications. Using the hooking mechanism it is possible to alter the behavior of the delay load helper for “online” better error recovery.
It is important to state here that the implementation of the __delayLoadHelper uses the calling thread’s context. The whole delay load mechanism works synchroniously from the calling thread, including hooks. This means that When a delay loaded function is called, the program’s flow blocks until all the arrangements are made by the delay load implementation, including running the hook functions.
Both hooking functions have the same prototype, which is presented in Delayimp.h as follows:
typedef FARPROC (WINAPI *PfnDliHook)( unsigned dliNotify, PDelayLoadInfo pdli );
The delayLoadInfo structure is the same we discussed earlier. dliNotify represents the type of notification or error. The notification enum can be found in Microsoft’s delayimp.h :
// // Delay load import hook notifications // enum { dliStartProcessing, // 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 FARPROC return value dliFailLoadLib, // failed to load library, fix it by // returning a valid HMODULE dliFailGetProc, // failed to get proc address, fix it by // returning a valid FARPROC dliNoteEndProcessing, // called after all processing is done, no // no bypass possible at this point except // by longjmp()/throw()/RaiseException. };
In other words, the hook function doesn’t only get the notification, but it is also able to override the default behavior or fix the default behavior on errors. If the error is fixed with the hooking function, the exception is not thrown.
In this case we are only interested in failures, which means we would like to hook only dliFailGetProc and dliFailLoadLib. The hook function also gets a PDelayLoadInfo struct which includes all the details about the delay load dll and the required function if any in order to let the hook function realize what has gone wrong. Note that for the hook to work, the __pfnDliFailureHook function has to be explicitly declared to be our hook function, as in V1.03 ahead.
The final version:
#include <Windows.h> #include <DelayImp.h> // Required for hooking #include <SHLWAPI.h> #pragma comment(lib, "Delayimp.lib") #pragma comment(lib, "shlwapi.lib") // Tell the linker that my DLL should be delay loaded #pragma comment(linker, "/DelayLoad:shlwapi.dll") int WINAPI WinMain(HINSTANCE hinstExe, HINSTANCE, LPSTR pszCmdLine, int nCmdShow) { // Wrap all calls to delay-load DLL functions inside SEH __try { BOOL x,y = 0; x = PathIsRootA("c:\\"); y = PathIsRelativeA("c:\\temp"); } __except (EXCEPTION_EXECUTE_HANDLER) { // Handle the error. Errors will reach here only if // the hook function could not fix it. } return(0); } // delayHookFunc - Delay load hooking function FARPROC WINAPI delayHookFailureFunc (unsigned dliNotify, PDelayLoadInfo pdli) { FARPROC fp = NULL; // Default return value // NOTE: The members of the DelayLoadInfo structure pointed // to by pdli shows the results of progress made so far. switch (dliNotify) { case dliFailLoadLib: // LoadLibrary failed. // In here a second attempt could be made to load the dll somehow. // If fp is still NULL, the ERROR_MOD_NOT_FOUND exception will be raised. fp = NULL; break; case dliFailGetProc: // GetProcAddress failed. // A second attempt could be made to get the function pointer somehow. // We can override and give our own function pointer in fp. // Ofcourse, fp is still going to be NULL, // the ERROR_PROC_NOT_FOUND exception will be raised. fp = (FARPROC) NULL; break; } return(fp); } // __delayLoadHelper gets the hook function in here: PfnDliHook __pfnDliFailureHook = delayHookFailureFunc;
In the hook function, the given PDelayLoadInfo can be used to know exactly what happened, as in V1.02, and act accordingly.
Although not entirely relevant for the purpose of error handling, it is worth mentioning that a second kind of hook is available in the delay load implementation – the notify hook, which is used for any notification event. In order to hook notifications all we have to do is to declare a function of the same prototype as delayHookFailureFunc, and declare:
PfnDliHook __pfnDliNotifyHook = delayHookNotifyFunc;
In version 1.03, we presented an easy way to recover from loading errors without changing the original code. The hooking function only has to be declared once in the entire project and its existence does not require any special code techniques when writing code that uses delay load dlls. The only additions to the original code was the try-except block, which arguably may have been there, delay loading or not.
Over the fence: GCC Delay loading
A little out of this article’s scope, it is always interesting to see what’s on the other side of the fence. In GCC, delay loading is called lazy binding, and works a little differently. Loading a shared library dynamically is done with a “LoadLibrary” equivalent named dlopen:
void * dlopen(const char * filename, int flags)
Loading any symbol from a shared library is done with dlsym:
void* dlsym(void * libhandle, char * symbol);
Besides being the “GetProcAddress” equivalent, dlsym can also load classes, variables and any other symbol.
Anyway, in contrast to LoadLibrary, dlopen has a second parameter that can be RTLD_LAZY which means lazy loading or RTLD_NOW which means immediate loading. When using immediate loading, all the library’s symbols are loaded immediately. Lazy loading means that any symbol in that library is only loaded on demand – only upon a call to dlsym.
GCC’s lazy loading mechanism looks more like the LoadLibrary/GetProcAddress combination and really doesn’t meet the Windows delay loading implementation standards.
Conclusion
Every Win32 project can easily handle errors by using delay loading – only adding a few compiler switches as seen in V1.02 and catching the exception.
Adding a few other compiler switches and a global hook function once in the entire project can easily make your project virtually invincible against dll loading errors, as seen in V1.03.
Working with Dlls, there has to be a good enough reason not to use the delay loading implementation as an error recovery and error handling mechanism for loading errors. Otherwise, either the project cannot immune itself against these errors, or ,as seen in V1.01, you will have to work real hard to identify and recover from loading errors.