TN011: Using MFC as Part of a DLL

This note describes regular DLLs, which allow you to use the MFC library as part of a Windows dynamic-link library (DLL). It assumes you are familiar with Windows DLLs and how to build them. For information about MFC extension DLLs, which allow you to create extensions to the MFC library, see DLL Version of MFC.

DLL Interfaces

Regular DLLs assume interfaces between the application and the DLL are specified in normal C-like functions or explicitly exported classes. MFC class interfaces cannot be exported.

If both a DLL and an application want to use MFC, then both have a choice to either use the shared version of the MFC libraries or have a copy of the MFC library statically linked into them. In versions prior to Visual C++ 4.0, the MFC static link libraries were different for applications and DLLs. With the current version of MFC, the application and DLL may both use one of the standard versions of the MFC library. There is not a separate library for DLLs in this version (MFC makes the choice at runtime).

Regular DLLs have several advantages:

  • The application using the DLL does not have to use MFC or, for that matter, it does not have to be a Visual C++ application.

  • With regular DLLs that statically link to MFC, the size of the DLL depends only on those MFC and C runtime routines that are used and linked by the linker.

  • The file size of regular DLLs that dynamically link to MFC may be much smaller than regular DLLs that statically link to MFC, and the savings in memory from using the shared version of MFC can be significant. However, you must distribute the shared DLLs MFCx0.DLL and MSVCRTx0.DLL (or similar files) with your DLL.

  • There are no problems with classes changing underneath you. Your DLL design exports only those APIs you wish it to.

  • With regular DLLs that statically link to MFC, if both DLL and application use MFC, there are no problems with the application wanting a different version of MFC than the DLL (or vice versa). Since the MFC library is statically linked into each DLL or EXE, there is no question about which version you have.

API Limitations

Some MFC capabilities are not applicable to the DLL version, either because of technical limitations or because those services are usually provided by the application. These limitations are listed below:

  • CWinApp::Enable3dControls

  • CWinApp::SetDialogBkColor (color is ignored for message boxes)

Building Your DLL

When compiling regular DLLs that statically link to MFC, the symbols "_USRDLL" and "_WINDLL" must be defined. Your DLL code must also be compiled with the following compiler switches:

  • /D_WINDLL signifies the compilation is for a DLL

  • /D_USRDLL specifies you are building a regular DLL

When compiling regular DLLs that dynamically link to MFC, you must define the above symbols and use the above compiler switches. Additonally, the symbol "_AFXDLL” must be defined and your DLL code must be compiled with:

  • /D_AFXDLL specifies that you are building a regular DLL that dynamically links to MFC

The interfaces (APIs) between the application and the DLL must be explicitly exported. It is recommended that you define your interfaces to be low bandwidth, sticking to C interfaces where possible. More direct C interfaces are easier to maintain than more complex C++ classes.

Place your APIs in a separate header that can be included by both C and C++ files (that way you won't limit your DLL customers to C++ programmers). See the header TRACEAPI.H in the MFC Advanced Concepts sample for an example. To export your functions, enter them in the EXPORTS section of your module definition file (.DEF) or include __declspec(dllexport) on your function definitions. Use __declspec(dllimport) to import these functions into the client executable.

You must add the AFX_MANAGE_STATE macro at the begining of all the exported functions in regular DLLs that dynamically link to MFC to set the current module state to the one for the DLL. This is done by adding the following line of code to the beginning of functions exported from the DLL:

  

AFX_MANAGE_STATE(AfxGetStaticModuleState( ))

  

WinMain -> DllMain

The MFC library defines the standard Win32 DllMain entry point that initializes your CWinApp derived object as in a normal MFC application. Place all DLL-specific initialization in the InitInstance member function as in a normal MFC application.

Note that the CWinApp::Run mechanism doesn't apply to a DLL, since the application owns the main message pump. If your DLL brings up modeless dialogs or has a main frame window of its own, your application's main message pump must call a DLL-exported routine that calls CWinApp::PreTranslateMessage.

See the DLLTRACE sample for use of this function.

The ExitInstance member function of your CWinApp derived class will be called from the MFC provided DllMain function before the DLL is unloaded.

What to Do to Link It All Together

With regular DLLs that statically link to MFC, you must link your DLL with this library (NAFXCWD.LIB or NAFXCW.LIB) along with the version of the C runtimes called 'LIBCMT.LIB'. These libraries are pre-built and may be installed by specifying them when you run Visual C++ setup.

Sample Code

Please see the MFC Advanced Concepts sample program for a complete sample. This includes a simple DLL called 'TRACER.DLL' that implements the AFX Trace flags dialog (see Technical Note 7). It also has a simple HELLO application that calls the DLL to use the dialog.

Several interesting thing to note:

  • The compiler flags of the DLL and the application are very different.

  • The link lines and .DEF files for the DLL and the application are also very different.

  • The application using the DLL doesn't even have to be in C++.

  • The interface between the application and the DLL is a "C"-like API and are exported with TRACER.DEF.

The following extract from TRACEAPI.H illustrates what is needed for one API that is defined in a regular DLL that statically links to MFC:

#ifdef __cplusplus
extern "C" {
#endif  /* __cplusplus */

struct TracerData
{
    BOOL    bEnabled;
    UINT    flags;
};

BOOL FAR PASCAL EXPORT PromptTraceFlags(TracerData FAR* lpData);

#ifdef __cplusplus
}
#endif

In this example, the declaration is enclosed in an 'extern "C" { }' block for C++ users. This has several advantages. First, it makes your DLL APIs usable by non-C++ client applications. Second, it reduces DLL overhead since C++ name mangling will not be applied to the exported name. Lastly, it makes it easier to explicitly add to a .DEF file (for exporting by ordinal) without having to worry about name mangling.

All API functions are "FAR PASCAL EXPORT". Although not stricly necessary for Win32 DLLs, these definitions have been kept for easy back-porting to 16-bit Windows. The FAR, PASCAL, and EXPORT macros all expand to nothing under Win32.

The structures used by the API are not derived from MFC classes and are defined completely in the API header. This reduces the complexity of the interface between the DLL and the application and, once again, makes the DLL usable by C programs as well.

Any data pointers used in the API are explicit FAR pointers. Again, FAR, is not really necessary for Win32, but is useful if you plan to compile the code for 16-bit Windows sometime in the future.

Technical Notes by NumberTechnical Notes by Category