MFC TN011--Using MFC as Part of a DLL

Created: April 15, 1992

ABSTRACT

This article describes how you can use the MFC library as part of a Windows dynamic link library (DLL). It assumes that you are familiar with Windows DLLs and know how to build them.

The Microsoft Foundation Class (MFC) library provides a full-featured set of C++ object classes for the MicrosoftÒ WindowsÔ graphical environment. It includes classes that directly support application development for Windows as well as general-purpose classes for collections, files, persistent storage, exceptions, diagnostics, memory management, strings, and time. Each MFC technical note describes a feature of MFC using code fragments and examples.

INTERFACES

MFC provides simplified dynamic link library (DLL) support to reduce the impact on the MFC and C++ user. This simplified DLL support assumes that interfaces between the application and the DLL are specified in standard C-like functions or in explicitly exported classes. MFC class interfaces are not automatically exported.

If a DLL and an application both want to use MFC, a copy of the MFC library is statically linked into each. The library versions are different: The application uses a standard version of the MFC library (for example, MAFXCW.LIB, depending on the memory model), and the DLL uses a special version of the MFC library (LAFXDWD.LIB).

This gives you several advantages, including:

The application using the DLL does not have to use MFC, nor does it have to be a C++ application.

The memory model of the application can be different from that of the DLL. For example, our DLLs are large model, but the application using the DLL can be medium model.

The size of the DLL depends only on MFC and C run-time routines that it uses and that the linker links in. Thus, an MFC DLL is only slightly larger than the same code in a large-model application.

DLL designers export only those interfaces that they want, thus relieving you of problems associated with schema evolution.

If the DLL and the application both use MFC, there are no problems with the application wanting a different version of MFC than the DLL (or vice versa). Because the MFC library is statically linked into each DLL or EXE, there is no question about which version you have.

Several difficult technical problems are avoided because class interfaces do not cross DLL boundaries. The Microsoft C++ compiler supports this feature by using the __export keyword on classes. See the C7 language reference for details.

LIMITATIONS

DLLs do not support some of the C run-time functions. As a result, the following MFC functionality is not available in DLL-compatible MFC libraries:

CTime::Format

CTime::FormatGmt

CTimeSpan::Format

The MFC classes assume large model for the WINDLL version. As with any DLL, we assume SS != DS (SS points to the application’s stack segment, and DS points to the DLL’s data segment).

The OLE classes are not part of the WINDLL version. This is a practical consideration because OLE servers tend to be small stand-alone executables, and MFC does not support OLE handlers (handlers provide a more efficient way to build an OLE server packaged in a DLL).

WHAT TO DO IN YOUR DLL CODE

Building the Code

When compiling your MicrosoftÒ WindowsÔ DLL, you must define the _WINDLL symbol and use the following compiler switches:

/ALw—Large model, SS!=DS

/GD—Signifies that you are a Windows DLL (and causes the compiler to automatically define _WINDLL)

You must explicitly export the interfaces between the application and the DLL. You should define your interfaces to be low bandwidth and use C interfaces where possible. Direct C interfaces are easier to maintain than complex C++ classes.

If you want to use high-bandwidth C++ classes for your DLL interfaces, see the C7 language reference for a description of how to export a class.

We recommend that you place these interfaces in a separate header that can be included by both C and C++ files (in this way, you won’t limit your DLL customers to C++ programmers). See the TRACEAPI.H header file in the DLLTRACE sample program for an example.

Be sure to declare these interfaces as FAR PASCAL __export routines. If you want your interfaces to work for mixed-model client applications, add explicit far keywords for data pointers.

WinMain->LibMain

The MFC library defines the standard Windows LibMain entry point that will initialize your CWinApp derived object as in a normal MFC application. Place all DLL-specific initialization in the InitInstance member function, as with a normal MFC application.

Note:

The CWinApp::Run mechanism does not apply to a DLL because 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’s PreTranslateMessage. See the DLLTRACE sample program for information on using this function.

HOW TO LINK IT ALL TOGETHER

You must do a one-time build of the WINDLL version of the MFC library with the command line

nmake MODEL=L TARGET=W DLL=1 DEBUG=1

for the debugging version of the LAFXDWD.LIB library, and

nmake MODEL=L TARGET=W DLL=1 DEBUG=0

for the retail version of the LAFXDW.LIB library.

You must link your DLL with this library (LAFXDWD or LAFXDW) and with the large-model WINDLL version of the C run-time library LDLLCEW.LIB.

SAMPLE CODE

The MFC DLLTRACE sample program directory includes a simple DLL called TRACER.DLL that implements the AFX trace flags dialog box (see “Windows Debugging and Trace Options,” TN007) and a simple HELLO.EXE application that calls the DLL to use the dialog box.

Note the following:

The memory model and 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.

Although MFC DLLs must be large model, the application that uses them can be any memory model you like.

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 with the compiler _export keyword set (see the TRACEAPI.H file). The API defined in TRACEAPI.H illustrates the requirements for the APIs you will define in your DLL:

#ifdef __cplusplus

extern "C" {

#endif /* __cplusplus */

struct TracerData

{

BOOL bEnabled;

UINT flags;

};

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

#ifdef __cplusplus

}

#endif

The declaration is enclosed in an extern “C” block for C++ users. This has several advantages:

It makes your DLL APIs usable by non-C++ client applications.

It reduces DLL overhead because C++ name mangling will not be applied to the exported name.

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. This generates the correct prolog/epilog sequence for the DLL entry points when we use the /GD compiler switch to build the DLL. There is no need to use the archaic MakeProcInstance function or _loadds entry points.

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 makes the DLL usable by C programs as well.

Any data pointers used in the API are explicit far pointers. Because the DLL is compiled large model, data pointers are already far pointers by default. Adding the extra far keyword allows your client applications to be small or medium model without having to change your header. Never use near pointers in an interface for a DLL.