Download demo project - 11 Kb
Usually, programmers use static linking when using functions in a DLL. This
is the most easiest way to use a DLL. However, if a DLL is missing, application
cannot be started. This is not a big problem: missing DLL means that something
is wrong with application installation and the application should not be started
anyway.
However, if an application is designed in such a way as to offer some optional
features with the help of one or more DLLs, missing DLL should not prevent the
application to run normally (but without optional features). In this case, DLL
must be loaded dynamically. This article presents a base class which offers
all the necessary functionality for dynamic DLL loading. It is limited to DLLs
exporting plain functions (not C++ classes). This is not a big limitation since
all platform DLLs are written in this way. DLLs which export C++ classes are
almost always used with static linking.
Complete functionality is implemented in a class TDllModule. This class is
not intended to be used directly, instead programmers should derive application
specific class and extend it in a way explained in the following example. Only
2 virtual functions are important in this class: Create() and Destroy(). Create()
function returns TRUE if the DLL is loaded correctly and an instance handle
obtained. Destroy() will unload the DLL from process memory.
Let's assume that some application needs the services of Microsoft's PSAPI
(Process Status API) DLL to enumerate all running processes and display process
executable filename. PSAPI DLL may not be installed on the machine since it
is not part of the operating system. So, we need to dynamically load it and
check whether it is loaded before using it.
Therefore, we create a class TProcessStatusModule derived from TDllModule.
We also need to create typedefs for functions needed from the DLL (this is not
necessary but will keep the code easier to read).
typedef BOOL (WINAPI *TFEnumProcesses)(
DWORD * lpidProcess, DWORD cb, DWORD * cbNeeded
);
typedef BOOL (WINAPI *TFEnumProcessModules)(
HANDLE hProcess, HMODULE *lphModule, DWORD cb, LPDWORD lpcbNeeded
);
typedef DWORD (WINAPI *TFGetModuleBaseName)(
HANDLE hProcess, HMODULE hModule, LPSTR lpBaseName, DWORD nSize
);
typedef DWORD (WINAPI *TFGetModuleFileNameEx)(
HANDLE hProcess, HMODULE hModule, LPSTR lpFilename, DWORD nSize
);
typedef BOOL (WINAPI *TFGetModuleInformation)(
HANDLE hProcess, HMODULE hModule, LPMODULEINFO lpmodinfo, DWORD cb
);
class TProcessStatusModule : public TDllModule {
private:
TFEnumProcesses FEnumProcesses;
TFEnumProcessModules FEnumProcessModules;
TFGetModuleBaseName FGetModuleBaseName;
TFGetModuleFileNameEx FGetModuleFileNameEx;
TFGetModuleInformation FGetModuleInformation;
public:
TProcessStatusModule();
virtual ~TProcessStatusModule();
virtual BOOL Create(void);
virtual void Destroy(void);
BOOL EnumProcesses(DWORD * lpidProcess, DWORD cb, DWORD * cbNeeded);
BOOL EnumProcessModules(HANDLE hProcess,HMODULE *lphModule,DWORD cb,
LPDWORD lpcbNeeded);
DWORD GetModuleBaseName(HANDLE hProcess,HMODULE hModule,LPSTR lpBaseName,
DWORD nSize);
DWORD GetModuleFileNameEx(HANDLE hProcess,HMODULE hModule,
LPSTR lpFilename,DWORD nSize);
BOOL GetModuleInformation(HANDLE hProcess,HMODULE hModule,
LPMODULEINFO lpmodinfo,DWORD cb);
};
Most important function in the class example above is Create(). It needs first
to call the base class that loads the DLL into the process memory and obtains
a handle and second, to obtain pointers to functions in a DLL that are needed
for the current application. If this function returns FALSE, then the DLL is
not loaded correctly and cannot be used. Its implementation is the following:
BOOL TProcessStatusModule::Create(void)
{
if (TDllModule::Create()) {
FEnumProcesses = (TFEnumProcesses)::GetProcAddress(m_hHandle,
_T("EnumProcesses"));
FEnumProcessModules = (TFEnumProcessModules)::GetProcAddress(m_hHandle,
_T("EnumProcessModules"));
...
return TRUE;
}
return FALSE;
}
All that is left to be done is the actual execution of functions in a DLL.
All DLL functions needed in the application have a corresponding method in the
class above (each method has exactly the same arguments as the actual DLL function,
the return code is also the same). Example of one function is the following:
BOOL TProcessStatusModule::EnumProcesses(DWORD *lpidProcess, DWORD cb, DWORD *cbNeeded)
{
ASSERT(FEnumProcesses != NULL);
if (FEnumProcesses)
return FEnumProcesses(lpidProcess,cb,cbNeeded);
return FALSE;
}
The only remaining issue is the error handling of the function(s) like the
one above. Original DLL function returns BOOL as the function above. Even if
the DLL is loaded correctly, we still do not know (if the return code of the
function is FALSE) whether the function really returned FALSE or the function
is not defined as exported from the DLL (this is valid for release version of
the code since debug version will assert). There are 2 solutions for this problem:
- Check whether all needed DLL functions are exported from the DLL (in Create())
by comparing all obtained function pointers to NULL. If any one of them is
NULL, unload the DLL and prevent application to use it.
- In the function above (and all other functions), use exception handling
instead of a simple return code. If the function succeeds then it is guarantied
that the return code comes from the DLL function.
I have used this technique in many application for DLLs like Winsock2, TAPI,
PDH (Performance Data Helper), PSAPI (Process Status API) and similar.
Latest update of this article may be found here.