Professional MFC
Chapter 12
Previous Contents Next


Using and Writing DLLs with MFC

Up to this point, we've looked at how to use DLLs in Windows. What we've covered so far in the chapter applies to using C or C++ alone to write applications that use DLLs or are DLLs - but, in all combinations, I've neglected to describe what happens when you add MFC to the equation. There are a couple of added twists that will affect your approach to writing a DLL if you're planning on using MFC within that module. We've got lots of different situations to consider: we need to see what happens if we use a DLL from an MFC program, and we need to see what happens if we want to write a DLL which, itself, uses MFC.

It's trivial to use a DLL written in C from an MFC application. The DLL will expose a 'flat' function-level interface. Since it is written in C, it isn't capable of exporting C++ interfaces. That gives us the knowledge that all the functions exported from the DLL follow the C calling rules we talked about earlier.

You can include the header files in your modules and call the functions as you see fit, or as the documentation for the DLL prescribes. If the DLL is very old or wasn't very carefully written, you might have to use an extern "C" around the #include directive. Since you're building a C++ program, the compiler will assume that all function and data declarations it sees are to follow the C++ name mangling rules - even if they're not class member functions or member variables. On the other hand, since the DLL was built using C naming rules, it will always export symbols that are named using those C rules.

Since the compiler decides what symbol to reference based on the function's declaration, you'll need to make sure the declaration matches the definition. If the header file that declares the functions in your DLL is well-written, it will include the necessary extern "C" references so that the functions in the DLL will be properly available from both C and C++ programs. Older header files (or those that are just plain poorly written) can benefit from a trick using the extern "C" syntax. For example, you might have to pull a stunt like this:

extern "C" {

#include "somefile.h"

}

You'll know that you need to use this trick if you get lots of link errors stating that functions from the DLL you're using weren't found. Before you try it, make sure that you have told the linker about the import library you need; another way to get lots of link errors stating that functions from the DLL weren't found is to forget to link to the import library!

It's easy to use MFC to link to a C language DLL, or even to expose C language interfaces from your MFC DLL. You can use any of the tricks we've talked about so far to call your DLL and build the correct .def file. However, if you're writing a DLL with MFC, you'll usually want to provide more than a C language interface.


MFC DLL Models

If you're writing a DLL with MFC, you might decide to write a regular DLL or an extension DLL. We'll examine the difference between those approaches through this chapter.

Back in versions of MFC before 4.0, you had to choose between DLL architectures named _AFXDLL and _USRDLL. The architectures were named to denote the preprocessor symbols you had to define when you were building each type of library. Concisely: _USRDLL was ideal for situations where you'd be calling the DLL from non-MFC applications (this is, essentially, a regular DLL that statically links to MFC); the _AFXDLL architecture, on the other hand, was designed for applications specifically written for MFC, using DLLs that were also written for MFC (this is like an extension DLL). Although the preprocessor symbols are still in use, the terms _AFXDLL and _USRDLL, when applied to DLL architectures, are no longer used.


Regular MFC DLLs

A regular MFC DLL is a DLL that uses MFC internally and can export C language functions and standard C++ classes. A regular MFC DLL can implement its own classes, and APIs, and export them to the application; it can't, however, exchange pointers or references to MFC-derived classes with the application.

Let me stress that the code in the DLL simply can't move pointers to MFC-based objects across the boundary between the DLL and the application. If you need to implement a DLL that can do this, you'll need to write an MFC extension DLL, which we'll describe later in the chapter. The reason for this is that MFC maintains a lot of state information about its objects, and this information needs special handling if pointers are to be passed across DLL boundaries.

However, it turns out that there are a few MFC classes that can be safely passed across the boundaries of even a regular MFC DLL. I've coined the term transferable object to describe such an object.

A transferable object is an object of any class in MFC that meets these criteria:

Does not derive from CObject

Does not use static data within the MFC library

Does not use state information within the MFC library

Has no member functions which require the use of state information or static data from within MFC

Has no members that are non-transferable MFC objects

I guess the term 'transferable' is a bit awkward, but it doesn't collide with other definitions and it does make some sort of sense: a transferable object is an object created from a class that you can throw over the border between one MFC DLL and another, or an MFC DLL and an MFC application, regardless of whether the DLL is an extension DLL or a regular DLL.

In MFC 4.21, the only classes that produce objects that meet the above criteria are the template-based collection classes and the MFC ISAPI classes (which we'll examine in Chapter 19). The collection classes only meet the criteria if they're containing objects that also meet those same criteria. My new term doesn't appear in the MFC documentation, I'm afraid, because the MFC documentation doesn't endeavor to describe what I'm using my new term to describe!

When you build a regular MFC DLL, you can choose whether to statically or dynamically link it to MFC. The way your DLL uses MFC is independent of the way your application will use MFC, since the application and the library can't exchange MFC objects. Of course, if you expect that applications that use your DLL will be likely to use MFC, you ought to link your DLL to MFC's DLL implementation, so that everyone can share the same MFC DLL instance and dilute its overhead. You ought to give serious consideration to using MFC's DLL implementation anyway because it will improve your DLL's load time and help keep down the memory that it uses.


Using MFC in your Regular DLL

MFC exists in your regular DLL independently of anything else. That's all there is to it: your DLL needs to have a CWinApp-derived object in it so that MFC will properly initialize. Once loaded, your DLL can be called directly by any stock C or C++ program that doesn't use MFC. No extra initialization or protection is necessary.

Your regular DLL can also be called by any program that uses MFC. (Again, since you've written a regular DLL, you can't reliably pass non-transferable MFC objects across the boundary between the DLL and MFC.) It doesn't matter if your program is using MFC in a shared library or not.

However, if your DLL itself uses the DLL version of MFC, you'll need to protect any external entry point to your DLL by using the AFX_MANAGE_STATE() macro. MFC, as I've alluded to in various descriptions throughout the book, manages a tremendous amount of state information about your threads, processes, and executable modules. For MFC to properly keep that state information in the proper context, you'll need to invoke this macro as the very first line of your exported function. If you've written a function that pops up a dialog box, for example, you might code something like this:

extern "C" int ShowIt(HWND hwParent)

{

   AFX_MANAGE_STATE(AfxGetStaticModuleState());

   CWnd* pParent = CWnd::FromHandle(hwParent);

   CDialog dlg(IDD_DIALOG1, pParent);

   return dlg.DoModal();

}

The highlighted line invokes the AFX_MANAGE_STATE() macro to protect the rest of the function. In this situation where this macro is needed - that is, in implementing a dynamically linked regular MFC DLL, failing to provide this function will result in lots of failed assertions in debug builds at worst, and calls that just plain fail at best. Even the seemingly innocent function above absolutely requires the AFX_MANAGE_STATE() macro to work properly: if it's not there, the code won't find the IDD_DIALOG1 resource when it needs to, and that means the dialog can't be displayed.

If you're building a DLL that doesn't use MFC in a DLL itself, you'll find that the AFX_MANAGE_STATE() macro resolves to nothing - the macro is declared to ignore its argument, and is not even seen by the compiler.

When you build your application to use a regular DLL, there's nothing special that you need to do to get it to work. Regular DLLs are very useful for situations in which you want to call your MFC-based routines from a C program, or even from a non-MFC C++ application. Regular DLLs are the architecture of choice when you want to write a DLL that is called from a different front-end development tool, such as Visual Basic or PowerBuilder. The other architecture, MFC extension DLL, requires that both the calling program and the library must be built to dynamically link to MFC.


MFC Extension DLLs

The MFC extension DLL architecture is the Cuban cigar of MFC dynamic-link libraries: it gives you a lot of things you really need, is somewhat expensive, and is illegal in some countries. It has one architectural disadvantage: a library built with the MFC extension DLL architecture must be called from MFC applications which were also built to link to the MFC libraries dynamically. It has one practical disadvantage: like anything else built against the DLL version of the MFC library, the Mfc*.dll files must be installed with your executables.

On the other hand, the advantages are clear: you can pass pointers and references to MFC objects happily between your DLL and applications that use it. This applies to non-transferable MFC classes too. Remember that the description of transferable versus non-transferable objects applied to regular DLLs only. With extension DLLs, all MFC objects can be passed across the DLL boundary.

If you'll recall from our discussions of CWinApp, MFC utilizes an incredible amount of information about the module in which it is running. Unlike the regular method of building DLLs, extension DLLs actually have code that manages the state information for each application that's running. This allows MFC to know which module is currently calling the library. Since the MFC library routines aren't statically linked, this method is necessary to allow MFC to reference the state information it needs for the application while running.


CDynLinkLibrary

The object that handles this is created from MFC's CDynLinkLibrary class. This object allows MFC to know that your DLL is a part of the application as a whole - the application can reference resources and classes in your DLL only if the CDynLinkLibrary is present in your application. You'll create a CDynLinkLibrary object in your application, indirectly, by calling an MFC function named AfxInitExtensionModule().

You'll rarely need to play with a CDynLinkLibrary directly, so it isn't documented in the MFC manuals. The class is referenced, though, in a few of the tech notes. At any rate, the class has a constructor that takes two parameters: the first is a pointer to an AFX_EXTENSION_MODULE structure, and the second is a BOOL.

You'll need to make an AFX_EXTENSION_MODULE object in your application so that the CDynLinkLibrary object you have can initialize it. AFX_EXTENSION_MODULE holds that little chunk of internal information that MFC needs to know about your DLL. The CDynLinkLibrary initializes that information and adds it to a list of DLLs that MFC can handle. Your declaration of AFX_EXTENSION_MODULE is just as trivial as can be:

static AFX_EXTENSION_MODULE extMyExtension;

The second parameter to CDynLinkLibrary is defaulted to FALSE. MFC uses it to indicate that an extension module is actually a for-real part of MFC. So, when your application loads Mfco42d.dll to gain OLE support, the CDynLinkLibrary in that module passes TRUE. You'll never have a good excuse to pass TRUE in your own DLLs, so don't.

If you're writing an extension DLL, you'll need to follow a few other rules. The DLL shouldn't have a CWinApp-derived class. This makes sense: there's only one application in an application, so why would you want to have two applications in your application? Your DLL should also implement its own DllMain() function which calls the global MFC function AfxInitExtensionModule().

AfxInitExtensionModule() does exactly what the name makes it sound like it does - it wakes up an instance of MFC's internal state information and lets the DLL work with MFC properly. If you call AfxInitExtensionModule() and the function returns FALSE, something is terribly wrong and you had better return zero from your DllMain() function. AfxInitExtensionModule() takes two parameters. The first is a reference to your AFX_EXTENSION_MODULE structure. The second is the handle to the instance of your DLL.

An acceptable DllMain() for an MFC extension might go something like this:

BOOL WINAPI DllMain(HMODULE hInst, ULONG uReason, LPVOID /* lpReserved */)

{

   if (uReason == DLL_PROCESS_ATTACH)

   {

      if (!AfxInitExtensionModule(extMyExtension, hInst))

         return 0; // big trouble - split!

   }

  return 1;   // oh happy day!

}

You'll note that I've commented-out the name of the unused lpvReserved parameter in this code fragment to avoid 'unused formal parameter' warnings.

Of course, you might have some other work to do in your DllMain() function. If so, you should do that work after your call to AfxInitExtensionModule().


Dynamically Unlinking

Versions of MFC starting with 4.0 have the ability to dynamically load and discard extension DLLs. In the pre-4.0 days, there was no way for MFC to correctly remove a DLL's information from the internal linked list of DLLs that it manages. As such, you just couldn't reliably load and unload extension DLLs - MFC would end up seeing the information for a DLL that was once loaded and then try to access it. Of course, accessing a DLL that isn't in memory is just not a good way to impress people!

Now you can do a tiny bit of extra legwork to let MFC know about the comings and goings of your DLL. First you'll need to add a call to a function named AfxTermExtensionModule()to your DllMain() function. Where you call AfxInitExtensionModule() in response to DLL_PROCESS_ATTACH in your DllMain(), you'll want to call AfxTermExtensionModule() in response to DLL_PROCESS_DETACH notifications in DllMain(). The whole thing comes out looking like this:

BOOL WINAPI DllMain(HMODULE hInst, ULONG uReason,

   LPVOID /* lpReserved */)

{

   if (uReason == DLL_PROCESS_ATTACH)

   {

      if (!AfxInitExtensionModule(extMyExtension, hInst))

         return 0; // big trouble-split!

   }

   else if (uReason == DLL_PROCESS_DETACH)

   {

      AfxTermExtensionModule(extMyExtension);

   }

  return 1;   // oh happy day!

}

Calling AfxTermExtensionModule() is optional, though I would strongly recommend it. If you don't call AfxTermExtensionModule(), information about your DLL will sit around in memory for the lifetime of your application. That's harmless, since most of the time your extension module's life span is exactly equal to the life span of the application using it. But you can bet a dollar that someday, someone, somewhere will try to dynamically discard your DLL when they call it from one of their applications. If they do this, they'll be in trouble if you didn't call AfxTermExtensionModule().

With your DllMain() function suitably modified, you'll need to remember to use special MFC functions which load and discard MFC extension DLLs properly. In places where you'd normally call the ::LoadLibrary()API, call AfxLoadLibrary()instead. In places where you'd normally call the ::FreeLibrary() API, call the AfxFreeLibrary() function instead. MFC implementations of these functions work in the same way as the 'real' Win32 API implementations - internally, MFC is just doing a little extra work before and after the calls to the Windows APIs. AfxLoadLibrary() and AfxFreeLibrary() work just fine for DLLs that don't use MFC, so you might want to get into the habit of using them all of the time.

Of course, you're allowed to use the ::LoadLibrary() and ::FreeLibrary() APIs to load and discard non-extension DLLs. The rules about MFC's special library management functions, and AfxTermExtensionModule() only apply to MFC extension DLLs.


Exporting Functions

Unfortunately, while MFC offers a couple of cool solutions to DLL developers, you'll still have to export the functions in your DLL yourself. Of course, the normal methods still apply: you can use the __declspec(dllexport) keyword to let the compiler do all of your work, or manually export what you want by writing your own .def file.

If you're writing an MFC extension, you can use the AFX_EXT_CLASS macro in your class definition to make sure everything is exported correctly. The DllApp sample application uses this declaration in its Bcd.h file - the code looks like this:

class AFX_EXT_CLASS CBCDNumber : public CObject

{

   DECLARE_DYNCREATE(CBCDNumber)

   // :

   // more stuff

When you're writing your own .def file, you should take great care to only export functions that you've defined yourself and that calling applications will need to use. Don't export any part of MFC, particularly if you've written a regular DLL. If you do so, the calling application might link with this version of MFC and get terribly, terribly sick as a result.


Loading Resources

I mentioned before that resources can be stored in DLLs. You can easily retrieve them providing you have the handle to the module that contains the resource. MFC makes things easier for you by providing the AfxGetResourceHandle()function, which is a function designed to return the handle your application should use to retrieve resources. If you're executing in the context of your DLL, MFC will return the handle of your DLL's module; otherwise, it will return the handle for the executing application.

If you need access to resources in your DLL or your executable, make sure that you use AfxGetResourceHandle() to get the module handle for the resource. This will ensure that you can use the appropriate LoadResource() call to retrieve the resource correctly.


Lots of Assertions?

Many developers who are taking their first shot at writing a DLL with MFC use the regular DLL model to build their project and then get lots of ASSERT messages from MFC when they try to run the code. In these cases, the ASSERTs are almost always at the beginning of some MFC function which is doing a check to see that a passed pointer is of the correct type. MFC defends itself from bad function calls by doing checks to make sure a pointer to an object is really an object of the type it should be; if not, then it tosses an ASSERT.

Because they were written before run-time type information was available in the Microsoft C++ compiler, the Microsoft Foundation Classes use a linked list of run-time type information structures to manage the relationship between inherited classes. This list is maintained separately for each executable module in normal builds of MFC applications and regular builds of dynamic-link libraries.

In extension builds, the information is a part of the data that MFC tracks for each module. If you use regular DLLs, on the other hand, each module maintains its own chunk of state information - including the lists of run-time type information for CObject-derived classes. That separation is what causes all the ASSERT() messages if you pass objects from one module to the other without using the right DLL architecture.

In a regular DLL, for example, that DLL's CDialog run-time class information ends up having an entry in the linked list associated with the DLL, while the calling application has a semantically different CDialog run-time class tucked away in its own linked list. For this reason, the test on the run-time type of a pointer will fail when it is passed across the DLL/application boundary; even though the object is really of the type that MFC is expecting, the test fails as MFC can't find the appropriate entry in the correct list. Of course, this isn't a problem if you're using the MFC extension architecture.

This is only one example of how the wrong set of application state information can cause trouble. The Microsoft Foundation Classes also maintains a great deal of information about the application's state. In particular, OLE support requires a vast amount of transient state information be kept up to date. If your application is asserting after crossing the line between the main line application code and any of its DLLs, it's very likely that the state information is not being properly communicated between these two halves of your code, make sure that you have used the AFX_MANAGE_STATE() macro where necessary.


Summary of DLL Models

With the description of the two MFC DLL architectures out of the way, let's talk a little bit about how to build each one and when you might want to use one or the other. Some of these paragraphs will concisely restate what we've explained in the previous sections - I want to make sure you have a concise reference to use when you think you might have to write a DLL.

Let's start by looking at the preprocessor symbols involved. There are four that are related to the current discussion:


SymbolDescription
_AFXDLLDefine this symbol to dynamically link to MFC
_WINDLLDefine this symbol if you're building a Windows DLL
_USRDLLDefine this symbol to build a regular DLL
_AFXEXTDefine this symbol to build an extension DLL

Note that the _USRDLL and _AFXEXT symbols are mutually exclusive - if one is defined, the other should not be - and that these symbols are only relevant to DLLs so should only be used if _WINDLL is defined. _AFXDLL can be used in both applications and dynamic-link libraries to tell the compiler to dynamically link to MFC. Of course, you will usually use Developer Studio to set these symbols using the Project Settings dialog.

If you want to statically link MFC and write a DLL that uses MFC but doesn't expose MFC-derived classes to applications which use your DLL, you can do so by writing a regular DLL that statically links to MFC. To perform your build, you'll want to make sure the _USRDLL preprocessor symbol is defined when you include Afx.h, and you'll want to make sure that _AFXDLL is not defined.

If you want to dynamically link to MFC and write a DLL that uses MFC but doesn't expose MFC-derived classes to applications which use your DLL, you can do so by writing a regular DLL that dynamically links to MFC. You should make sure both the _USRDLL and _AFXDLL symbols are defined before you include Afx.h.

If you want to write a DLL which lets applications referencing the DLL use MFC-derived classes from the DLL, you can make an MFC extension DLL. You'll need to make sure that the _AFXDLL preprocessor symbol is defined, but the _USRDLL symbol is not defined. You'll also need to endow your application with a CDynLinkLibrary object as discussed previously.

Now that we have all of the different kinds of MFC projects out on the table, let's summarize which preprocessor symbols they use:


Project Type_AFXDLL_WINDLL_USRDLL_AFXEXT
Application without MFC----
Application using MFC static link----
Application using MFC dynamic linkx---
Regular DLL using MFC static link-xx-
Regular DLL using MFC dynamic linkxxx-
Extension DLLxx-x

Which Architecture Is Best?

I'm frequently asked: "Which DLL architecture is best?" It's a question I can't answer, simply because I'd need to be completely aware of the exact problems the questioner is trying to solve. You'll need to analyze what's happening in your system and decide for yourself what approach suits all of the needs you have and which approaches result in compromises that are acceptable.

If you're interested in writing a DLL and an application that exchange MFC-derived non-transferable objects, you'll absolutely have to write an extension DLL. If you're writing an extension DLL, you'll need to write an application that's linked to the DLL version of MFC. It only makes sense. If you can't change the application in question, you have no real reason to be passing MFC objects back to it.

When you need to reuse MFC code, the simplest approach is to write a regular DLL and wrap it with a C-style interface. This means that single functions will end up using real objects behind the scenes. You might have to design an API for your DLL that lets you return a context number or connection handle or some such thing and then translate that identifier to the object for the particular context-using a CMap or CArray, for example.

This table concisely spells out the differences among the different builds. This table is concise because the details of the differences have been explained throughout the chapter.


Calling ApplicationDynamic Link LibraryPass MFC-derived objects over DLL boundaryComments
ArchitectureLink to MFCArchitectureLink to MFC
C or C++ with SDKn/aNo MFC n/a-
C or C++ with SDKn/aMFC regular DLLStatic Link to MFCn/a-
C or C++ with SDKn/aMFC regular DLLShared MFCn/a-
C or C++ with SDKn/aMFC Extension DLLn/an/aThis won't work
MFC Static Link to MFCMFC regular DLLStatic Link to MFCno-
MFC Shared MFCMFC regular DLLStatic Link to MFCno-
MFC Static Link to MFCMFC regular DLLShared MFCnoRequires AFX_MANAGE_STATE()
MFC Shared MFCMFC regular DLLShared MFCnoRequires AFX_MANAGE_STATE()
MFC Static Link to MFCMFC Extension DLLShared MFCn/aThis won't work
MFC Shared MFCMFC Extension DLLShared MFCyesJust works

I must admit that, perhaps for only this subject within the book, I have not explained every possible workaround to the limitations that I've noted in MFC. I'm afraid that is entirely by choice: there are devious ways to get around the limitations that I've discussed in the various DLL models, but they're just too fragile (and too slimy) to write down on paper and tell people to use. Doing so would be irresponsible at best, since folks who followed that advice would probably end up with code that was broken by the next version of MFC.


Previous Contents Next

©1997 Wrox Press Ltd.