Wrapper Classes for Dynamic DLL Loading

Wednesday Apr 24th 2002 by Petko Popov
Share:

A wrapper classes for dynamic DLL loading.

Environment: Win32, C++, MSVC 6.0

I needed to develop a program which requires a DLL that is usually found in the Windows system directory. Sometimes this DLL is not installed at all. To make things worse, the utility that needs the call the DLL is not allowed to leave any footprint when removed, so installing in the system directory or changing the registry is not an option. However, it is required to use the DLL from the system directory whenever it is available.

The installation utility doesn't allow the customization of the installed files based on the OS. In some cases, however, a different implementation is needed for different operating systems. A possible solution is to put different DLLs in different subdirectories, then first try to access the system directory. If the DLL is not there, then go to the subdirectory specific for the operating system.

Additionally, I don't believe people love to write all these LoadLibrary and GetProcAddress statements, even if the delayed DLL loading linker option can help here in the simpler cases.

To handle the requirements, I wrote a set of classes and templates that wrap LoadLibrary and GetProcAddress and allow to specify multiple directories to search for the DLL and the order to look in them. The most interesting part of the solution are the templates, which may look like this:

template
<
   typename T_RET, 
   typename T_P1, 
   typename T_P2, 
   const TCHAR * const * _pszDllName = NULL, 
   const char  * const * _pszExportName = NULL
> 
class LoadProc2Wrapper : public LoadProcWrapperBase
{
protected:
   typedef  T_RET (WINAPI * t_pf) (T_P1, T_P2);
 
public:
   LoadProc2Wrapper() : LoadProcWrapperBase()
   { 
      if (_pszDllName != NULL && _pszExportName != NULL)
      {
         SetNames(*_pszDllName, *_pszExportName);
      }
   }
 
   virtual T_RET operator() (T_P1 p1, T_P2 p2)
   {
      GetTheFunction();
      return ((t_pf)m_pf)(p1, p2);
   }
};

The constructor initializes the base class with the DLL name and function name from the template instance. The function operator first calls the base class to locate and load dynamically the DLL and put the function address into m_pf, then casts this pointer to the actual type and returns with the return type and value of the function. The class LoadProcWrapperBase handles all path and module manipulations.

The above sample is simplified to illustrate the main idea. If you look at the attached sources, there are two intermediate layers of templates there one that provides the constructor, and another that manages default return values that can be used if the function is not located in the DLL.

Two sets of macros expand that the templates are included for zero to four parameters. One set is used for functions that return values, the other is for functions returning void or when return value is not necessary. I am not sure if casting void return type on functions that return a value is 100% safe, so I would recommend to use the macros with no return value really only for exported functions returning void. The macros must be expanded at global level, because templates can only take constant pointers to objects that have external linkage type.

Sample use

// macros must be expanded at global level

// potentially unsafe, Sleep returns a value
LOADPROC_NO_RETURN_VALUE_1(kernel32_Sleep, "kernel32",
                           "Sleep" , int); 

// potentially unsafe, Beep returns a value
LOADPROC_NO_RETURN_VALUE_2(MyBeep, "kernel32", "Beep",
                           int, int);

// 4th parameter in LOADPROC_WITH_RETURN_VALUE_X 
// macros is the return type
LOADPROC_WITH_RETURN_VALUE_2(MyBeep2, "kernel32",
                             "Beep", int, int, int);
LOADPROC_WITH_RETURN_VALUE_1(TESTMISSING, "kernel32",
                             "ThisOneDoesntExistFroSure",
                             int, int);

void test(void)
{
   kernel32_Sleep slp;
   MyBeep         f2;
   MyBeep2        f3;
   TESTMISSING    foo;

   // Loading order, up to three directories + default
   // LoadLibrary behavior
   // CLsd provides an easy way to define the Windows
   // directory, system directory,
   // current directory and executable directory
   slp.SetPreferredPath("d:\\",
                        CLsd(CLsd::E_SYSTEMDIR),
                        ".");

   f2(880,200); // On Windows 9x Beep doesn't work 
                // properly :(, better try on NT/2K/XP
   slp(500);
   f3(440,200); // returns a value, but the program
                // ignores it

   // default is no exceptions on missing entries
   int   nTRV;

   nTRV = foo(777);
   printf("default return value is %d\n", nTRV);

   // now we'll be throwing (exceptions) - and 
   // catching too
   foo.SetThrowExceptionOnMissingEntry(true);
   try {
      nTRV = foo(777);
      // never gets here
      printf("default return value is %d\n", nTRV);
   } catch ( int e ) {
      printf("exception #%02d caught\n", e);
   }
}

The classes can be further improved:

  • The thrown exceptions can be improved, the existing are just a sketch

  • Not only the load path, but also the DLL name can be different in the alternative variants

  • It can be made possible to manage different versions of the DLL

Please post your comments and improvements, or e-mail me.

Downloads

Download source - 8Kb

Share:
Home
Mobile Site | Full Site
Copyright 2017 © QuinStreet Inc. All Rights Reserved