We've been looking at Win32 DLLs that have a DllMain function and some exported functions. Now we'll move into the world of the MFC application framework, which adds its own support layer on top of the Win32 basics. AppWizard 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 before you decide which one is best for your needs.
Of course, Visual C++ lets you build a pure Win32 DLL without the MFC library, just as it lets you build a Windows program without the MFC library. This is an MFC-oriented book, however, so we'll ignore the Win32 option here.
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 AppWizard default) and that both the client program and the extension DLL be synchronized to the same version of the MFC DLLs (mfc42.dll, mfc42d.dll, and so on). Extension DLLs are quite small; you can build a simple extension DLL with a size of 10 KB, which loads quickly.
If you need a DLL that can be loaded by any Win32 programming environment (including Visual Basic version 6.0), 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.
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 in size. If you choose dynamic linking, the size drops 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 AppWizard 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 |
* Visual C++ Learning Edition does not support the static linking option.
If you look inside the MFC source code and header files, you'll see a ton 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.
If you build a Windows Debug target with the shared MFC DLL option, your program is dynamically linked to one or more of these (ANSI) MFC DLLs:
mfc42d.dll | Core MFC classes |
mfco42d.dll | ActiveX (OLE) classes |
mfcd42d.dll | Database classes (ODBC and DAO) |
mfcn42d.dll | Winsock, WinInet classes |
When you build a Release target, your program is dynamically linked to mfc42.dll only. Linkage to these MFC DLLs is implicit via import libraries. You might assume implicit linkage to the ActiveX and ODBC DLLs in Windows, in which case you would expect all these DLLs to be linked to your Release-build client when it loads, regardless of whether it uses ActiveX or ODBC features. However, this is not what happens. Through some creative thunking, MFC loads the ActiveX and ODBC DLLs explicitly (by calling LoadLibrary) when one of their functions is first called. Your client application thus loads only the DLLs it needs.
If your extension DLL contains only exported C++ classes, you'll have an easy time building and using it. The steps for building the EX22A example show you how to tell AppWizard 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 situationit exports the class in the DLL and imports the class in the client.
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) are stored in the MFC DLLs (mfc42.dll, mfco42.dll, and so on), but your application has its own resources too. When you call an MFC function such as CString::LoadString or CBitmap::LoadBitmap, the framework steps in and searches 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 is 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 loads the string in the EXE file.
If the extension DLL loads a resource, the sequence is 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. 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.
This example makes an extension DLL out of the CPersistentFrame class you saw in Chapter 15. First you'll build the ex22a.dll file, and then you'll use it in a test client program, EX22B.
Here are the steps for building the EX22A example:
In this example, only one AppWizard screen appears. Choose MFC Extension DLL, as shown here.
// ex22a.cpp : Defines the initialization routines for the DLL. // #include "stdafx.h" #include <afxdllx.h> #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = _ _FILE_ _; #endif static AFX_EXTENSION_MODULE Ex22aDLL = { 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("EX22A.DLL Initializing!\n"); // Extension DLL one-time initialization if (!AfxInitExtensionModule(Ex22aDLL, hInstance)) return 0; // Insert this DLL into the resource chain (generated comment lines deleted) new CDynLinkLibrary(Ex22aDLL); } else if (dwReason == DLL_PROCESS_DETACH) { TRACE0("EX22A.DLL Terminating!\n"); // Terminate the library before destructors are called AfxTermExtensionModule(Ex22aDLL); } return 1; // ok }
If you don't want to use the OGX component, you can copy the files Persist.h and Persist.cpp into your project directory and add them to the project by choosing Add To Project from the Visual C++ Project menu.
class CPersistentFrame : public CFrameWnd
to read
class AFX_EXT_CLASS CPersistentFrame : public CFrameWnd
This example starts off as a client for ex22a.dll. It imports the CPersistentFrame class from the DLL and uses it as a base class for the SDI frame window. Later you'll add code to load and test the other sample DLLs in this chapter.
Here are the steps for building the EX22B example:
#include "persist.h"
You must specify the full pathname for the ex22a.lib file unless you have a copy of that file in your project directory.
When AppWizard generates a regular DLL, the DllMain function is inside the framework and you end up with a class derived from CWinApp (and a global object of that class), just as you would with an EXE program. You can get control by overriding CWinApp::InitInstance and CWinApp::ExitInstance. Most of the time, you don't bother overriding those functions, though. You simply write the C functions and then export them with the __declspec(dllexport) modifier (or with entries in the project's DEF file).
When mfc42.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, mfc42.dll knows how to set these global variables on behalf of the calling process. If you call into mfc42.dll from a regular MFC DLL, however, the global variables are not synchronized and the effects will be unpredictable. To solve this problem, insert the line
AFX_MANAGE_STATE(AfxGetStaticModuleState());
at the start of all exported functions in your regular DLL. If the MFC code is statically linked, the macro will have no effect.
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. The code will be nearly the same as that shown in "The MFC Extension DLL Resource Search Sequence." If you're writing an application that needs to be localized, you can put language-specific strings, dialogs, 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 would explicitly load the correct DLL and use code such as that in "The MFC Extension DLL Resource Search Sequence" to load the resources, which would have the same IDs in all the DLLs.
This example creates a regular DLL that exports a single square root function. First you'll build the ex22c.dll file, and then you'll modify the test client program, EX22B, to test the new DLL.
Here are the steps for building the EX22C example:
// ex22c.cpp : Defines the initialization routines for the DLL. // #include "stdafx.h" #include "ex22c.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = _ _FILE_ _; #endif (generated comment lines omitted) ////////////////////////////////////////////////////////////////////// // CEx22cApp BEGIN_MESSAGE_MAP(CEx22cApp, CWinApp) //{{AFX_MSG_MAP(CEx22cApp) // 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() ////////////////////////////////////////////////////////////////////// // CEx22cApp construction CEx22cApp::CEx22cApp() { // TODO: add construction code here, // Place all significant initialization in InitInstance } ////////////////////////////////////////////////////////////////////// // The one and only CEx22cApp object CEx22cApp theApp;
extern "C" __declspec(dllexport) double Ex22cSquareRoot(double d) { AFX_MANAGE_STATE(AfxGetStaticModuleState()); TRACE("Entering Ex22cSquareRoot\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. You'll need to include math.h in the file containing this code.
When you first built the EX22B program, it linked dynamically to the EX22A MFC extension DLL. Now you'll update the project to implicitly link to the EX22C MFC regular DLL and to call the DLL's square root function.
Following are the steps for updating the EX22B example.
Then use ClassWizard to generate a class CTest22cDialog, derived from CDialog. The controls, data members, and message map function are shown in the following table.
Control ID | Type | Data Member | Message Map Function |
IDC_INPUT | edit | m_dInput (double) | |
IDC_OUTPUT | edit | m_dOutput (double) | |
IDC_COMPUTE | button | OnCompute |
void CTest22cDialog::OnCompute() { UpdateData(TRUE); m_dOutput = Ex22cSquareRoot(m_dInput); UpdateData(FALSE); }
You'll have to declare the Ex22cSquareRoot function as an imported function. Add the following line to the Test22cDialog.h file:
extern "C" __declspec(dllimport) double Ex22cSquareRoot(double d);
void CEx22bView::OnTestEx22cdll() { CTest22cDialog dlg; dlg.DoModal(); }
Of course, you'll have to add this line to the Ex22bView.cpp file:
#include "Test22cDialog.h"