Figure 1 Incorrect SEH to Selectively Load DLLs
#include <windows.h>
// Decalre a pointer to the MessageBeep function
typedef BOOL (WINAPI *PFNMESSAGEBEEP)(UINT);
PFNMESSAGEBEEP g_pfnMessageBeep = NULL;
LONG ExceptionFilter(); // Forward reference
int WINAPI WinMain (HINSTANCE hinstExe, HINSTANCE hinstExePrev,
LPSTR szCmdLine, int nCmdShow) {
__try {
// Try to call MessageBeep
g_pfnMessageBeep(MB_OK);
}
__except(ExceptionFilter()) {
}
return(0);
}
LONG ExceptionFilter () {
// Make sure that USER32.DLL is loaded
HMODULE hinstDll = LoadLibrary(__TEXT("User32"));
// Get the address of MessageBeep and save it
g_pfnMessageBeep = (PFNMESSAGEBEEP)
GetProcAddress(hinstDll, "MessageBeep");
// Re-execute the instruction that
// raised the access violation
return(EXCEPTION_CONTINUE_EXECUTION);
}
Figure 2 Correct SEH to Selectively Load DLLs
/*************************************************************
Module name: DemandLoadDll.c
Written by: Jeffrey Richter
Purpose: Demonstrates how to load a DLL and call
functions in it on demand
*************************************************************/
#define STRICT
#include <windows.h>
//////////////////////////////////////////////////////////////
// Here is where you declare the function address variables
// for the functions that you wish to link to on demand.
// Each address is offset from PFN_FIRSTINDEX which points
// to address space that must raise an access violation
// when we attempt to execute it. I have chosen an address
// of 0xFFFF0000 which is just below 4GB.
#define PFN_FIRSTINDEX (0xFFFF0000)
// Dynamically link to MessageBeep
typedef BOOL (WINAPI *PFNMESSAGEBEEP)(UINT);
PFNMESSAGEBEEP g_pfnMessageBeep =
(PFNMESSAGEBEEP) (PFN_FIRSTINDEX + 0);
// Dynamically link to MessageBox (A/W)
typedef int (WINAPI *PFNMESSAGEBOX)(HWND,
LPCTSTR, LPCTSTR, int);
PFNMESSAGEBOX g_pfnMessageBox =
(PFNMESSAGEBOX) (PFN_FIRSTINDEX + 1);
// Make sure that PFN_LASTINDEX is set to the
// last function address index used above.
#define PFN_LASTINDEX (PFN_FIRSTINDEX + 1)
//////////////////////////////////////////////////////////////
// The DemandLoadDll_ExceptionFilter function changes a
// thread's program counter. We can restrict the amount
// of CPU-dependent code by defining the PROGCTR macro below.
#if defined(_X86_)
#define PROGCTR(Context) ((Context).Eip)
#endif
#if defined(_MIPS_)
#define PROGCTR(Context) ((Context).Fir)
#endif
#if defined(_ALPHA_)
#define PROGCTR(Context) ((Context).Fir)
#endif
#if defined(_PPC_)
#define PROGCTR(Context) ((Context).Iar)
#endif
#if !defined(PROGCTR)
#error Module contains CPU-specific code; modify & recompile.
#endif
//////////////////////////////////////////////////////////////
// Macro that places double quotes around the argument
#define Stringize(str) #str
// Macro for setting dynamic call function information
// dwIndex: offset from PFN_FIRSTINDEX
// szDllName: Name of DLL that contains the function
// szFuncName: Name of function contained in the DLL
// pfnProcVar: Name of variable to get the function's address
#define DemandLoadDll_FuncInfo(dwIndex, \
szDllName, szFuncName, pfnProcVar) \
case dwIndex: \
pszDllName = __TEXT(szDllName); \
pszFuncName = Stringize(szFuncName); \
ppfnFuncAddr = (PROC*) &pfnProcVar; \
break;
// NOTE: Stringize above expands a function macro to its
// ANSI/Unicode equivalent. For example, passing MessageBox
// for szFuncName will cause either MessageBoxA or MessageBoxW
// to be looked-up depending on whether UNICODE is defined
// Of course, you can pass MessageBoxW for szFuncName which
// will always get the address of MessageBoxW
//////////////////////////////////////////////////////////////
// This exception filter determines if an attempted call
// is made to a dynamic call function and if so, loads
// the appropriate DLL, fixes up function address and
// calls the function.
static LONG DemandLoadDll_ExceptionFilter (LPEXCEPTION_POINTERS pep) {
// Don't swallow exceptions we don't recognize
LONG lDisposition = EXCEPTION_CONTINUE_SEARCH;
// Get the address where we attempted the memory access
DWORD pvAttemptedAddress =
pep->ExceptionRecord->ExceptionInformation[1];
// To resolve a dynamic function call, we need
// a DLL name, a function name, and a place to
// get the resolved function address.
LPCTSTR pszDllName = NULL;
LPSTR pszFuncName = NULL; // Always ANSI; never Unicode
PROC* ppfnFuncAddr = NULL;
// We can only handle access violations
if (EXCEPTION_ACCESS_VIOLATION !=
pep->ExceptionRecord->ExceptionCode)
return(lDisposition);
// If the address is outside the range of our function
// pointers, we don't know how to handle it
if ((pvAttemptedAddress < PFN_FIRSTINDEX) ||
(pvAttemptedAddress > PFN_LASTINDEX))
return(lDisposition);
// If there are any problems beyond this point,
// execute our exception handler.
lDisposition = EXCEPTION_EXECUTE_HANDLER;
// Function addresses are initialized relative to
// PFN_FIRSTINDEX so we know which function to resolve
switch (pvAttemptedAddress - PFN_FIRSTINDEX) {
DemandLoadDll_FuncInfo(0, "User32",
MessageBeep, g_pfnMessageBeep);
DemandLoadDll_FuncInfo(1, "User32",
MessageBox, g_pfnMessageBox);
}
// Now that we know how to get the function address, get it
if (pszDllName != NULL) {
// If the DLL is not loaded, load it
HMODULE hinstDll = GetModuleHandle(pszDllName);
if (hinstDll == NULL)
hinstDll = LoadLibrary(pszDllName);
if (hinstDll != NULL) {
// Get the address of the function
*ppfnFuncAddr = GetProcAddress(hinstDll, pszFuncName);
if (*ppfnFuncAddr != NULL) {
// We fixed up the address should we ever call
// this function again. But for this call, the
// thread's PC points to the wrong address; we
// need to change PC so that this call succeeds.
PROGCTR(*pep->ContextRecord) = (DWORD) *ppfnFuncAddr;
// Continue execution from our new IP address
lDisposition = EXCEPTION_CONTINUE_EXECUTION;
}
}
}
return(lDisposition);
}
//////////////////////////////////////////////////////////////
int WINAPI WinMain (HINSTANCE hinstExe,
HINSTANCE hinstExePrev, LPSTR szCmdLine, int nCmdShow) {
// Place an SEH frame at the top of the thread so that
// dynamically called functions work properly
__try {
// Dynamically call MessageBox(A/W)
g_pfnMessageBox(NULL, "Here is a message box",
"DLL loaded on demand", MB_OK);
// Dynamically call MessageBeep
g_pfnMessageBeep(MB_OK);
// Dynamically call MessageBox(A/W) again
g_pfnMessageBox(NULL, "Here is another message box",
"DLL already loaded", MB_OK);
}
__except(DemandLoadDll_ExceptionFilter( GetExceptionInformation())) {
// We get in here only if a DLL cannot be
// found for a dynamically called function
MessageBox(NULL, __TEXT("Dynamic function call failed."),
__TEXT("DemandLoadDll"), MB_OK);
}
return(0);
}
///////////////////////// End of File /////////////////////////
Figure 3 Multithreaded Usage Counts in a DLL
DllWork.c
/*************************************************************
Module name: DllWork.c
Written by: Jeffrey Richter
Purpose: A DLL that creates multiple threads when loaded
*************************************************************/
#define STRICT
#include <Windows.h>
#define DLLWORKAPI __declspec(dllexport)
#include "DllWork.h"
//////////////////////////////////////////////////////////////
// The handle of this DLL Module
HINSTANCE g_hinstDll;
// Event used to signal when threads should terminate
HANDLE g_hEventTerminate;
//////////////////////////////////////////////////////////////
DWORD WINAPI ThreadFunc (LPVOID lp) {
WaitForSingleObject(g_hEventTerminate, INFINITE);
DebugBox("Worker thread is terminating", "DllWork");
FreeLibraryAndExitThread(g_hinstDll, 0);
return(0); // Never executes but makes the compiler happy
}
//////////////////////////////////////////////////////////////
void ShutdownLibrary() {
// Notify the worker threads to shutdown
SetEvent(g_hEventTerminate);
}
//////////////////////////////////////////////////////////////
void SomeFunc() {
MessageBox(NULL, "Doing something",
"SomeFunc in DllWork", MB_OK);
}
//////////////////////////////////////////////////////////////
void IncrementLibraryUsageCount(HINSTANCE hinst, int nCount) {
TCHAR szModuleName[_MAX_PATH];
GetModuleFileName(hinst, szModuleName, _MAX_PATH);
while (nCount--) LoadLibrary(szModuleName);
}
//////////////////////////////////////////////////////////////
BOOL WINAPI DllMain (HINSTANCE hinstDll,
DWORD fdwReason, LPVOID fImpLoad) {
int nThread;
const int nMaxWorkerThreads = 2;
switch (fdwReason) {
case DLL_PROCESS_DETACH:
DebugBox("Dll unloading", "DllWork");
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_ATTACH:
DebugBox("Dll loading", "DllWork");
// Save this module's handle
g_hinstDll = hinstDll;
// When signaled, this event tells the
// worker threads when to terminte
g_hEventTerminate = CreateEvent(NULL, TRUE, FALSE, NULL);
// Create the worker threads
nThread = 0;
for (; nThread < nMaxWorkerThreads; nThread++) {
DWORD dwThreadId;
CloseHandle(CreateThread(NULL, 0,
ThreadFunc, 0, 0, &dwThreadId));
}
// Increment this module's usage count so that it is
// not freed while threads are still executing its code
IncrementLibraryUsageCount(hinstDll,
nMaxWorkerThreads - 1);
break;
}
return(TRUE); // Successful initialization
}
///////////////////////// End of File ////////////////////////
DllWork.h
/*************************************************************
Module name: DllWork.h
Written by: Jeffrey Richter
Purpose: A DLL that creates multiple threads when loaded
*************************************************************/
// If this file is included by DllWork source code modules,
// DLLWORKAPI will be defined as '__declspec(dllexport)
// All other EXEs and DLLs will import these functions
#ifndef DLLWORKAPI
#define DLLWORKAPI __declspec(dllimport)
#endif
//////////////////////////////////////////////////////////////
// Function prototypes
DLLWORKAPI void ShutdownLibrary();
DLLWORKAPI void SomeFunc();
//////////////////////////////////////////////////////////////
// Useful macro for debugging
#ifdef _DEBUG
#define DebugBox(szText, szCaption) \
MessageBox(NULL, szText, szCaption, MB_OK)
#else
#define DebugBox(szText, szCaption)
#endif
///////////////////////// End of File ////////////////////////
Figure 4 Forwarding Functions Through a Stub DLL
DllStub.c
/*************************************************************
Module name: DllStub.c
Written by: Jeffrey Richter
Purpose: Stub DLL that contains only a DllMain function
and function forwarders to functions in DllWork.
*************************************************************/
#define STRICT
#include <Windows.h>
#include "..\DllWork\DllWork.h"
//////////////////////////////////////////////////////////////
// Function forwarders to functions in DllWork
#pragma comment(linker, "/export:SomeFunc=DllWork.SomeFunc")
//////////////////////////////////////////////////////////////
BOOL WINAPI DllMain (HINSTANCE hinstDll,
DWORD fdwReason, LPVOID p) {
switch (fdwReason) {
case DLL_PROCESS_ATTACH:
DebugBox("Dll loading", "DllStub");
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
// When this DLL is detached, tell the DllWork DLL
// to shut itself down cleanly
ShutdownLibrary();
DebugBox("Dll unloading", "DllStub");
break;
}
return(TRUE); // Successful initialization
}
///////////////////////// End of File ////////////////////////