As you've seen, a Windows program is an executable file that generally creates one or more windows and uses a message loop to receive user input. Dynamic-link libraries are generally not directly executable, and they generally do not receive messages. They are separate files containing functions that can be called by programs and other DLLs to perform certain jobs. A dynamic-link library is brought into action only when another module calls one of the functions in the library.
The term "dynamic linking" refers to the process that Windows uses to link a function call in one module to the actual function in the library module. "Static linking" occurs during program development when you link various object (.OBJ) modules, run-time library (.LIB) files, and usually a compiled resource (.RES) file to create a Windows .EXE file. Dynamic linking instead occurs at run time.
KERNEL32.DLL, USER32.DLL, and GDI32.DLL; the various driver files such as KEYBOARD.DRV, SYSTEM.DRV, and MOUSE.DRV; and the video and printer drivers are all dynamic-link libraries. These are libraries that all Windows programs can use.
Some dynamic-link libraries (such as font files) are termed "resource-only." They contain only data (usually in the form of resources) and no code. Thus, one purpose of dynamic-link libraries is to provide functions and resources that can be used by many different programs. In a conventional operating system, only the operating system itself contains routines that other programs can call on to do a job. In Windows, the process of one module calling a function in another module is generalized. In effect, by writing a dynamic-link library, you are writing an extension to Windows. Or you can think of dynamic-link libraries, including those that make up Windows, as extensions to your program.
Although a dynamic-link library module can have any extension (such as .EXE or .FON), the standard extension is .DLL. Only dynamic-link libraries with the extension .DLL will be loaded automatically by Windows. If the file has another extension, the program must explicitly load the module by using the LoadLibrary or LoadLibraryEx function.
You'll generally find that dynamic libraries make most sense in the context of a large application. For instance, suppose you write a large accounting package for Windows that consists of several different programs. You'll probably find that these programs use many common routines. You could put these common routines in a normal object library (with the extension .LIB) and add them to each of the program modules during static linking with LINK. But this approach is wasteful, because each of the programs in this package contains identical code for the common routines. Moreover, if you change one of the routines in this library, you'll have to relink all the programs that use the changed routine. If, however, you put these common routines in a dynamic-link library called, for instance, ACCOUNT.DLL, you've solved both problems. Only the library module need contain the routines required by all the programs, thus requiring less disk space for the files and less memory space when running two or more of the applications simultaneously, and you can make changes to the library module without relinking any of the individual programs.
Dynamic-link libraries can themselves be viable products. For instance, suppose you write a collection of three-dimensional drawing routines and put them in a DLL called GDI3.DLL. If you then interest other software developers in using your library, you can license it to be included with their graphics programs. A user who has several of these programs would need only one GDI3.DLL file.
Part of the confusion surrounding dynamic-link libraries results from the appearance of the word "library" in several different contexts. Besides dynamic-link libraries, we'll also be talking about "object libraries" and "import libraries."
An object library is a file with the extension .LIB containing code that is added to your program's .EXE file in the process called static linking when you run the linker. For example, in Microsoft Visual C++, the normal C run-time object library that you link with your program is called LIBC.LIB.
An import library is a special form of an object library file. Like object libraries, import libraries have the extension .LIB and are used by the linker to resolve function calls in your source code. However, import libraries contain no code. Instead, they provide the linker with information necessary to set up relocation tables within the .EXE file for dynamic linking. The KERNEL32.LIB, USER32.LIB, and GDI32.LIB files included with the Microsoft compiler are import libraries for Windows functions. If you call the Rectangle function in a program, GDI32.LIB tells LINK that this function is in the GDI32.DLL dynamic-link library. This information goes into the .EXE file so that Windows can perform dynamic linking with the GDI32.DLL dynamic-link library when your program is executed.
Object libraries and import libraries are used only during program development. Dynamic-link libraries are used during run time. A dynamic library must be present on the disk when a program is run that uses the library. When Windows needs to load a DLL module before running a program that requires it, the library file must be stored in the directory containing the .EXE program, the current directory, the Windows system directory, the Windows directory, or a directory accessible through the PATH string in the MS-DOS environment. (The directories are searched in that order.)
Although the whole idea of dynamic-link libraries is that they can be used by multiple applications, generally you'll initially design a dynamic-link library in connection with just one application, perhaps a "test" program that puts the DLL through its paces.
That's what we'll do here. We'll create a DLL called EDRLIB.DLL. The "EDR" of this filename stands for "easy drawing routines." Our version of EDRLIB will contain only one function (named EdrCenterText), but you can add other functions to it that simplify the drawing functions in your applications. An application named EDRTEST.EXE will take advantage of EDRLIB.DLL by calling the function contained in it.
To do this requires an approach a little different than the one we've been taking, involving a feature of Developer Studio we haven't examined yet. Developer Studio differentiates between "workspaces" and "projects." A project is generally associated with the creation of an application file (.EXE) or a dynamic-link library (.DLL). A workspace can contain one or more projects. Until now, all our workspaces have contained just one project. We'll now create a workspace called EDRTEST that will contain two projects—one to create EDRTEST.EXE and the other to create EDRLIB.DLL, the dynamic-link library used by EDRTEST.
Let's begin. In Developer Studio, select New from the File menu. Select the Workspaces tab. (We haven't selected this before.) Select the directory where you want the workspace to be in the Location field, and type EDRTEST in the Workspace Name field. Press Enter.
This creates an empty workspace. The Developer Studio will create a subdirectory named EDRTEST and the workspace file EDRTEST.DSW (as well as a couple other files).
Now let's create a project in this workspace. Select New from the File menu, and select the Projects tab. Whereas in the past you've selected Win32 Application, this time select Win32 Dynamic-Link Library. Also, click the radio button Add To Current Workspace. That makes this project part of the EDRTEST workspace. Type EDRLIB in the Project Name field, but don't press OK just yet. As you type EDRLIB in the Project Name field, Developer Studio alters the Location field to show EDRLIB as a subdirectory of EDRTEST. You don't want this! In the Location field, remove the EDRLIB subdirectory so that the project is created in the EDRTEST directory. Now press OK. Developer Studio will create a project file EDRLIB.DSP and a make file EDRLIB.MAK.
Now you're ready to add a couple files to this project. From the File menu, select New and then the Files tab. Select C/C++ Header File, and type the filename EDRLIB.H. Type in the file shown in Figure 21-1 (or copy it from this book's CD-ROM). Select New from the File menu again, and then the Files tab. This time select C++ Source File, and type the filename EDRLIB.C. Again type the file shown in Figure 21-1.
Figure 21-1.
The EDRLIB library.
EDRLIB.H
|
EDRLIB.C
|
At this point you can build EDRLIB.DLL in either a Release or Debug configuration. After the build, the RELEASE and DEBUG directories will contain EDRLIB.LIB, which is the import library for the dynamic-link library, and EDRLIB.DLL, the dynamic-link library itself.
Throughout this book we've been creating programs that can be compiled for Unicode or non-Unicode character strings depending on the definition of the UNICODE identifier. When you create a DLL, it should include both Unicode and non-Unicode versions of any function that has arguments involving characters or character strings. Thus, EDRLIB.C contains functions named EdrCenterTextA (the ANSI version) and EdrCenterTextW (the wide-character version). EdrCenterTextA is defined as taking a PCSTR (pointer to const string) parameter and EdrCenterTextW is defined as take PCWSTR (pointer to const wide string) parameter. The EdrCenterTextA function explicitly calls lstrlenA, GetTextExtentPoint32A, and TextOutA. EdrCenterTextW explicitly calls lstrlenW, GetTextExtentPoint32W, and TextOutW. The EDRLIB.H file defines EdrCenterText to be EdrCenterTextW if the UNICODE identifier is defined and EdrCenterTextA if it's not. This is just like the Windows header files.
EDRLIB.H also includes a function named DllMain, which takes the place of WinMain in a DLL. This function is used to perform initialization and deinitialization, as I'll discuss later in this chapter. For our purposes, all we need do right now is return TRUE from DllMain.
The only remaining mystery in these two files should be the definition of the EXPORT identifier. Functions in a DLL that are used by an application must be "exported." This doesn't involve any tariffs or commerce regulations, just a few keywords that ensure that the function name is added to EDRLIB.LIB (so that the linker can resolve the function name when linking an application that uses the function) and that the function is visible from EDRLIB.DLL. The EXPORT identifier includes the storage-class specifier __declspec (dllexport) and also extern "C" if the header is being compiled in C++ mode. This prevents the compiler from doing the customary "name mangling" of C++ functions and thus allows the DLL to be used by both C and C++ programs.
Now let's create a second project in the EDRTEST workspace, this one for a program named EDRTEST that will use EDRLIB.DLL. With the EDRTEST workspace loaded in Developer Studio, select New from the File menu. Select the Projects tab in the New dialog box. This time select Win32 Application. Make sure the Add To Current Workspace button is checked. Type in the project name EDRTEST. Again, in the Locations field, erase the second EDRTEST subdirectory.
From the File menu, select New again. Select the Files tab and C++ Source File. Make sure the Add To Project list box shows EDRTEST rather than EDRLIB. Type in the filename EDRTEST.C, and type in the file shown in Figure 21-2. This program uses the EdrCenterText function to center a text string in its client area.
Figure 21-2.
The EDRTEST program.
EDRTEST.C
|
Notice that EDRTEST.C includes the EDRLIB.H header file for the definition of the EdrCenterText function, which it calls during the WM_PAINT message.
Before you compile this program, there are a few things you'll want to do. First, in the Project menu, choose Select Active Project. You should see EDRLIB and EDRTEST. You should select EDRTEST. When you build this workspace, you really want to build the program. Also, in the Project menu, select Dependencies. In the Select Project To Modify list box, choose EDRTEST. In the Dependent On The Following Project(s) list, check EDRLIB. This means that EDRTEST requires the EDRLIB dynamic-link library. Whenever you build EDRTEST, EDRLIB will be rebuilt, if necessary, before compiling and linking EDRTEST.
From the Project menu, select Settings. Pick the General tab. When you select the EDRLIB or EDRTEST projects in the left pane, the Intermediate Files and Output Files shown in the right pane should be the RELEASE directory for the Win32 Release configuration and the DEBUG directory for the Win32 Debug configuration. Change them if they are not. This will ensure that EDRLIB.DLL ends up in the same directory as EDRTEST.EXE and that the program will have no problem using the DLL.
Still in the Project Setting dialog box, click the C/C++ tab. In Preprocessor Definitions, add UNICODE in the Debug configuration, as is customary for the programs in this book.
Now you should be able to build EDRTEST.EXE in both Debug and Release configurations. Developer Studio will first compile and link EDRLIB, if necessary. The RELEASE and DEBUG directories will contain EDRLIB.LIB (the import library) and EDRLIB.DLL. When Developer Studio links EDRTEST, it will include the import library automatically.
It is important to understand that the EdrCenterText code is not included in the EDRTEST.EXE file. Instead, there is simply a reference in the executable to the EDRLIB.DLL file and the EdrCenterText function. EDRTEST.EXE requires EDRLIB.DLL to run.
When you execute EDRTEST.EXE, Windows performs fixups to functions in external library modules. Many of these functions are in the normal Windows dynamic-link libraries. But Windows also sees that the program calls a function from EDRLIB, so Windows loads the EDRLIB.DLL file into memory and calls EDRLIB's initialization routine. The call within EDRTEST to the EdrCenterText function is dynamically linked to the function in EDRLIB.
Including EDRLIB.H in the EDRTEST.C source code file is similar to including WINDOWS.H. Linking with EDRLIB.LIB is similar to linking with the Windows import libraries (such as USER32.LIB). When your program runs, it links with EDLIB.DLL in the same way it links with USER32.DLL. Congratulations! You' ve just created an extension to Windows!
A few words on the subject of dynamic-link libraries before we continue:
First, although I've just categorized a DLL as an extension to Windows, it is also an extension to your application program. Everything the DLL does is done on behalf of the application. For example, all memory it allocates is owned by the application. Any windows it creates are owned by the application. And any files it opens are owned by the application. Multiple applications can use the same DLL simultaneously, but under Windows these applications are shielded from interfering with each other.
Multiple processes can share the same code in a dynamic-link library. However, the data maintained by a DLL is different for each process. Each process has its own address space for any data the DLL uses. Sharing memories among processes requires extra work, as we'll see in the next section.
It's very nice that Windows isolates applications that are using the same dynamic-link libraries at the same time. However, sometimes it's not preferable. You may want to write a DLL that contains some memory that can be shared among various applications, or perhaps among multiple instances of the same application. This involves using shared memory, which is actually a memory-mapped file.
Charles, on my Windows 95_based machine STRPROG simply beeps when I attempt to add a string; nothing appears in the client area (of any number of instances). On my Windows NT_based box, both the release and the debug versions crash ("access violation") when attempting to run a second instance. Let's examine how this works with a program called STRPROG ("string program") and a dynamic-link library called STRLIB ("string library"). STRLIB has three exported functions that STRPROG calls. Just to make this interesting, one of the functions in STRLIB uses a call-back function defined in STRPROG.
STRLIB is a dynamic-link library module that stores and sorts up to 256 character strings. The strings are capitalized and maintained by shared memory in STRLIB. STRPROG can use STRLIB's three functions to add strings, delete strings, and obtain all the current strings from STRLIB. The STRPROG test program has two menu items (Enter and Delete) that invoke dialog boxes to add and delete these strings. STRPROG lists in its client area all the current strings stored by STRLIB.
This function defined in STRLIB adds a string to STRLIB's shared memory:
EXPORT BOOL CALLBACK AddString (pStringIn)
The argument pStringIn is a pointer to the string. The string is capitalized within the AddString function. If an identical string already exists in STRLIB's list of strings, this function adds another copy of the string. AddString returns TRUE (nonzero) if it is successful and FALSE (0) otherwise. A FALSE return value can result if the string has a length of 0, if memory could not be allocated to store the string, or if 256 strings are already stored.
This STRLIB function deletes a string from STRLIB's shared memory:
EXPORT BOOL CALLBACK DeleteString (pStringIn)
Again, the argument pStringIn is a pointer to the string. If more than one string matches, only the first is removed. DeleteString returns TRUE (nonzero) if it is successful and FALSE (0) otherwise. A FALSE return value indicates that the length of the string is 0 or that a matching string could not be found.
This STRLIB function uses a call-back function located in the calling program to enumerate the strings currently stored in STRLIB's shared memory:
EXPORT int CALLBACK GetStrings (pfnGetStrCallBack, pParam)
The call-back function must be defined in the calling program as follows:
EXPORT BOOL CALLBACK GetStrCallBack (PSTR pString, PVOID pParam)
The pfnGetStrCallBack argument to GetStrings points to the call-back function. GetStrings calls GetStrCallBack once for each string or until the call-back function returns FALSE (0). GetStrings returns the number of strings passed to the call-back function. The pParam parameter is a far pointer to programmer-defined data.
Of course, this is all complicated by Unicode or, rather, by the necessity of STRLIB supporting both Unicode and non-Unicode applications. Internally, STRLIB stores all the strings in Unicode. If a non-Unicode program uses STRLIB, the strings are converted to and from Unicode.
The workspace associated with the STRPROG and STRLIB projects is named STRPROG. The files are assembled in the same way as the EDRTEST workspace. Figure 21-3 shows the two files necessary to create the STRLIB.DLL dynamic-link library module.
Figure 21-3.
The STRLIB library.
STRLIB.H
|
STRLIB.C
|
As you can see in STRLIB.C, we've now made some use of DllMain. This function is called when the library first begins and when it terminates. Although I also included a DllMain function in EDRLIB.C, it's not really necessary; a function that performs identically would have been included by the linker by default.
The first parameter to DllMain is the instance handle of the library. If your library uses resources that require an instance handle (such as DialogBox), you should save hInstance as a global variable. The last parameter to DllMain is reserved by the system.
The fdwReason parameter can be one of four values that indicate why Windows is calling the DllMain function. In the following discussion, keep in mind that a single program can be loaded multiple times and run concurrently under Windows. Each time a program is loaded, it is considered a separate process.
A fdwReason value of DLL_PROCESS_ATTACH indicates that the dynamic-link library has been mapped into the address space of a process. This is a cue for the library to do any initialization tasks it requires to service subsequent requests from the process. Such initialization might include memory allocation, for example. During the time that a process is running, DllMain is called with a DLL_PROCESS_ATTACH parameter only once during the lifetime of that process. Any other process using the same DLL causes another call to DllMain with a DLL_PROCESS_ATTACH parameter, but that's on behalf of the new process.
If the initialization is successful, DllMain should return a nonzero value. Returning zero will cause Windows to not run the program.
When fdwReason has a value of DLL_PROCESS_DETACH, it means that the DLL is no longer needed by the process. This provides an opportunity for the library to clean up after itself. Under the 32-bit versions of Windows this is not strictly necessary, but it's a good programming practice.
Similarly, when DllMain is called with an fdwReason parameter of DLL_THREAD_ ATTACH, it means that an attached process has created a new thread. When the thread terminates, Windows calls DllMain with an fdwReason parameter of DLL_THREAD_ DETACH. Be aware that it's possible to get a DLL_THREAD_DETACH call without an earlier DLL_THREAD_ATTACH call if the dynamic-link library is attached to a process after the thread has been created.
The thread still exists when DllMain is called with a parameter of DLL_THREAD_ DETACH. It can even send the thread messages during this process. But it shouldn't use PostMessage because the thread might be gone before the message is retrieved.
Aside from the DllMain function, STRLIB contains only the six functions that it will export to be used by other programs. All these functions are defined as EXPORT. This causes LINK to list them in the STRLIB.LIB import library.
The STRPROG program, shown in Figure 21-4, is fairly straightforward. The two menu options, Enter and Delete, invoke dialog boxes that allow you to enter a string. STRPROG then calls AddString or DeleteString. When the program needs to update its client area, it calls GetStrings and uses the function GetStrCallBack to list the enumerated strings.
Figure 21-4.
The STRPROG program.
STRPROG.C
|
STRPROG.RC (excerpts)
|
RESOURCE.H (excerpts)
|
STRPROG.C includes the STRLIB.H header file; this defines the three functions in STRLIB that STRPROG will use.
What's most interesting about this program becomes evident when you run multiple instances of STRPROG. STRLIB stores the character strings and their pointers in shared memory, which lets all instances of STRPROG share this data. Let's look at how it's done.
Windows erects a wall around the address space of a Win32 process. Normally, data in an address space is private, invisible to other processes. But running multiple instances of STRPROG shows that STRLIB has no trouble sharing its data with all instances of the program. When you add or delete a string in a STRPROG window, the change is immediately reflected in the other windows.
STRLIB shares two types of data: the strings and the pointers to the strings. STRLIB stores each string in a memory-mapped file, making the string visible to all processes. STRLIB keeps the string pointers in a special section of memory that it designates as shared:
#pragma data_seg ("shared")
PWSTR pszStrings [MAX_STRINGS] = {NULL} ;
int iTotal = 0 ;
#pragma data_seg ()
The first #pragma statement creates the data section, here named shared. You can name the section whatever you wish. All initialized variables after the #pragma go into the shared section. The second #pragma statement marks the end of the section. It's important to specifically initialize the variables; otherwise, the compiler puts them in the normal uninitialized section rather than in shared.
The linker has to be told about shared. In the Project Settings dialog box, select the Link tab. In the Project Options field for STRLIB (in both the Release and Debug configurations), include the following linker argument:
/SECTION:shared,RWS
The "RWS" letters indicate that the section has read, write, and shared attributes.
Now all instances of STRPROG see one instance of the strings and pointers. STRPROG uses a broadcast registered message to notify all STRPROG's instances that the contents of STRLIB's data segments have changed. You can easily imagine an enhanced version of STRLIB managing a database that is shared by several instances of the same program or by single instances of different programs.
Visit Microsoft Press for more information on Programming Windows, Fifth Ed.