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
Using Dynamic Link Libraries
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:
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.
Loading the DLL
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
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.
Loading DLLs Dynamically
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.
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.
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().
Loading MFC Extension DLLs
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.
Creating Your Own DLLs
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.
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.
Exporting Functions from Your DLL
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.
Module Definition Files
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.
DLL Memory Issues
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.
Building the DLL
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 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.
DLLs and MFC
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
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.
Comenteaza documentul:Nu esti inregistrat
Trebuie sa fii utilizator inregistrat pentru a putea comenta
Creaza cont nou
A fost util?Daca documentul a fost util si crezi ca merita
sa adaugi un link catre el la tine in site
in pagina web a site-ului tau.