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 ////////////////////////