A Class for Dynamically-Loaded DLLs |
|
The third time you write the code, write a subroutine.
Or, in this case, the third time you do something the hard way, write a class to do it.
This is a somewhat-cleaned-up version of a class I wrote about a year ago to handle dynamically-loaded DLLs. It consists of a set of macros and a superclass definition, and allows you to write suitable interfaces to a dynamically-loaded DLL by treating the class as a set of methods on the DLL.
To use it, you must first derive a subclass from the class, and use the macros to define the actual methods.
The class is called DynamicDLL, and the header file is dynamicDLL.h, and must precede the subclass definition.
#include "DynamicDLL.h" #include "yourSubclass.h"
To use it, create a variable of the subclass type. There are two constructor forms, one of which simply creates an empty library, and one of which creates the library and initializes it.
All the methods here are either declared implicitly by the use of the macros for defining the subclass, or are inherited from the base class, DynamicDLL.
yourSubclass::yourSubclass( )
Creates an object to hold the reference to the library, but does not load the library or initialize the method pointers. [Example]
yourSubclass::yourSubclass(LPCTSTR name)
Creates an object to hold the reference to the library, loads the library specified by name, and initializes the method pointers. [Example]
BOOL yourSubclass::Load(LPCTSTR name)
When applied to an uninitialized library, will cause the module to be loaded, and the method pointers to be initialized. Returns FALSE if the library was already loaded without having been freed, the library could not load, or a method could not be found. Use ::GetLastError() to determine the cause. If the library was already loaded, the error will be ERROR_INVALID_HANDLE.If any error occurred trying to load the library, no attempt will be made to initialize the pointers. If any GetProcAddress fails, the library will be unloaded and all pointers will be set to NULL.[Example]
yourSubclass::~yourSubclass( )
Unloads the library.
void yourSubclass::Free( )
Unloads the library, sets all the method pointers to NULL.[Example]
BOOL yourSubclass::IsLoaded( )
Returns TRUE if the library has been successfully loaded, and FALSE if there was any error. [Example]
DECLARE_PROC_DLL(yourSubclass, superclass)
This defines the constructors for the subclass. It is typically the first line in the class. [Example]
DEFINE_PROC(result, name, (args) )
This declares the method. The first argument is the result type. The second argument is the name of the method. The third argument is the list of arguments, enclosed in parentheses; an empty argument list must be specified as the expected ( ) sequence. [Example]
DEFINE_PROC_LINKAGE(result, linkage, name, (args) )
This declares the method. The first argument is the result type. The second argument is the linkage type (e.g., CALLBACK, CDECL) if required. The third argument is the name of the method. The fourth argument is the list of arguments, enclosed in parentheses; an empty argument list must be specified as the expected ( ) sequence. Note that if the linkage type is other than CDECL, the IMPLEMENT_PROC_SPECIAL macro must be used to specify the explicit name used by the compiler, e.g., for all variants of the __stdcall linkage (WINAPI, CALLBACK, etc.) the name must have a leading underscore and the number of bytes of parameters must be specified. [Example]
DEFINE_PROC_LINKAGE(int, WINAPI, Test, (int, int) )requires that you initialize it as shown below; note the highlighted items and their effect on the name.
IMPLEMENT_PROC_SPECIAL(Test, "_Test@8")
This declares the initialization of the method pointers, and must be followed by one or more IMPLEMENT_PROC or IMPLEMENT_PROC_SPECIAL declarations. The entire sequence must be terminated by an END_PROC_TABLE( ) declaration. There can be only one procedure table per subclass, and it must follow all the DEFINE_PROC or DEFINE_PROC_LINKAGE declarations. Nothing can appear between the BEGIN_PROC_TABLE and END_PROC_TABLE declarations except IMPLEMENT_PROC and IMPLEMENT_PROC_SPECIAL declarations. [Example]
Terminates a procedure table. [Example]
IMPLEMENT_PROC(name)
Defines a method. This declaration causes the method to be initialized. The external name used is the string form of the input parameter. [Example]
IMPLEMENT_PROC_SPECIAL(name, externalname)
Defines a method. This declaration causes the method to be initialized. The external name is specified as an 8-bit character string. This form is used whenever a linkage other than CDECL is used, or the name mangling has not been suppressed by the use of the "extern "C"" qualifier (see my essay on The Ultimate DLL Header File). [Example]
Note: The definition if IMPLEMENT_PROC is
#define IMPLEMENT_PROC(name) IMPLEMENT_PROC_SPECIAL(name, #name)The "stringizing" operator, #, takes the parameter name and makes it a string.
When you declare your subclass, it will resemble the following form
class yourSubclass : public DynamicDLL { DECLARE_PROC_DLL(yourSubclass, DynamicDLL) DEFINE_PROC(result, name, (args) ) DEFINE_PROC_LINKAGE(result, CALLBACK, name, (args) ) ... as many DEFINE_PROCs as required BEGIN_PROC_TABLE() IMPLEMENT_PROC(name) ...as many IMPLEMENT_PROCs or IMPLEMENT_PROC_SPECIALs as required END_PROC_TABLE() };
Here are two methods defined in a DLL
__declspec(dllexport) LRESULT Test(LPCTSTR s, int n) { for(int i = 0, sum = 0; i < lstrlen(s) && i < n; i++) sum += s[i]; return sum; } __declspec(dllexport) BOOL WINAPI Test2() { return FALSE; }
Here is the header file used to declare a class to provide for dynamic loading of this DLL. Note that I have highlighted the use of the linkage type WINAPI and its impact on the IMPLEMENT_PROC_SPECIAL declaration.
class TestDynDLL: public DynamicDLL { DECLARE_PROC_DLL(TestDynDLL, DynamicDLL) DEFINE_PROC(LRESULT, Test, (LPCTSTR, int)) DEFINE_PROC_LINKAGE(void, WINAPI, Test2, ( ) ) BEGIN_PROC_TABLE() IMPLEMENT_PROC(Test) IMPLEMENT_PROC_SPECIAL(Test2, "_Test2@0") END_PROC_TABLE() };
Here are two examples of using it. In these trivial examples the object is declared, the DLL is loaded, the methods are called, and the object is destroyed or the DLL is unloaded. Obviously, you would split these up in some fashion, for example, loading the DLL when a menu item is selected, and using it based on other menu items. You also have to determine when you are going to unload the DLL (if ever).
TestDynDLL * DLL; DLL = new TestDynDLL(_T("Test.dll")); ASSERT(DLL->IsLoaded()); if(!DLL->IsLoaded()) { /* failed */ AfxMessageBox(_T("Load Failed - 1")); return TRUE; } /* failed */ LRESULT sum = DLL->Test(_T("Test"), 2); BOOL flag = DLL->Test2(); delete DLL;
TestDynDLL DLL; if(!DLL.Load(_T("Test.dll"))) { /* load failed */ AfxMessageBox(_T("Load Failed - 2")); return TRUE; } /* load failed */ LRESULT sum = DLL.Test(_T("Test"), 2); BOOL flag = DLL.Test2(); DLL.Free(); // implicit if the variable goes out of scope!
The views expressed in these essays are those of the author, and in no way represent, nor are they endorsed by, Microsoft.