Dll Tips






4.56/5 (15 votes)
Jun 26, 2000

262895
Tips for writting Dynamic Link Libraries
Dynamic Link Libraries using with MFC
This document is intended to give a beginner knowledge that may be helpful when designing a project for the Windows platform. When I start a new project I try to put all code that has the greatest chance of changing into a DLL. This provides the ability to make updates to the project without having to recompile every piece. This allows for a minimum piece of code to be redistributed.
For this to work properly, there are some tips that must be followed. Much of you are asking “Why this @$&^# doesn’t use COM ?”. Of course COM is a good choice, but I’m here to show another way. Some of this code looks like COM (of course I use it’s architecture!!).
The problem with DLL’s (specially those that use MFC) is that the debug and release versions are incompatible. You have probably seen this when running a debug application version with the DLL release version. The whole world gets crazy!! The best way, in fact the way Microsoft does it, is to give different names to the different builds of the DLL. The release build of the DLL uses the [Project Name].DLL format while the debug version will use the [Project Name]D.DLL format. Note the "D" after the project name. Using this approach allows you to send the two DLLs to the installation directory with an application build and the application will work properly.
Example: Naming convention for different builds of the same DLL
These are the steps needed to achieve this (Assume the project name isAAA
):
- Copy the
AAA.def
toAAAD.def
and change all theAAA
occurrences toAAAD
. - In the
Project/Settings
dialog, selectWin32 Debug
Build. - Under the tab
Link
change theOutput file name
toAAAD.DLL
. - At the bottom of the property page you will see something like:
/def:".\AAA.def" /out:"Debug/AAAD.DLL"
Change to:
/def:".\AAAD.def" /out:"Debug/AAAD.DLL"
- Now the debug version will create
AAAD.lib
andAAAD.DLL
files.
When I create a DLL, I create an include header for it (I think everybody does), which I named DLL header. This header has all the exported class definitions. And to be more efficient I include the linking stuff in this file also. I would do this so you do not have to add the lib file to the Project Settings. My header looks like:
#ifndef DEF_MUDASDASDASDASDAS #define DEF_MUDASDASDASDASDAS #ifdef _DEBUG #pragma comment(lib, AAAD.lib) #else #pragma comment(lib, AAA.lib) #endif ... the class definitions go here #endif //MUDASDASDASDASDAS
PROGRAMMING FOR CHANGES
The preferred kind of DLL used to export classes is a MFC extension DLL. By using this you can easily instantiate a class that is within a DLL. To do this you just declare the class as follows:
class AFX_EXT_CLASS CFoo
{
...
}
Evolving Implementations
In the application that uses this class you need to include the DLL header and everything is cool. The problem is: every time you need to include a member variable or a method to an exported class you have to change the DLL header. This means that every component that uses the dll must be recompiled. For new methods I don’t know a way to overcome this recompilation problem, but for new variables there’s a workaround.
Example: Handling Evolving Data Member Implmentation
Instead of declaring the member variables directly in the class body, you create a kind of implementation class, like the sample code:
class CFooImpl; class CFoo { ... protected: CFooImpl* m_pThis; };
The CFooImpl class doesn’t need to be exposed to the components that use the DLL. The implementation of CFooImpl would look like:
class CFooImpl { public: CString m_sName; }; CFoo::CFoo() { m_pThis = new CFooImpl; m_pThis->m_sName = _T(8220;Unknown8221;); } CFoo::~CFoo() { delete m_pThis; }
Evolving Data Structures
Another way to prepare for changes is to use intelligent structs the way that the Windows API does. You declare a method that has an LPVOID as in and out parameter. These pointers are address of struct instances. The trick is to define the first struct a member of DWORD type to hold it’s size. This way you can determine which data is available in the struct.
Example: Evolving Data Structures
typedef struct tagCHANGEABLE { DWORD dwSize; long lBytes; }CHANGEABLE, *LPCHANGEABLE; BOOL CFoo::Method(LPVOID lpIn) { LPCHANGEABLE lpChangeable = (LPCHANGEABLE)lpIn; if (lpChangeable->dwSize == sizeof(CHANGEABLE)) { ... return TRUE; } return FALSE; }
Using it:
CFoo myFoo; CHANGEABLE changeable; memset(&changeable, 0, sizeof(changeable)); changeable.dwSize = sizeof(changeable); myFoo.Method(&changeable);
DLL LOADED WHEN NEEDED
Sometimes you have situations that requires you to call a dialog or create a class instance. So you decide to put those items in a DLL, but you don’t want the DLL to be loaded when the application gets executed. You want to load the DLL when needed (COM). This kind of DLL I call Dynamic DLL (stupid name I know “Dynamic Dynamic link libraries”).
Example: Loading A DLL As Needed
So you declare the exported function as:
__declspec( DLLexport )
void MyExportedFunc(DWORD dw)
{
...
}
We need to include this function in the def files (debug and release). The debug def file would look like this:
; AAAD.def : Declares the module parameters for the DLL. LIBRARY "AAAD" DESCRIPTION 'AAAD Windows Dynamic Link Library' EXPORTS MyExportedFunc @1 ; Explicit exports can go here
Now to use this function we need to load the library, find the function entry point and call it.
typedef void (*MYFUNC)(DWORD); #ifdef _DEBUG HINSTANCE hDLL = AfxLoadLibrary("AAADLLD"); #else HINSTANCE hDLL = AfxLoadLibrary("AAADLL"); #endif if (hDLL) { FARPROC pnProc = GetProcAddress(hDLL, "MyExportedFunc"); MYFUNC pnMyfunc = (MYFUNC)pnProc; pnMyfunc(0); FreeLibrary(hDLL); }
Remember that to show a dialog you must take care of the resource stuff (AfxSetResource..).
You can also use this approach to create class instances.
The class definition must use pure virtual functions (to avoid an unresolved external symbol).
This is just like COM.
The class definition should look like this:
class CFoo { public: virtual void Initialize (CString sName) = 0; };
You implement this “interface” with another class that is not visible through the DLL header file.
class CFooImp : public CFoo { public: CFooImp(); virtual ~CFooImp(); void Initialize (CString sName) { m_sName = sName; } protected: CString m_sName; };
To create an instance of this class (interface) you create an exported function.
__declspec(DLLexport) CFoo* CreateFoo(DWORD dwVersion) { if (dwVersion == CURRENT_VERSION) return new CFooImp; return NULL; }
The application creates the class instance like this:
typedef CFoo* (*MYFUNC)(DWORD); #ifdef _DEBUG HINSTANCE hDLL = AfxLoadLibrary("AAADLLD"); #else HINSTANCE hDLL = AfxLoadLibrary("AAADLL"); #endif if (hDLL) { FARPROC pnProc = GetProcAddress(hDLL, " CreateFoo"); MYFUNC pnMyfunc = (MYFUNC)pnProc; CFoo* pFoo = pnMyfunc(0); pFoo->Initialize(_T("Hi")); delete pFoo; FreeLibrary(hDLL); }
Remember that you cannot free the library that contains the CFoo implementation until you have deleted the CFoo instance.
CONCLUSION
These examples explain the powers of well-designed DLLs. A good design is the first and most important step to the successful project. Unfortunately, if the whole project has a poor design no miracle will make your applications easy to change and update.