First time users attempting to create an application that
contains Dynamic Link Libraries (DLL) and dialog resources may
run into a few problems that can get very confusing. The
Microsoft Developer Network (MSDN) provides many useful articles
and examples for building DLLs, however, the information
contained in those articles is very distributed, and incorrect in
some instances, making it difficult to find problems related to
compiling, linking and executing applications. Compiling errors
and runtime asserts can be extremely frustrating and difficult to
track down when the basic DLL concepts are as elusive as the
errors.
The main purpose of this document is to provide a concise and
simple explanation of how to build Dynamic Link Libraries (DLL)
that contain dialog resources and use Microsoft Foundation
Libraries (MFC). A simple example is used to illustrate some
common pitfalls encountered in DLL development and some of the
basic interaction between application and DLL.
This article assumes some basic usage and understanding of
building MFC applications using the Microsoft Developer Studio
and Visual C++ 6.0.
Keywords
Dynamic Link Library, DLL, MFC, Assert, CWnd, compile, MSDN,
errors, Microsoft Developer Studio, Visual C++ 6.0
Introduction
This project contains a sample application, MFCdll_demo.zip.
The problems all began when I received the Developers Studio 6.0
and revisited a program that originated it’s 32-bit days with
Visual C++ 4.0. Compiling this old project and it’s dependent
DLLs no longer worked correctly. The culprit; the implementation
of the DLLs. What I was attempting to do is not difficult in
concept (I had already done this in the original version of the
DLL) and is a very common with application development. The
scenario is an application with two DLLs; one DLL containing a
dialog box resource template and its class, and the other DLL
contains basic functionality. The application and the second DLL
need to be able to instantiate the dialog boxes contained in the
first DLL. The problems here are created when dealing with
regular DLLs, extension DLLs with MFC, exporting and importing
functions, and managing the projects in the work space in such a
way things compile and link fairly easily.
The simplest method to explain this complication is to use an
example project, which can be found in the zip file "MFCdll_demo.zip."
The work space consists of four projects, TheApp, DllA, DllB, and DllC.
The dependencies of the project are: TheApp depends on all three DLLs,
DllA depends on DllB, DllB has no dependencies, and DllC depends on the
other two, DllA and DllB. Each of the DLLs were created with the AppWizard(DLL)
with DllA as an Extension DLL using shared MFC DLL, DllB a MFC Extension DLL,
and DllC is a Regular DLL. These terms refer to the options available during
the AppWizard setup of a DLL as shown in Figure 1.
Figure 1. MFC DLL App Wizard
Now here is where a lot of confusion may occur. A extension DLL as
described by the label in the AppWizard contains the dll entry point
function DllMain(). Code created for the DllA in the file DllA.CPP is
shown below.
static AFX_EXTENSION_MODULE DllADLL = { NULL, NULL }; extern "C" int APIENTRY DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved) { // Remove this if you use lpReserved UNREFERENCED_PARAMETER(lpReserved); if (dwReason == DLL_PROCESS_ATTACH) { TRACE0("DLLA.DLL Initializing!\n"); // Extension DLL one-time initialization if (!AfxInitExtensionModule(DllADLL, hInstance)) return 0; // Insert this DLL into the resource chain // NOTE: If this Extension DLL is being implicitly linked to by // an MFC Regular DLL (such as an ActiveX Control) // instead of an MFC application, then you will want to // remove this line from DllMain and put it in a separate // function exported from this Extension DLL. The Regular DLL // that uses this Extension DLL should then explicitly call that // function to initialize this Extension DLL. Otherwise, // the CDynLinkLibrary object will not be attached to the // Regular DLL's resource chain, and serious problems will // result. new CDynLinkLibrary(DllADLL); } else if (dwReason == DLL_PROCESS_DETACH) { TRACE0("DLLA.DLL Terminating!\n"); // Terminate the library before destructors are called AfxTermExtensionModule(DllADLL); } return 1; // ok }
A Regular DLL on the other had does not create a DllMain(), but instead creates a CWinApp class which looks like the following code fragment taken from the DLLC.CPP file.
///////////////////////////////////////////////////////////////////////////// // CDllCApp BEGIN_MESSAGE_MAP(CDllCApp, CWinApp) //{{AFX_MSG_MAP(CDllCApp) // NOTE - the ClassWizard will add and remove mapping macros here. // DO NOT EDIT what you see in these blocks of generated code! //}}AFX_MSG_MAP END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // CDllCApp construction CDllCApp::CDllCApp() { // TODO: add construction code here, // Place all significant initialization in InitInstance } ///////////////////////////////////////////////////////////////////////////// // The one and only CDllCApp object CDllCApp theApp;
In the example used here, DllB is initially defined to
be a Regular DLL which has code similar to the above segment.
No problem so far. However, looking at the documentation in
the MSDN library you will find Technical Article 33, "TN033:
DLL Version of MFC" which has the following two bullets:
DllMain function that you can modify.
Since an extension DLL does not have a CWinApp and a Regular DLL does,
when working with both types it is very easy to get confused. If an
Regular DLL has the CWinApp class removed, your program will not work
and you will end up getting an assert when your dialog box attempts
to set up its message pump.
Another distinction between the two DLL types is in the preprocessor
directives. For the DLL (DllC) you will notice two definitions,
_AFXDLL and _USRDLL. An important
distinction between the Regular and
Extension DLL is the inclusion of the _USRDLL
preprocessor definition.
An Extension DLL does not define the _USRDLL definition.
To further confuse this issue there are MFC bugs and MSDN documentation errors (see
references). To help clarify some of these potential problems we will examine the project
TheApp. This project has a DLLs each containing a simple dialog box. The dependencies are
illustrated in Figure 2.
Figure 2. TheApp Project Dependencies
The three DLLs contain a simple dialog box, each created using the resource editor and
class wizards in the Developer Studio. Two of the dialogs, DllA and DllC have buttons to
activate the other dialogs, whereas DialogB only contains an edit box to display text. All
three dialogs are illustrated in figures 3 through 5.
Figure 3. Dialog A
Figure 4. Dialog A
Figure 5. Dialog A
With everything created let us take at look at the document class for TheApp. Starting very
simply we will have CTheAppDoc call all three of the dialog boxes using various methods. In
order for the dialogs to be called the appropriate information must be imported from the appropriate
DLL. More on Import/Export concepts later. There are two methods for calling DialogA. The first
uses the exported function
int ShowDialogA( CString& text );
The advantage here is that the calling program does not need to have the CDialogA class defined,
which also means you do not need to know anything about the resource ID for that dialog. The only
thing that needs to be exported from the DllA is the above function. The implementation of this
exported function is very simple and is a basic usage of a dialog object.
DLGAPorting int ShowDialogA( CString& text ) { CDialogA dlg; dlg.m_sEdit = text; int nReturn = dlg.DoModal(); if ( nReturn = IDOK ) text = dlg.m_sEdit; return nReturn; }
The second method is to use the CDialogA class directly, as is typically done. The only
trick here is to export CDialogA from the DLL and import it into the document class. Here is the code
that implements these two methods.
void CTheAppDoc::OnDialogsDialoga() { // This function does not need to have the CDialogA class exported, only // will need to see the ShowDialogA function exported from the dll. CString sTheText = "Text String Stating in DialogA."; ShowDialogA( sTheText ); // It should be different now if the user changed it. } void CTheAppDoc::OnDialogsDialoga2() { CDialogA dlg; dlg.m_sEdit = "Text String Stating in DialogA using method 2."; // Typical Dialog usage can be done here. dlg.DoModal(); }
Next, for the document to open up CDialogB which is defined in the DllB DLL, the user may
again use the same procedure as above and call the int ShowDialogB(CString& s) function. This
method works perfectly well with a couple of provisions not needed for the CDialogA. Looking at
the implementation of the ShowDialogB() one can see a couple of differences.
int ShowDialogB( CString& text ) { // Removing the following line will cause an First chance // Exception during runtime when you call this function. AFX_MANAGE_STATE(AfxGetStaticModuleState()); // If you use the following line of code you will get an assertation // on line 884 of WinCore.CPP in the CWnd::AssertValid() function // in debug mode // CDialogB dlg; // The work around is CDialogB dlg( AfxGetMainWnd() ); dlg.m_sText = text; int nReturn = dlg.DoModal(); if ( nReturn = IDOK ) text = dlg.m_sText; return nReturn; }
In order to prevent a debug assert in the WinCore.cpp file you need to add the macro
AFX_MANAGE_STATE( AfxGetStaticModuleState() ) as the first command in the function.
Also, the CWnd* parameter for the CDialog constructor needs to be assigned with the
function AfxGetMainWnd(). This is discussed in more detail in the MS technical article Q194300.
Now, there is one pitfall here to be aware of; do not add this code to the implementation
of the ShowDialogA() function. Doing so will give you linker errors that look like the following.
mfcs42d.lib(dllmodul.obj) : error LNK2005: __pRawDllMain already defined in DllA.obj mfcs42d.lib(dllmodul.obj) : error LNK2005: _DllMain@12 already defined in DllA.obj mfcs42d.lib(dllmodul.obj) : error LNK2005: __pRawDllMain already defined in DllA.obj mfcs42d.lib(dllmodul.obj) : warning LNK4006: _DllMain@12 already defined in DllA.obj; second definition ignored mfcs42d.lib(dllmodul.obj) : warning LNK4006: __pRawDllMain already defined in DllA.obj; second definition ignored
For the CDialogA class, there is a second method of implementing
the dialog by using the dialog class directly exported from the DLL.
Note however, that this does not work with the Regular DLL. This
does not work because dialog object is unable to locate its resource
template when the DoModal() function is called. Now the astute
programmer would say, "Well, why not overload the DoModal()
function in the CDialogA class and add the
AFX_MANAGE_STATE( AfxGetStaticModuleState() )
macro at the beginning of the function." It is a good suggestion,
one which I believe worked in previous versions of Visual C++, but
now prone to problems. Executing such a program will cause runtime
asserts in the CWnd::AssertValid() function which are discussed in
detail in MS technical article Q194300. The only solution I found
was to use the implementation mentioned above. The code segment
below illustrates problematic code.
void CTheAppDoc::OnDialogsDialogb2() { // This implementation of using the actual dialog fails. The reason // is in the attempt to locate the resource template for the dialog // in the DLL. CDialogB dlg; dlg.m_sText = "Text String Stating in DialogB using method 2."; // Typical Dialog usage can be done here. dlg.DoModal(); // Display a message telling the user that their attempt to // display the dialog box failed. AfxMessageBox( "This Calling method compiles but fails." ); } int CDialogB::DoModal() { // Putting this code here will cause an Assert in the CWnd::AssertValid() // AFX_MANAGE_STATE(AfxGetStaticModuleState()); return CDialog::DoModal(); }
Finally, the last dialog box in the DllC.DLL, CDialogC, is the same as CDialogB
and must follow the same rules. For the sake of completeness, here is its calling code.
void CTheAppDoc::OnDialogsDialogc() { CString text = "Text String Stating in DialogC"; ShowDialogC( text ); }
The next issue involving DLLs and dialog boxes, involves a
DLL invoking a dialog which is located in a different DLL. There
are four calling possibilities: 1. Regular calling and Regular DLL,
2. An Extension calling and Regular DLL, 3. A Regular calling an
Extension DLL, 4. Extension calling a Extension DLL. The fourth
calling method is not directly implemented in the sample program
TheApp. However, a good exercise is to change CDialogB from an
Extension DLL to a Regular DLL to test CDialogA calling CDialogB
when both are regular DLLs.
In the first implementation with an Regular DLL calling
an another Regular DLL there is really no surprise. Since it is difficult
to export the dialog class, the calling is done through the helper
functions I created in the exports, ShowDialogB(). Here CDialogC initiates CDialogB.
void CDialogC::OnButtonb() { UpdateData(); if ( ShowDialogB( m_sText ) == IDOK ) UpdateData(FALSE); }
The second possibility is an Extension DLL calling an Regular DLL.
Again this is pretty straight forward for the reasons mentioned above. The code
for the CDialogA calling CDialogB is the nearly the same.
void CDialogA::OnButtonb() { UpdateData(); if ( ShowDialogB( m_sEdit ) == IDOK ) UpdateData(FALSE); }
The third case, however, will cause a problem. Attempting to
have a Regular DLL call a dialog in an Extension DLL cannot easily be accomplished,
if at all. The following code segment demonstrates this problem.
void CDialogC::OnButtona() { // if I tried to do this I get an Assert in the CWnd::AssertValid() // Function. /* CDialogC dlg; dlg.DoModal(); */ // and this method does not crash, but there is a first chance exception // error but the program does not halt. UpdateData(); if ( ShowDialogA( m_sText ) == IDOK ) UpdateData(FALSE); }
There are a couple indications that you would see if you tried to make this happen.
Trying to use the ShowDialogA( m_sText ) call will compile and run,
but you will get a trace error that gives you a first
chance exception error when the DoModal() call is made. Attempting to use the dialog class to create
the dialog will also compile and run, but will give you an assert in the
CWnd::AssertValid() function.
I have no solution for this situation and I will defer details of the problem to the MSDN references.
The final combination is a regular DLL calling a dialog in another regular DLL. In this case either
of the discussed methods will work. Either use the calling function ShowDialogB(),
or using the dialog class directly. To test this all that needs to be done is to convert DllB to a regular DLL
from a externsion DLL. This can easily be done by making a couple of simple code modifications. In
the example files DllB.cpp and DllB.h are #ifndef B_IS_REGULAR
statements to simplify the conversion.
Open up the project settings for the DllB project and edit the pre-processor definitions as shown
in Figure 6.
Figure 6. DllB Compiler Settings
The preprocessor definitions when the DLL is an Extension DLL should be
_DEBUG, WIN32, _WINDOWS,_WINDLL,_AFXDLL,_MBCS,_AFXEXT, COMPILING_DLL
nd when the DLL is a Regular DLL
_DEBUG,WIN32,_WINDOWS,_WINDLL,_AFXDLL,_MBCS,_AFXEXT, COMPILING_DLLB,_USRDLL, B_IS_REGULAR
When B_IS_REGULAR is defined and _USRDLL
is not defined the DLL will be a regular DLL.
When B_IS_REGULAR is not defined and _USRDLL
is define the DLL will be an extension DLL.
I will leave it to the reader examine the code differences. The real key here is the
_USERDLL definition.
To help assist the reader in making the conversion the preprocessor
key B_IS_REGULAR
has been created. This key changes what code is included when compiling the DLL and
separates the code difference between an extension and regular DLL. Basically, changes
the usage of a DllMain() entry point function or a CWinApp class existence in the DLL.
Also, removes or adds the AFX_MANAGE_STATE(AfxGetStaticModuleState()) macro when necessary.
A final item to contend with in the conversion of the DLL is how the function is called
from the CDialogA button. The button handler function should be modified to the following.
void CDialogA::OnButtonb() { // use this block for the Extension DLL { /* UpdateData(); if ( ShowDialogB( m_sEdit ) == IDOK ) UpdateData(FALSE); */ } // Use this block of code for a regular DLL { CDialogB dlg; dlg.m_sText = m_sEdit; if ( dlg.DoModal() == IDOK ) { m_sEdit = dlg.m_sText; UpdateData(FALSE); } } }
This concludes the discussion about the implementation of the different
DLL types and how to interact dialogs between them.
Additional Tips
Using Batch Files
When working with DLLs it is very useful to include the DLL project in the
workspace for the dependant application. This way you can set up the dependencies
which will link the required libraries. However, when you attempt to run the
application the program will not be able to locate the DLL. This is basically
a search path problem and there are several ways to work around this. The method
I prefer is to take advantage of the Post Build commands. Simply make a batch
file that will copy the successfully compiled DLL to the appropriate directory
for your application. For example, I use the following batch file for the DllA
project.
DllBd.bat copy ..\DllA\Debug\*.dll ..\Debug
When the DLL is done compiling it copies the DLL to the debug directory
of the application that will be using it. Likewise I have a similar batch
file for the release mode, and for the other DLLs.
Handling Exports and Imports
There are many possible methods for implementing the imports and exports of
a DLL. The method I prefer is to use the #defines to indicate if the class
or functions are being imported or exported. Using the .def file during
development can be troublesome, however, there are some benefits for a library
nearing final release. The nice feature of using the #define’s is the same
header files used for exporting the functions, can also be linked to the projects
that are importing the functions. Technical Article 33 describes a similar
process using the AFX_EXT_CLASS macro.
In your preprocessor definitions for the DLL define a unique string; for DllA
the string COMPILING_DLLA was used. Then in the header files where you need
to export functions include the following code:
// This is a little error checking to verify I only define this once #ifdef DLGAPorting #pragma message( "Warning! DLGAPorting already defined!" ) #undef DLGAPorting #endif #ifdef COMPILING_DLLA #define DLGAPorting __declspec( dllexport ) #pragma message( " Exporting DllA" ) #else #define DLGAPorting __declspec( dllimport ) #pragma message( " Importing DllA" ) #endif
This is a pretty simple and efficient method of handling imports and
exports. There are many other methods and this is only one of them.
The choice is ultimately the programmers.
Debugging
One simple tip for debugging a DLL that is quite obvious once you have
done it, however, until that point you might seem a bit lost. After compiling
your new DLL in debug mode and attempt to execute the DLL you will receive a
dialog box asking for an executable that for the library. In this box simple
reference a application (.EXE) that uses your newly compiled DLL. You will
then be able to debug your functions when they are called.
This is especially useful for add-in DLLs. For example, Developer Studio Add-in
DLLs. After creating your DLL, and you would like to debug it, specify the
MSDEV.EXE as the executable for your DLL. When you start the debugger a second
instance of MSDEV will start using your DLL giving you the ability to debug it.
MSDN References:
About The Author
Christopher A. Snyder has a Master of Science in Electrical
Engineering from Ohio University and is currently working as a
research engineer at the Ohio University Avionics Engineering
Center.
Chris’s home page can be found at http://www.ent.ohiou.edu/~csnyder
Demo
Download demo project – 127 KB
Date Last Updated: May 17, 1999