Chapter 19  Dynamic Link Libraries
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ

Dynamic link libraries (also called DLLs, dynamic libraries, "dynalink"
libraries, or library modules) are one of the most important structural
elements of Windows. Most of the disk files associated with Windows are
either program modules or dynamic link library modules. So far we've been
writing Windows programs; now it's time to take a stab at writing dynamic
link libraries. Many of the principles you've learned in writing programs
are also applicable to writing these libraries, but there are some important
differences.

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 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 when you run LINK to create a Windows .EXE file from
various object (.OBJ) modules and run time library (.LIB) files. Dynamic
linking occurs at run time.

TKERNEL.EXE, USER.EXE, and GDI.EXE files, the various driver files such as
KEYBOARD.DRV, SYSTEM.DRV, and SOUND.DRV, and the video and printer drivers
are all dynamic link libraries. These are libraries that all Windows
programs can use.

The various font resource files with the extension .FON are "resource-only"
dynamic link libraries. They contain no code and no data but instead have
fonts that all Windows programs can use. 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. The
code, data, and resources in a dynamic link library module are shared among
all programs using the module.

Although a dynamic link library module may have any extension (such as .EXE
or .FON), the standard extension in Windows 3 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 using the LoadLibrary 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 these 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, then 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), 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 dynamic link library 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 when you run the linker during static
linking. For example, the normal Microsoft C object library that you link
with small-model Windows programs is SLIBCEW.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 LINK with information
necessary to set up relocation tables within the .EXE file for dynamic
linking. The LIBW.LIB and WIN87EM.LIB files included with the Windows
Software Development Kit are import libraries for Windows functions. If you
call Rectangle in a program, LIBW.LIB tells LINK that this function is in
the GDI.EXE library and has an "ordinal number" of 27. This information goes
into the .EXE file so that Windows can perform dynamic linking with the
GDI.EXE 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 dynamic link library module before
running a program that requires it, the library file must be stored in
either the current directory, a directory accessible through the PATH string
in the MS-DOS environment, the Windows directory, or the SYSTEM subdirectory
of the Windows directory.


Examining Libraries with EXEHDR

Both program files and dynamic link library files are in the New Executable
format. You can get some sense of the workings of dynamic linking by running
the EXEHDR program (included with Microsoft C 6) with the -v (verbose)
parameter on the various files included with Windows and seeing what type of
information the files contain. EXEHDR divides its output into five main
sections, in this order:

  þ   The old MS-DOS .EXE header information

  þ   The New Executable format header information

  þ   A list of the code and data segments in the module

  þ   The exported functions of the module

  þ   Relocation information for each segment

Some of this information won't be present for resource-only library modules
(such as the .FON files). You'll notice that the first line of the second
section begins with either "Module," indicating a program module, or
"Library," indicating a dynamic link library.

If you run EXEHDR on KERNEL.EXE, USER.EXE, or GDI.EXE, you'll find that many
of the names of the exported functions (the fourth section of the output)
are familiar. These are the functions that the library makes available for
other modules to call. Each exported function has an "ordinal number"
associated with it. This is simply a positive number in the "ord" column of
the EXEHDR output.

Both program modules and library modules can call functions that are
exported from other library modules. To the module that makes the call, the
function is said to be "imported." These imported functions show up in the
last section of the EXEHDR display as relocation items, generally in the
form of the library module name followed by a period and the ordinal number
of the function.

When Windows loads a program into memory for execution, it must resolve the
calls that the program makes to imported functions. If the library module
containing these functions has not yet been loaded into memory, Windows
loads at least the data segment and one code segment into memory and calls a
short initialization routine in the library module. Windows also creates
"reload thunks" (a topic discussed in Chapter 7) for the exported functions
in the library. The calls in the program to external functions can then be
resolved by inserting the addresses of the reload thunks in the code segment
of the program.

The second section of the EXEHDR output points up some differences between
programs and libraries. Windows programs have a line that reads:

Data:     NOSHARED

This means that new data segments are created for each instance of the
program. Because a single instance of a Windows library is shared by all
programs that need it, this line is different for Windows libraries. It can
be either:

Data:     SHARED

or:

Data:     NONE

depending on whether the library has one data segment or none.

A Windows program must have at least one data segment (called the automatic
data segment), because this data segment contains the program's stack. In
the EXEHDR output of a program file, you'll see an indication of the stack
size ("Extra Stack Allocation"). However, a library module doesn't have its
own stack, and thus EXEHDR won't show this line. A dynamic link library
always uses the stack of the program that calls the functions in the
library. The absence of a stack for the library module has some significant
implications that I'll cover later in this chapter.

Because each Windows program has its own stack, Windows must switch between
stacks when switching from one program to another. A stack's presence in a
program identifies the program as a distinct process that can receive
messages from Windows. A library module is not a process and does not
receive messages. When a program calls a function in a library module, no
task switch takes place. To Windows, the program making the call to the
library is still running even though code in the library is being executed.



STRPROG AND STRLIB

We'll begin by writing a program with a dedicated dynamic link library and
see how they work together. The program is called STRPROG ("string
program"), and the dynamic link library is called STRLIB ("string library").
STRLIB has three exported functions that STRPROG calls. Just to make this
interesting (and to force you to think about some of the implications), 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 stored in STRLIB's own
data segment. STRPROG can use STRLIB's three functions to add strings,
delete strings, and obtain all the current strings from STRLIB. The program
has two menu items (Enter and Delete) that invoke dialog boxes to add and
delete these strings. STRPROG lists all the current strings stored in
STRLIB's data segment in STRPROG's client area.

This function defined in STRLIB adds a string to STRLIB's data segment:

BOOL FAR PASCAL AddString (lpStringIn)

The parameter lpString is a far pointer to the string. The string is
capitalized within the AddString function. If an identical string already
exists in STRLIB's data segment, 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 data segment:

BOOL FAR PASCAL DeleteString (lpStringIn)

Again, the parameter lpString is a far 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 data segment:

short FAR PASCAL GetStrings (lpfnGetStrCallBack, lpParam)

The call-back function must be defined as follows:

BOOL FAR PASCAL GetStrCallBack (LPSTR lpString, LPSTR lpParam)

The GetStrCallBack function must be exported from the program that calls
GetStrings. The lpfnGetStrCallBack parameter to GetStrings must be obtained
from MakeProcInstance. 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 lpParam parameter is
a far pointer to programmer-defined data. Note that all the pointers passed
as function parameters are far pointers, because STRLIB must reference data
in the caller's data segment.

The STRLIB Library

Figure 19-1 shows the three files necessary to create the STRLIB.DLL dynamic
link library module. STRLIB has a lot in common with the Windows programs
that we've been writing, but there are also some subtle (and some
not-so-subtle) differences.

 STRLIB.MAK

#----------------------
# STRLIB.MAK make file
#----------------------

strlib.dll : strlib.obj strlib.def
     link strlib libentry, strlib.dll /align:16, NUL, /nod sdllcew libw,
strlib
     rc strlib.dll

strlib.obj : strlib.c
     cl -c -ASw -Gsw -Ow -W2 -Zp strlib.c

 STRLIB.C

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

#include 

typedef BOOL FAR PASCAL GETSTR (LPSTR, LPSTR) ;
HANDLE hStrings [256] ;
short  nTotal = 0 ;

int FAR PASCAL LibMain (HANDLE hInstance, WORD wDataSeg, WORD wHeapSize,
                        LPSTR lpszCmdLine)
     {
     if (wHeapSize > 0)
          UnlockData (0) ;

     return 1 ;
     }

BOOL FAR PASCAL AddString (LPSTR lpStringIn)
     {
     HANDLE hString ;
     NPSTR  npString ;
     short  i, nLength, nCompare ;



     if (nTotal == 255)
          return FALSE ;

     if (0 == (nLength = lstrlen (lpStringIn)))
          return FALSE ;

     if (NULL == (hString = LocalAlloc (LHND, 1 + nLength)))
          return FALSE ;

     npString = LocalLock (hString) ;
     lstrcpy (npString, lpStringIn) ;
     AnsiUpper (npString) ;
     LocalUnlock (hString) ;

     for (i = nTotal ; i > 0 ; i--)
          {
          npString = LocalLock (hStrings [i - 1]) ;
          nCompare = lstrcmpi (lpStringIn, npString) ;
          LocalUnlock (hStrings [i - 1]) ;

          if (nCompare > 0)
               {
               hStrings [i] = hString ;
               break ;
               }
          hStrings [i] = hStrings [i - 1] ;
          }

     if (i == 0)
          hStrings [0] = hString ;

     nTotal++ ;
     return TRUE ;
     }

BOOL FAR PASCAL DeleteString (LPSTR lpStringIn)
     {
     NPSTR npString ;
     short i, j, nCompare ;

     if (0 == lstrlen (lpStringIn))
          return FALSE ;

     for (i = 0 ; i < nTotal ; i++)
          {
          npString = LocalLock (hStrings [i]) ;
          nCompare = lstrcmpi (npString, lpStringIn) ;
          LocalUnlock (hStrings [i]) ;
          if (nCompare == 0)
               break ;
          }

     if (i == nTotal)
          return FALSE ;

     for (j = i ; j < nTotal ; j++)
          hStrings [j] = hStrings [j + 1] ;

     nTotal-- ;
     return TRUE ;
     }

short FAR PASCAL GetStrings (GETSTR lpfnGetStrCallBack, LPSTR lpParam)
     {
     BOOL  bReturn ;
     NPSTR npString ;
     short i ;

     for (i = 0 ; i < nTotal ; i++)
          {
          npString = LocalLock (hStrings [i]) ;
          bReturn = (*lpfnGetStrCallBack) ((LPSTR) npString, lpParam) ;
          LocalUnlock (hStrings [i]) ;

          if (bReturn == FALSE)
               return i + 1 ;
          }
     return nTotal ;
     }

 STRLIB.DEF

;-----------------------------------
; STRLIB.DEF module definition file
;-----------------------------------

LIBRARY        STRLIB

DESCRIPTION    'DLL for STRPROG Program (c) Charles Petzold, 1990'
EXETYPE        WINDOWS
CODE           PRELOAD MOVEABLE DISCARDABLE
DATA           PRELOAD MOVEABLE SINGLE
HEAPSIZE       1024
EXPORTS        AddString
               DeleteString
               GetStrings

In addition, you'll need the LIBENTRY.OBJ file included with the Windows
Software Development Kit and stored in the library subdirectory of the SDK
directory. (The

assembly-language source code for this object module is also included in the
SDK.)


Make File Differences

With a close look, you'll notice a couple differences between the STRLIB.MAK
make file and previous make files used for creating Windows programs.

First, the compile line in STRLIB.MAK includes a compiler switch, -ASw, that
isn't required when compiling Windows programs. The -A switch is the flag
for a "customized memory model." The S indicates small model. The w means
that the compiler is to assume that the data segment isn't equal to the
stack segment. This isn't normal, but it's important that you use this for a
library module. The implications of the -ASw switch are discussed in greater
detail later in this chapter.

Second, the link section of the make file creates the STRLIB.DLL file using
the following statement:

link strlib libentry, strlib.dll /align:16, NUL, /nod sdllcew libw, strlib

Note the inclusion of the LIBENTRY.OBJ file in the object module field. In
the field that contains the nondefault libraries, I've listed SDLLCEW.LIB
rather than SLIBCEW.LIB. SLIBCEW.LIB is the C run time library for Windows
programs; SDLLCEW.LIB is the C run time library for dynamic link libraries.
LIBW.LIB is the import library.


The Library Entry Point

The most obvious difference between STRLIB.C and our Windows programs is the
absence of WinMain. Instead, there is a function called LibMain. LibMain is
called from the LIBENTRY.OBJ module. This is required because of the
different ways in which Windows programs and Windows dynamic link libraries
are initialized during startup.

When you link a Windows program with LINK, a function called __astart is
linked into the program. This is the entry point to the program. On entry to
__astart, the CPU registers contain the following information:

ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
BX                 Stack size
CX                 Heap size
DI                 Instance handle
SI                 Previous instance
ES                 Program segment prefix

The start-up code (which comes from SLIBCEW.LIB) performs some
initialization and then calls the WinMain function, which is the perceived
entry point when you program for Windows in C.

For dynamic link libraries, no start-up code is provided in SDLLCEW.LIB.
That's why LIBENTRY.OBJ (or something similar) is required. Windows calls
LibEntry once (when the first program that requires the dynamic link library
is loaded) with the CPU registers set as follows:

ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
DI                   Instance handle
DS                   Library's data segment
CX                   Heap size
ES:SI                Command line

Note the differences between these registers and those for a Windows
program. A register containing the stack size isn't required, because
library modules don't have a stack. A register containing the previous
instance handle isn't required, because library modules can't have multiple
instances. For most uses of libraries, the command-line parameter in ES:SI
isn't used. LibEntry must return nonzero if initialization is successful and
0 if errors are encountered. A failed initialization causes Windows to not
run the program that requires the library.

LIBENTRY initializes the local heap by calling LocalInit and then calls
LibMain, which is in STRLIB.C. The LibMain definition looks like this:

int FAR PASCAL LibMain (HANDLE hInstance, WORD wDataSeg, WORD wHeapSize,
                        LPSTR lpszCmdLine)
     {
     if (wHeapSize > 0)
          UnlockData (0) ;

     return 1 ;
     }

This simply unlocks the data segment of the library (which is locked by the
LocalInit call in LIBENTRY) and returns 1. If you need to do additional
initialization when the library is first loaded, you can do it here.

Note the differences implied here between programs and libraries. On entry
to a program, the start-up code passes control to WinMain, which performs
initialization and then enters a message loop. Multitasking takes place
during GetMessage calls. The program exits the message loop (and WinMain)
only when the program retrieves a WM_QUIT message from the message queue. On
entry to a library, the start-up code must perform initialization and then
return control to Windows with a nonzero value. The rest of the library sits
dormant in memory until another module calls one of the exported functions.

You can also add a "de-initialization" routine to a library; the routine is
called when a program using the library terminates. Information on this can
be found in Chapter 20 of the Guide to Programming book, included in the
Windows Software Development Kit.


The STRLIB Functions

Aside from the LibMain initialization function, STRLIB contains only the
three functions that it will export to be used by other programs. All these
functions are defined as FAR and PASCAL. They must be FAR because they will
be called from the code segment of another module (STRPROG). You aren't
required to define them as PASCAL, however: That's simply a convention used
in other Windows libraries to save a few bytes of space. These three
functions use Windows' local memory allocation functions to allocate space
in the local heap for storing the character strings. Because the AddString
function allocates moveable local blocks, we've essentially given Windows
the job of reorganizing the local heap when necessary to allocate more
memory.


The Library Module Definition File

The module definition file for a library looks somewhat similar to the .DEF
file for a program, but there are also significant differences between the
two. For program modules, the module definition file contains a NAME
statement indicating that the module is a program. For libraries, the first
line is a LIBRARY statement:

LIBRARY   STRLIB

This statement identifies the module as a library. The library CODE
statement is the same as that used for programs:

CODE      PRELOAD MOVEABLE DISCARDABLE

You can also use LOADONCALL, but for a library with a single code segment,
the segment must be loaded into memory so that the initialization routine
can be executed.

For a Windows program, the DATA statement indicates that the data segment is
MULTIPLE, which means that each instance of the program uses the same data
segment. For the STRLIB library, the data segment is marked as SINGLE,
because a library can have only one instance:

DATA      PRELOAD MOVEABLE SINGLE

If the library doesn't include a data segment, the DATA statement is:

DATA      NONE

Notice how these directives relate to the information obtained from EXEHDR.

Because we want STRLIB to use its local heap to store its character strings,
we have to give it a local heap in the HEAPSIZE statement:

HEAPSIZE  1024

This is the initial size of the local heap. Windows can expand the data
segment of the library to accommodate a larger heap if one is needed. Notice
there's no STACKSIZE statement in the module definition file--a library
module doesn't have its own stack.

For a Windows program, the EXPORTS section of the module definition file
lists all far functions within the program that can be called by Windows.
Generally, this list includes at least one window procedure. For a dynamic
link library, the EXPORTS section lists all far functions that can be called
by programs and other library modules. This is the EXPORTS section of
STRLIB.DEF:

EXPORTS        AddString
               DeleteString
               GetStrings


The STRPROG Program

The STRPROG program, shown in Figure 19-2, 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.

 STRPROG.MAK

#-----------------------
# STRPROG.MAK make file
#-----------------------

strprog.exe : strprog.obj strprog.res strprog.def
     link strprog, /align:16, NUL, /nod slibcew libw, strprog
     rc strprog.res

strprog.obj : strprog.c strprog.h
     cl -c -Gsw -Ow -W2 -Zp strprog.c

strprog.res : strprog.rc strprog.h
     rc -r strprog.rc

 STRPROG.C

/*--------------------------------------------------------
   STRPROG.C -- Program using STRLIB dynamic link library
                (c) Charles Petzold, 1990
  --------------------------------------------------------*/

#include 
#include 
#include "strprog.h"

#define MAXLEN 32
#define WM_DATACHANGE WM_USER

typedef struct
     {
     HDC   hdc ;
     short xText ;
     short yText ;
     short xStart ;
     short yStart ;
     short xIncr ;
     short yIncr ;
     short xMax ;
     short yMax ;
     }
     CBPARM ;

BOOL  FAR PASCAL AddString    (LPSTR) ;      // functions in STRLIB
BOOL  FAR PASCAL DeleteString (LPSTR) ;
short FAR PASCAL GetStrings   (FARPROC, CBPARM FAR *) ;

long  FAR PASCAL WndProc (HWND, WORD, WORD, LONG) ;

char  szAppName [] = "StrProg" ;
char  szString  [MAXLEN] ;

int PASCAL WinMain (HANDLE hInstance, HANDLE hPrevInstance,
                    LPSTR lpszCmdLine, int nCmdShow)
     {
     HWND     hwnd ;
     MSG      msg ;
     WNDCLASS wndclass ;

     if (!hPrevInstance)
          {
          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 = GetStockObject (WHITE_BRUSH) ;
          wndclass.lpszMenuName  = szAppName ;
          wndclass.lpszClassName = szAppName ;

          RegisterClass (&wndclass) ;
          }
     hwnd = CreateWindow (szAppName, "DLL Demonstration Program",
                          WS_OVERLAPPEDWINDOW,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          NULL, NULL, hInstance, NULL) ;

     ShowWindow (hwnd, nCmdShow) ;
     UpdateWindow (hwnd) ;

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

BOOL FAR PASCAL DlgProc (HWND hDlg, WORD message, WORD wParam, LONG lParam)
     {
     switch (message)
          {
          case WM_INITDIALOG :
               SendDlgItemMessage (hDlg, IDD_STRING, EM_LIMITTEXT,
                                   MAXLEN - 1, 0L) ;
               return TRUE ;

          case WM_COMMAND :
               switch (wParam)
                    {
                    case IDOK :
                         GetDlgItemText (hDlg, IDD_STRING, szString, MAXLEN)
;
                         EndDialog (hDlg, TRUE) ;
                         return TRUE ;

                    case IDCANCEL :
                         EndDialog (hDlg, FALSE) ;
                         return TRUE ;
                    }
          }
     return FALSE ;
     }

BOOL FAR PASCAL EnumCallBack (HWND hwnd, LONG lParam)
     {
     char szClassName [16] ;

     GetClassName (hwnd, szClassName, sizeof szClassName) ;
     if (0 == strcmp (szClassName, szAppName))
          SendMessage (hwnd, WM_DATACHANGE, 0, 0L) ;

     return TRUE ;
     }

BOOL FAR PASCAL GetStrCallBack (LPSTR lpString, CBPARM FAR *lpcbp)
     {
     TextOut (lpcbp->hdc, lpcbp->xText, lpcbp->yText,
              lpString, lstrlen (lpString)) ;

     if ((lpcbp->yText += lpcbp->yIncr) > lpcbp->yMax)
          {
          lpcbp->yText = lpcbp->yStart ;
          if ((lpcbp->xText += lpcbp->xIncr) > lpcbp->xMax)
               return FALSE ;
          }
     return TRUE ;
     }

long FAR PASCAL WndProc (HWND hwnd, WORD message, WORD wParam, LONG lParam)
     {
     static FARPROC lpfnDlgProc, lpfnGetStrCallBack, lpfnEnumCallBack ;
     static HANDLE  hInst ;
     static short   cxChar, cyChar, cxClient, cyClient ;
     CBPARM         cbparam ;
     HDC            hdc ;
     PAINTSTRUCT    ps ;
     TEXTMETRIC     tm ;

     switch (message)
          {
          case WM_CREATE :
               hInst = ((LPCREATESTRUCT) lParam)->hInstance ;

               lpfnDlgProc        = MakeProcInstance (DlgProc, hInst) ;
               lpfnGetStrCallBack = MakeProcInstance (GetStrCallBack, hInst)
;
               lpfnEnumCallBack   = MakeProcInstance (EnumCallBack, hInst) ;

               hdc = GetDC (hwnd) ;
               GetTextMetrics (hdc, &tm) ;
               cxChar = tm.tmAveCharWidth ;
               cyChar = tm.tmHeight + tm.tmExternalLeading ;
               ReleaseDC (hwnd, hdc) ;

               return 0 ;

          case WM_COMMAND :
               switch (wParam)
                    {
                    case IDM_ENTER :
                         if (DialogBox (hInst, "EnterDlg", hwnd,
lpfnDlgProc))
                              {
                              if (AddString (szString))
                                   EnumWindows (lpfnEnumCallBack, 0L) ;
                              else
                                   MessageBeep (0) ;
                              }
                         break ;

                    case IDM_DELETE :
                         if (DialogBox (hInst, "DeleteDlg", hwnd,
lpfnDlgProc))
                              {
                              if (DeleteString (szString))
                                   EnumWindows (lpfnEnumCallBack, 0L) ;
                              else
                                   MessageBeep (0) ;
                              }
                         break ;
                    }
               return 0 ;

          case WM_SIZE :
               cxClient = LOWORD (lParam) ;
               cyClient = HIWORD (lParam) ;
               return 0 ;

          case WM_DATACHANGE :
               InvalidateRect (hwnd, NULL, TRUE) ;
               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 * MAXLEN ;
               cbparam.yIncr = cyChar ;
               cbparam.xMax  = cbparam.xIncr * (1 + cxClient /
cbparam.xIncr) ;
               cbparam.yMax  = cyChar * (cyClient / cyChar - 1) ;

               GetStrings (lpfnGetStrCallBack, &cbparam) ;

               EndPaint (hwnd, &ps) ;
               return 0 ;

          case WM_DESTROY :
               PostQuitMessage (0) ;
               return 0 ;
          }
     return DefWindowProc (hwnd, message, wParam, lParam) ;
     }

 STRPROG.RC

/*----------------------------
   STRPROG.RC resource script
  ----------------------------*/

#include 
#include "strprog.h"

StrProg MENU
     {
     MENUITEM  "&Enter!",  IDM_ENTER
     MENUITEM  "&Delete!", IDM_DELETE
     }

EnterDlg DIALOG 24, 24, 190, 44
     STYLE WS_POPUP | WS_DLGFRAME
     {
     LTEXT          "&Enter:",  0,            4,  8,  24,  8
     EDITTEXT                   IDD_STRING,  32,  6, 154, 12
     DEFPUSHBUTTON  "Ok",       IDOK,        44, 24,  32, 14
     PUSHBUTTON     "Cancel",   IDCANCEL,   114, 24,  32, 14
     }

DeleteDlg DIALOG 24, 24, 190, 44
     STYLE WS_POPUP | WS_DLGFRAME
     {
     LTEXT          "&Delete:", 0,            4,  8,  28,  8
     EDITTEXT                   IDD_STRING,  36,  6, 150, 12
     DEFPUSHBUTTON  "Ok",       IDOK,        44, 24,  32, 14
     PUSHBUTTON     "Cancel",   IDCANCEL,   114, 24,  32, 14
     }

 STRPROG.H

/*-----------------------
   STRPROG.H header file
  -----------------------*/

#define IDM_ENTER      1
#define IDM_DELETE     2
#define IDD_STRING  0x10

 STRPROG.DEF

;------------------------------------
; STRPROG.DEF module definition file
;------------------------------------

NAME           STRPROG

DESCRIPTION    'Program using STRLIB DLL (c) Charles Petzold, 1990'
EXETYPE        WINDOWS
STUB           'WINSTUB.EXE'
CODE           PRELOAD MOVEABLE DISCARDABLE
DATA           PRELOAD MOVEABLE MULTIPLE
HEAPSIZE       1024
STACKSIZE      8192
EXPORTS        WndProc
               DlgProc
               GetStrCallBack
               EnumCallBack
IMPORTS        STRLIB.AddString
               STRLIB.DeleteString
               STRLIB.GetStrings

Near the top of the STRPROG.C source code file are declarations of the three
functions in STRLIB that STRPROG will call:

BOOL  FAR PASCAL AddString    (LPSTR) ;
BOOL  FAR PASCAL DeleteString (LPSTR) ;
short FAR PASCAL GetStrings   (FARPROC, CBPARM FAR *) ;

If you plan to use library functions in several different programs, you'll
want to put the declarations in a header file. This header file will be
similar to (although I hope not as long as) WINDOWS.H.

These three functions are also listed in the IMPORTS section of STRPROG's
module definition file:

IMPORTS   STRLIB.AddString
          STRLIB.DeleteString
          STRLIB.GetStrings

These correspond to the three functions in the EXPORTS section of
STRLIB.DEF. The IMPORTS section directs LINK to add information to
STRPROG.EXE that allows Win- dows to dynamically link STRPROG's calls to
these functions with the actual function routines in STRLIB.DLL. The EXPORTS
section in STRLIB.DEF makes the functions in STRLIB.DLL available to other
modules. The IMPORTS section in STRPROG.DEF indicates the
module--STRLIB--and the functions in STRLIB that STRPROG requires.


Running STRPROG

Once you've created STRLIB.DLL and STRPROG.EXE, you're ready to run STRPROG.
Before you do so, be sure that STRLIB.DLL is in the current directory or a
directory that is listed in the PATH string of the MS-DOS environment.
Windows must be able to load STRLIB.DLL when you execute STRPROG. If Windows
can't find STRLIB.DLL, it will display a message box asking you to put the
STRLIB.DLL disk in drive A.

When you execute STRPROG.EXE, Windows performs fixups to functions in
external library modules. Many of these functions are in the normal KERNEL,
USER, and GDI library modules. But Windows also sees that the program calls
three functions from STRLIB, so Windows loads the STRLIB.DLL file into
memory, creates reload thunks for the three functions, and calls STRLIB's
initialization routine. The far calls within STRPROG to these three
functions are dynamically linked with the reload thunks that branch to
functions in STRLIB. You can then use STRPROG to add and delete strings from
STRLIB's internal table. STRPROG's client area shows the strings currently
in the table.

The calls from STRPROG to the AddString, DeleteString, and GetStrings
functions in STRLIB are very efficient and have almost no overhead except
for the reload thunk. In fact, the link between STRPROG and STRLIB is as
efficient as if the three functions in STRLIB were simply in another
moveable code segment in STRPROG. So what? you say. Why do I have to make
this a dynamic link library? Can't I include these three routines in
STRPROG.EXE?

Well, you could. In one sense, STRLIB is nothing more than an extension of
STRPROG. However, you may be interested to see what happens when you execute
a second instance of STRPROG. Because only one instance of STRLIB is loaded
for both instances of the program, and because STRLIB uses its own local
heap to store the character strings, all instances of STRPROG essentially
share this data. (The EnumCallBack function in STRPROG serves to notify all
STRPROG's instances that the contents of STRLIB's data segments have
changed. EnumWindows causes Windows to call EnumCallBack with handles to all
parent windows. EnumCallBack then checks to see if the class name of each
window equals "StrProg"--if it does, the function sends the window a
privately defined WM_DATACHANGE message.) And 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.


Far Function Prologs

In the discussion of memory management in Chapter 7, I went into great
detail about how Windows moves code and data segments in memory and handles
multiple instances of programs. Some of that discussion centered on the
prolog that the compiler adds to far functions.

When Windows loads a code segment into memory, it alters the function prolog
of all exported far functions in the segment. This table shows the results
of that process:


Nonexported               Exported Function        Exported Function
Far Function              in a Program             in a Library
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
PUSH DS                   NOP                      MOV  AX, xxxx

POP  AX                   NOP

NOP                       NOP

INC  BP                   INC  BP                  INC  BP

PUSH BP                   PUSH BP                  PUSH BP

MOV  BP, SP               MOV  BP, SP              MOV  BP, SP

PUSH DS                   PUSH DS                  PUSH DS

MOV  DS, AX               MOV  DS, AX              MOV  DS, AX



These three prologs differ in the way that DS (the data segment address
register) is set on entry to the function. In each case, the original value
of DS is saved in the function prolog and restored in the function epilog.

The nonexported far function simply sets AX equal to DS and then DS equal to
AX. This does nothing.

For an exported function in a program, Windows inserts NOPs in the first 2
bytes of the function. The resultant prolog then sets DS equal to AX. This
prolog requires that AX be set to the data segment of the particular
instance of the program. By itself, the function is incomplete. You must
call MakeProcInstance for these exported functions so that Windows builds an
"instance thunk" that sets AX equal to the data segment address of the
instance. (The only case in which you don't need to call MakeProcInstance
for an exported function is for a window procedure specified in a window
class structure.)

The exported function in a library is somewhat simpler. Because the library
can have only one instance, Windows can simply insert a 3-byte instruction
that sets AX equal to the data segment address of the library. Thus, you
don't need to use MakeProcInstance with exported far functions in library
modules. When a program calls a far function exported from a library module,
this prolog sets the data segment equal to the library's data segment. The
library function can then use its own data segment. It continues to use the
stack segment of the program that called it.


The Use of Call-Back Functions

Now that we know what these function prologs look like, let's examine what
happens when STRPROG calls the GetStrings function. GetStrings requires a
call-back function in STRPROG called GetStrCallBack.

Because GetStrCallBack is exported, Windows inserts NOPs in the first 2
bytes of the function when STRPROG's code segment is loaded into memory.
While processing the WM_CREATE message, STRPROG calls MakeProcInstance for
this function:

lpfnGetStrCallBack = MakeProcInstance (GetStrCallBack, hInst) ;

On return from MakeProcInstance, the variable lpfnGetStrCallBack points to
code that looks like this:

MOV AX, yyyy
JMP GetStrCallBack

where yyyy is the data segment address of this instance of STRPROG.

STRPROG calls GetStrings to update its client area:

GetStrings (lpfnGetStrCallBack, &cbparam) ;

The GetStrings function is in STRLIB. The parameter cbparam is a structure
containing information that GetStrings simply passes back to GetStrCallBack,
which then uses the information to display the strings in the client area.

In STRLIB, the prolog to GetStrings sets AX equal to the data segment of the
library, saves the current value of DS (the data segment of STRPROG), and
sets DS equal to AX. Now the function can use its own data segment to obtain
the strings currently stored. When it obtains a string, it calls the
call-back function passed as a parameter to GetStrings:

bReturn = (*lpfnGetStrCallBack) ((LPSTR) npString, lpParam) ;

This actually calls the instance thunk for GetStrCallBack set up by
MakeProcInstance. The instance thunk sets AX equal to STRPROG's data
segment. The function prolog saves the current value of DS (the data segment
of STRLIB) and sets DS equal to AX. Now GetStrCallBack is using STRPROG's
own data segment and can process the string. When GetStrCallBack returns
control to GetStrings, the function epilog restores the original value of
DS, which is STRLIB's data segment. GetStrings is ready to find the next
string.

When GetStrings is finished, the function epilog restores the value of DS to
STRPROG's data segment. As you can see, although control bounces back and
forth between STRPROG and STRLIB, each module is always using its own data
segment. During this entire process, however, the stack segment never
changes. It is always STRPROG's stack segment. For code in STRPROG, this
situation is just fine. For code in STRLIB, it can pose some problems.



THE DS != SS ISSUE

The segmented architecture of the Intel 8086 family of microprocessors has
been giving programmers grief for many years now. But nowhere does segmented
architecture cause more problems than in Windows libraries. If you skipped
the first half of Chapter 7, thinking that you'd never need to know about
segmented architecture and the intricacies of near and far pointers, now is
the time to read it. And even if you've read it carefully, you might still
benefit from this quick review.

The Intel 8086 family of microprocessors operating in real mode can address
1 megabyte of memory. This memory is addressed by a combination of a 16-bit
segment  address and a 16-bit offset address. The 16-bit segment address
marks the beginning of a 64-KB area of memory. The offset address is
relative to the beginning of the segment. In protected mode, the segment
address references a 24-bit base address in a descriptor table. The offset
address is added to this.

The 8086-family microprocessors have four registers that contain segment
addresses: the code segment register (CS), the data segment register (DS),
the stack segment register (SS), and the extra segment register (ES). The
instruction pointer (IP) always addresses code within the code segment. The
stack pointer (SP) always addresses the stack within the stack segment.
Registers that address data can do so relative to any of the four current
segments. When programming in Microsoft C, 16-bit pointers that specify only
an offset address are called near or short pointers, and 32-bit pointers
that contain both a segment address and an offset address are called far or
long pointers.

In C, all variables defined as outside functions (on the external level) and
all variables defined as static within functions are stored in static
memory. The compiler uses near pointers relative to the 8086 data segment
(DS) to address variables stored in static memory.

All parameters to functions and all variables within functions that are not
defined as static are stored on the stack. The compiler uses near pointers
relative to the 8086 stack segment (SS) to address the stack.

When you use a near pointer in a C program, the pointer can reference a
variable either in static memory or on the stack. The compiler has no way to
determine whether the near pointer is an offset to DS or SS. For this
reason, C programs are normally con- structed to use the same segment for
data and the stack. Simply put, DS == SS. This is almost required for a C
implementation on 8086-family microprocessors, because C does not
differentiate between pointers to static variables and pointers to stack
variables.

Let's take an example. In a small-model or medium-model program, you can use
the normal C strlen function to find the length of a string. The parameter
to strlen is a near pointer to the string:

wLength = strlen (pString) ;

The string itself could be stored either in static memory or on the stack.
You could define pString like this:

char *pString = "This is a string" ;

In this case, the string "This is a string" is stored in static memory, and
the near pointer is relative to the beginning of the data segment. However,
you could do something like this within a function:

char szString [20] ;
[other program lines]
wLength = strlen (szString) ;

In this case, the szString array takes up 20 bytes on the stack. When you
refer to szString, you're actually referring to a near pointer relative to
the stack segment.

How does the strlen function know whether the near pointer is an offset in
the stack segment or in the data segment? It doesn't. If you take a look at
the assembly-language code for strlen in the SLIBCEW.LIB library, this is
what you'll find:

_strlen   NEAR PROC

          PUSH BP        ; Prologue
          MOV  BP, SP
          MOV  DX, DI    ; Save DI

          MOV  AX, DS    ; Set ES equal to DS
          POP  ES, AX

          MOV  DI,[BP+4] ; Get DI ptr off stack
          XOR  AX, AX
          MOV  CX, -1
          REPNZ SCASB    ; Search for zero in ES:DI
          NOT  CX        ; Calculate length
          DEC  CX
          XCHG AX, CX

          MOV  DI, DX    ; Restore DI

          MOV  SP, BP    ; Epilogue
          POP  BP
          RET

_strlen   ENDP

The strlen function assumes that the near pointer is an offset in the data
segment. It sets ES equal to DS using this code:

MOV  AX, DS
MOV  ES, AX

It then uses ES to scan the string for a terminating 0. To write a strlen
function that would work with a near pointer in the stack segment, you would
need to replace these lines with:

MOV  AX, SS
MOV  ES, AX

But you've never had to worry about this little problem in Windows programs,
because DS equals SS.

Windows dynamic libraries are another story. The data segment is the
library's own data segment, but the stack segment is the stack of the
caller. That is, DS != SS. If you call strlen in a Windows library for a
string that is stored on the stack, the function won't work correctly,
because the strlen function assumes that the near pointer is relative to the
data  segment. When you first realize the implications of this, you're
likely to assume that programming Windows dynamic libraries is very
difficult. Let's just say that it's not quite as carefree a process as
writing a Windows program, but the job certainly isn't impossible. After
all, the bulk of Windows consists of dynamic libraries--the KERNEL, USER,
and GDI modules.

At one time, the recommended practice was to use no normal C library
functions within a dynamic library and to instead write your own functions.
This restriction has now been loosened, and information is available to let
you use C library functions intelligently. The conventions followed in the
strlen function hold in most of the functions in the normal C library
distributed with the Microsoft C Compiler: Most functions that accept
pointers assume that the pointer is relative to the data segment; these
functions do not assume that DS is equal to SS. Any C run time function that
cannot be used in a dynamic link library is not included in SDLLCEW.LIB.

When you compile C source code for a small-model Windows library, include
the switch -ASw, and for a medium-model Windows library, include the switch
-AMw. These switches tell the compiler to assume that DS is not equal to SS.
Nothing very magical happens here. The primary purpose of these switches is
to alert you to possible problems in your code. For instance, within a
function, you might define an array and a pointer:

int  array [3] ;
int  *ptr ;

If you say:

array [0] = array [1] + array [2] ;

the compiler uses SS to reference the elements of array, because the
compiler knows that array is on the stack. However, you might have code like
this:

ptr = array ;
*ptr = *(ptr + 1) + *(ptr + 2) ;

In the first statement, the compiler assigns the near address of array
(which happens to be referenced from the stack segment) to the near pointer
ptr. But when generating code for the second line, the compiler assumes that
ptr references a variable in the data segment. That's wrong.

If you have a program with a construction like this and you compile with the
-ASw switch and a warning level of 1 or 2, you'll get a warning message for
the assignment of the array address to ptr:

warning C4058: address of automatic (local) variable taken, DS != SS

You can translate this message as: "You've assigned the address of a local
variable on the stack to a near pointer. Future use of this near pointer
will involve the data segment.  You've specified that you want the compiler
to assume DS is not equal to SS. This assignment statement contradicts your
intentions."

You can fix this by making ptr a far pointer, as follows:

int array [3] ;
int far *ptr ;

ptr = array ;

Now the compiler assigns the full 32-bit address of array (the stack segment
and the offset) to ptr. You're safe in using ptr. Or you can make array a
static variable:

static int array [3] ;
int *ptr ;

ptr = array ;

The compiler now assumes that ptr references data in the data segment; this
is correct, because array is defined as static.

Don't assume that you'll always be alerted to problems like this. Here's
another example. You have a function that sums up the first 100 elements of
an integer array:

int sumup (int array [])
     {
     int i, n = 0 ;

     for (i = 0 ; i < 100 ; i++)
          n += array [i] ;
     return n ;
     }

If you call this function and the array happens to be on the stack, you're
in trouble:

int array [100] ;
[other program lines]
sumup (array) ;          /* A problem here */

This won't even generate a warning message, but it's obviously incorrect.
How do you get around it? Use far pointers. Define the function like this:

int sumup (int far array [])

and call the function like this:

sumup ((int far *) array) ;

Or make array a static variable:

static int array [100] ;
[other program lines]
sumup (array) ;          /* No problem here */

This is a better solution for an array of this size anyway, because it
avoids putting 200 bytes on the calling program's stack.

You can avoid many of the DS != SS problems simply by not using stack
variables and instead defining all your local variables as static. If you
want to use stack variables for some items to save space in the data
segment, you should avoid using the stack either for arrays or for any
variables that require pointers. Finally, if you use pointers with stack
variables, make them far pointers.

The parameters to a library function are always on the stack. If you need to
use a pointer to reference a function parameter, use a far pointer.


OTHER LIBRARY RESTRICTIONS

The start-up code that is added to a Windows program during linking with
LINK uses the registers passed to the program on entry, together with some
DOS function calls, to set various global variables in the program's data
segment. These variables allow programs access to the DOS environment and to
other information. This start-up code isn't present in Windows libraries.
For this reason, you can't use the getenv or putenv functions in libraries,
nor can you use the following global variables defined in Microsoft C:

ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
_dosvermajor                 _dosverminor
_osmajor                     _osminor
_psp                         environ
__argc                       __argv

I mentioned earlier that a dynamic library module doesn't receive messages.
However, a library module can call GetMessage and PeekMessage. The messages
the library pulls from the queue with these functions are actually messages
for the program that called the library function. In general, the library
works on behalf of the program calling it, a rule that holds for most
Windows functions that a library calls. The obvious exceptions are local
memory allocation functions. As you saw with the STRLIB library, these
functions use the library's local heap.

A library can allocate global memory for the program instance calling the
library. The global memory blocks are automatically freed when the program
instance terminates.

A dynamic library can load resources (such as icons, strings, and bitmaps)
either from the library file or from the file of the program that calls the
library. The functions that load resources require an instance handle. If
the library uses its own instance handle (which is passed to the library
during initialization), then the library can obtain resources from its own
file. To load resources from the calling program's .EXE file, the library
function requires the instance handle of the program calling the function.

Registering window classes and creating windows in a library can be a little
tricky. Both the window class structure and the CreateWindow call require an
instance handle. Although you can use the library module's instance handle
in creating the window class and the window, the window messages still go
through the message queue of the program calling the library when the
library creates the window. If you must create window classes and windows
within a library, then it's probably best to use the calling program's
instance handle.

Because messages for modal dialog boxes are retrieved outside a program's
message loop, you can create a modal dialog box in a library by calling
DialogBox. The instance handle can be that of the library, and the
hwndParent parameter to DialogBox can be set to NULL.


DIFFERENT METHODS FOR SPECIFYING LINKS

The module definition files for STRPROG and STRLIB show only one of several
possible methods for listing functions to be exported from one module and
imported in another. STRLIB's EXPORTS section looks like this:

EXPORTS   AddString
          DeleteString
          GetStrings

STRPROG's IMPORTS section refers to both the library module and the function
names:

IMPORTS   STRLIB.AddString
          STRLIB.DeleteString
          STRLIB.GetStrings

Here's another method: The module definition file for STRLIB can assign
"ordinals" to each of the functions. These are simply unique positive
integers preceded by @:

EXPORTS   AddString      @1
          DeleteString   @2
          GetStrings     @3

STRPROG's IMPORTS section then references these numbers:

IMPORTS   AddString    = STRLIB.1
          DeleteString = STRLIB.2
          GetStrings   = STRLIB.3

This method gives STRPROG a smaller .EXE file, because the file simply
stores the ordinal numbers rather than the names of all the functions. For a
large number of imported functions, this method provides a significant
reduction in .EXE size. It's a little trickier to use than the first method,
because you have to be sure you get the numbers right.

You can also use function names in the program that are different from those
in the library. For instance, suppose that in STRPROG you use the names
AddStr, DelStr, and  GetStr instead of AddString, DeleteString, and
GetStrings. You can reference these aliases to the real function names in
the IMPORTS section:

IMPORTS   AddStr = STRLIB.AddString
          DelStr = STRLIB.DeleteString
          GetStr = STRLIB.GetStrings

Or if the module definition file for STRLIB defines ordinals for each of the
functions, the IMPORTS section will look like this:

IMPORTS   AddStr = STRLIB.1
          DelStr = STRLIB.2
          GetStr = STRLIB.3

Even if you don't explicitly specify ordinal numbers for the exported
functions in the library module definition file, LINK assigns ordinal
numbers to the functions. You can determine these ordinal numbers by running
EXEHDR on the library module.


USING IMPORT LIBRARIES

I implied earlier that in writing your own library module, you're adding an
extension to Windows--an extension that serves you in a manner similar to
that of the standard KERNEL, USER, and GDI library modules. So, you ask, why
do I have to list all the names of imported functions from my own dynamic
libraries when I don't have to specifically import the KERNEL, USER, and GDI
functions that I use? Well, in the early days of Windows programming (long
before the introduction of the product), programmers had to do precisely
that. They ended up with module definition files that looked like this:

IMPORTS   USER.RegisterClass
          USER.CreateWindow
[etc, etc, etc.]

This process was simplified greatly by the use of "import libraries." Import
libraries are much like object libraries in that LINK uses them to resolve
function calls within a program. But the import library contains no (or very
little) code, only a reference that reconciles the function name you use in
your program with the library module containing this function and the actual
function name. This is exactly what you do in the IMPORTS section of a .DEF
file. An import library for STRLIB would allow LINK to know that a function
call to AddString is really an imported function from STRLIB called
AddString or an imported function from STRLIB with an ordinal number of 1.

When you link a Windows program or dynamic link library, the LIBW.LIB import
library reconciles all the normal Windows functions you use in the program
(mostly from KERNEL, USER, and GDI) and the ordinal numbers. That's why this
import library must be specified in the library field of the LINK command
line when linking. Think of it this way: LINK has to resolve all calls that
a program makes to external functions. It can do this in one of three ways:
extract the function itself from an object library, get a reference to a
library module name and function name (or ordinal) from an import library,
or get a library module name and function name (or ordinal) from the IMPORTS
section of the module definition file.

You can create an import library for a dynamic library module by running the
IMPLIB program included with the Windows Software Development Kit. The
syntax is:

IMPLIB libname.LIB libname.DEF

IMPLIB looks only at the EXPORTS section of the module definition file. It
creates a file with the extension .LIB. After the import library is created,
you can add normal object modules to the .LIB file using the LIB.EXE program
included with the Microsoft C Compiler.

Figure 19-3 shows a revised make file and module definition file for STRLIB;
Figure 19-4 on the following page shows a revised make file and module
definition file for STRPROG. The new STRLIB make file creates an import
library called STRLIB.LIB. In the STRPROG make file, this import library
must be specified in the library field of the LINK command line. The new
STRPROG.DEF file requires no IMPORTS section.

 STRLIB.MAK

#----------------------
# STRLIB.MAK make file
#----------------------

strlib.dll : strlib.obj strlib.def
     link strlib libentry, strlib.dll /align:16, NUL, /nod sdllcew libw,
strlib
     rc strlib.dll
     implib strlib.lib strlib.def

strlib.obj : strlib.c
     cl -c -ASw -Gsw -Ow -W2 -Zp strlib.c

 STRLIB.DEF

;-----------------------------------
; STRLIB.DEF module definition file
;-----------------------------------

LIBRARY        STRLIB



DESCRIPTION    'DLL for STRPROG Program (c) Charles Petzold, 1990'
EXETYPE        WINDOWS
CODE           PRELOAD MOVEABLE DISCARDABLE
DATA           PRELOAD MOVEABLE SINGLE
HEAPSIZE       1024
EXPORTS        AddString      @1
               DeleteString   @2
               GetStrings     @3

 STRPROG.MAK

#-----------------------
# STRPROG.MAK make file
#-----------------------

strprog.exe : strprog.obj strprog.res strprog.def
     link strprog, /align:16, NUL, /nod slibcew libw strlib, strprog
     rc strprog.res

strprog.obj : strprog.c strprog.h
     cl -c -Gsw -Ow -W2 -Zp strprog.c

strprog.res : strprog.rc strprog.h
     rc -r strprog.rc

 STRPROG.DEF

;------------------------------------
; STRPROG.DEF module definition file
;------------------------------------

NAME           STRPROG

DESCRIPTION    'Program using STRLIB DLL (c) Charles Petzold, 1990'
EXETYPE        WINDOWS
STUB           'WINSTUB.EXE'
CODE           PRELOAD MOVEABLE DISCARDABLE
DATA           PRELOAD MOVEABLE MULTIPLE
HEAPSIZE       1024
STACKSIZE      8192
EXPORTS        WndProc
               DlgProc
               GetStrCallBack
               EnumCallBack




INTERCEPTING WINDOWS FUNCTION CALLS

One interesting use of dynamic libraries is in debugging. For instance, you
might want to write a dynamic link library that extensively checks the
parameters your program is passing to the normal Windows functions. Such a
library for checking parameters to the GDI functions might be called
CHECKGDI; a typical function in CHECKGDI.C would look something like this:

int xRectangle (hdc, xLeft, yTop, xRight, yBottom)
     HDC   hdc ;
     short xLeft, yTop, xRight, yBottom ;
     {
     BOOL  bError = FALSE ;
     int   iCode ;

          /* check parameters, set bError to FALSE and iCode
               to an error code if errors are encountered */

     if (bError)
          FatalExit (iCode) ;

     return Rectangle (hdc, xLeft, yTop, xRight, yBottom) ;
     }

You can give this function any name you want, as long as it isn't the same
as the name of a real Windows function. (I've called it xRectangle.) The
FatalExit function causes the debugging version of Windows to display a
stack trace on a terminal attached to the COM1 port.

The EXPORTS section of CHECKGDI.DEF lists all these checking functions but
gives each of them an external name that is the name of the actual Windows
function:

EXPORTS   Rectangle = xRectangle @1
          Ellipse   = xEllipse   @2
          LineTo    = xLineTo    @3
[and so forth]

You compile and link this CHECKGDI library in the same way as STRLIB. The
creation of the CHECKGDI.LIB import library looks like this:

implib checkgdi.lib checkgdi.def

Now if you have a program in development that calls normal GDI functions,
you can do parameter checks during run time by linking the program (I'll
assume the program is called MYPROG) as shown below:

link myprog, /align:16, NUL, /nod /noe checkgdi slibcew libw, myprog

Because you have included the CHECKGDI.LIB library in the library field of
LINK, any calls in your program to Rectangle, Ellipse, LineTo, and so forth
will actually reference the  xRectangle, xEllipse, and xLineTo functions in
the CHECKGDI.EXE library. In CHECKGDI the functions perform the checks you
want and then call the actual GDI functions.

Once you're done debugging, you can make the program call the regular
Windows GDI functions simply by deleting MYPROG.EXE and relinking like this
without the CHECKGDI.LIB import library:

link myprog, /align:16, NUL, slibcew libw, myprog

Now LINK will use LIBW.LIB to reconcile your GDI calls with the functions in
the normal GDI module. Because this approach requires no changes to your
program source code, it is a clean way of inserting debugging code between
your calls to Windows functions and the actual functions.


DYNAMIC LINKING WITHOUT IMPORTS

Rather than have Windows perform dynamic linking when your program is first
loaded into memory, you can link a program with a library module while the
program is running. We used this technique in Chapter 15 when we had to call
the DeviceMode function in a printer driver module.

For instance, you would normally call the Rectangle function like this:

Rectangle (hdc, xLeft, yTop, xRight, yBottom) ;

You can also call Rectangle by first defining two variables:

HANDLE  hLibrary ;
FARPROC lpfnRectangle ;

Now you set hLibrary to the handle of the library and lpfnRectangle to the
address of the Rectangle function:

hLibrary = LoadLibrary ("GDI.EXE") ;
lpfnRectangle = GetProcAddress (hLibrary, MAKEINTRESOURCE (27)) ;

The LoadLibrary function returns an MS-DOS error code (less than 32) if the
library file can't be found. In GetProcAddress, the second parameter is a
number (27) that you convert to a far pointer to a string by setting the
segment address equal to 0; 27 is the ordinal number of Rectangle obtained
from the EXEHDR listing of GDI.EXE. Now you can call the function and then
free the library:

(*lpfnRectangle) (hdc, xLeft, yTop, xRight, yBottom) ;
FreeLibrary (hLibrary) ;

For libraries in which the module definition file doesn't define ordinals
for the exported functions, you can use the function name in the
GetProcAddress call:

lpfnFunction = GetProcAddress (hLibrary, "FunctionName") ;

Don't use this method for linking to modules that use ordinal numbers for
the exported functions. The names of the functions in a library remain
resident in memory only if the module definition file doesn't include
ordinals or if the keyword RESIDENTNAME is used with the functions in the
EXPORTS statement.

Although this technique doesn't make much sense for the Rectangle function,
it will definitely come in handy. You need to use it when you don't know the
name of the library module until run time, as is the case with the
DeviceMode function in the printer drivers.

The code above uses the LoadLibrary and FreeLibrary functions. Windows
maintains "reference counts" for all library modules. LoadLibrary causes the
reference count to be incremented. The reference count is also incremented
when Windows loads any program that uses the library. FreeLibrary causes the
reference count to be decremented, as does the termination of an instance of
a program that uses this library. When the reference count is 0, Windows can
discard the library from memory, because the library is no longer needed.


RESOURCE-ONLY LIBRARIES

Any function in a dynamic link library that Windows programs or other
libraries can use must be exported. However, a dynamic link library need not
contain any exported functions. What would such a DLL contain? The answer is
resources.

Let's say you're working on a Windows application that requires a number of
bitmaps. Normally, you would list these in the resource script of the
program and load them into memory with the LoadBitmap function. But perhaps
you want to create several sets of bitmaps, each set customized for one of
the major display adapters used with Windows. It would make most sense to
store these different sets of bitmaps in different files, because a user
would need only one set of bitmaps on the fixed disk. These files are
resource-only libraries.

Figure 19-5 shows how to create a resource-only library file called
BITLIB.DLL that contains nine bitmaps. The BITLIB.RC file lists all the
separate bitmap files and assigns each one a number. To create BITLIB.DLL,
you need nine bitmaps named BITMAP1.BMP, BITMAP2.BMP, and so forth. You can
create these bitmaps in SDK Paint.

 BITLIB.MAK

#----------------------
# BITLIB.MAK make file
#----------------------

bitlib.dll : bitlib.obj bitlib.def bitlib.res
     link bitlib libentry, bitlib.dll /align:16, NUL, /nod sdllcew libw,
bitlib
     rc bitlib.res bitlib.dll



bitlib.obj : bitlib.c
     cl -c -ASw -Gsw -Ow -W2 -Zp bitlib.c

bitlib.res : bitlib.rc
     rc -r bitlib.rc

 BITLIB.C

/*--------------------------------------------------------------
   BITLIB.C -- Code entry point for BITLIB dynamic link library
               (c) Charles Petzold,  1990
  --------------------------------------------------------------*/
#include 

int FAR PASCAL LibMain (HANDLE hInstance, WORD wDataSeg, WORD wHeapSize,
                        LPSTR lpszCmdLine)
     {
     if (wHeapSize > 0)
          UnlockData (0) ;

     return 1 ;
     }

 BITLIB.RC

/*---------------------------
   BITLIB.RC resource script
  ---------------------------*/

1 BITMAP bitmap1.bmp
2 BITMAP bitmap2.bmp
3 BITMAP bitmap3.bmp
4 BITMAP bitmap4.bmp
5 BITMAP bitmap5.bmp
6 BITMAP bitmap6.bmp
7 BITMAP bitmap7.bmp
8 BITMAP bitmap8.bmp
9 BITMAP bitmap9.bmp

 BITLIB.DEF

;-----------------------------------
; BITLIB.DEF module definition file
;-----------------------------------

LIBRARY        BITLIB

DESCRIPTION    'Bitmap DLL for SHOWBIT.EXE'
EXETYPE        WINDOWS
CODE           PRELOAD MOVEABLE DISCARDABLE
DATA           PRELOAD MOVEABLE SINGLE

The SHOWBIT program, shown in Figure 19-6, reads the bitmap resources from
BITLIB and copies them to the clipboard. You can cycle through the bitmaps
by pressing a key on the keyboard.

 SHOWBIT.MAK

#-----------------------
# SHOWBIT.MAK make file
#-----------------------

showbit.exe : showbit.obj showbit.def
     link showbit, /align:16, NUL, /nod slibcew libw, showbit
     rc showbit.exe

showbit.obj : showbit.c
     cl -c -Gsw -Ow -W2 -Zp showbit.c

 SHOWBIT.C

/*-----------------------------------------------------------
   SHOWBIT.C -- Shows bitmaps in BITLIB dynamic link library
                (c) Charles Petzold, 1990
  -----------------------------------------------------------*/

#include 

long FAR PASCAL WndProc (HWND, WORD, WORD, LONG) ;



int PASCAL WinMain (HANDLE hInstance, HANDLE hPrevInstance,
                    LPSTR lpszCmdLine, int nCmdShow)
     {
     static   char szAppName [] = "ShowBit" ;
     HWND     hwnd ;
     MSG      msg ;
     WNDCLASS wndclass ;

     if (!hPrevInstance)
          {
          wndclass.style         = CS_HREDRAW | CS_VREDRAW ;
          wndclass.lpfnWndProc   = WndProc ;
          wndclass.cbClsExtra    = 0 ;
          wndclass.cbWndExtra    = 0 ;
          wndclass.hInstance     = hInstance ;
          wndclass.hIcon         = NULL ;
          wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;
          wndclass.hbrBackground = GetStockObject (WHITE_BRUSH) ;
          wndclass.lpszMenuName  = NULL ;
          wndclass.lpszClassName = szAppName ;

          RegisterClass (&wndclass) ;
          }

     hwnd = CreateWindow (szAppName, "Show Bitmaps from BITLIB (Press Key)",
                          WS_OVERLAPPEDWINDOW,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          NULL, NULL, hInstance, NULL) ;

     ShowWindow (hwnd, nCmdShow) ;
     UpdateWindow (hwnd) ;

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

void DrawBitmap (HDC hdc, short xStart, short yStart, HBITMAP hBitmap)
     {
     BITMAP bm ;
     DWORD  dwSize ;
     HDC    hMemDC ;
     POINT  pt ;
     hMemDC = CreateCompatibleDC (hdc) ;
     SelectObject (hMemDC, hBitmap) ;
     GetObject (hBitmap, sizeof (BITMAP), (LPSTR) &bm) ;
     pt.x = bm.bmWidth ;
     pt.y = bm.bmHeight ;

     BitBlt (hdc, xStart, yStart, pt.x, pt.y, hMemDC, 0, 0, SRCCOPY) ;

     DeleteDC (hMemDC) ;
     }

long FAR PASCAL WndProc (HWND hwnd, WORD message, WORD wParam, LONG lParam)
     {
     static HANDLE hLibrary ;
     static short  nCurrent = 1 ;
     HANDLE        hBitmap ;
     HDC           hdc ;
     PAINTSTRUCT   ps ;

     switch (message)
          {
          case WM_CREATE :
               if ((hLibrary = LoadLibrary ("BITLIB.DLL")) < 32)
                    MessageBeep (0) ;

               return 0 ;

          case WM_CHAR :
               if (hLibrary >= 32)
                    {
                    nCurrent ++ ;
                    InvalidateRect (hwnd, NULL, TRUE) ;
                    }
               return 0 ;

          case WM_PAINT :
               hdc = BeginPaint (hwnd, &ps) ;

               if (hLibrary >= 32)
                    {
                    if (NULL == (hBitmap = LoadBitmap (hLibrary,
                                             MAKEINTRESOURCE (nCurrent))))
                         {
                         nCurrent = 1 ;
                         hBitmap = LoadBitmap (hLibrary,
                                             MAKEINTRESOURCE (nCurrent)) ;
                         }
                    if (hBitmap)
                         DrawBitmap (hdc, 0, 0, hBitmap) ;
                    }

               EndPaint (hwnd, &ps) ;
               return 0 ;

          case WM_DESTROY :
               if (hLibrary >= 32)
                    FreeLibrary (hLibrary) ;

               PostQuitMessage (0) ;
               return 0 ;
          }
     return DefWindowProc (hwnd, message, wParam, lParam) ;
     }

 SHOWBIT.DEF

;------------------------------------
; SHOWBIT.DEF module definition file
;------------------------------------

NAME           SHOWBIT

DESCRIPTION    'Program to show bitmaps from BITLIB (c) Charles Petzold,
1990'
EXETYPE        WINDOWS
STUB           'WINSTUB.EXE'
CODE           PRELOAD MOVEABLE DISCARDABLE
DATA           PRELOAD MOVEABLE MULTIPLE
HEAPSIZE       1024
STACKSIZE      8192
EXPORTS        WndProc

During processing of the WM_CREATE message, SHOWBIT gets a handle to
BITLIB.DLL:

if ((hLibrary = LoadLibrary ("BITLIB.DLL")) < 32)
     MessageBeep (0) ;

If BITLIB.DLL isn't in the current directory or in a directory accessible
through the PATH string in the MS-DOS environment, Windows displays a
message box asking the user to insert the BITLIB.DLL disk in drive A. If the
user presses Cancel, LoadLibrary returns an MS-DOS error code (less than
32), in which case SHOWBIT simply beeps.

SHOWBIT can obtain a handle to a bitmap by calling LoadBitmap with the
library handle and the number of the bitmap:

hBitmap = LoadBitmap (hLibrary, MAKEINTRESOURCE (nCurrent)) ;

This returns an error if the bitmap corresponding to the number nCurrent
isn't valid or if not enough memory exists to load the bitmap.

While processing the WM_DESTROY message, SHOWBIT frees the library:

FreeLibrary (hLibrary) ;

When the last instance of SHOWBIT terminates, the reference count of
BITLIB.DLL drops to 0 and the memory it occupies is freed. As you can see,
this is a simple method of implementing a "clip art" program that could load
precreated bitmaps (or metafiles) into the clipboard for use by other
programs.