Introduction Especially when controlling hardware through APIs provided by the vendor, but also when trying to avoid errors during application startup due to certain dlls not being available on the system, dynamic loading of the DLL is a common technique. The different ways of linking to a dll from C++ have been described in several articles on this site. For those not entirely familiar with the subject, I recommend the article Step by Step: Calling C++ DLLs from VC++ and VB - Part 4 by Hans Dietrich. Unfortunately, dynamic loading involves a lot of repetitive but error prone coding (see below) and extra care is necessary when keeping the code up to date in case the API of the dll changes. In order to reduce the amount of necessary coding, DLL-wrappers have been proposed, see e.g. the LateLoad DLL Wrapper. Besides avoiding code duplication, such a wrapper allows for more sophisticated error checking. However, the function declarations still have to be extracted from the header of the dll, kept up to date and the code accessing the API needs to be changed. So-called delay loading on the other hand does not require any code changes or extra code but it lacks the flexibility of dynamic loading. This article proposes a new type of wrapper requiring only minimal code changes to switch between dynamic and static loading but still offering all the features of existing DLL-wrappers and more.
Background Why use dynamic loading? Several good reasons why dynamic loading can be useful or even necessary are listed in Hand Dietrich's article and most of them are still valid 7 years later. In addition to the reasons listed there, I have encountered cases where I needed to link to different versions of the dll in response to some user input or depending on the hardware version I accessed and, beleive it or not, where I had to load a dll twice under different names in order to be able to access two pieces of identical hardware.
Delay loading to the rescue?
Delay loading is also described in detail elsewhere. What it does in essence is that it automates the dynamic loading process by having the linker generate the code necessary. It is, if you wish, a linker generated DLL-wrapper. Indeed, it takes care of some of the headaches of the other two methods: No code changes are necessary as delay loading of a dll is simply enabled by a linker switch and unlike for statical loading your application will still start even if it links to a library which is not present on the current system or if it imports a symbol which is missing from the available version of the Dll. Existing symbols are loaded when accessed at runtime from your code. On the downside, when used without extra error checking, your application crashes as soon as you try to access a symbol that is not present in the loaded dll (but was part of the import library) or if the dll cannot be loaded when the first symbol is accessed. To make sure this does not happen, you could call __HrLoadAllImportsForDll (see delayimp.h but careful: LoadLibrary is not case sensitive but this API is.) manually and disable use of the DLL in case not all symbols could be loaded or you could try to implement more granular error checking by setting hooks that are called when loading a symbol fails. But this involves calling into APIs much more compelx than GetProcAddress() and thus writing (unnecessarily) complex code.
What's wrong with the traditional way?
Nothing. It merely is unelegant, requires a lot of cut and paste and search and replace and it is error prone, especially when APIs change or the vendor ships headers that are out of sync with the dll (well, or, if you mix them up). Also it requires you to change your code when switching from static to dynamic linking. The demo code includes the code necessary to dynamically load psapi.dll in your application and illustrates this. Ideally you create one header file, say psapi_dynamic.h that contains the necessary typedefs and declaration of the function poitnters. It also contains the declaration of a function that loads the DLL and symbols load_psapi(): #ifndef _PSAPI_DYNLINK_H_
#define _PSAPI_DYNLINK_H_#include <psapi.h>typedef BOOL
(WINAPI *
EnumProcesses_t)(
__out_bcount(cb) DWORD * lpidProcess,
__in DWORD cb,
__out LPDWORD lpcbNeeded
);typedef BOOL
(WINAPI *
EnumProcessModules_t)(
__in HANDLE hProcess,
__out_bcount(cb) HMODULE *lphModule,
__in DWORD cb,
__out LPDWORD lpcbNeeded
);... extern EnumProcesses_t pEnumProcesses;
extern EnumProcessModules_t pEnumProcessModules;
...// Try to load the psapi.dll
// returns
// 0 if library was not found,
// 1 if all symbols were loaded and
// 2 if some symbols are NULL
int psapi_load();
Read more: Codeproject
QR:
Background Why use dynamic loading? Several good reasons why dynamic loading can be useful or even necessary are listed in Hand Dietrich's article and most of them are still valid 7 years later. In addition to the reasons listed there, I have encountered cases where I needed to link to different versions of the dll in response to some user input or depending on the hardware version I accessed and, beleive it or not, where I had to load a dll twice under different names in order to be able to access two pieces of identical hardware.
Delay loading to the rescue?
Delay loading is also described in detail elsewhere. What it does in essence is that it automates the dynamic loading process by having the linker generate the code necessary. It is, if you wish, a linker generated DLL-wrapper. Indeed, it takes care of some of the headaches of the other two methods: No code changes are necessary as delay loading of a dll is simply enabled by a linker switch and unlike for statical loading your application will still start even if it links to a library which is not present on the current system or if it imports a symbol which is missing from the available version of the Dll. Existing symbols are loaded when accessed at runtime from your code. On the downside, when used without extra error checking, your application crashes as soon as you try to access a symbol that is not present in the loaded dll (but was part of the import library) or if the dll cannot be loaded when the first symbol is accessed. To make sure this does not happen, you could call __HrLoadAllImportsForDll (see delayimp.h but careful: LoadLibrary is not case sensitive but this API is.) manually and disable use of the DLL in case not all symbols could be loaded or you could try to implement more granular error checking by setting hooks that are called when loading a symbol fails. But this involves calling into APIs much more compelx than GetProcAddress() and thus writing (unnecessarily) complex code.
What's wrong with the traditional way?
Nothing. It merely is unelegant, requires a lot of cut and paste and search and replace and it is error prone, especially when APIs change or the vendor ships headers that are out of sync with the dll (well, or, if you mix them up). Also it requires you to change your code when switching from static to dynamic linking. The demo code includes the code necessary to dynamically load psapi.dll in your application and illustrates this. Ideally you create one header file, say psapi_dynamic.h that contains the necessary typedefs and declaration of the function poitnters. It also contains the declaration of a function that loads the DLL and symbols load_psapi(): #ifndef _PSAPI_DYNLINK_H_
#define _PSAPI_DYNLINK_H_#include <psapi.h>typedef BOOL
(WINAPI *
EnumProcesses_t)(
__out_bcount(cb) DWORD * lpidProcess,
__in DWORD cb,
__out LPDWORD lpcbNeeded
);typedef BOOL
(WINAPI *
EnumProcessModules_t)(
__in HANDLE hProcess,
__out_bcount(cb) HMODULE *lphModule,
__in DWORD cb,
__out LPDWORD lpcbNeeded
);... extern EnumProcesses_t pEnumProcesses;
extern EnumProcessModules_t pEnumProcessModules;
...// Try to load the psapi.dll
// returns
// 0 if library was not found,
// 1 if all symbols were loaded and
// 2 if some symbols are NULL
int psapi_load();
Read more: Codeproject
QR:
0 comments:
Post a Comment