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 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.

The Shared MFC DLLs and the Windows DLLs

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.

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 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 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) 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.

The EX22A Example—An MFC Extension DLL

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:

  1. Run AppWizard to produce \vcpp32\ex22a\ex22a. Choose New from Visual C++'s File menu, and then click on the Projects tab as usual. Instead of selecting MFC AppWizard (exe), choose MFC AppWizard (dll), as shown here.

click to view at full size.

    In this example, only one AppWizard screen appears. Choose MFC Extension DLL, as shown here.

click to view at full size.

  1. Examine the ex22a.cpp file. AppWizard generates the following code, which includes the DllMain function:

     // 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 } 

  2. Insert the CPersistentFrame class into the project. Choose Add To Project from the Project menu, and then choose Components And Controls from the submenu. Locate the file Persistent Frame.ogx that you created in Chapter 15 (or locate the copy on the companion CD-ROM). Click the Insert button to insert the class into the current project.

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.

  1. Edit the persist.h file. Modify the line

     class CPersistentFrame : public CFrameWnd 

    to read

     class AFX_EXT_CLASS CPersistentFrame : public CFrameWnd 

  2. Build the project and copy the DLL file. Copy the file ex22a.dll from the \vcpp32\ex22a\Debug directory to your system directory (\Windows\System or \Winnt\System32).

The EX22B Example—A DLL Test Client Program

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:

  1. Run AppWizard to produce \vcpp32\ex22b\ex22b. This is an ordinary MFC EXE program. Select Single Document. Otherwise, accept the default settings. Be absolutely sure that in Step 5 you accept the As A Shared DLL option.

  2. Copy the file persist.h from the \vcpp32\ex22a directory. Note that you're copying the header file, not the CPP file.

  3. Change the CMainFrame base class to CPersistentFrame as you did in EX15A. Replace all occurrences of CFrameWnd with CPersistentFrame in both MainFrm.h and MainFrm.cpp. Also insert the following line into MainFrm.h:

    #include "persist.h"

  4. Add the ex22a import library to the linker's input library list. Choose Settings from Visual C++'s Project menu. Select All Configurations in the Settings For drop-down list. Then fill in the Object/Library Modules control on the Link page as shown below.

    You must specify the full pathname for the ex22a.lib file unless you have a copy of that file in your project directory.

click to view at full size.

  1. Build and test the EX22B program. If you run the program from the debugger and Windows can't find the EX22A DLL, Windows displays a message box when EX22B starts. If all goes well, you should have a persistent frame application that works exactly like the one in EX15A. The only difference is that the CPersistentFrame code is in an extension DLL.

MFC Regular DLLs—The CWinApp Derived Class

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).

Using the AFX_MANAGE_STATE Macro

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.

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. 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.

The EX22C Example—An MFC Regular DLL

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:

  1. Run AppWizard to produce \vcpp32\ex22c\ex22c. Proceed as you did for EX22A, but accept Regular DLL Using Shared MFC DLL (instead of choosing MFC Extension DLL) from the one and only AppWizard page.

  2. Examine the ex22c.cpp file. AppWizard generates the following code, which includes a derived CWinApp class:

     // 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; 

  3. Add the code for the exported Ex22cSquareRoot function. It's okay to add this code in the ex22c.cpp file, although you can use a new file if you want to:

     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.

  4. Build the project and copy the DLL file. Copy the file ex22c.dll from the \vcpp32\ex22c\Debug directory to your system directory.

Updating the EX22B Example—Adding Code to Test ex22c.dll

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.

  1. Add a new dialog resource and class to \vcpp32\ex22b\ex22b. Use the dialog editor to create the IDD_EX22C template, as shown here.

    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

  1. Code the OnCompute function to call the DLL's exported function. Edit the ClassWizard-generated function in Test22cDialog.cpp as shown here:

     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); 

  2. Integrate the CTest22cDialog class into the EX22B application. You'll need to add a top-level menu, Test, and an Ex22c DLL option with the ID ID_TEST_EX22CDLL. Use ClassWizard to map this option to a member function in the CEx22bView class, and then code the handler in Ex22bView.cpp as follows:

     void CEx22bView::OnTestEx22cdll() {     CTest22cDialog dlg;     dlg.DoModal(); } 

    Of course, you'll have to add this line to the Ex22bView.cpp file:

     #include "Test22cDialog.h" 

  3. Add the EX22C import library to the linker's input library list. Choose Settings from Visual C++'s Project menu, and then add \vcpp32\ex22c\Debug\ex22c.lib to the Object/Library Modules control on the Link page. (Use a space to separate the new entry from the existing entry.) Now the program should implicitly link to both the EX22A DLL and the EX22C DLL. As you can see, the client doesn't care whether the DLL is a regular DLL or an extension DLL. You just specify the LIB name to the linker.

  4. Build and test the updated EX22B application. Choose Ex22c 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.


Programming Microsoft Visual C++
Programming Microsoft Visual C++
ISBN: 1572318570
EAN: 2147483647
Year: 1997
Pages: 332

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net