MFC DLLs: Extension vs. Regular
We've been looking at Win32 DLLs that have a DllMain function and some exported functions. Now we'll move on to the MFC application framework, which adds its own support layer on top of the Win32 basics. The MFC Application Wizard lets you build two kinds of DLLs with MFC library support: extension DLLs and regular DLLs. You must understand the differences between these two types so you can decide which one is best for your needs.
Note | Of course, Visual C++ .NET lets you build a pure Win32 DLL without the MFC library, just as it lets you build a Windows-based program without the MFC library. |
An extension DLL supports a C++ interface. In other words, the DLL can export whole classes and the client can construct objects of those classes or derive classes from them. An extension DLL dynamically links to the code in the DLL version of the MFC library. Therefore, an extension DLL requires that your client program be dynamically linked to the MFC library (the MFC Application Wizard default) and that both the client program and the extension DLL be synchronized to the same version of the MFC DLLs (mfc70.dll, mfc70d.dll, and so on). Extension DLLs are quite small; you can build a simple extension DLL with a size of 10 KB, which will load quickly.If you need a DLL that can be loaded by any Win32 programming environment, you should use a regular DLL. A big restriction here is that the regular DLL can export only C-style functions. It can't export C++ classes, member functions, or overloaded functions because every C++ compiler has its own method of decorating names. You can, however, use C++ classes (and MFC library classes, in particular) inside your regular DLL. Implementing a COM interface for your DLL also solves the issue of integrating with Visual Basic.When you build an MFC regular DLL, you can choose to statically link or dynamically link to the MFC library. If you choose static linking, your DLL will include a copy of all the MFC library code it needs and will thus be self-contained. A typical release-build statically linked regular DLL is about 144 KB. If you choose dynamic linking, the size will drop to about 17 KB but you'll have to ensure that the proper MFC DLLs are present on the target machine. That's no problem if the client program is already dynamically linked to the same version of the MFC library.
When you tell the MFC wizards what kind of DLL or EXE you want, compiler #define constants are set as shown in the following table.
Dynamically Linked to Shared MFC Library | Statically Linked to MFC Library | |
Regular DLL | _AFXDLL, _USRDLL | _USRDLL |
Extension DLL | _AFXEXT, _AFXDLL | Unsupported option |
Client EXE | _AFXDLL | No constants defined |
If you look inside the MFC source code and header files, you'll see a lot of #ifdef statements for these constants. This means that the library code is compiled quite differently depending on the kind of project you're producing.
MFC Extension DLLs: Exporting Classes
If your extension DLL contains only exported C++ classes, you'll have an easy time building and using it. The steps shown later for building the Ex20a example show you how to tell the MFC DLL Wizard that you're building an extension DLL skeleton. That skeleton has only the DllMain function. You simply add your own C++ classes to the project. There's only one special thing you must do: You must add the macro AFX_EXT_CLASS to the class declaration, as shown here:
class AFX_EXT_CLASS CStudent : public CObject
This modification goes into the H file that's part of the DLL project, and it also goes into the H file that client programs use. In other words, the H files are exactly the same for both client and DLL. The macro generates different code depending on the situation—it exports the class in the DLL and imports the class in the client.
The MFC Extension DLL Resource Search Sequence
If you build a dynamically linked MFC client application, many of the MFC library's standard resources (error message strings, print preview dialog templates, and so on) will be stored in the MFC DLLs, but your application will have its own resources, too. When you call an MFC function such as CString::LoadString or CBitmap::LoadBitmap, the framework will step in and search first the EXE file's resources and then the MFC DLL's resources.If your program includes an extension DLL and your EXE needs a resource, the search sequence will be first the EXE file, then the extension DLL, and then the MFC DLLs. If you have a string resource ID, for example, that is unique among all resources, the MFC library will find it. If you have duplicate string IDs in your EXE file and your extension DLL file, the MFC library will load the string in the EXE file.
If the extension DLL loads a resource, the sequence will be first the extension DLL, then the MFC DLLs, and then the EXE.You can change the search sequence if you need to. Suppose you want your EXE code to search the extension DLL's resources first. You can use code such as this:
HINSTANCE hInstResourceClient = AfxGetResourceHandle();
// Use DLL's instance handle
AfxSetResourceHandle(::GetModuleHandle("mydllname.dll"));
CString strRes;
strRes.LoadString(IDS_MYSTRING);
// Restore client's instance handle
AfxSetResourceHandle(hInstResourceClient);
You can't use AfxGetInstanceHandle instead of ::GetModuleHandle. In an extension DLL, AfxGetInstanceHandle returns the EXE's instance handle, not the DLL's handle.
The Ex20a Example: An MFC Extension DLL
This example makes an extension DLL out of the CPersistentFrame class you saw in Chapter 14. First you'll build the

Run the MFC DLL Wizard to produce the Ex20a project. Choose New Project from the Visual Studio .NET File menu. Select Visual C++ Projects, and then select MFC DLL from the list of templates. On the Application Settings page, select the MFC Extension DLL, as shown here:

Examine the

// Ex20a.cpp : Defines the initialization routines for the DLL.
//
#include "stdafx.h"
#include <afxdllx.h>
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
static AFX_EXTENSION_MODULE Ex20aDLL = { 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("Ex20a.DLL Initializing!\n");
// Extension DLL one-time initialization
if (!AfxInitExtensionModule(Ex20aDLL, 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(Ex20aDLL);
}
else if (dwReason == DLL_PROCESS_DETACH)
{
TRACE0("Ex20a.DLL Terminating!\n");
// Terminate the library before destructors are called
AfxTermExtensionModule(Ex20aDLL);
}
return 1; // ok
}
Insert the CPersistentFrame class into the project. Choose Add Existing Item from the Project menu and locate the files


Edit the

class CPersistentFrame : public CFrameWnd
to read
class AFX_EXT_CLASS CPersistentFrame : public CFrameWnd
Build the project and copy the DLL file. Copy the file

The Ex20b Example: A DLL Test Client Program
This example starts off as a client for

Run the MFC Application Wizard to produce the Ex20b project. This is an ordinary MFC EXE program. Select Single Document. Otherwise, accept the default settings. Be absolutely sure that you accept the Use MFC In A Shared DLL option on the Application Type page.
Copy the file

Change the CFrameWnd base class to CPersistentFrame, as you did in Ex14a. Replace all occurrences of CFrameWnd with CPersistentFrame in both



#include "persist.h"
Add the Ex20a import library to the linker's input library list. Choose Add Existing Item from the Visual Studio .NET Project menu.
Locate the

Build and test the Ex20b program. If you run the program from the debugger and Windows can't find the Ex20a DLL, Windows will display a message box when Ex20b starts. If all goes well, you should have a persistent frame application that works exactly like the one in Ex14a. The only difference is that the CPersistentFrame code will be in an extension DLL.
MFC Regular DLLs: The AFX_EXTENSION_MODULE Structure
When the MFC DLL Wizard generates a regular DLL, the DllMain function will be inside the framework and you'll end up with a structure of type AFX_EXTENSION_MODULE (and a global instance of the structure). AFX_EXTENSION_MODULE is used during initialization of MFC extension DLLs to hold the state of extension DLL module.You usually don't need to do anything with this structure. You normally just write C functions and then export them using the __declspec(dllexport) modifier (or using entries in the project's DEF file).
Using the AFX_MANAGE_STATE Macro
When mfc70.dll is loaded as part of a process, it stores data in some truly global variables. If you call MFC functions from an MFC program or extension DLL, mfc70.dll will know how to set these global variables on behalf of the calling process. If you call into mfc70.dll from a regular MFC DLL, however, the global variables will not be synchronized and the effects will be unpredictable. To solve this problem, insert the following line at the start of all exported functions in your regular DLL:
AFX_MANAGE_STATE(AfxGetStaticModuleState());
If the MFC code is statically linked, the macro will have no effect.
The MFC Regular DLL Resource Search Sequence
When an EXE links to a regular DLL, resource loading functions inside the EXE will load the EXE's own resources. Resource loading functions inside the regular DLL will load the DLL's own resources.If you want your EXE code to load resources from the DLL, you can use AfxSetResourceHandle to temporarily change the resource handle. If you're writing an application that needs to be localized, you can put language-specific strings, dialog boxes, menus, and so forth in an MFC regular DLL. You might, for example, include the modules English.dll, German.dll, and French.dll. Your client program will explicitly load the correct DLL and load the resources using regular resource-management function calls, which will have the same IDs in all the DLLs.
The Ex20c Example: An MFC Regular DLL
This example creates a regular DLL that exports a single square root function. First we'll build the

Run the MFC DLL Wizard to produce the project Ex20c. Proceed as you did for Ex20a, but accept Regular DLL Using Shared MFC DLL (instead of selecting MFC Extension DLL) on the Application Settings page.
Examine the

// Ex20c.cpp : Defines the initialization routines for the DLL.
//
#include "stdafx.h"
#include "Ex20c.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
//
// Note!
// If this DLL is dynamically linked against the MFC
// DLLs, any functions exported from this DLL which
// call into MFC must have the AFX_MANAGE_STATE macro
// added at the very beginning of the function.
//
// For example:
//
// extern "C" BOOL PASCAL EXPORT ExportedFunction()
// {
// AFX_MANAGE_STATE(AfxGetStaticModuleState());
// // normal function body here
// }
//
// It is very important that this macro appear in each
// function, prior to any calls into MFC. This means that
// it must appear as the first statement within the
// function, even before any object variable declarations
// as their constructors may generate calls into the MFC
// DLL.
//
// Please see MFC Technical Notes 33 and 58 for additional
// details.
//
// CEx20cApp
BEGIN_MESSAGE_MAP(CEx20cApp, CWinApp)
END_MESSAGE_MAP()
// CEx20cApp construction
CEx20cApp::CEx20cApp()
{
// TODO: add construction code here,
// Place all significant initialization in InitInstance
}
// The one and only CEx20cApp object
CEx20cApp theApp;
// CEx20cApp initialization
BOOL CEx20cApp::InitInstance()
{
CWinApp::InitInstance();
return TRUE;
}
Add the code for the exported Ex20cSquareRoot function. It's okay to add this code in the

extern "C" __declspec(dllexport) double Ex20cSquareRoot(double d)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
TRACE("Entering Ex20cSquareRoot\n");
if (d >= 0.0) {
return sqrt(d);
}
AfxMessageBox("Can't take square root of a negative number.");
return 0.0;
}
You can see that there's no problem with the DLL displaying a message box or another modal dialog box. You'll need to include math.h in the file that contains this code.Be sure to prototype the Ex20cSquareRoot function in the

Build the project and copy the DLL file. Copy the file

Updating the Ex20b Example: Adding Code to Test Ex20c.dll
When we built the Ex20b program, it linked dynamically to the Ex20a MFC extension DLL. Now we'll update the project to implicitly link to the Ex20c MFC regular DLL and to call the DLL's square root function.Here are the steps for updating the Ex20b example:
Add a new dialog resource and class to the Ex20b project. Use the dialog editor to create the IDD_EX20C template, as shown here:

Control ID | Type | Data Member | Message Map Function |
---|---|---|---|
IDC_INPUT | Edit control | m_dInput (double) | |
IDC_OUTPUT | Edit control | m_dOutput (double) | |
IDC_COMPUTE | Button | OnBnClickedCompute |
Code the OnBnClickedCompute function to call the DLL's exported function. Edit the generated function in

void CTest20cDialog::OnBnClickedCompute()
{
UpdateData(TRUE);
m_dOutput = Ex20cSquareRoot(m_dInput);
UpdateData(FALSE);
}
You must declare the Ex20cSquareRoot function as an imported function. Add the following line to the

extern "C" __declspec(dllimport) double Ex20cSquareRoot(double d);
Integrate the CTest20cDialog class into the Ex20b application. You must add a top-level menu, Test, and an Ex20c DLL option with the ID ID_TEST_EX20CDLL. Use Class View's Properties window to map this option to a member function in the CEx20bView class, and then code the handler in

void CEx20bView::OnTestEx20cdll()
{
CTest20cDialog dlg;
dlg.DoModal();
}
Of course, you must add the following line to the

#include "Test20cDialog.h"
Add the Ex20c import library to the linker's input library list. Choose Add Existing Item from the Visual Studio .NET Project menu, and then add \vcppnet\Ex20c\Debug\

Build and test the updated Ex20b application. Choose Ex20c DLL from the Test menu. Type a number in the Input edit control, and then click the Compute Sqrt button. The result should appear in the Output control.