Dynamic Link Libraries (DLLs)
Since the dawn of time (or thereabouts), Windows operating systems have used dynamic link libraries (DLLs) to support commonly used functions. Descendants of Windows, including Windows NT and Windows 95 as well as OS/2, also depend on DLLs to provide a large segment of their functionality.
In this chapter, you look at several different aspects of using and creating DLLs. Here, you will see how to
· Statically link to DLLs
· Load DLLs dynamically
· Create your own DLLs
· Create MFC extension DLLs
For Windows applications, it is virtually impossible to create an application that does not use DLLs. All the Win32 API and countless other functions of the Win32 operating systems are contained in DLLs, although you may not have been aware that the examples shown so far use DLLs at all.
In general, DLLs are just collections of functions in a library. However, unlike their static cousins (.lib files), DLLs are not linked directly into executable files by the linker. Instead, only reference information is included in the executable file. The bulk of the library code is then loaded at runtime. This allows different processes to share libraries in memory, thus cutting down on the memory required to run different applications that share many of the same libraries, as well as keeping the size of EXEs manageable.
However, if your library will be used by a single application, it might be more efficient to create a simple, static link library. Of course, if your functions are to be used in only one program, you might as well simply compile the source into your one application.
In most cases, your project will link to DLLs statically, or implicitly, at link-time. The operating system then manages the process of loading the DLL for you at runtime. However, you can also explicitly or dynamically load DLLs at runtime, as you will see later in this chapter.
When statically linking to a DLL, you will specify a .lib file in the linker options, either on the command line or in the Link page of Developer Studio's project settings. However, the .lib file that you link to is not your average static library. The .lib files that are used to implicitly link to DLLs are known as import libraries. They do not contain the real meat of the code contained in the library, but only references to each function exported by a DLL file, which has all the good stuff in it. In general, this results in the import libraries being much smaller than the DLL files. Yo 818b14i u will look at just how these files are created later in this chapter; for now, let's look at some of the other issues involved in linking to DLLs implicitly.
In the examples that you have looked at so far, you haven't had to worry about the different calling conventions that may be used to handle parameter passing and calls to functions. That is because the libraries and headers provided in Visual C++ have taken care of this for you. However, if you will be using your own libraries, or those from third parties, you will need to pay attention to this. (These calling convention details apply to plain old static libraries as well.)
If this were a perfect world, you wouldn't have to worry about calling conventions for libraries—they would all be the same. However, this is not a perfect world and a great deal of large-scale development is dependent on some sort of non-C++ library.
By default, Visual C++ will use the C++ calling convention. This means that parameters will be placed on the stack from right to left, the caller is responsible for removing parameters from the stack when the call returns, and function names are mangled (or decorated, depending on your political correctness).
Name mangling allows the linker to differentiate between overloaded functions—that is, functions with the same name but different argument lists. However, if you look for a mangled function name in an old C library, you won't find it.
Although the rest of the C calling convention is identical, C libraries do not mangle the names of their functions, other than prepending an underscore (_) to the name.
If you plan to use a C library in your C++ application, you will need to declare all the functions from the C library as extern 'C', like this:
extern 'C' int MyOldCFunction(int myParm);
Declarations for library functions are usually done in a header supplied with the library, although most C libraries do not ship with a header designed for C++ use. In this case, you add the extern 'C' modifier to each of the functions in a copy of the header you will make for use with C++. This can be quite a chore, so I generally use a shortcut: You can apply the extern 'C' modifier to an entire block of code—namely, the #include that brings in the old C header file. Thus, instead of the drudgery of modifying each function in a header, you can do something like this:
If you run into unresolved external errors when linking, the linker will include the function name that it is looking for at the end of the error message. If this name includes strange characters (generated by C++ name mangling), other than a preceding underscore (which is used in standard C linkages), the extern 'C' modifier is not being used.
In programs for older versions of Windows, the PASCAL calling convention was also used for Windows API calls. In newer programs, you should use the WINAPI modifier, which maps to _stdcall. Although this is not really a standard C or C++ calling convention, it is the one used by Windows API calls. However, this is generally all taken care of for you in the standard Windows headers.
When your application starts, it will try to find all the DLL files that have been implicitly linked to the application and map them into the process's memory space. To find DLL files, the operating system will look in the following places:
1. The directory from which the EXE was run
2. The current directory for the process
3. The Windows system directory
4. The Windows directory
5. The directories in the PATH environment variable
For Windows NT, the Windows system directory in number 3 includes both the 32-bit Windows system directory (usually SYSTEM32), which is searched first, and the 16-bit Windows system directory (SYSTEM).
If the DLL is not found, your application will display a dialog box, showing the user the DLL that was not found and the path that was searched. The process then quietly shuts down.
If the proper DLL is found, it is then mapped into the process's memory space, where it will remain until the process terminates. Your application can now call the functions contained in the DLL without any further ado. If you want to dynamically load and unload DLLs, you use the methods discussed next.
Unlike Win3.1, global memory in a DLL is not truly global—each process gets its own copy. Sharing memory between processes is discussed in Chapter 8, 'Memory Management.'
Occasionally, it is useful to allow your application a bit more control over the loading of DLLs than normal, implicit linking will allow. For instance, you may wish to specify which DLL the user can use, or require the user to select options that affect which DLL is to be used. The dynamic, or explicit, loading process allows you to decide which DLLs will be loaded. This allows you to use several different DLLs that provide the same functions, but work differently. For example, if you develop a transport-independent communications module, your application could decide at runtime whether to load the DLL for TCP/IP or NetBIOS.
The first thing you do to load a DLL dynamically is to map the DLL module into the memory of your process. This is done with the ::LoadLibrary() call, which takes a single parameter—the name of the module to load. Your code should look something like this:
hMyDll = LoadLibrary('MyLib');
if(hMyDll == NULL)
// Could not load DLL, handle the error…
Windows will assume a default file extension of .dll if you don't specify an extension. In the example, Windows will look for MyLib.dll. If you specify a path in the filename, only that specific path is used to find the file; otherwise, Windows will search for the file in the same way that it searches for implicitly linked DLLs (as shown previously), starting with the directory the process's EXE loaded from and continuing on through to the PATH.
Once Windows locates the file, it will compare the full path of the file found to the full path of DLLs already loaded in that process. If there is a match, the handle for that library is returned, rather than having another copy loaded.
LoadLibrary() may also be used to load executable files into memory. The handle to the executable module can then be used in calls to FindResource() or LoadResource().
If the file is found and the DLL is loaded successfully, LoadLibrary() returns a handle to the module. Hang on to this handle; you will be using it shortly. If an error occurs, LoadLibrary() will return NULL.
If the specified file is not found, Windows will normally display a dialog to the user, stating that the file was not found. However, if you are using MFC, it makes a call to ::SetErrorMode(), instructing Windows to simply return errors to the application, rather than notifying the user before functions such as LoadLibrary() return. This allows your applications to deal with errors as they see fit. If you are not using MFC, you can call SetErrorMode() yourself.
Provided the DLL has been loaded properly, you will next need to find the addresses for the individual functions before you can use them. This can be done by calling ::GetProcAddress() with the handle returned by LoadLibrary() and the name of the function. This name should be the name of the function as it is exported from the DLL, as shown here:0
UINT (*pfnMyFunc)(char* strMyName);
hMyDll = ::LoadLibrary('MyLib');
ASSERT(hMyDll != NULL);
pfnMyFunc = (UINT (*)(char*))::GetProcAddress(hMyDll, 'MyFunc');
ASSERT(pfnMyFunc != NULL);
UINT nRc = (*pfnMyFunc)('Dave');
Alternatively, you may also reference the function by the ordinal number it is exported with:
pfnMyFunc = (UINT (*)(char*)) ::GetProcAddress(hMyDll, MAKEINTRESOURCE(42));
If the function is not found, GetProcAddress() returns NULL. If the function is found, this will return a generic pointer to a function. It is up to your application to make sure that the pointer you use is defined to point to a function with the same parameter list and return value as the function loaded from the DLL. In the previous example, MyFunc takes a char pointer and returns a UINT. If there is a mismatch in parameter lists, your stack will become corrupted when you make calls through the pointer. This will almost certainly kill your process.
When your application is finished with a particular DLL, it may be unloaded from your process with a call to ::FreeLibrary().
If you are loading an MFC extension DLL, you should use AfxLoadLibrary() and AfxFreeLibrary() instead of LoadLibrary() and FreeLibrary(). These functions are almost identical to the Win32 API calls, but they will ensure that the MFC structures initialized by an extension DLL are not corrupted by multiple threads. You will see more about MFC extension DLLs later in this chapter.
You can also use dynamic loading to load a resource DLL, which MFC will then use to load the default resources for the application. To do this, you first make a call to LoadLibrary() to map the DLL into memory; then you call AfxSetResourceHandle() to let the framework know that it should get resources from the newly loaded DLL, rather than those linked with the process's executable file. This can be useful if you need to use different sets of resources, as in localization for different languages.
Now that you have seen how DLLs can be used in your application, let's look at how you can create your own. If you are developing real applications, you will most likely want to try to put functions common to more than one process into DLLs so that Windows can more efficiently manage the memory used.
The easiest way to get started with building a DLL project is to use AppWizard to create a new project for you. For simple DLLs, such as the ones you will see in this chapter, you should use the DLL project type. This will create a new project for you, with all the necessary project settings for building a DLL. You will then have to add your own source files to the project manually.
If you plan to use higher level MFC functionality, such as documents and views, or are creating an OLE automation server, the MFC AppWizard (.dll) project type will do some extra work for you. This project type will add the appropriate references to the MFC libraries and add source files to declare and implement a CWinApp-derived application object for your DLL.
It is often handy to first create a top-level project for a tester for your DLL and then create the DLL project as a subproject. This way, you can build the tester, and the DLL will be automatically built if necessary.
Most DLLs are simply a collection of loosely related functions that are exported for other applications to use. In addition to the exported functions that are used, every DLL includes a DllMain() function, which is used to initialize the DLL, as well as to clean up when the DLL is unloaded. This function replaces the LibMain and WEP functions used in previous versions of Windows. A sample skeleton for your DllMain() function may look something like this:
BOOL WINAPI DllMain (HANDLE hInst,
// end switch
} // end DllMain()
Your DllMain() function may be called at several different times. The dwReason parameter will tell you why DllMain() was called, from one of the following values:
When a process first loads the DLL, DllMain() is called with a dwReason of DLL_PROCESS_ATTACH. Whenever this process then creates a new thread, DllMain() is called with DLL_THREAD_ATTACH. (This is not done for the first thread, because it will call with DLL_PROCESS_ATTACH.)
When the process is finished with the DLL, this function is called with dwReason of DLL_PROCESS_DETACH. When a thread of the process (other than the first thread) is destroyed, dwReason will be DLL_THREAD_DETACH.
Based on the value of dwReason, you should do any per-process or per-thread initialization and cleanup that your DLL requires, as shown in the previous example. In general, per-process initialization deals with setting up any resources that are shared by multiple threads, such as loading shared files or initializing libraries. Per-thread initialization should be used for setting up things that are unique to the thread, such as initializing thread local storage.
Your DLL may include resources that are separate from those in the calling application. If the functions in your DLL will be working with resources from the DLL, you will certainly want to save the hInst handle somewhere safe. This handle will be used in calls to load resources from the DLL.
The lpReserved pointer is reserved for use by Windows, so your application shouldn't muck with it. However, you may test the value of the pointer. If the DLL has been loaded dynamically, this will be NULL; static loads will pass a non-NULL pointer.
If all goes well in your DllMain(), it should return TRUE. If something goes wrong, you can return FALSE to abort the operation.
If your code does not supply its own DllMain(), the compiler will add its own default version, which simply returns TRUE.
In order for applications to be able to use the functions in your DLL, each function must have an entry in the DLL's exports table. To get the compiler to add an entry to the exports table for a function, you have two options.
You may export functions in your DLL by using the __declspec(dllexport) modifier in front of all your function declarations. MFC also provides several macros that evaluate to __declspec(dllexport), including AFX_CLASS_EXPORT, AFX_DATA_EXPORT, and AFX_API_EXPORT. In the current version of Visual C++, these are all the same, but they are provided to support future enhancements that may require different handling.
The __declspec method is not used as often as the second method, which involves module definition (.def) files and gives you more control of the export process.
The syntax of .def files in Visual C++ is pretty straightforward, particularly because most of the more complicated options used in earlier versions of Windows no longer apply under Win32. As you can see in the following simple example, the .def file gives a name and description for the library, then a list of the functions to be exported:
DESCRIPTION `MYDLL Example Dynamic Link Library'
MyConnect @3 NONAME
You can specify an ordinal number to a function by adding it to the exports line for the function with an @. This ordinal can then be used in calls to GetProcAddress(). Actually, the compiler will assign ordinals to all exports, but the way this is done is somewhat unpredictable if you do not specify ordinals explicitly.
In addition, you will notice the NONAME option in the example. This tells the compiler not to include the name of the function in the export table of the DLL. In some cases, this can save a lot of space in the DLL file. Applications that use an import library to link to the DLL implicitly will not notice a difference, because implicit linking uses only ordinal numbers internally. However, applications that load the DLL dynamically will need to pass the ordinal number, rather than the function name, to GetProcAddress().
Creating a .def file to export even simple classes from your DLL can be a bit tricky. You will have to explicitly export every function that may be used by an outside application, including functions that you have not defined yourself.
If you take a look at the map file generated by code that implements a class, you may be surprised to see some of the functions listed there. These will include things such as implicit constructors and destructors or the functions that MFC declares in macros such as DECLARE_MESSAGE_MAP, as well as the functions that you implement yourself.
Although you can export each of these functions yourself, there is an easier way. If you use the AFX_CLASS_EXPORT modifier macro in the declaration of your class, the compiler will take care of exporting all necessary functions to allow applications to use the class contained in the DLL.
Unlike static libraries, which effectively become part of an application's code, dynamic link libraries in pre-Win32 versions of Windows handled memory a bit differently. Under Win16, DLL memory was kept outside a task's address space and provided the ability to share memory between tasks with the global memory in a shared DLL.
In Win32, the DLL's memory is mapped into memory space of the loading process. Each process gets its own copy of the 'global' memory for the DLL, which is reinitialized when a new process loads the DLL. This means that the DLL cannot be used to share memory between processes in the same way that Win16 allows.
However, you can pull a few tricks with the DLL's data segment that will allow you to create a single section of memory that is shared for all processes that use the DLL.
Suppose you have an array of ints that you want to be used by all processes that loaded the DLL. This could be done with the following code:
// Other shared variables
#pragma comment(lib, 'msvcrt' '-section:.myseg,rws');
All variables declared between the data_seg pragmas will be allocated in the segment named .myseg. The comment pragma is not just a comment in the traditional sense; rather it tells the C runtime library to mark your new section as readable, writable, and shared.
If you have created a project with AppWizard, and properly updated the .def file for your DLL, it should be all set to go. However, if you are creating your own make files, or otherwise building without AppWizard projects, you should specify the /DLL option to the linker. This will cause the linker to generate a DLL rather than a stand-alone executable.
If you are using a .def file with the LIBRARY line in it, you do not need to explicitly declare the /DLL option to the linker.
If you are using MFC, there are also some special options that concern how your DLL will use MFC libraries. These are covered in the next section on MFC DLLs.
You are by no means forced to use MFC in your DLLs, but there are several very important issues involved with using MFC in your DLL.
There are two levels at which your DLL can work with the MFC framework. The first of these levels is the regular MFC DLL, which can use MFC but may not pass pointers to MFC objects between the DLL and the application. The second level of MFC support is implemented in an MFC extension DLL. This class of DLL requires some extra work to set up, but will allow you to freely pass pointers to MFC objects between the DLL and the application.
Regular MFC DLLs allow you to use MFC in your DLL, but they do not require that the calling application also use MFC. In regular DLLs, you can use MFC in any way you see fit, including deriving your own MFC-derived classes in the DLL and exporting them for use in applications.
However, a regular DLL cannot exchange pointers to MFC-derived classes with the application.
If you need to exchange pointers to MFC objects or classes derived from MFC classes, across the application-DLL boundary, you use the extension DLL shown in the next section.
The regular DLL replaces the USRDLL architecture used in previous implementations of MFC. (A regular DLL that links to MFC statically works the same as the obsolete USRDLL-type DLL.) The regular DLL is the architecture of choice for DLLs that will be used by other programming environments, such as Visual Basic or PowerBuilder.
To create a regular MFC DLL with AppWizard, create a new project with the MFC AppWizard (.dll) and choose one of the Regular DLL options in step 1 of 1. You may choose to link to the MFC libraries either statically or dynamically. If you want to change how your DLL links to MFC, you can do so with the combo box in the General page of the project settings dialog.
In previous versions of MFC, USRDLLs required special versions of the static MFC libraries. This is no longer true, so go ahead and use the standard MFC static libs. In addition, the USRDLL architecture would not allow you to link with the dynamic MFC libraries. This is not true of the regular DLL type—you are free to use the dynamic libraries for MFC.
Each module in an MFC process keeps its own state information. This means that your DLL will have different state information than the calling application. Because of this, any functions that you export that will be called directly by the application's code must tell MFC which state information to use. Before calling any MFC routines in your regular MFC DLL that uses the dynamic MFC libraries, you use the following line at the beginning of your exported functions:
This will set the correct state information for the duration of the function in which it is called.
MFC allows you to create DLLs that will appear to applications as if they were just that—extensions to MFC, rather than a separate collection of functions. This sort of DLL can be used to create your own MFC-derived classes for applications to use.
To enable your DLL to freely pass pointers to MFC objects between the application and your DLL, you need to create an MFC extension DLL. These DLLs must link to the dynamic MFC libraries, as must any application that will use your MFC extension DLL. In older versions of MFC, this type of DLL was called an AFXDLL.
To create a new MFC extension DLL, it is easiest to start with the MFC AppWizard (.dll) and choose the MFC Extension DLL option in step 1. This will create the new project for you and set up all the proper project settings to create an MFC extension DLL. In addition, it will supply you with a DllMain() for your DLL that does some special processing required to initialize an extension DLL. You should also notice that this type of DLL does not and should not have a CWinApp derived object declared in it.
MFC extension DLLs require some special initialization to fit into the MFC framework. To see how this works, let's look at the DllMain() that is created by AppWizard for you:
static AFX_EXTENSION_MODULE MyExtDLL = ;
extern 'C' int APIENTRY
DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
else if (dwReason == DLL_PROCESS_DETACH)
return 1; // ok
The most important part of this function is the call to AfxInitExtenstionModule(), which initializes your DLL to properly work in the MFC framework. It takes a handle to the DLL's instance, which is passed into DllMain(), and an AFX_EXTENSION_MODULE structure, which will hold information about your DLL for MFC's use.
You will not have to explicitly initialize your AFX_EXTENSION_MODULE structure, but you will need to make sure you declare one. The constructor for CDynLinkLibrary will initialize it for you. You must create a CDynLinkLibrary in your DLL—its constructor will initialize the AFX_EXTENSION_MODULE structure and add your DLL to the list of DLLs that MFC can work with.
Beginning with MFC 4.0, the framework supports dynamic loading and unloading of MFC DLLs, including your extensions. To make this work properly for your DLL, you should add a call to AfxTermExtensionModule() in your DllMain() when a process detaches. This function should be passed the AFX_EXTENSION_MODULE that was used earlier in the example. This functionality can be added by adding the following code to the DllMain() shown previously:
if(dwReason == DLL_PROCESS_DETACH)
In addition, remember that your new DLL is now an extension DLL and should be dynamically loaded with the AfxLoadLibrary() and AfxFreeLibrary() calls rather than LoadLibrary() and FreeLibrary().
You need to export any functions or classes that you intend for applications to be able to access from your DLL. Although you could add all the mangled names to your DEF file manually, you can use modifiers for your class and function declarations provided by MFC for exporting from extension DLLs. These include AFX_EXT_CLASS and AFX_EXT_API, as shown here:
class AFX_EXT_CLASS CMyClass : public CObject
void AFX_EXT_API MyFunc();
Debugging DLLs can be a bit different from debugging regular executables. This is mostly due to the fact that a DLL does not run on its own, but rather must be called by another application.
I mentioned briefly that it is convenient to create a project for a test application, then create a subproject of the test app for your DLL. This is a handy way to keep the projects in sync. To debug your DLL, you will need to add it to the Additional DLLs list in the Debug page of the test application's project settings. (If the DLL is not in the path, you should specify the complete pathname.) Then when you debug the test app, you can step into the DLL or set breakpoints or whatever you normally would do in the debugger.
Occasionally, you may find yourself developing a DLL that is called by another program for which you don't have access to a debug version or source code. To allow you to debug DLLs in these situations, you can specify an executable in the Executable for debug session box on the Debug page of the project settings for your DLL project. Then, when you choose Debug from the Build menu, this application will start up, although any breakpoints you set in the DLL will stop execution and allow you to use the debugger normally within your DLL.
If you are ever curious about the internals of an executable image, such as an EXE or DLL file, you may find the Dumpbin.exe tool to be quite useful. It is found in the MsdevBin directory if it's not already in your path. Although this utility has several different options, allowing you to dump just about anything about an image that you would ever care to know, the most interesting options for DLLs are the /IMPORT and /EXPORT flags. /IMPORT will show you all the functions that the image will need to import and the files in which it expects to find them. On the other hand, the /EXPORT options will show you all the functions exported by an image and their ordinals.
In this chapter, you took a look at dynamic link libraries, how they are used, and how they can be created.
You saw how applications can link with import libraries to implicitly link to DLLs, as well as how to manage dynamic loading of DLLs at runtime with LoadLibrary() and GetProcAddress().
You also explored how to create your own DLLs, including how to create the projects, export functions with DEF files or __declspec(dllexport), and initialize them in DllMain().
In addition, you learned how to create MFC extension DLLs that can work directly within the framework of MFC, including the specifics of initializing and loading extension DLLs.