Dynamic Link Libraries With Microsoft Foundation, Just the Facts.

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:

  • An MFC Extension DLL does not have a CWinApp derived class.
  • An MFC Extension DLL must provide a special DllMain. AppWizard supplies a
    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

    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 3. Dialog A


    Figure 4. Dialog B

    Figure 4. Dialog A


    Figure 5. Dialog C

    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

    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:

    Technical Article 33: TN033: DLL Version of MFC
    Technical Article 58: TN058: MFC Module State Implementation
    Q148791: How to Provide Your Own DllMain in an MFC Regular DLL
    Q194300: BUG: Asserts When Creating a Dialog Box in an MFC Regular DLL
    Q192853: BUG: Wincore.cpp Line 879 Assert When Using MFC Classes
    Q161589: DOC: AfxGetStaticModuleState() Causes Errors in an Extension DLL

    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

  • More by Author

    Get the Free Newsletter!

    Subscribe to Developer Insider for top news, trends & analysis

    Must Read