| Tenouk C & C++ | MFC Home | Context-Sensitive Help 3 | C++, MFC & DLL 2 | Download | Site Index |


 

 

 

Module 16:

C++, MFC & Dynamic Link Libraries - DLL 1

 

 

 

Program examples compiled using Visual C++ 6.0 (MFC 6.0) compiler on Windows XP Pro machine with Service Pack 2. Topics and sub topics for this Tutorial are listed below. If you think the terms used in this DLL tutorial quite blur, you can try studying the Win32 DLL first.

  1. Dynamic Link Libraries - DLL

  2. Fundamental DLL Theory

  3. How Imports Are Matched to Exports

  4. Implicit Linkage vs. Explicit Linkage

  5. Symbolic Linkage vs. Ordinal Linkage

  6. The DLL Entry Point: DllMain()

  7. Instance Handles: Loading Resources

  8. How the Client Program Finds a DLL

  9. Debugging a DLL

  10. MFC DLLs: Extension vs. Regular

  11. The Shared MFC DLLs and the Windows DLLs

  12. MFC Extension DLLs: Exporting Classes

  13. The MFC Extension DLL Resource Search Sequence

  14. The MYMFC22A Example: An MFC Extension DLL

 

 

Dynamic Link Libraries - DLL

 

If you want to write modular software, you'll be very interested in dynamic link libraries (DLLs). You're probably thinking that you've been writing modular software all along because C++ classes are modular. But classes are build-time modular, and DLLs are runtime modular. Instead of programming giant EXEs that you must rebuild and test each time you make a change, you can build smaller DLL modules and test them individually. You can, for example, put a C++ class in a DLL, which might be as small as 12 KB after compiling and linking. Client programs can load and link your DLL very quickly when they run. Microsoft Windows itself uses DLLs for its major functions. DLLs are getting easier to write. Win32 has greatly simplified the programming model, and there's more and better support from AppWizard and the Microsoft Foundation Class (MFC) library. This module shows you how to write DLLs in C++ and how to write client programs that use DLLs. You'll explore how Win32 maps DLLs into your processes, and you'll learn the differences between MFC library regular DLLs and MFC library extension DLLs. You'll see examples of simple DLLs of each type as well as a more complex DLL example that implements a custom control.

 

Fundamental DLL Theory

 

Before you look at the application framework's support for DLLs, you must understand how Win32 integrates DLLs into your process. You might want to review Module 20 to refresh your knowledge of processes and virtual memory. Remember that a process is a running instance of a program and that the program starts out as an EXE file on disk.

Basically, a DLL is a file on disk (usually with a DLL extension) consisting of global data, compiled functions, and resources, that becomes part of your process. It is compiled to load at a preferred base address, and if there's no conflict with other DLLs, the file gets mapped to the same virtual address in your process. The DLL has various exported functions, and the client program (the program that loaded the DLL in the first place) imports those functions. Windows matches up the imports and exports when it loads the DLL. Win32 DLLs allow exported global variables as well as functions.

 

In Win32, each process gets its own copy of the DLL's read/write global variables. If you want to share memory among processes, you must either use a memory-mapped file or declare a shared data section as described in Jeffrey Richter's Advanced Windows (Microsoft Press, 1997). Whenever your DLL requests heap memory, that memory is allocated from the client process's heap.

 

How Imports Are Matched to Exports

 

A DLL contains a table of exported functions. These functions are identified to the outside world by their symbolic names and (optionally) by integers called ordinal numbers. The function table also contains the addresses of the functions within the DLL. When the client program first loads the DLL, it doesn't know the addresses of the functions it needs to call, but it does know the symbols or ordinals. The dynamic linking process then builds a table that connects the client's calls to the function addresses in the DLL. If you edit and rebuild the DLL, you don't need to rebuild your client program unless you have changed function names or parameter sequences. In a simple world, you'd have one EXE file that imports functions from one or more DLLs. In the real world, many DLLs call functions inside other DLLs. Thus, a particular DLL can have both exports and imports.

 

 This is not a problem because the dynamic linkage process can handle cross-dependencies. In the DLL code, you must explicitly declare your exported functions like this:

 

__declspec(dllexport) int MyFunction(int n);

 

The alternative is to list your exported functions in a module-definition [DEF] file, but that's usually more troublesome. On the client side, you need to declare the corresponding imports like this:

 

__declspec(dllimport) int MyFunction(int n);

 

If you're using C++, the compiler generates a decorated name for let say MyFunction(), that other languages can't use. These decorated names are the long names the compiler invents based on class name, function name, and parameter types. They are listed in the project's MAP file. If you want to use the plain name MyFunction(), you have to write the declarations this way:

 

extern "C" __declspec(dllexport) int MyFunction(int n);

extern "C" __declspec(dllimport) int MyFunction(int n);

 

By default, the compiler uses the __cdecl argument passing convention, which means that the calling program pops the parameters off the stack. Some client languages might require the __stdcall convention, which replaces the Pascal calling convention, and which means that the called function pops the stack. Therefore, you might have to use the __stdcall modifier in your DLL export declaration. Just having import declarations isn't enough to make a client link to a DLL. The client's project must specify the import library (LIB) to the linker, and the client program must actually contain a call to at least one of the DLL's imported functions. That call statement must be in an executable path in the program.

 

Implicit Linkage vs. Explicit Linkage

 

The preceding section primarily describes implicit linking, which is what you as a C++ programmer will probably be using for your DLLs. When you build a DLL, the linker produces a companion import LIB file, which contains every DLL's exported symbols and (optionally) ordinals, but no code. The LIB file is a surrogate for the DLL that is added to the client program's project. When you build (statically link) the client, the imported symbols are matched to the exported symbols in the LIB file, and those symbols (or ordinals) are bound into the EXE file. The LIB file also contains the DLL filename (but not its full pathname), which gets stored inside the EXE file. When the client is loaded, Windows finds and loads the DLL and then dynamically links it by symbol or by ordinal.

Explicit linking is more appropriate for interpreted languages such as Microsoft Visual Basic, but you can use it from C++ if you need to. With explicit linking, you don't use an import file; instead, you call the Win32 LoadLibrary() function, specifying the DLL's pathname as a parameter. LoadLibrary() returns an HINSTANCE parameter that you can use in a call to GetProcAddress(), which converts a symbol (or an ordinal) to an address inside the DLL. Suppose you have a DLL that exports a function such as this:

 

extern "C" __declspec(dllexport) double SquareRoot(double d);

 

Here's an example of a client's explicit linkage to the function:

 

typedef double (SQRTPROC)(double);

HINSTANCE hInstance;

SQRTPROC* pFunction;

VERIFY(hInstance = ::LoadLibrary("c:\\winnt\\system32\\mydll.dll"));

VERIFY(pFunction = (SQRTPROC*)::GetProcAddress(hInstance, "SquareRoot"));

double d = (*pFunction)(81.0); // Call the DLL function

 

With implicit linkage, all DLLs are loaded when the client is loaded, but with explicit linkage, you can determine when DLLs are loaded and unloaded. Explicit linkage allows you to determine at runtime which DLLs to load. You could, for example, have one DLL with string resources in English and another with string resources in Spanish. Your application would load the appropriate DLL after the user chose a language.

 

 

 

 

 

 

Symbolic Linkage vs. Ordinal Linkage

 

In Win16, the more efficient ordinal linkage was the preferred linkage option. In Win32, the symbolic linkage efficiency was improved. Microsoft now recommends symbolic over ordinal linkage. The DLL version of the MFC library, however, uses ordinal linkage. A typical MFC program might link to hundreds of functions in the MFC DLL. Ordinal linkage permits that program's EXE file to be smaller because it does not have to contain the long symbolic names of its imports. If you build your own DLL with ordinal linkage, you must specify the ordinals in the project's DEF file, which doesn't have too many other uses in the Win32 environment. If your exports are C++ functions, you must use decorated names in the DEF file (or declare your functions with extern "C"). Here's a short extract from one of the MFC library DEF files:

 

?ReadList@CRecentFileList@@UAEXXZ @ 5458 NONAME

?ReadNameDictFromStream@CPropertySection@@QAEHPAUIStream@@@Z @ 5459 NONAME

?ReadObject@CArchive@@QAEPAVCObject@@PBUCRuntimeClass@@@Z @ 5460 NONAME

?ReadString@CArchive@@QAEHAAVCString@@@Z @ 5461 NONAME

?ReadString@CArchive@@QAEPADPADI@Z @ 5462 NONAME

?ReadString@CInternetFile@@UAEHAAVCString@@@Z @ 5463 NONAME

?ReadString@CInternetFile@@UAEPADPADI@Z @ 5464 NONAME

 

The numbers after the at (@) symbols are the ordinals. Kind of makes you want to use symbolic linkage instead, doesn't it?

 

The DLL Entry Point: DllMain()

 

By default, the linker assigns the main entry point _DllMainCRTStartup() to your DLL. When Windows loads the DLL, it calls this function, which first calls the constructors for global objects and then calls the global function DllMain(), which you're supposed to write. DllMain() is called not only when the DLL is attached to the process but also when it is detached (and at other times as well). Here is a skeleton DllMain() function:

 

HINSTANCE g_hInstance;

extern "C" int APIENTRY

DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)

{

    if (dwReason == DLL_PROCESS_ATTACH)

    {

        TRACE0("MYMFC22A.DLL Initializing!\n");

        // Do initialization here

    }

    else if (dwReason == DLL_PROCESS_DETACH)

    {

        TRACE0("MYMFC22A.DLL Terminating!\n");

        // Do cleanup here

    }

    return 1;   // ok

}

 

If you don't write a DllMain() function for your DLL, a do-nothing version is brought in from the runtime library.

The DllMain() function is also called when individual threads are started and terminated, as indicated by the dwReason parameter. Richter's book tells you all you need to know about this complex subject.

 

Instance Handles: Loading Resources

 

Each DLL in a process is identified by a unique 32-bit HINSTANCE value. In addition, the process itself has an HINSTANCE value. All these instance handles are valid only within a particular process, and they represent the starting virtual address of the DLL or EXE. In Win32, the HINSTANCE and HMODULE values are the same and the types can be used interchangeably. The process (EXE) instance handle is almost always 0x400000, and the handle for a DLL loaded at the default base address is 0x10000000. If your program uses several DLLs, each will have a different HINSTANCE value, either because the DLLs had different base addresses specified at build time or because the loader copied and relocated the DLL code.

Instance handles are particularly important for loading resources. The Win32 FindResource() function takes an HINSTANCE parameter. EXEs and DLLs can each have their own resources. If you want a resource from the DLL, you specify the DLL's instance handle. If you want a resource from the EXE file, you specify the EXE's instance handle.

How do you get an instance handle? If you want the EXE's handle, you call the Win32 GetModuleHandle() function with a NULL parameter. If you want the DLL's handle, you call the Win32 GetModuleHandle() function with the DLL name as a parameter. Later you'll see that the MFC library has its own method of loading resources by searching various modules in sequence.

 

How the Client Program Finds a DLL

 

If you link explicitly with LoadLibrary(), you can specify the DLL's full pathname. If you don't specify the pathname, or if you link implicitly, Windows follows this search sequence to locate your DLL:

 

  1. The directory containing the EXE file.

  2. The process's current directory.

  3. The Windows system directory.

  4. The Windows directory.

  5. The directories listed in the Path environment variable.

 

Here's a trap you can easily fall into. You build a DLL as one project, copy the DLL file to the system directory, and then run the DLL from a client program. So far, so good. Next you rebuild the DLL with some changes, but you forget to copy the DLL file to the system directory. The next time you run the client program, it loads the old version of the DLL. Be careful!

 

Debugging a DLL

 

Visual C++ makes debugging a DLL easy. Just run the debugger from the DLL project. The first time you do this, the debugger asks for the pathname of the client EXE file. Every time you "run" the DLL from the debugger after this, the debugger loads the EXE, but the EXE uses the search sequence to find the DLL. This means that you must either set the Path environment variable to point to the DLL or copy the DLL to a directory in the search sequence.

 

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

 

Table 1

 

* 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:

 

DLL

Description

mfc42d.dll

Core MFC classes.

mfco42d.dll

ActiveX (OLE) classes.

mfcd42d.dll

Database classes (ODBC and DAO).

mfcn42d.dll

Winsock, WinInet classes.

 

Table 2.

 

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 thinking, 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 MYMFC22A 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("my_dll_file_name.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 MYMFC22A Example: An MFC Extension DLL

 

This example makes an extension DLL out of the CPersistentFrame class you saw in Module 9. First you'll build the mymfc22A.dll file, and then you'll use it in a test client program, MYMFC22B.

 

Here are the steps for building the MYMFC22A example:

 

Run AppWizard to produce \mfcproject\mymfc22A. 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.

 

 

 

 

 

 

--------------------------------------------------------------

AppWizard new DLL project creation dialog.

 

Figure 1: AppWizard new DLL project creation dialog.

 

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

 

The only step 1 of 1 DLL project.

 

Figure 2: The only step 1 of 1 DLL project.

 

MYMFC22A DLL project summary.

 

Figure 3: MYMFC22A DLL project summary.

 

Examine the mymfc22A.cpp file. AppWizard generates the following code, which includes the DllMain() function:

 

// mymfc22A.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 Mymfc22ADLL = { 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("MYMFC22A.DLL Initializing!\n");

              // Extension DLL one-time initialization

              if (!AfxInitExtensionModule(Mymfc22ADLL, 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(Mymfc22ADLL);

       }

       else if (dwReason == DLL_PROCESS_DETACH)

       {

              TRACE0("MYMFC22A.DLL Terminating!\n");

              // Terminate the library before destructors are called

              AfxTermExtensionModule(Mymfc22ADLL);

       }

       return 1;   // ok

}

 

Insert the CPersistentFrame class into the project. Choose Add To Project from the Project menu, and then choose Components And Controls from the submenu.

 

Inserting CPersistentFrame class into the MYMFC22A project.

 

Figure 4: Inserting CPersistentFrame class into the MYMFC22A project.

 

Locate the file Persistent Frame.ogx that you created in Module 9. Click the Insert button to insert the class into the current project.

 

Our previous CPersistentFrame class that we stored in the gallery.

 

Figure 5: Our previous CPersistentFrame class that we stored in the gallery.

 

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.

 

Adding Persist.h and Persist.cpp files manually to the MYMFC22A project.

 

Figure 6: Adding Persist.h and Persist.cpp files manually to the MYMFC22A project.

 

Selecting Persist.h and Persist.cpp files.

 

Figure 7: Selecting Persist.h and Persist.cpp files.

 

Edit the persist.h file. Modify the line:

 

class CPersistentFrame : public CFrameWnd

 

to read:

 

class AFX_EXT_CLASS CPersistentFrame : public CFrameWnd

 

MFC Visual C++ code segment

 

Listing 1.

 

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

 

The generated DLL file, copied to the Windows system directory.

 

Figure 8: The generated DLL file, copied to the Windows system directory.

 

System directory for Win Xp Pro is shown below (or C:\WINDOWS\system32).

 

Copy the previous generated DLL file to the Windows directory, so that it can be found by applications from any path.

 

Figure 9: Copy the previous generated DLL file to the Windows directory, so that it can be found by applications from any path.

 

 

Continue on next module...part 2.

 

 

 

 

 

Further reading and digging:

  1. MSDN MFC 7.0 class library online documentation.

  2. MSDN What's New (MFC Feature Pack) - feature pack.

  3. Porting & Migrating your older programs.

  4. MSDN Library

  5. DCOM at MSDN.

  6. COM+ at MSDN.

  7. COM at MSDN.

  8. Windows data type.

  9. Win32 programming Tutorial.

  10. The best of C/C++, MFC, Windows and other related books.

  11. Unicode and Multi-byte character set: Story and program examples.

 

 


| Tenouk C & C++ | MFC Home | Context-Sensitive Help 3 | C++, MFC & DLL 2 | Download | Site Index |