VC++ 6.0 ebook chapter index
homepage aranna.altervista.org
Free counters!

VC++ 6.0 ebook chapter index<./a>
homepage aranna.altervista.org
Free counters!

Library Basics

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 DLLs, 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.

Library: One Word, Many Meanings

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.)

A Simple DLL

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 Visual C++ we haven't examined yet. Visual C++ 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 Visual C++, 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, Visual C++ 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. Visual C++ will create a project file EDRLIB.DSP and (if you've selected the Export Makefile option on the Build tab of the Tools Options dialog box) 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.H header file

  ----------------------*/

#ifdef __cplusplus
#define EXPORT extern "C" __declspec (dllexport)
#else
#define EXPORT __declspec (dllexport)
#endif

EXPORT BOOL CALLBACK EdrCenterTextA (HDC, PRECT, PCSTR) ;
EXPORT BOOL CALLBACK EdrCenterTextW (HDC, PRECT, PCWSTR) ;

#ifdef UNICODE
#define EdrCenterText EdrCenterTextW
#else
#define EdrCenterText EdrCenterTextA
#endif

EDRLIB.C

/*-------------------------------------------------
   EDRLIB.C -- Easy Drawing Routine Library module

               (c) Charles Petzold, 1998
  -------------------------------------------------*/

#include  windows.h>
#include "edrlib.h"

int WINAPI DllMain (HINSTANCE hInstance, DWORD fdwReason, PVOID pvReserved)
{
     return TRUE ;
}

EXPORT BOOL CALLBACK EdrCenterTextA (HDC hdc, PRECT prc, PCSTR pString)
{
     int  iLength ;
     SIZE size ;

     iLength = lstrlenA (pString) ;

     GetTextExtentPoint32A (hdc, pString, iLength, &size) ;

     return TextOutA (hdc, (prc->right - prc->left - size.cx) / 2,
                           (prc->bottom - prc->top - size.cy) / 2,
                      pString, iLength) ;
}

EXPORT BOOL CALLBACK EdrCenterTextW (HDC hdc, PRECT prc, PCWSTR pString)
{
     int  iLength ;
     SIZE size ;

     iLength = lstrlenW (pString) ;

     GetTextExtentPoint32W (hdc, pString, iLength, &size) ;

     return TextOutW (hdc, (prc->right - prc->left - size.cx) / 2,
                           (prc->bottom - prc->top - size.cy) / 2,
                      pString, iLength) ;
}

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.

The Library Entry and Exit Point

The DllMain function is called when the library first begins and when it terminates. 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 often 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.

The Test Program

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 Visual C++, 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. Press OK, and select An Empty Project from the next dialog box. Press Finish.

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

/*--------------------------------------------------------
   EDRTEST.C -- Program using EDRLIB dynamic-link library

                (c) Charles Petzold, 1998
  --------------------------------------------------------*/

#include <windows.h>
#include "edrlib.h"

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    PSTR szCmdLine, int iCmdShow)
{
     static TCHAR szAppName[] = TEXT ("StrProg") ;
     HWND         hwnd ;
     MSG          msg ;
     WNDCLASS     wndclass ;

     wndclass.style         = CS_HREDRAW | CS_VREDRAW ;
     wndclass.lpfnWndProc   = WndProc ;
     wndclass.cbClsExtra    = 0 ;
     wndclass.cbWndExtra    = 0 ;
     wndclass.hInstance     = hInstance ;
     wndclass.hIcon         = LoadIcon (NULL, IDI_APPLICATION) ;
     wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;
     wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
     wndclass.lpszMenuName  = NULL ;
     wndclass.lpszClassName = szAppName ;
     
     if (!RegisterClass (&wndclass))
     {
          MessageBox (NULL, TEXT ("This program requires Windows NT!"),
                      szAppName, MB_ICONERROR) ;
          return 0 ;
     }
     
     hwnd = CreateWindow (szAppName, TEXT ("DLL Demonstration Program"),
                          WS_OVERLAPPEDWINDOW,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          NULL, NULL, hInstance, NULL) ;

     ShowWindow (hwnd, iCmdShow) ;
     UpdateWindow (hwnd) ;
     
     while (GetMessage (&msg, NULL, 0, 0))
     {
          TranslateMessage (&msg) ;
          DispatchMessage (&msg) ;
     }
     return msg.wParam ;
}

LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
     HDC         hdc ;

     PAINTSTRUCT ps ;
     RECT        rect ;
     
     switch (message)
     {
     case WM_PAINT:
          hdc = BeginPaint (hwnd, &ps) ;
          
          GetClientRect (hwnd, &rect) ;
          
          EdrCenterText (hdc, &rect, 
                         TEXT ("This string was displayed by a DLL")) ;
          
          EndPaint (hwnd, &ps) ;
          return 0 ;
          
     case WM_DESTROY:
          PostQuitMessage (0) ;
          return 0 ;
     }
     return DefWindowProc (hwnd, message, wParam, lParam) ;
}

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 and with EDRTEST selected, 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. Visual C++ 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.

Shared Memory in DLLs

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.

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. Like EDRLIB, it has A and W versions of all its functions. Internally, STRLIB stores all the strings in Unicode. If a non-Unicode program uses STRLIB (that is, the program calls AddStringA, DeleteStringA, and GetStringsA) 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.H header file
  ----------------------*/


#ifdef __cplusplus
#define EXPORT extern "C" __declspec (dllexport)
#else
#define EXPORT __declspec (dllexport)
#endif

     // The maximum number of strings STRLIB will store and their lengths

#define MAX_STRINGS 256
#define MAX_LENGTH 63

     // The callback function type definition uses generic strings

typedef BOOL (CALLBACK * GETSTRCB) (PCTSTR, PVOID) ;

     // Each function has ANSI and Unicode versions

EXPORT BOOL CALLBACK AddStringA (PCSTR) ;
EXPORT BOOL CALLBACK AddStringW (PCWSTR) ;

EXPORT BOOL CALLBACK DeleteStringA (PCSTR) ;
EXPORT BOOL CALLBACK DeleteStringW (PCWSTR) ;

EXPORT int CALLBACK GetStringsA (GETSTRCB, PVOID) ;
EXPORT int CALLBACK GetStringsW (GETSTRCB, PVOID) ;
 
     // Use the correct version depending on the UNICODE identifier

#ifdef UNICODE
#define AddString    AddStringW
#define DeleteString DeleteStringW
#define GetStrings   GetStringsW
#else
#define AddString    AddStringA
#define DeleteString DeleteStringA
#define GetStrings   GetStringsA
#endif

STRLIB.C

/*------------------------------------------------
   STRLIB.C -- Library module for STRPROG program
               (c) Charles Petzold, 1998

------------------------------------------------*/
#include <windows.h>
#include <wchar.h>       // for wide-character string functions
#include "strlib.h"

     // shared memory section (requires /SECTION:shared,RWS in link options)

#pragma data_seg ("shared")
int   iTotal = 0 ;
WCHAR szStrings [MAX_STRINGS][MAX_LENGTH + 1] = { ‘\0’ } ;
#pragma data_seg ()

#pragma comment(linker,"/SECTION:shared,RWS")

int WINAPI DllMain (HINSTANCE hInstance, DWORD fdwReason, PVOID pvReserved)
{
     return TRUE ;
}

EXPORT BOOL CALLBACK AddStringA (PCSTR pStringIn)
{ BOOL bReturn ; int iLength ; PWSTR pWideStr ; // Convert string to Unicode and call AddStringW iLength = MultiByteToWideChar (CP_ACP, 0, pStringIn, -1, NULL, 0) ; pWideStr = malloc (iLength) ; MultiByteToWideChar (CP_ACP, 0, pStringIn, -1, pWideStr, iLength) ; bReturn = AddStringW (pWideStr) ; free (pWideStr) ; return bReturn ; } EXPORT BOOL CALLBACK AddStringW (PCWSTR pStringIn) {
PWSTR pString ; int i, iLength ; if (iTotal == MAX_STRINGS - 1) return FALSE ; if ((iLength = wcslen (pStringIn)) == 0) return FALSE ; // Allocate memory for storing string, copy it, convert to upper case
pString = malloc (sizeof (WCHAR) * (1 + iLength)) ; wcscpy (pString, pStringIn) ; _wcsupr (pString) ; // Alphabetize the strings for (i = iTotal ; i > 0 ; i—) { if (wcscmp (pString, szStrings[i - 1]) >= 0) break ; wcscpy (szStrings[i], szStrings[i - 1]) ;
} wcscpy (szStrings[i], pString) ; iTotal++ ; free (pString) ; return TRUE ; } EXPORT BOOL CALLBACK DeleteStringA (PCSTR pStringIn)
{ BOOL bReturn ; int iLength ; PWSTR pWideStr ; // Convert string to Unicode and call DeleteStringW iLength = MultiByteToWideChar (CP_ACP, 0, pStringIn, -1, NULL, 0) ; pWideStr = malloc (iLength) ; MultiByteToWideChar (CP_ACP, 0, pStringIn, -1, pWideStr, iLength) ; bReturn = DeleteStringW (pWideStr) ; free (pWideStr) ; return bReturn ; } EXPORT BOOL CALLBACK DeleteStringW (PCWSTR pStringIn) { int i, j ; if (0 == wcslen (pStringIn)) return FALSE ; for (i = 0 ; i < iTotal ; i++) { if (_wcsicmp (szStrings[i], pStringIn) == 0) break ; } // If given string not in list, return without taking action
if (i == iTotal) return FALSE ; // Else adjust list downward for (j = i ; j < iTotal ; j++) wcscpy (szStrings[j], szStrings[j + 1]) ; szStrings[iTotal—][0] = ‘\0’ ; return TRUE ; } EXPORT int CALLBACK GetStringsA (GETSTRCB pfnGetStrCallBack, PVOID pParam) { BOOL bReturn ; int i, iLength ; PSTR pAnsiStr ; for (i = 0 ; i < iTotal ; i++) { // Convert string from Unicode iLength = WideCharToMultiByte (CP_ACP, 0, szStrings[i], -1, NULL, 0, NULL, NULL) ; pAnsiStr = malloc (iLength) ; WideCharToMultiByte (CP_ACP, 0, szStrings[i], -1, pAnsiStr, iLength, NULL, NULL) ; // Call callback function
bReturn = pfnGetStrCallBack (pAnsiStr, pParam) ; if (bReturn == FALSE) return i + 1 ; free (pAnsiStr) ; } return iTotal ; } EXPORT int CALLBACK GetStringsW (GETSTRCB pfnGetStrCallBack, PVOID pParam) { BOOL bReturn ; int i ; for (i = 0 ; i < iTotal ; i++) { bReturn = pfnGetStrCallBack (szStrings[i], pParam) ; if (bReturn == FALSE) return i + 1 ; } return iTotal ; }

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

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.C -- Program using STRLIB dynamic-link library
                (c) Charles Petzold, 1998

  --------------------------------------------------------*/
#include <windows.h>
#include "strlib.h"
#include "resource.h"

typedef struct
{
     HDC hdc ;
     int xText ;
     int yText ;
     int xStart ;
     int yStart ;
     int xIncr ;
     int yIncr ;
     int xMax ;
     int yMax ;
}
CBPARAM ;

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;

TCHAR szAppName [] = TEXT ("StrProg") ;
TCHAR szString [MAX_LENGTH + 1] ;

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    PSTR szCmdLine, int iCmdShow)
{
     HWND     hwnd ;
     MSG      msg ;
     WNDCLASS wndclass ;
     
     wndclass.style         = CS_HREDRAW | CS_VREDRAW ;
     wndclass.lpfnWndProc   = WndProc ;
     wndclass.cbClsExtra    = 0 ;
     wndclass.cbWndExtra    = 0 ;
     wndclass.hInstance     = hInstance ;
     wndclass.hIcon         = LoadIcon (NULL, IDI_APPLICATION) ;
     wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;
     wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
     wndclass.lpszMenuName  = szAppName ;
     wndclass.lpszClassName = szAppName ;
     
     if (!RegisterClass (&wndclass))
     {
          MessageBox (NULL, TEXT ("This program requires Windows NT!"),
                      szAppName, MB_ICONERROR) ;
          return 0 ;
     }

     hwnd = CreateWindow (szAppName, TEXT ("DLL Demonstration Program"),
                          WS_OVERLAPPEDWINDOW,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          NULL, NULL, hInstance, NULL) ;
     
     ShowWindow (hwnd, iCmdShow) ;
     UpdateWindow (hwnd) ;
     
     while (GetMessage (&msg, NULL, 0, 0))
     {
          TranslateMessage (&msg) ;
          DispatchMessage (&msg) ;
     }
     return msg.wParam ;
}

BOOL CALLBACK DlgProc (HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
     switch (message)
     {
     case WM_INITDIALOG:
          SendDlgItemMessage (hDlg, IDC_STRING, EM_LIMITTEXT, MAX_LENGTH, 0) ;
          return TRUE ;
          
     case WM_COMMAND:
          switch (wParam)
          {
          case IDOK:
               GetDlgItemText (hDlg, IDC_STRING, szString, MAX_LENGTH) ;
               EndDialog (hDlg, TRUE) ;
               return TRUE ;
               
          case IDCANCEL:
               EndDialog (hDlg, FALSE) ;
               return TRUE ;
          }
     }
     return FALSE ;
}

BOOL CALLBACK GetStrCallBack (PTSTR pString, CBPARAM * pcbp)
{
     TextOut (pcbp->hdc, pcbp->xText, pcbp->yText,
              pString, lstrlen (pString)) ;
     
     if ((pcbp->yText += pcbp->yIncr) > pcbp->yMax)
     {
          pcbp->yText = pcbp->yStart ;
          if ((pcbp->xText += pcbp->xIncr) > pcbp->xMax)
               return FALSE ;
     }
     return TRUE ;
}

LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
     static HINSTANCE  hInst ;
     static int        cxChar, cyChar, cxClient, cyClient ;
     static UINT       iDataChangeMsg ;
     CBPARAM           cbparam ;
     HDC               hdc ;
     PAINTSTRUCT       ps ;
     TEXTMETRIC        tm ;
     
     switch (message)
     {
     case WM_CREATE:
          hInst = ((LPCREATESTRUCT) lParam)->hInstance ;
          hdc   = GetDC (hwnd) ;
          GetTextMetrics (hdc, &tm) ;
          cxChar = (int) tm.tmAveCharWidth ;
          cyChar = (int) (tm.tmHeight + tm.tmExternalLeading) ;
          ReleaseDC (hwnd, hdc) ;

               // Register message for notifying instances of data changes

          iDataChangeMsg = RegisterWindowMessage (TEXT ("StrProgDataChange")) ;
          return 0 ;
          
     case WM_COMMAND:
          switch (wParam)
          {
          case IDM_ENTER:
               if (DialogBox (hInst, TEXT ("EnterDlg"), hwnd, &DlgProc))
               {
                    if (AddString (szString))
                         PostMessage (HWND_BROADCAST, iDataChangeMsg, 0, 0) ;
                    else
                         MessageBeep (0) ;
               }
               break ;
               
          case IDM_DELETE:
               if (DialogBox (hInst, TEXT ("DeleteDlg"), hwnd, &DlgProc))
               {
                    if (DeleteString (szString))
                         PostMessage (HWND_BROADCAST, iDataChangeMsg, 0, 0) ;
                    else
                         MessageBeep (0) ;
               }
               break ;
          }
          return 0 ;
          
     case WM_SIZE:
          cxClient = (int) LOWORD (lParam) ;
          cyClient = (int) HIWORD (lParam) ;
          return 0 ;

     case WM_PAINT:
          hdc = BeginPaint (hwnd, &ps) ;
               
          cbparam.hdc   = hdc ;
          cbparam.xText = cbparam.xStart = cxChar ;
          cbparam.yText = cbparam.yStart = cyChar ;
          cbparam.xIncr = cxChar * MAX_LENGTH ;
          cbparam.yIncr = cyChar ;
          cbparam.xMax  = cbparam.xIncr * (1 + cxClient / cbparam.xIncr) ;
          cbparam.yMax  = cyChar * (cyClient / cyChar - 1) ;
               
          GetStrings ((GETSTRCB) GetStrCallBack, (PVOID) &cbparam) ;
              
          EndPaint (hwnd, &ps) ;
          return 0 ;
               
     case WM_DESTROY:
          PostQuitMessage (0) ;
          return 0 ;

     default:
          if (message == iDataChangeMsg)
               InvalidateRect (hwnd, NULL, TRUE) ;
          break ;
     }
     return DefWindowProc (hwnd, message, wParam, lParam) ;

STRPROG.RC (excerpts)

//Microsoft Developer Studio generated resource script.


#include "resource.h"
#include "afxres.h"

/////////////////////////////////////////////////////////////////////////////
// Dialog

ENTERDLG DIALOG DISCARDABLE  20, 20, 186, 47
STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "Enter"
FONT 8, "MS Sans Serif"
BEGIN
    LTEXT           "&Enter:",IDC_STATIC,7,7,26,9
    EDITTEXT        IDC_STRING,31,7,148,12,ES_AUTOHSCROLL
    DEFPUSHBUTTON   "OK",IDOK,32,26,50,14
    PUSHBUTTON      "Cancel",IDCANCEL,104,26,50,14
END

DELETEDLG DIALOG DISCARDABLE  20, 20, 186, 47
STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "Delete"
FONT 8, "MS Sans Serif"
BEGIN
    LTEXT           "&Delete:",IDC_STATIC,7,7,26,9
    EDITTEXT        IDC_STRING,31,7,148,12,ES_AUTOHSCROLL
    DEFPUSHBUTTON   "OK",IDOK,32,26,50,14
    PUSHBUTTON      "Cancel",IDCANCEL,104,26,50,14
END
/////////////////////////////////////////////////////////////////////////////
// Menu

STRPROG MENU DISCARDABLE 
BEGIN
    MENUITEM "&Enter!",                     IDM_ENTER
    MENUITEM "&Delete!",                    IDM_DELETE
END

RESOURCE.H (excerpts)

// Microsoft Developer Studio generated include file.
// Used by StrProg.rc


#define IDC_STRING                      1000
#define IDM_ENTER                       40001
#define IDM_DELETE                      40002
#define IDC_STATIC                      -1

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.

Sharing Data Among STRPROG Instances

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 variables among all its instances: an array of strings and an integer indicating the number of valid strings stored. STRLIB keeps these two variables in a special section of memory that it designates as shared:

#pragma data_seg ("shared")
int   iTotal = 0 ;
WCHAR szStrings [MAX_STRINGS][MAX_LENGTH + 1] = { ‘\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. Or you can specify the linker option directly in the DLL source code, as is done in STRLIB.C:

#pragma comment(linker,"/SECTION:shared,RWS")

The shared memory section allows the iTotal variable and the szStrings array of strings to be shared among all instances of STRLIB. Because MAX_STRINGS is equal to 256 and MAX_LENGTH is equal to 63, the shared memory section is 32,772 bytes in length—the 4 bytes required for the iTotal variable and 128 bytes each for the 256 pointers.

Using a shared memory section is probably the easiest way to share data among multiple applications. If you need to dynamically allocate shared memory space, you should look into the use of file mapping objects, documented at /Platform SDK/Windows Base Services/Interprocess Communication/File Mapping.