The Lazy Programmer

January 23, 2008

Simplifying dynamic DLL use

Filed under: Windows — ferruccio @ 10:53 pm
Tags:

What do I mean by dynamic DLL use? DLLs are already dynamic, aren’t they? After all that’s what the D in DLL stands for. Normally, to access functions in a DLL, programs are linked against an import library which has everything necessary for the linker to resolve function names. When the program loads, the Windows loader then finds any DLL references, loads the DLLs and makes the final link to any functions called within the DLL. If the loader can’t find the DLL then it pops up a message box complaining about it and doesn’t allow the program to load.

We can get a little more dynamic behavior by using delayed load DLLs. If you tell the linker that an import library should use delayed loading, it will set things up so that Windows will not try to load the DLL at program startup, but the very first attempt to call a function in that DLL will cause Windows to load the DLL right then and there before allowing the function call to proceed. If the DLL can’t be loaded or does not contain the required function, Windows will throw an exception. This gives us a little more dynamic behavior, but it’s not quite what we’re looking for.

What we want is to be able to do is load any DLL at any time, without necessarily knowing at build time what the name of the DLL will be. Maybe we want our application to have a plugin system so that third-party developers can add their own functionality to our software. Or, perhaps we just don’t know where a DLL is going to be located until run-time. The usual approach is to use the LoadLibrary() and GetProcAddress() Windows APIs to load the DLL into our process space and obtain pointers to the needed functions respectively. Unfortunately, this approach is tedious, error prone and overall rather painful.

An Example

Let’s say you want to call the GetSystemMetrics() function in user32.dll to find out how many monitors are available. Well, you would just link against user32.lib and call GetSystemMetrics(SM_MONITORS), since if user32.dll is missing Windows would not be in too good a shape. But, for our purposes, it will do nicely as an example. The first thing you would do is load user32.dll into our process space:

HMODULE hUser32 = ::LoadLibrary("user32.dll");

Next you would get a pointer to the GetSystemMetrics() function.

typedef WINAPI int PFN_GSM(int);
PFN_GSM* pfnGetSystemMetrics = (PFN_GSM*) ::GetProcAddress(hUser32, "GetSystemMetrics");

The typedef is there to simplify the code a little, but it’s already unnecessarily complicated. But, anyways, you can now call the function:

int nMonitors = pfnGetSystemMetrics(80);  // SM_CMONITORS

And, finally, once we’re done, we can drop the DLL from our process space:

::FreeLibrary(hUser32);

Looking for a Solution

After a few functions, this gets pretty tedious. The first thing I did, naturally, was to Google for a solution and I ran across this article in MSDN. This seemed like a good place to start, but there were a few things I felt could be done better.

  • If you tried to call a function that wasn’t in the DLL, it would return a NULL value. This is fine unless NULL is a legitimate return value for the function.
  • The whole approach of using a guard value (FUNC_LOADED) to determine if a function pointer has been initialized is rather dubious. The odds are that the initial value will be something else, but it can’t be guaranteed. That’s an intermittent, hard to find bug just waiting to happen.
  • It doesn’t allow you to call functions with different calling convention (cdecl, stdcall, fastcall) than used by your program.

So, naturally, I decided to roll my own solution. Here’s the same trivial example using my approach:

#include "stdafx.h"
#include "dll.h"

DLL_CLASS(TestDll)
   DLL_METHOD1(WINAPI, int, GetSystemMetrics, int)
   DLL_METHOD0(WINAPI, void, UninstallWindows)
DLL_END

int main(int argc, char** argv)
{
   TestDll user32(_T("user32.dll"));

   printf("user32.dll %s loaded\n", user32.IsLoaded() ? "is" : "is not");

   if (user32.__GetSystemMetrics())
      printf("monitors: %d\n", user32.GetSystemMetrics(80));    // SM_CMONITORS
   else
      printf("GetSystemMetrics() not found\n");

   try
   {
      user32.UninstallWindows();
   }
   catch (bad_dll_call ex)
   {
      printf("failed to call %s()\n", ex.function());
   }

	return 0;
}

The macros at the beginning define a class named TestDll which has two methods. A unary function named GetSystemMetrics and a nullary function called UninstallWindows. This class comes with certain pre-defined methods such as Load(), Unload() and IsLoaded() to control DLL loading. The macro DLL_METHOD1 creates a function called GetSystemMetrics() which takes an int and returns an int. It also creates a function called __GetSystemMetrics() which returns true if a DLL has been loaded into our process space and the entry point GetSystemMetrics actually exists within that DLL.

If you try to call a non-existent function, it will throw a bad_dll_call exception which has one method called function() that returns the name of the function you tried to call. When we run this program, it spits out:

user32.dll is loaded
monitors: 1
failed to call UninstallWindows()

How does it work?

You can download dll.h and the example files here. Let’s look at the relevant parts of dll.h. There is a base class called _dll_base:

class _dll_base
{
public :
   _dll_base(const TCHAR* filename = 0) { handle_ = 0; Load(filename); }
   virtual ~_dll_base(void) { Unload(false); }

   bool IsLoaded(void) { return handle_ != 0; }

   bool Load(const TCHAR* filename)
   {
      Unload();
      if (filename != 0)
      {
         UINT em = SetErrorMode(SEM_FAILCRITICALERRORS);
         handle_ = ::LoadLibrary(filename);
         SetErrorMode(em);
      }
      return IsLoaded();
   }

   void Unload(bool reset = true)
   {
      if (handle_ != 0)
      {
         ::FreeLibrary(handle_);
         handle_ = 0;
         if (reset)
            _reset();
      }
   }

protected :
   void* _getproc(const char* procname)
   {
      return ::GetProcAddress(handle_, procname);
   }

   virtual void _reset(void) = 0;

private :
   HMODULE  handle_;
};

Most of this code is pretty straightforward. _reset() is a virtual function that will be defined by classes derived from _dll_base. The reason we pass false to Unload() in the destructor is to prevent a virtual function call. Calling virtual functions from constructors and destructors can have some nasty side effects since the call would have been made into an object that had either not yet been constructed or had just been destroyed.

The next part of the code is where all the macro magic happens:

#define DLL_CLASS(cname) \
class cname : public _dll_base {\
   public : cname(const char* filename = 0) : _dll_base(filename) { _reset(); } \
   private : int start_; \
   protected : virtual void _reset(void) { memset(&start_, 0, sizeof cname - sizeof _dll_base); }

#define DLL_END };

#define _DLL_GETPROC(fn)   p_##fn != 0 || (p_##fn = (t_##fn) _getproc(#fn)) != 0
#define _DLL_TESTPROC(fn)  bool __##fn(void) { return _DLL_GETPROC(fn); }
#define _DLL_CALL(R,fn)    _DLL_GETPROC(fn); if (p_##fn == 0) throw bad_dll_call(_T(#fn)); else return (R) p_##fn

#define DLL_METHOD0(CC,R,fn) \
   private : typedef R (CC * t_##fn)(void); t_##fn p_##fn; \
   public : R fn(void) { _DLL_CALL(R,fn)(); } _DLL_TESTPROC(fn)

#define DLL_METHOD1(CC,R,fn,T1) \
   private : typedef R (CC * t_##fn)(T1); t_##fn p_##fn; \
   public : R fn(T1 p1) { _DLL_CALL(R,fn)(p1); } _DLL_TESTPROC(fn)

#define DLL_METHOD2(CC,R,fn,T1,T2) \
   private : typedef R (CC * t_##fn)(T1,T2); t_##fn p_##fn; \
   public : R fn(T1 p1,T2 p2) { _DLL_CALL(R,fn)(p1,p2); } _DLL_TESTPROC(fn)

The DLL_CLASS macro begins to write our class definition. The interesting part is how initialization is performed. We don’t know at this point what our methods are or even how many there are going to be. My first approach was to simply do a memset(this, 0, sizeof cname) but that would also wipe out anything in the base class (_dll_base). The base class contained only a handle which would get set to zero anyway so it would work but it would break encapsulation. I played with the idea of creating a DLL_INITMETHOD macro that would be used to set each method pointer to null, but I didn’t like taht idea. I was trying to simplify the code that needed to be written and that just seemed to create more work. In addition, I could see someone adding a method macro without the corresponding initializer. That would certainly lead to some debugging fun!

The solution lies in how class objects are stored in memory. An object will contain first its base class data immediately followed by its own data. i.e.

_base_dll vtable handle_ derived class vtable start_ DLL function pointers

The DLL_CLASS macro declares an int start_ which is never actually used in the code, but it marks the beginning of our subclass data. The reset function then becomes fairly simple:

memset(&start_, 0, sizeof cname - sizeof _dll_base);

By taking the difference in size from our class and the base class, we zero out only data that belongs to our class.

OK, next there are some helper macros. _DLL_GETPROC gets a pointer to a method if it hasn’t already been retrieved. _DLL_TESTPROC writes a method to test for the existence of a method, i.e. __Method(). And, finally, _DLL_CALL writes code to actually call a method. To demonstrate how these work, I’ve let the preprocessor expand the TestDll class in my previous example and reformatted the code to make it a bit more readable. The result is:

class TestDll : public _dll_base
{
public :
   TestDll(const char* filename = 0) : _dll_base(filename)
   {
      _reset();
   }
private :
   int start_;
protected :
   virtual void _reset(void)
   {
      memset(&start_, 0, sizeof TestDll - sizeof _dll_base);
   }
private :
   typedef int (__stdcall * t_GetSystemMetrics)(int);
   t_GetSystemMetrics p_GetSystemMetrics;
public :
   int GetSystemMetrics(int p1)
   {
      p_GetSystemMetrics != 0 ||
         (p_GetSystemMetrics = (t_GetSystemMetrics) _getproc("GetSystemMetrics")) != 0;
      if (p_GetSystemMetrics == 0)
         throw bad_dll_call("GetSystemMetrics");
      else
         return (int) p_GetSystemMetrics(p1);
   }
   bool __GetSystemMetrics(void)
   {
      return p_GetSystemMetrics != 0 ||
         (p_GetSystemMetrics = (t_GetSystemMetrics) _getproc("GetSystemMetrics")) != 0;
   }
private :
   typedef void (__stdcall * t_UninstallWindows)(void);
   t_UninstallWindows p_UninstallWindows;
public :
   void UninstallWindows(void)
   {
      p_UninstallWindows != 0 ||
         (p_UninstallWindows = (t_UninstallWindows) _getproc("UninstallWindows")) != 0;
      if (p_UninstallWindows == 0)
         throw bad_dll_call("UninstallWindows");
      else
         return (void) p_UninstallWindows();
   }
   bool __UninstallWindows(void)
   {
      return p_UninstallWindows != 0 ||
         (p_UninstallWindows = (t_UninstallWindows) _getproc("UninstallWindows")) != 0;
   }
};

Most of this looks straightforward. In fact, it’s pretty much the code I would have written if I didn’t have these macros to help me out. One thing that looks a little fishy though, is the UninstallWindows() method. The code I’m talking about is:

 return (void) p_UninstallWindows();

I really didn’t think this would work. It compiled and ran fine, but I had my doubts that it was valid C++ code. I’ve been burned before by Visual C++ allowing non-standard exceptions. Everything would be fine until the next version of the compiler became more standards compliant and code would suddenly break. I thought I would have to create separate macros for void functions. Turned out I was wrong. I looked it up in Stroustrup. It’s allowed specifically for this type of situation in templates. Hurray! I was done.

Conclusion

I’ve had to deal with loading and using DLL functions on the fly many time in several different applications and have wound up doing it the hard way too many times. Now that I have this nice little tool do the hard work for me, I’m never going to go down that path again.

About these ads

1 Comment

  1. Marvelous, very useful!

    I have transformed the declarations in UxTheme.h to DLL_* macro invocations:

    DLL_CLASS(UxTheme)
       DLL_METHOD2(STDAPICALLTYPE, HTHEME, OpenThemeData, HWND, LPCWSTR);
       DLL_METHOD1(STDAPICALLTYPE, HRESULT, CloseThemeData, HTHEME);
       DLL_METHOD6(STDAPICALLTYPE, HRESULT, DrawThemeBackground, HTHEME, HDC, int, int, const RECT *, OPTIONAL const RECT *);
       DLL_METHOD9(STDAPICALLTYPE, HRESULT, DrawThemeText, HTHEME, HDC, int, int, LPCWSTR, int, DWORD, DWORD, const RECT *);
       DLL_METHOD6(STDAPICALLTYPE, HRESULT, GetThemeBackgroundContentRect, HTHEME, OPTIONAL HDC, int, int,  const RECT *, OUT RECT *);
       DLL_METHOD6(STDAPICALLTYPE, HRESULT, GetThemeBackgroundExtent, HTHEME, OPTIONAL HDC, int, int, const RECT *, OUT RECT *);
       DLL_METHOD7(STDAPICALLTYPE, HRESULT, GetThemePartSize, HTHEME, HDC, int, int, OPTIONAL RECT *, enum THEMESIZE, OUT SIZE *);
       DLL_METHOD9(STDAPICALLTYPE, HRESULT, GetThemeTextExtent, HTHEME, HDC, int, int, LPCWSTR, int, DWORD, OPTIONAL const RECT *, OUT RECT *);
       DLL_METHOD5(STDAPICALLTYPE, HRESULT, GetThemeTextMetrics, HTHEME, OPTIONAL HDC, int, int, OUT TEXTMETRIC*);
       DLL_METHOD6(STDAPICALLTYPE, HRESULT, GetThemeBackgroundRegion, HTHEME, OPTIONAL HDC, int, int, const RECT *, OUT HRGN *);
       DLL_METHOD9(STDAPICALLTYPE, HRESULT, HitTestThemeBackground, HTHEME, OPTIONAL HDC, int, int, DWORD, const RECT *, OPTIONAL HRGN, POINT, OUT WORD *);
       DLL_METHOD8(STDAPICALLTYPE, HRESULT, DrawThemeEdge, HTHEME, HDC, int, int, const RECT *, UINT, UINT, OPTIONAL OUT RECT *);
       DLL_METHOD7(STDAPICALLTYPE, HRESULT, DrawThemeIcon, HTHEME, HDC, int, int, const RECT *, HIMAGELIST, int);
       DLL_METHOD3(STDAPICALLTYPE, BOOL, IsThemePartDefined, HTHEME, int, int);
       DLL_METHOD3(STDAPICALLTYPE, BOOL, IsThemeBackgroundPartiallyTransparent, HTHEME, int, int);
       DLL_METHOD5(STDAPICALLTYPE, HRESULT, GetThemeColor, HTHEME, int, int, int, OUT COLORREF *);
       DLL_METHOD6(STDAPICALLTYPE, HRESULT, GetThemeMetric, HTHEME, OPTIONAL HDC, int, int, int, OUT int *);
       DLL_METHOD6(STDAPICALLTYPE, HRESULT, GetThemeString, HTHEME, int, int, int, OUT LPWSTR, int);
       DLL_METHOD5(STDAPICALLTYPE, HRESULT, GetThemeBool, HTHEME, int, int, int, OUT BOOL *);
       DLL_METHOD5(STDAPICALLTYPE, HRESULT, GetThemeInt, HTHEME, int, int, int, OUT int *);
       DLL_METHOD5(STDAPICALLTYPE, HRESULT, GetThemeEnumValue, HTHEME, int, int, int, OUT int *);
       DLL_METHOD5(STDAPICALLTYPE, HRESULT, GetThemePosition, HTHEME, int, int, int, OUT POINT *);
       DLL_METHOD6(STDAPICALLTYPE, HRESULT, GetThemeFont, HTHEME, OPTIONAL HDC, int, int, int, OUT LOGFONT *);
       DLL_METHOD5(STDAPICALLTYPE, HRESULT, GetThemeRect, HTHEME, int, int, int, OUT RECT *);
       DLL_METHOD7(STDAPICALLTYPE, HRESULT, GetThemeMargins, HTHEME, OPTIONAL HDC, int, int, int, OPTIONAL RECT *, OUT MARGINS *);
       DLL_METHOD5(STDAPICALLTYPE, HRESULT, GetThemeIntList, HTHEME, int, int, int, OUT INTLIST *);
       DLL_METHOD5(STDAPICALLTYPE, HRESULT, GetThemePropertyOrigin, HTHEME, int, int, int, OUT enum PROPERTYORIGIN *);
       DLL_METHOD3(STDAPICALLTYPE, HRESULT, SetWindowTheme, HWND, LPCWSTR, LPCWSTR);
       DLL_METHOD6(STDAPICALLTYPE, HRESULT, GetThemeFilename, HTHEME, int, int, int, OUT LPWSTR, int);
       DLL_METHOD2(STDAPICALLTYPE, COLORREF, GetThemeSysColor, HTHEME, int);
       DLL_METHOD2(STDAPICALLTYPE, HBRUSH, GetThemeSysColorBrush, HTHEME, int);
       DLL_METHOD2(STDAPICALLTYPE, BOOL, GetThemeSysBool, HTHEME, int);
       DLL_METHOD2(STDAPICALLTYPE, int, GetThemeSysSize, HTHEME, int);
       DLL_METHOD3(STDAPICALLTYPE, HRESULT, GetThemeSysFont, HTHEME, int, OUT LOGFONT *);
       DLL_METHOD4(STDAPICALLTYPE, HRESULT, GetThemeSysString, HTHEME, int, OUT LPWSTR, int);
       DLL_METHOD3(STDAPICALLTYPE, HRESULT, GetThemeSysInt, HTHEME, int, int *);
       DLL_METHOD0(STDAPICALLTYPE, BOOL, IsThemeActive);
       DLL_METHOD0(STDAPICALLTYPE, BOOL, IsAppThemed);
       DLL_METHOD1(STDAPICALLTYPE, HTHEME, GetWindowTheme, HWND);
       DLL_METHOD2(STDAPICALLTYPE, HRESULT, EnableThemeDialogTexture, HWND, DWORD);
       DLL_METHOD1(STDAPICALLTYPE, BOOL, IsThemeDialogTextureEnabled, HWND);
       DLL_METHOD0(STDAPICALLTYPE, DWORD, GetThemeAppProperties);
       DLL_METHOD1(STDAPICALLTYPE, void, SetThemeAppProperties, DWORD);
       DLL_METHOD6(STDAPICALLTYPE, HRESULT, GetCurrentThemeName, OUT LPWSTR, int, OUT OPTIONAL LPWSTR, int, OUT OPTIONAL LPWSTR, int);
       DLL_METHOD4(STDAPICALLTYPE, HRESULT, GetThemeDocumentationProperty, LPCWSTR, LPCWSTR, OUT LPWSTR, int);
       DLL_METHOD3(STDAPICALLTYPE, HRESULT, DrawThemeParentBackground, HWND, HDC, OPTIONAL RECT*);
       DLL_METHOD1(STDAPICALLTYPE, HRESULT, EnableTheming, BOOL);
       DLL_METHOD6(STDAPICALLTYPE, HRESULT, DrawThemeBackgroundEx, HTHEME, HDC, int, int, const RECT *, OPTIONAL const DTBGOPTS *);
    DLL_END;

    Comment by Kirill Müller — October 5, 2008 @ 6:46 am


RSS feed for comments on this post.

The Rubric Theme. Blog at WordPress.com.

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: