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