Dynamic Link Libraries Under Microsoft Windows

DLLs let several applications share the same code space and, as Margaret and Mark show, they allow you to write smaller, faster, and more portable applications.


March 01, 1989
URL:http://www.drdobbs.com/windows/dynamic-link-libraries-under-microsoft-w/184408100

Figure 1


Copyright © 1989, Dr. Dobb's Journal

Figure 2


Copyright © 1989, Dr. Dobb's Journal

MAR89: DYNAMIC LINK LIBRARIES UNDER MICROSOFT WINDOWS

DYNAMIC LINK LIBRARIES UNDER MICROSOFT WINDOWS

Sharing code through DLLs is one way to deal efficiently with excess baggage in a multitasking environment

Margaret Johnson and Mark Solinski

Margaret K. Johnson is a software engineer at Beckman Instruments. She can be reached at 2500 Harbor Blvd., M/S D-33-B, Fullerton, CA 92634. CompuServe ID 74706,2325. Mark Solinski is director of technical services for the Whitewater Group. He can be reached at 906 University Place, Evanston, IL 60201.


With so many people considering the benefits of OS/2, dynamic link libraries (also known as dynalinks or DLLs) have become a hot topic. Few people, however, realize that DLLs have been available to programmers for more than two years as part of the Microsoft Windows operating environment. As a matter of fact, most of the Microsoft Windows environment is implemented as DLLs. This article discusses the virtues of DLLs and also provides a clear example of a DLL that can be integrated into any Windows' application. It is our hope that developers currently creating run-time libraries for MS-DOS, with plans for future products under OS/2, would consider DLLs Microsoft for windows as one of the platforms for their products.

DLLs Versus Run-Time Libraries

In the MS-DOS world, code is compiled and linked with external libraries or object modules to create an executable file. Frequently, routines in libraries are generic, consisting of often-used functions--for example, the run-time library included with the compiler. Each executable that calls a routine from the library contains a copy of the code for that fu ction. Under typical DOS single-tasking conditions, and assuming it doesn't matter how large the executable file is, run-time libraries are just fine.

However, having each application carry around a copy of common code is inefficient in a multitasking system such as Windows. Windows is an operating environment that sits on top of DOS and creates a nonpreemptive, event-driven multitasking environment for its applications. This common code "baggage" can cause particularly severe problems because Windows, DOS, and your application have to share the infamous 640K of memory (assuming your system has no expanded memory and the himem.sys driver is not installed).

Windows relieves some of this congestion by allowing multiple instances of the same application to share the same code space, but, unfortunately, if multiple applications are loaded, code is not shared. For example, if each application calls the same function to smooth data from a run-time library, then Windows will allocate code space for the smooth function for each application. As anyone who has run multiple applications under Windows knows, memory is to Windows what water is to the desert--a precious resource that should not be wasted.

If a function resides in a DLL, then only one copy will be loaded no matter how many applications call it. This approach keeps the application's executable small because the common code is not included in each application. Also, the code is dynamically loaded at run time, so as long as the parameters passed in a function call do not change, you can change the innards of a function in a DLL at any time without having to recompile and relink the application. Thus, judicious use of DLLs lets you customize and upgrade your applications without producing whole new versions.

Loading the library at run time provides other intriguing opportunities. Some packages-for example, Actor from Whitewater, Excel from Microsoft, and SQL Windows from Gupta --can integrate DLL functions within their programs. This ability allows developers to use their own warm and comfy functions in extremely powerful existing applications. But wait, there's more! DLLs need not contain any code or data at all. They can also be completely made of system resources such as fonts, bit maps, icons, or cursors --a great way to allow customization of applications without having to distribute source code, a must for library developers.

Problems with Creating DLLs

As is usually the case in life, additional benefits are not realized without some pain, and this is certainly true of DLLs. There are two big differences between code in a DLL and code in a standard run-time library under MS-DOS. The first one is on the tip of the tongue of anyone who has ventured into the DLL domain - the infamous SS!=DS issue. The second difference concerns the use of global variables.

Normally, when C code is compiled, it is assumed the data segment (DS) is the same as the stack segment (SS). This assumption is valid when passing the address of a parameter to a function for the small (one code segment and one data segment) and medium (multiple code segments and one data segment) models, because only the 16-bit offsets of the addresses are passed.

DLLs, however, have their own data segment and use the stack of the caller. In this case, the stack segment does not equal the data segment (SS!=DS) and the 16-bit offsets are not valid addresses because all automatic variables - parameters and local variables--are kept on the stack and all others--globaland static variables --are contained in the data segment. This means that an address passed to a function must contain both the segment and offset as 32-bit far pointers, and as a result, some of the standard C run-time library functions cannot be used. These functions are listed in an appendix of the Microsoft Software Development Kit (SDK) for Windows. In general, any of the buffered I/O functions fprintf, fscanf, sscanf, and soon), exec functions (for example, execlp, execv), and spawn functions (for example, spawnl, spawnv) are off limits.

The second problem occurs because of our disposition and familiarity with writing single-tasking software. Because these programs need not be reentrant, it is common to sprinkle code generously with global variables. This approach can be a source of grief to the person who wishes to convert existing code to work in a DLL. Although code is not reentrant in Windows, the data is reentrant because DLLs have only one data segment, which is shared by all applications. You cannot expect a variable that is set by one function of the DLL, to contain that value when retrieved in another function.

An example of this might be a DLL that contains routines for plotting the values of variables. Usually a sequence of calls consisting of initialization, setting plot variables, graphing the plot, and cleanup would be used to perform this function. Putting the plotting variables in global memory assumes each application that makes this series of calls has its own data area to hold these variables. If two applications (A and B) coexist and application A makes calls to set the plotting variables, and then B does the same before A can make a call to plot the variables, the variables used by the DLL will be set to B's preferences, not A's.

Most of these problems are inherent in multitasking and shared-code systems and are not particular "quirks" of Microsoft Windows. The solutions to these problems, implemented through careful software design, will certainly be of benefit to developers who wish to port their applications and libraries to multitasking environments, including OS/ 2. The example that follows illustrates how a simple, extendable help facility can be added to any application, and can be customized without recompiling and relinking the main application.

Help Library

The help library we have created follows the convention put forth in the Microsoft Windows Application Style Guide. All applications that follow these guidelines will have the same visual appearance and present a consistent user interface (keyboard, mouse, screen, and other ports). Bells should be ringing inside your head when you hear phrases such as "All (your) applications . . .", "same visual. . .," and "consistent user. . . ." These are usually some of the criteria for producing function libraries. Dynamic libraries are an even better choice because sometimes standards change and these changes can be incorporated into a new library and become immediately available to all the applications that used the old library without your having to recompile and relink.

This discussion illustrates another feature of DLLs -- the ability to isolate customization code into different libraries. Thus, developers need only publish function specifications for the routines in the library. Microsoft uses this technique for its hardware drivers. Printer manufacturers, for instance, can write DLL libraries that take advantage of the best features of their printers and provide the same "functional" appearance to Microsoft Windows. The helplib.c file (Listing One, page 82) defines the "public" protocol for the help library. The two functions we create are Topics() and Screen(). Topics() displays a list of available help topics; Screen() displays the text relating to a specific topic. There is a one-to-one mapping between a screen and a topic. This mapping is enforced by using the same token name in the resource file, helplib.rc (Listing Two, page 82), for the topic strings and the user-defined resources. For example, the token for the "caveats" topic string, CAVEATS, is the same as the token for the name of the user-defined resource named CAVEATS. The text for this screen is located in the ASCII file CAVEATS.ASC (see Example 1). This file can be created with any editor. There is one file per screen. Each application that shares the help library would have a sequential list of tokens (and associated text) for the help topics. Examples 2, 3, and 4 list other user-defined resources used by our library.

Example 1: The user-defined resource name CAVEATS.ASC

caveats
As is usually the case in life, it takes a little more to get a little more.  
This is certainly true with DLLs.  There are two big differences between code 
in a DLL and code in a standard run-time library under MS-DOS.  The first one 
is on the tip of the tongue of anyone who has ventured into the DLL domain.  
This is the SS != DS issue.  The second concerns the use of global variables.

Normally, when C code is compiled, it is assumed that the data segment (DS) 
is the same as the stack segment (SS).  This is valid when passing the address 
of a parameter to a function for the small (i.e., one code segment and one 
data segment) and medium (i.e., multiple code segments and one data segment) 
models since only the 16-bit offsets of addresses are passed.

DLLs, however, have their own data segment and use the stack of the caller.  
In this case, the stack segment does not equal the data segment (SS!=DS) and 
the 16-bit offsets are not valid addresses since all automatic variables 
(i.e., parameters and local variables) are kept on the stack and all others 
(i.e., global and static variables) are contained in the data segment.
  
This means that any addresses passed to   a function must contain both the 
segment and offset (i.e., 32-bit far   pointers).


Because of this, some of the standard C run-time library functions cannot be used. 
These are listed in an appendix of Microsoft's Software Development Kit (SDK) for Windows. 
In general, any of the buffered I/O functions (e.g., fprintf, fscanf, sscanf), exec 
functions (e.g., execlp, execv), and spawn functions (e.g., spawnl, spawnv) are off 
limits.

Because programs in DOS need not be reentrant, it is common to sprinkle 
code generously with global variables.  This can be a source of grief to 
the person who wishes to convert existing code to work in a DLL.  Although 
code is not reentrant, the data is.  A DLL has only one data segment, which 
is shared by all applications.  Don't expect a variable that is set by one 
function of the DLL to contain the set   value in another function.  A common 
case is plotting libraries.  Usually   the sequence of calls consists of setting 
plot variables, graphing the   plot using the plot variables set previously, 
then performing any   cleanup.




Putting the plotting variables in global memory assumes each application that makes this series of calls has its own data area to hold these variables. If these routines reside in a DLL, then the global variable that sets the x origin within the DLL is the same variable used by any application that calls this routine. For example, if two applications (A and B) coexist and application A makes calls to set the plotting variables, and then B does the same before A can call to plot, the variables used by the DLL will be set to B's preferences.

Example 2: Another user-defined resource, this one called COOKBOOK.ASC

  cookbook

  Steps to Follow when Creating a DLL:

  1. Create the resource file.
  2. Create the library source files.
  3. Make sure you have an initialization function.
  4. Create the module definition file.
  5. Create a make file to:
    a. Compile library and initialization source files.
    b. Use rc compiler to compile any resources.
    c. Use the link4 linker to create the .EXE file.
    d. Attach the resources to the .EXE file.
    e. Use the implib tool to create an import library.

Example 3: A user-defined resource that provides reference information


  references

  Two books are definite musts:

  Programming Windows by Charles Petzold
  (Microsoft Press, 1988)

  852 pages jam-packed with Windows
  programming tips and useful code.  This is an
  incredible source for Windows developers.

  Inside OS/2 by Gordon Letwin (Microsoft
  Press, 1988)

  289 pages from the chief architect for
  systems software at Microsoft.  This book
  is great for getting a feel for what OS/2
  has to offer and the philosophy behind
  it.  It also gives a good feeling for the
  differences between the API for Windows
  and OS/2 without getting bogged down in
  the code.

Example 4: A fourth user-defined resource, stored in the ASCII file

VSRUNTIME.ASC

  versus run-time libraries

  In the MS-DOS world, code is compiled and linked with external libraries
  or object modules to create an executable.  Frequently, routines in
  libraries are generic, consisting of often-used functions.  An example

  of this is the run-time library included with the compiler.  Each
  executable that calls a routine from the library contains a copy of the
  code for that function.  Under typical DOS single-tasking conditions,
  and assuming it doesn't matter how large the executable file is,
  run-time libraries are just fine.

  Having each application carry around a copy of common code is
  inefficient in a multitasking system such as Windows.  Windows is an
  operating environment that sits on top of DOS and creates a
  nonpreemptive, event-driven multitasking environment for its
  applications.  In fact, with Windows, this is even more important
  since Windows is constrained to sit on top of DOS, and the environment
  that DOS sits in only allows 640K of memory (assuming no expanded
  memory).  In Windows, if multiple instances of the same application are
  loaded, they will share the same code space.  If multiple applications
  are loaded, however, code is not shared.  If each application call the
  same function to smooth data from a run-time library, then Windows will
  allocate code space for the smooth function for each application.  As
  anyone who has run multiple applications under Windows knows, memory is
  to Windows what water is to the desert--a precious resource that should
  not be wasted.

  If a function resides in a DLL, then only one copy will be loaded no
  matter how many applications call it.  This also keeps the application's
  executable small by keeping the common code out of each application.  Not
  only that, but since the code is dynamically loaded at run time, as long
  as the parameter sequence of the function call does not change, the
  innards of a function in a DLL can be changed without forcing the
  application's executable to be recompiled and relinked.

  Loading the library at run time allows other exciting opportunities.
  Some packages allow functions in a DLL to be called within their program.
  These include Actor from Whitewater, Excel from Microsoft, and SQL
  Windows from Gupta.  Thus, the developers can use his warm and comfy
  functions in extremely powerful existing applications.  But wait, there's
  more!  DLLs need not contain any code or data at all!  They can also be
  completely made of resources such as fonts, bit maps, icons, or cursors.

Both functions take as input an LPSCREEN type variable (as defined in helplib.h, Listing Three, page 82). This type is a far pointer to a SCREEN type that is typedefed as a structure containing the variables wStart, wEnd, and wScreen. Topics() uses the wStart and wEnd token settings to reference the range of character strings to load. Both Topics() and Screen() use wScreen to reference the user-defined ASCII file resources. All public functions (that is, those having the ability to be invoked from outside the library) must be declared as FAR PASCAL. Declaring a function FAR allows it to be accessed from another application that will not have the same code segment. The PASCAL declaration specifies the use of the Pascal calling sequence, which means that parameters are pushed on the stack from left to right instead of from right to left, as in C.

Figure 1 shows the dialog box that pops up when Topics() is called. The dialog box displays a list box containing the strings mentioned previously and the two push buttons Help and Cancel. The Screen() function is called if you wish to get help on the highlighted topic. As shown in Figure 2, Screen() displays a dialog box that contains the help text in a scrollable window along with the four push buttons Topics, Next, Previous, and Cancel. Pushing Topics returns you to the Topics dialog box. Pushing Next displays the text for the next help topic listed in the Topics dialog box list; and pushing Previous displays the previous one. You can also call Screen() directly to display a specific help text.

The "private" protocol of the DLL is implemented in the file, helpdlg.c ( Listing Four, page 82). The ScreenDlgProc, Screen WndProc, and TopicDlgProc functions process the messages that Microsoft Windows sends to the dialogs. Typically, we are most concerned with the user-interface messages such as scrolling the window and moving it around and the system messages such as redisplaying a portion of the screen that is now visible because it was moved in front of a window that was previously obscuring it. A full discussion of the Microsoft Windows message system is beyond the scope of this article and we recommend that those people interested in the subject get a copy of Programming Windows by Charles Petzold (Microsoft Press, 1988) -- an invaluable aid for all Windows programmers.

Another important part of the private protocol of the DLL is the maintenance of the screen information. One "feature" of a DLL is reentrancy. This complicates matters if you need to maintain global data. One way of keeping track of global data is to allocate extra bytes to the window structure when registering the window class. We have done this by setting the cbWndExtra field to sizeof(HANDLE) bytes when registering the help screen window class in libinitc.c (Listing Five, page 87). When Screen WndProc() receives a WM_CREATE message, it allocates a chunk of local memory to hold the window's screen information and puts the handle into the window structure at offset 0. This allows easy retrieval when an event occurs that requires the help window's screen data.

DLL libraries are different from applications in that they don't and can't invoke the C run-time/Windows initialization. Any initialization that the library needs must be done by the developer, usually with a short assembly program that in turn calls a C function in the library to perform the initialization. libintc.c and libinita.asm ( Listing Six, page 87) provide this service.

The final piece of our help library puzzle is the module definition file, helplib.def (Listing Seven, page 88). The primary purpose of the module definition file is to define the characteristics of the data and code segments, the local heap, and the exported functions. If this were a module definition file for a Windows application, it would also include a parameter for the stack size, which is not necessary in a DLL because the DLL uses the calling application's stack. Also because all libraries have only one data segment, the option SINGLE must be used on the DATA definition line.

The EXPORTS section defines the functions that are accessible to the outside world. Of course, Screen() and Topics() are in this list, but so are the functions ScreenDlgProc, ScreenWndProc, and TopicsDlgProc. This is necessary because these DLL routines are called by Windows to process user input and that is why these functions are declared FAR PASCAL. The LINK4 linker uses the module definition file to add the necessary information to the .EXE file, which allows run-time linking and execution of the library routine.

The make file, helplib (Listing Eight, page 88) uses the files discussed previously to create the library files helplib.lib and helplib.exe. The compiler switch that is necessary for a DLL is the - Alnw switch (or -Asnw for a small model). This switch is needed to ensure that SS!=DS.

Testing the Library

The help library we provide gives an overview of this article and a step-by-step description of the process of creating a library. We have also included two examples of how the library can be used. The first, helpdemo.exe, and its associated source code (in Listings Nine - Eighteen, pages 89 - 91) shows how easy it is to call a DLL from a Windows application. The function HelpDemoMsg() sets up the screen information and calls the Topics() function in the DLL whenever the F1 key is pressed or the Help! menu item is selected.

The second example, helplib.act (Listing Nineteen, page 91), is written in Actor, an incrementally compiled language for developing Windows applications. This example illustrates how different DLLs can be specified at run time. As long as the application knows what functions a library contains, it can load the library, execute the function, and then free the library. Actor provides a way to experiment with changing libraries on the fly and seeing how these changes affect the way your application behaves.

Conclusion

Dynamic link libraries provide a great way to reuse code. With correct modular design, applications can be customized without having to recompile and relink. DLLs perform best when they are called to process data and then return. This is because the DLL is shared by all the applications in the system that call the library, and therefore global data cannot be maintained. One way to overcome this limitation is to store information specific to the window in the window's data structure.

We hope this article gives you a taste of the flexibility of DLLs. Developers who have built DLLs for Microsoft Windows have had an early taste of OS/2 programming. Those people who develop third-party libraries for sale to the PC programming community will find great acceptance from the Microsoft Windows community and will also provide an easy platform for porting their libraries to OS/2.

_DYNAMIC LINK LIBRARIES UNDER MICROSOFT WINDOWS_ by Margaret Johnson and Mark Solinski

[LISTING ONE]



/***************************************************************************
 * MODULE: HELPLIB.C
 * COMMENTS: contains the functions Screen and Topics (see HELPDLG.C)
 ***************************************************************************/
#include  <windows.h>          /* used by all modules written for Windows */
#include  "helplib.h"          /* library's include file */
#define   HELPLIB
#include  "prothelp.h"          /* function prototypes */
/***************************************************************************
 * external variables
 ***************************************************************************/
extern HANDLE hInst;  /* set by the initialization function in libinitc.c */
/***************************************************************************
 * global variables
 ***************************************************************************/
LPSCREEN    lpsc;
BOOL        TOPICS;
BOOL        SCR;
 /***************************************************************************
 * Local variables
 ***************************************************************************/
 static FARPROC     lpfnScreenDlgProc;
 static FARPROC     lpfnTopicsDlgProc;
/************************************************************************
 * FUNCTION: Screen
 * PURPOSE:  Display help text on a topic.
 ************************************************************************/
BOOL FAR PASCAL Screen( LPSCREEN sc )
{MSG     msg;
 HWND    hWnd;
 LockData(0);
 lpsc = sc;

 if (!(lpfnScreenDlgProc = MakeProcInstance(ScreenDlgProc,hInst)))
    return FALSE;

 if (!(hWnd = CreateDialog(hInst,"HELP_BOX",GetActiveWindow(),
                          lpfnScreenDlgProc)))
    return FALSE;
 while (GetMessage (&msg,NULL,0,0))
    {if (!IsDialogMessage(hWnd,&msg) )
       {TranslateMessage(&msg);
        DispatchMessage(&msg);
       }
    }

 FreeProcInstance(lpfnScreenDlgProc);

 if (TOPICS)
    {Topics(lpsc);
    }

 UnlockData(0);
 return TRUE;
}
/************************************************************************
 * FUNCTION: Topics
 * PURPOSE:  to present a listbox of currently available help topics.
 ************************************************************************/
VOID FAR PASCAL Topics( LPSCREEN sc )

{LockData(0);
 lpsc = sc;

 lpfnTopicsDlgProc = MakeProcInstance(TopicsDlgProc,hInst);
 DialogBox(hInst,"TOPICS_BOX",GetActiveWindow(),lpfnTopicsDlgProc);
 FreeProcInstance(lpfnTopicsDlgProc);

 if (SCR)
    {Screen(lpsc);
    }

 UnlockData(0);
 return;
}





[LISTING TWO]


/***************************************************************************
 * FILE: helplib.rc
 * PURPOSE:resource file for the helplib DLL
 **************************************************************************/
#include <style.h>
#include "helplib.h"
#define  TABGRP (WS_TABSTOP | WS_GROUP)

VSRUNTIME   TEXT     vsruntime.asc
CAVEATS     TEXT     caveats.asc
COOKBOOK    TEXT     cookbook.asc
REF        TEXT     ref.asc
A           TEXT     a.asc
B           TEXT     b.asc
C           TEXT     c.asc

STRINGTABLE
   BEGIN
       IDS_MEMERROR    "Out of Memory"
       VSRUNTIME,      "versus run time libraries"
       CAVEATS,        "caveats"
       COOKBOOK,       "cookbook"
       REF,            "references"
       A,              "a"
       B,              "b"
       C,              "c"
   END

rcinclude  HELP.dlg
rcinclude  TOPICS.dlg







[LISTING THREE]


/***************************************************************************
 * FILE: helplib.h
 * PURPOSE: include file for the helplib DLL
 **************************************************************************/
typedef struct {
     WORD   wScreen;
     WORD   wStart;
     WORD   wEnd;
    }SCREEN, FAR * LPSCREEN, NEAR *NPSCREEN;
#define ID_SCREEN_HELP         100
#define ID_LB_TOPICS           101
#define ID_NEXT_HELP           102
#define ID_PREVIOUS_HELP       103
#define ID_TOPICS_HELP         104
#define ID_SCROLL_HELP         105

#define VSRUNTIME              300
#define CAVEATS                301
#define COOKBOOK               302
#define REF                    303

#define A                      500
#define B                      501
#define C                      502

#define IDS_MEMERROR           1000







[LISTING FOUR]


/**************************************************************************
 * MODULE: HELPDLG.C
 **************************************************************************/

#include <windows.h>         /* required for all Windows applications */
#include "helplib.h"         /* library's include file */
#define  HELPLIB
#include "prothelp.h"        /* function prototypes */
#include "string.h"          /* strlen */

/*************************************************************************
 * to allow multiple screens from different apps/instances
 *************************************************************************/
typedef struct screenStruct {
    WORD                wScreen;
    WORD                wStart;
    WORD                wEnd;
    int                 nPage;
    int                 nTopics;
    int                 nNumLines;
    HWND                hScroll;
    int                 nVscrollPos;
   }HELPSCREEN, *NPHELPSCREEN;
/***************************************************************************
 * local variables
***************************************************************************/
#define   GWW_SCREENHANDLE  0
#define   MAXBUFLEN        80
#define   MAXLINES        250
#define   LOCAL        static
/*************************************
 * scroll bar positioning variables
*************************************/
LOCAL int      nVscrollMax;
/*************************************
 * buffer to hold the help text
*************************************/
LOCAL char      szText[MAXLINES][MAXBUFLEN];
/*******************************************
 * screen information
 *******************************************/
LOCAL NPHELPSCREEN sptr;
/***************************************************************************
 * local function prototypes
***************************************************************************/
LOCAL VOID   NEAR getText         ( VOID );
LOCAL VOID   NEAR setScroll       ( VOID );
LOCAL VOID   NEAR setNewHelp      ( HWND );
LOCAL HANDLE NEAR setToScreen     ( HWND );
LOCAL BOOL   NEAR differentScreen ( HWND );
LOCAL BOOL   NEAR initScreen      ( HWND );
LOCAL VOID   NEAR freeScreen      ( HANDLE );
/***************************************************************************
 * external variables
***************************************************************************/
extern HANDLE   hInst; /* set by the initialization function in libinitc.c */
extern LPSCREEN lpsc;  /* passed in by the calling function */
extern BOOL     TOPICS;/* used by ScreenDlgProc when the user requests Topics */
extern BOOL     SCR;   /* used by TopicsDlgProc when the use requests Help */
/***************************************************************************
 * Function: ScreenDlgProc
 * Purpose:  To respond to the Push Buttons: Topics, Next, Previous, and
 *      Cancel on the Screen() dialog box.
 ***************************************************************************/
BOOL  FAR  PASCAL  ScreenDlgProc(HWND hDlg, WORD wMessage, WORD wParam,
                                 LONG lParam)
 {int             i;
  static BOOL     bImoved=FALSE;
  HWND            hWndScreen;
  HANDLE          hScreen;
  switch(wMessage)
     {
      case WM_INITDIALOG:
        hScreen = setToScreen(hDlg);
        sptr->nTopics = sptr->wEnd - sptr->wStart + 1;
        sptr->hScroll = GetDlgItem(hDlg,ID_SCROLL_HELP);
        getText();
        LocalUnlock(hScreen);
        break;
      case WM_MOVE:
        bImoved=TRUE;
        break;
      case WM_VSCROLL:
        if (bImoved)
          {hScreen = setToScreen(hDlg);
           getText();
           setScroll();
           LocalUnlock(hScreen);
           bImoved=FALSE;
          }
        hWndScreen = GetWindow(hDlg,GW_CHILD);
        SendMessage(hWndScreen,WM_VSCROLL,wParam,lParam);
        break;
      case WM_COMMAND:
         hScreen = setToScreen(hDlg);
         hWndScreen = GetWindow(hDlg,GW_CHILD);
         switch(wParam)
           {
            case ID_PREVIOUS_HELP:
               sptr->wScreen = sptr->wStart + (sptr->wScreen-sptr->wStart+
                                 (sptr->nTopics-1)) % sptr->nTopics;
               setNewHelp(hWndScreen);
               LocalUnlock(hScreen);
               break;
            case ID_NEXT_HELP:
               sptr->wScreen = sptr->wStart + (sptr->wScreen-sptr->wStart+1)
                                             %sptr->nTopics;
               setNewHelp(hWndScreen);
               LocalUnlock(hScreen);
               break;
            case ID_TOPICS_HELP:
               TOPICS = TRUE;
            case IDCANCEL:
               freeScreen(hScreen);
               DestroyWindow(hDlg);
               break;
            default:
               return FALSE;
           }
         break;
      case WM_ACTIVATE:  /* when the dialog is activated, check to
                          * see if the correct screen mode is
                          * is selected.
                          */
         if (!wParam)
            break;
      case WM_PAINT: /* User could be switching 'tween n application's
                      * (or n instances) Help Dialogs
                      */
         hScreen = setToScreen(hDlg);
         if (differentScreen(hDlg))
           {getText();
           }
         if (WM_ACTIVATE == wMessage)
           {SetFocus(sptr->hScroll);
            setScroll();
           }
         LocalUnlock(hScreen);
         return FALSE;
      default:
         return FALSE;
     }
  return TRUE;
 }
/***************************************************************************
 * Function: ScreenWndProc
 * Purpose: To respond to messages received by the Screen() window.
 * Notes:   The WM_VSCROLL, WM_PAINT message handling was derived from
 *        Programming Windows by Charles Petzold, pp. 117-122.
 ***************************************************************************/
long FAR PASCAL  ScreenWndProc(HWND hWnd, WORD wMessage, WORD wParam,
                               LONG lParam)
{PAINTSTRUCT ps;
 LOCAL               int          xChar,yChar;
 LOCAL               int          yClient;
 LOCAL               TEXTMETRIC   tm;
 int                i;
 int                nVscrollInc;
 int                nPaintBeg,nPaintEnd;
 HDC                hDC;
 HANDLE             hScreen;
 switch(wMessage)
   {
    case WM_CREATE:
        if (!initScreen(hWnd))
            break;
        hDC = GetDC(hWnd);
        GetTextMetrics(hDC,&tm);
        ReleaseDC(hWnd,hDC);
        xChar = tm.tmAveCharWidth;
        yChar = tm.tmHeight + tm.tmExternalLeading;
        TOPICS = FALSE;
        break;
    case WM_SIZE:
        hScreen = GetWindowWord(hWnd,GWW_SCREENHANDLE);
        sptr = (NPHELPSCREEN)LocalLock(hScreen);
        yClient = HIWORD(lParam);
        sptr->nPage = yClient / yChar ;
        LocalUnlock(hScreen);
        break;
    case WM_SETFOCUS:
        hScreen = GetWindowWord(hWnd, GWW_SCREENHANDLE);
        sptr = (NPHELPSCREEN)LocalLock(hScreen);
        SetFocus(sptr->hScroll);
        LocalUnlock(hScreen);
        break;
    /* the WM_VSCROLL message is sent by the ScreenDlgProc, which has
     * already taken care of sptr
     */
    case WM_VSCROLL:
        switch (wParam)
           {
            case SB_TOP:
               nVscrollInc = -sptr->nVscrollPos;
               break;
            case SB_BOTTOM:
               nVscrollInc = nVscrollMax - sptr->nVscrollPos;
               break;
            case SB_LINEUP:
               nVscrollInc = -1;
               break;
            case SB_LINEDOWN:
               nVscrollInc = 1;
               break;
            case SB_PAGEUP:
               nVscrollInc = min(-1, -sptr->nPage) ;
               break;
            case SB_PAGEDOWN:
               nVscrollInc = max(1, sptr->nPage) ;
               break;
            case SB_THUMBPOSITION:
               nVscrollInc = LOWORD(lParam) - sptr->nVscrollPos;
               break;
            default:
               nVscrollInc = 0;
           }
        if (nVscrollInc = max(-sptr->nVscrollPos,
            min(nVscrollInc, nVscrollMax - sptr->nVscrollPos)))
           {sptr->nVscrollPos += nVscrollInc;
            ScrollWindow(hWnd, 0, -yChar * nVscrollInc, NULL, NULL);
            UpdateWindow(hWnd);
            SetScrollPos(sptr->hScroll,SB_CTL,sptr->nVscrollPos,TRUE);
           }
        break;
    case WM_PAINT:
       hScreen = GetWindowWord(hWnd, GWW_SCREENHANDLE);
       sptr = (NPHELPSCREEN)LocalLock(hScreen);
       BeginPaint( hWnd, &ps );
       nPaintBeg = max(0,sptr->nVscrollPos + ps.rcPaint.top/yChar - 1);
       nPaintEnd = min(sptr->nNumLines,
                    sptr->nVscrollPos + ps.rcPaint.bottom / yChar);
       for (i=nPaintBeg; i < nPaintEnd; ++i)
           {TextOut(ps.hdc,xChar,yChar * (1 - sptr->nVscrollPos+i),
                szText[i],strlen(szText[i]));
            }
       EndPaint(hWnd,&ps);
       LocalUnlock(hScreen);
       break;
    case WM_DESTROY:
       PostQuitMessage(0);
       break;
    default:
        return DefWindowProc( hWnd, wMessage, wParam, lParam );
   }
 return 0L;
}
/***************************************************************************
 * Function: TopicsDlgProc
 * Purpose: To respond to the list box and push button messages
 *        of the Topics() dialog box.
 ***************************************************************************/
BOOL  FAR  PASCAL  TopicsDlgProc(HWND hDlg, WORD wMessage, WORD wParam,
                                 LONG lParam)
 {int i;
  LOCAL char szBuff[MAXBUFLEN];

  switch(wMessage)
     {
      case WM_INITDIALOG:
        SetSysModalWindow(hDlg);
        for (i=lpsc->wStart;i<=lpsc->wEnd;++i)
           {LoadString(hInst,i,szBuff,MAXBUFLEN);
            SendDlgItemMessage(hDlg,ID_LB_TOPICS,LB_ADDSTRING,0,
                                    (LONG)(LPSTR)szBuff);
           }
        SendDlgItemMessage(hDlg,ID_LB_TOPICS,LB_SETCURSEL,0,0L);
        SCR = FALSE;
        break;
      case WM_COMMAND:
         switch(wParam)
           {
            case ID_LB_TOPICS:
               if ((HIWORD(lParam)) != LBN_DBLCLK)
                  {break;
                  }
            case IDOK:
               lpsc->wScreen = (WORD)SendDlgItemMessage(hDlg,ID_LB_TOPICS,
                               LB_GETCURSEL,0,0L) + lpsc->wStart;
               SCR=TRUE;
            case IDCANCEL:
               EndDialog(hDlg,TRUE);
               break;
            default:
               return FALSE;
           }
         break;
      default:
         return FALSE;
     }
  return TRUE;
 }
/***************************************************************************
 * Function: getText
 * Purpose: To retrieve the help text for the current screen from the
 *        Help library's resource file.
 ***************************************************************************/
LOCAL VOID NEAR getText( )
  {LPSTR   lpText,lpTextBeg;
   HANDLE  hRes;
   hRes = LoadResource(hInst,
          FindResource(hInst,MAKEINTRESOURCE(sptr->wScreen),"TEXT"));
   lpText = LockResource(hRes);
   sptr->nNumLines=0;
   lpTextBeg = lpText;
   while (*lpText != '\0' && *lpText != '\x1A' )
        {if (*lpText == '\r')
           {*lpText = '\0';
            lstrcpy(szText[sptr->nNumLines++],lpTextBeg);
            if (sptr->nNumLines >= MAXLINES)
                break;
            *lpText='\r';
            lpText = AnsiNext(lpText);
            if (*lpText = '\l')
                lpText = AnsiNext(lpText);
            lpTextBeg = lpText;
           }
         else
           lpText = AnsiNext(lpText);
        }
   *lpText = '\0';
   lstrcpy(szText[sptr->nNumLines++],lpTextBeg);
   GlobalUnlock(hRes);
   FreeResource(hRes);
  }
/***************************************************************************
 * Function: setScroll
 * Purpose: To set the scroll bar control's range and initial position.
 ***************************************************************************/
LOCAL VOID NEAR setScroll( )
  {nVscrollMax = max(0, sptr->nNumLines + 2 - sptr->nPage);
   SetScrollRange(sptr->hScroll, SB_CTL,0, nVscrollMax, FALSE);
   SetScrollPos  (sptr->hScroll, SB_CTL,sptr->nVscrollPos,TRUE);
  }
/***************************************************************************
 * Function: setNewHelp
 * Purpose: Get the right screen help info
 ***************************************************************************/
LOCAL VOID NEAR setNewHelp(HWND hWnd)
 {sptr->nVscrollPos = 0;
  getText();
  setScroll();
  InvalidateRect(hWnd,NULL,TRUE);
  SetFocus(sptr->hScroll);
 }
/***************************************************************************
 * Function: setToScreen
 * Purpose: Lock the screen pointer to the correct screen info.
 ***************************************************************************/
LOCAL HANDLE NEAR setToScreen(HWND hWnd )
{HANDLE       hScreen;
 HWND         hWndScreen;
 hWndScreen   = GetWindow(hWnd,GW_CHILD);
 hScreen      = GetWindowWord(hWndScreen, GWW_SCREENHANDLE);
 sptr         = (NPHELPSCREEN)LocalLock( hScreen );
 return hScreen;
}
/***************************************************************************
 * Function: differentScreen
 * Purpose: Find out if hDlg references the same window as the last call
 ***************************************************************************/
LOCAL BOOL NEAR differentScreen(HWND hDlg)
{LOCAL  HWND   hWnd = NULL;
 if (hWnd != hDlg)
   {hWnd = hDlg;
    return TRUE;
   }
 return FALSE;
}
/***************************************************************************
 * Function: initScreen
 * Purpose: Set up the screen on a WM_CREATE message.
 ***************************************************************************/
LOCAL BOOL NEAR initScreen(HWND hWnd)
 {HANDLE hScreen;
  char   szMemErr[15];
  if (!(hScreen = LocalAlloc(LHND,sizeof(HELPSCREEN))))
    {LoadString(hInst,IDS_MEMERROR,szMemErr,15);
     MessageBox( GetParent(hWnd), szMemErr, NULL, MB_ICONEXCLAMATION);
     return FALSE;
    }
  sptr = (NPHELPSCREEN)LocalLock(hScreen);
  sptr->wScreen = lpsc->wScreen;
  sptr->wStart = lpsc->wStart;
  sptr->wEnd = lpsc->wEnd;
  LocalUnlock(hScreen);
  SetWindowWord( hWnd, GWW_SCREENHANDLE, hScreen );
  return TRUE;
 }
/***************************************************************************
 * Function: freeScreen
 * Purpose: All done with the screen, so release the memory.
 ***************************************************************************/
LOCAL VOID NEAR freeScreen(HANDLE hScreen)
{lpsc->wScreen = sptr->wScreen;
 lpsc->wEnd    = sptr->wEnd;
 lpsc->wStart  = sptr->wStart;
 LocalUnlock(hScreen);
 LocalFree(hScreen);
}






[LISTING FIVE]


/*
 * Utilities library C language initialization.
 */
#include <windows.h>
#include "helplib.h"
#include "prothelp.h"
/**********************************************************************
 * function prototypes
 **********************************************************************/
#define LOCAL  static
int         NEAR PASCAL LibInitC       ( HANDLE );
LOCAL BOOL  NEAR        registerWindow ( VOID );
/**********************************************************************
 * Global vars
 **********************************************************************/
HANDLE      hInst;
/**********************************************************************
 * C init called from the asm entry point init.
 * We require that the library have exactly one DS. See libinita.asm.
 * The DS can (should) be moveable.
 ***********************************************************************/
int NEAR PASCAL LibInitC(HANDLE hInstance)
{
    hInst = hInstance;
    return(registerWindow());
}
/************************************************************************/
LOCAL BOOL  NEAR registerWindow ( )
{WNDCLASS WndClass;         /* Window class structure */
 WndClass.style           = CS_HREDRAW | CS_VREDRAW;
 WndClass.lpfnWndProc     = ScreenWndProc;
 WndClass.cbClsExtra      = 0;
 WndClass.cbWndExtra      = sizeof(HANDLE);
 WndClass.hInstance       = hInst;
 WndClass.hIcon           = NULL;
 WndClass.hCursor         = LoadCursor(NULL, IDC_ARROW);
 WndClass.hbrBackground   = COLOR_WINDOW+1;
 WndClass.lpszMenuName    = NULL;
 WndClass.lpszClassName   = "Screen";

 return RegisterClass(&WndClass);

}






[LISTING SIX]


; LIBINITA.ASM - - - Define entry point and perform initialization
;                    for libraries that have their own data segments.
        TITLE   LIBINITA
?PLM = 1
?WIN = 1
memM = 1

.xlist
include cmacros.inc
.list

externFP    <LocalInit>
externFP    <UnlockSegment>
externNP    <LibInitC>
;
;   cx = size of the heap as defined in the .def file.
;   di = "Instance handle". This is the C hInstance passed to WinMain.
;        NOTE: For a WINDOWS library, hModule is interchangeable with
;           hInstance.
;        This is a handle to the global object containing the DS if there
;        is one. If there is no DS for a library, it is the module handle,
;        which is also a pointer to the module since that's a fixed global
;        object.
;        NOTE: The meaning and contents of hInstance are undocumented and
;           should not be relied upon. That is, you may asuume that the value
;           in di may be passed to any routine expecting hInstance. Making
;           any other assumptions is VERY dangerous since the contents of
;           hInstance may change in future versions.
;   ds = data segment for our heap.
;   es:si = pointer to the command line.

_INIT SEGMENT BYTE PUBLIC   'CODE'
assume CS: _INIT            ; ???? assume vs assumes? ???????????????????
assumes DS,NOTHING          ; ???? assume vs assumes? ???????????????????
assumes ES,NOTHING          ; ???? assume vs assumes? ???????????????????

cProc   LibInitA, <FAR, PUBLIC, NODATA>, <si,di>
cBegin
    xor     ax,ax                   ; Return failure if there is no heap.
    jcxz    ourexit
    cCall   LocalInit,<ds,ax,cx>    ; Set up our DS for doing LocalAllocs.

    or      ax,ax
    jz      ourexit

    cCall   LibInitC,<di>           ; Do any C initialization.
                                    ; di = hInstance.
    push    ax                      ; Save the return value.
    mov     ax,-1
    cCall   UnlockSegment,<ax>      ; NOTE that we leave DS unlocked.
                                    ; This implies that we must use
                                    ; Lock/UnlockData as we enter and
                                    ; any routines which access our
                                    ; data segment.
    pop     bx


    or      ax,ax                   ; Check if either one failed.
    jz      ourexit
    or      bx,bx
    jnz     ourexit
    xor     ax,ax

ourexit:
cEnd
_INIT ENDS
end LibInitA






[LISTING SEVEN]


LIBRARY      helplib
DESCRIPTION  'Copyright 1988, mkj'
STUB         'WINSTUB.EXE'
CODE         LOADONCALL   MOVEABLE  DISCARDABLE
DATA         MOVEABLE     SINGLE
HEAPSIZE     2048
CODE         MOVEABLE
SEGMENTS    _INIT      PRELOAD        MOVEABLE      DISCARDABLE
            HELPLIB    MOVEABLE       LOADONCALL    DISCARDABLE

EXPORTS
       Screen        @1
       Topics        @2
       ScreenDlgProc @3
       ScreenWndProc @4
       TopicsDlgProc @5







[LISTING EIGHT]


#
# FILE: helplib
# PURPOSE: make file for the helplib DLL
#
COMP  =  -c -Alnw  -Gsw -Zp -Os -FPa -W2 -D LINT_ARGS
OBJS  = helplib.obj helpdlg.obj libinita.obj libinitc.obj helplib.res
LOBJS = helplib+helpdlg+libinita+libinitc

.rc.res:
  rc -r $*.rc

.asm.obj:
  masm $*;

helplib.res:        helplib.rc helplib.h vsruntim.asc caveats.asc \
             cookbook.asc ref.asc help.dlg topics.dlg

helplib.obj:        helplib.c helplib.h prothelp.h
    cl $(COMP) -NT $* $*.c

helpdlg.obj:        helpdlg.c helplib.h prothelp.h
    cl $(COMP) -NT HELPLIB $*.c

libinita.obj:       libinita.asm
    masm $*;

libinitc.obj:          libinitc.c
    cl $(COMP) -NT _INIT $*.c

helplib.exe: $(OBJS) helplib.def
    link4 $(LOBJS),helplib.exe/align:16,/map/li,mwinlibc mlibw mlibca  /NOE  ,helplib.def
    rc    helplib.res helplib.exe
    implib helplib.lib helplib.def






[LISTING NINE]


 /**********************************************************************
 * FILE: helpdemo.c
 * PURPOSE: to demonstrate the use of the helplib DLL
 **********************************************************************/
#include <windows.h>
#include "helpdemo.h"

HANDLE        hInstance;         /* The Instance handle */
char          szClass[10];       /* Window class name (see the .rc file) */
char          szTitle[40];       /* Window title (see the .rc file) */
char          szAbout[40];       /* About box string (see the .rc file */

static HWND   hWnd;

long FAR PASCAL WndProc (HWND, unsigned, WORD, LONG);
BOOL NEAR       Initialize( HANDLE, int );
 /***********************************************************************
 *Application main program.
 ***********************************************************************/
int PASCAL WinMain( hInst, hPrevInst, lpszCmdLine, nCmdShow )
     HANDLE     hInst;          /* Our instance handle */
     HANDLE     hPrevInst;      /* Previous instance of this application */
     LPSTR      lpszCmdLine;    /* Pointer to any command line params */
     int        nCmdShow;       /* Parameter to use for first ShowWindow */
     {
      MSG     msg;              /* Message structure */
      HANDLE     hAccel;        /* Accelerator handle */
      /* Save our instance handle in a global variable */
      hInstance = hInst;
      /* Initialize application, quit if any errors */
      if( ! Initialize( hPrevInst, nCmdShow ) )
                 {return FALSE;
                 }
      /* Main message processing loop.  Get each message, then translate
       * keyboard messages and finally dispatch each message to its window
       * function.
       */
       hAccel = LoadAccelerators(hInstance,szClass);
       while( GetMessage( &msg, NULL, 0, 0 ) )
            {if (!TranslateAccelerator(hWnd, hAccel, &msg))
                  {TranslateMessage( &msg );
                   DispatchMessage( &msg );
                  }
            }
      return msg.wParam;
     }
 /************************************************************************
 *Initialize the application.
 *************************************************************************/
 BOOL NEAR Initialize( hPrevInst, nCmdShow )
     HANDLE     hPrevInst;         /* Previous instance handle, 0 if first */
     int     nCmdShow;             /* Parameter from WinMain for ShowWindow */
     {
      WNDCLASS    WndClass;        /* Class structure for RegisterClass */
      HMENU       hMenu;           /* Handle to the (system) menu */
      if( ! hPrevInst ) {
           /* Initialization for first instance only */
           /* Load strings from resource file */
           LoadString( hInstance, IDS_CLASS,    szClass,    sizeof(szClass) );
           LoadString( hInstance, IDS_TITLE,    szTitle,    sizeof(szTitle) );
           /* Register our window class */
           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    = COLOR_WINDOW + 1;
           WndClass.lpszMenuName     = szClass;
           WndClass.lpszClassName    = szClass;
           if( ! RegisterClass( &WndClass ) )
               return FALSE;
          }
      else
         {/* Initialization for subsequent instances only */
           /* Copy data from previous instance */
           GetInstanceData( hPrevInst, szClass,    sizeof(szClass) );
           GetInstanceData( hPrevInst, szTitle,    sizeof(szTitle) );
          }
      /* Initialization for every instance */

      /* Create the window  */
      hWnd = CreateWindow(
           szClass,          /* Class name */
           szTitle,          /* Window title */
           WS_OVERLAPPEDWINDOW             ,      /* window style */
           CW_USEDEFAULT,    /* x */
           0,                /* y */
           CW_USEDEFAULT,    /* x width */
           0,                /* y width */
           NULL,             /* Parent hWnd (none for top-level) */
           NULL,             /* Menu handle */
           hInstance,        /* Owning instance handle */
           NULL              /* Parameter to pass in WM_CREATE (none) */
       );

 /* Insert "About..." into system menu */
      LoadString( hInstance, IDS_ABOUT,    szAbout,    sizeof(szAbout) );
      hMenu = GetSystemMenu(hWnd, FALSE);
      ChangeMenu(hMenu, 0, NULL, 999, MF_APPEND | MF_SEPARATOR);
      ChangeMenu(hMenu, 0, (LPSTR)szAbout, IDS_ABOUT, MF_APPEND | MF_STRING);

      /* Make the window visible  */
      ShowWindow( hWnd, nCmdShow );

      /* Got all the information, update our display */
      UpdateWindow( hWnd );

      return TRUE;
     }






[LISTING TEN]


 /**************************************************************************
 * FILE: wndproc.c
 * PURPOSE: The functions WndProc and About for the helplib DLL demo.
 **************************************************************************/
#include "windows.h"
#include "helpdemo.h"
extern HANDLE hInstance;
 /******************* Message function prototypes *******************/
extern   VOID   HelpDemoMsg  ( HWND, WORD, LONG );
 /******************* local function prototype **********************/
BOOL NEAR           paint  ( HWND );
long FAR PASCAL     WndProc( HWND, unsigned, WORD, LONG );
BOOL FAR PASCAL     AboutDlgProc( HWND, unsigned, WORD, LONG );
long FAR PASCAL WndProc( hWnd, message, wParam, lParam )
     HWND hWnd;
     unsigned message;
     WORD wParam;
     LONG lParam;
     {
      FARPROC lpprocAbout;
      switch (message)
           {
            case WM_SYSCOMMAND:
                 switch (wParam)
                 {
                  case IDS_ABOUT:
                       /* Bind callback function with module instance */
                       lpprocAbout = MakeProcInstance( (FARPROC)AboutDlgProc,
                                                       hInstance);
                       DialogBox( hInstance, MAKEINTRESOURCE(ABOUTBOX), hWnd,
                                                       lpprocAbout );
                       FreeProcInstance( (FARPROC)AboutDlgProc );
                       break;
                  default:
                       return DefWindowProc( hWnd, message, wParam, lParam );
                       }
                 break;
            case WM_COMMAND:
                 switch(wParam)
                 {
                  case IDM_HELP:
                       HelpDemoMsg (hWnd, wParam, lParam);
                       break;
                 }
            case WM_PAINT:
                 paint( hWnd );
                 break;
            case WM_DESTROY:
            case WM_CLOSE:
                 PostQuitMessage( 0 );
                 break;
            default:
                 return DefWindowProc( hWnd, message, wParam, lParam );
                 break;
           }
      return(0L);
     }
 /*************************************************************************
 * Paint procedure (for processing of WM_PAINT messages)
 **************************************************************************/
BOOL   NEAR  paint ( hWnd )
     HWND hWnd;
     {
      PAINTSTRUCT ps;
      BeginPaint(hWnd,&ps);
      EndPaint(hWnd,&ps);
      return TRUE;
     }
 /**************************************************************************
 * About Box
 **************************************************************************/
BOOL FAR PASCAL AboutDlgProc( hDlg, message, wParam, lParam )
     HWND hDlg;
     unsigned message;
     WORD wParam;
     LONG lParam;
     {
      if (message == WM_COMMAND)
           {EndDialog( hDlg, TRUE );
            return TRUE;
           }
      else if (message == WM_INITDIALOG)
           return TRUE;
      else return FALSE;
     }






[LISTING ELEVEN]


 /**************************************************************************
 * The message functions.
 **************************************************************************/
#include "windows.h"
#include "helpdemo.h"
#include "helplib.h"          /* library's help file */
#include "prothelp.h"         /* prototypes for library's functions */

extern HANDLE hInstance;

 /******************* local function prototypes *******************/
VOID   HelpDemoMsg  ( HWND, WORD, LONG );

 /**************************************************************************/
VOID    HelpDemoMsg( hWnd, wParam, lParam )
     HWND hWnd;
     WORD wParam;
     LONG lParam;
    {SCREEN sc;
     sc.wStart = VSRUNTIME;
     sc.wEnd = REF;
     Topics(&sc);
     }






[LISTING TWELVE]


/**************************************************************************
 * FILE: prothelp.h
 * PURPOSE:function prototypes for the helplib DLL
 *************************************************************************/
#ifndef   HELPDLG
  #define HELPDLG extern
#endif
#ifndef   HELPLIB
  #define HELPLIB extern
#endif

HELPLIB BOOL  FAR PASCAL  Screen          ( LPSCREEN );
HELPLIB VOID  FAR PASCAL  Topics          ( LPSCREEN );
HELPDLG BOOL  FAR PASCAL  ScreenDlgProc   ( HWND, WORD, WORD, LONG );
HELPDLG BOOL  FAR PASCAL  TopicsDlgProc   ( HWND, WORD, WORD, LONG );
HELPDLG LONG  FAR PASCAL  ScreenWndProc   ( HWND, WORD, WORD, LONG );
extern LPSTR  FAR PASCAL  lstrcpy         ( LPSTR, LPSTR );
extern int    FAR PASCAL  lstrlen         ( LPSTR );






[LISTING THIRTEEN]


/*************************************************************************
 * FILE: helpdemo.h
 * PURPOSE: include file for the help DLL demonstration window
 *************************************************************************/

/************** strings **************************************************/
#define    IDS_CLASS              0  /* String Table ID for the Class Name */
#define    IDS_TITLE              1  /* String Table ID for the Title */
#define    IDS_ABOUT              2  /* String Table ID for the About box */

/************** menus ****************************************************/
#define     ABOUTBOX          3      /* About dialog resource ID */
#define    IDM_HELP           1000   /* Menu resource ID */


[LISTING FOURTEEN]


NAME         HELPDEMO
DESCRIPTION  'Help DLL Demonstration, Copyright 1988, mkj'
STUB         'WINSTUB.EXE'
CODE         LOADONCALL  MOVEABLE  DISCARDABLE
DATA         MOVEABLE    MULTIPLE
HEAPSIZE     2048
STACKSIZE    4096
EXPORTS
       WndProc        @1
       AboutDlgProc   @2







[LISTING FIFTEEN]


 /**********************************************************************
 * FILE: helpdemo.rc
 * PURPOSE: used with the helplib DLL for demonstration
 **********************************************************************/

#include <style.h>
#include "helpdemo.h"

STRINGTABLE
BEGIN
     IDS_CLASS           "HelpDemo"
     IDS_TITLE           "Help Demonstration"
     IDS_ABOUT           "About..."
END

ABOUTBOX DIALOG 22, 17, 154, 75
STYLE WS_POPUP | WS_DLGFRAME
BEGIN
     CTEXT "DLL Example"                      -1, 0,  5, 154, 8
     CTEXT "Help Library Demonstration"       -1,  0, 14, 154, 8
     CTEXT "Version 1.00"                     -1, 30, 34, 94, 8
     CTEXT "Copyright ) 1988, mkj"            -1,  0, 47,154, 9
     DEFPUSHBUTTON "Ok"                 IDOK, 61, 59, 32, 14, WS_GROUP
END

HelpDemo   MENU
BEGIN
  MENUITEM "\aF1=Help",                 IDM_HELP,HELP
END

HelpDemo  ACCELERATORS
BEGIN
   VK_F1,   IDM_HELP, VIRTKEY
END






[LISTING SIXTEEN]


TOPICS_BOX DIALOG LOADONCALL MOVEABLE DISCARDABLE 28, 19, 206, 79
STYLE  WS_DLGFRAME | WS_POPUP
BEGIN
    CONTROL "", ID_LB_TOPICS, "listbox", LBS_NOTIFY | WS_BORDER |
WS_VSCROLL | WS_CHILD | TABGRP, 12, 18, 144, 49
    CONTROL "&Help", IDOK, "button", BS_DEFPUSHBUTTON | WS_TABSTOP |
WS_CHILD | TABGRP, 165, 17, 32, 14
    CONTROL "Cancel", IDCANCEL, "button", BS_PUSHBUTTON | WS_TABSTOP |
WS_CHILD , 165, 43, 32, 14
    CONTROL "Help Topics on DLL's", 103, "static", SS_LEFT | WS_CHILD,
12, 5, 100, 11
END







[LISTING SEVENTEEN]


HELP_BOX DIALOG LOADONCALL MOVEABLE DISCARDABLE 10, 9, 262, 131
STYLE WS_BORDER | WS_CAPTION | WS_POPUP | WS_SYSMENU | WS_VISIBLE
CAPTION  "Help"
BEGIN
CONTROL "", ID_SCREEN_HELP, "Screen",  WS_BORDER | WS_CHILD | TABGRP,
9, 9, 234, 90
CONTROL "", ID_SCROLL_HELP, "scrollbar", SBS_VERT | WS_CHILD |
WS_CLIPSIBLINGS, 242, 9, 8, 90
CONTROL "&Next", ID_NEXT_HELP, "button", BS_DEFPUSHBUTTON |
WS_TABSTOP | WS_CHILD, 76, 105, 44, 16
CONTROL "&Previous", ID_PREVIOUS_HELP, "button", BS_PUSHBUTTON |
WS_TABSTOP | WS_CHILD, 139, 105, 44, 16
CONTROL "Cancel", IDCANCEL, "button", BS_PUSHBUTTON | WS_TABSTOP |
WS_CHILD, 201, 105, 44, 16
CONTROL "&Topics", ID_TOPICS_HELP, "button", BS_PUSHBUTTON |
WS_TABSTOP | WS_CHILD, 14, 105, 44, 16
END







[LISTING EIGHTEEN]


#
# FILE: helpdemo
# PURPOSE: make file for the helplib DLL demo
#
COMP  = -c -AS -D LINT_ARGS -Os -Zp  -Gsw -FPa
ASM   =

.rc.res:
  rc -r $*.rc

.c.obj:
  cl $(COMP) $*.c

.asm.obj:
  masm $(ASM) $*;

helpdemo.res:    helpdemo.rc  helpdemo.h

helpdemo.obj:    helpdemo.c   helpdemo.h

wndproc.obj:     wndproc.c    helpdemo.h

msgs.obj:        msgs.c         helpdemo.h helplib.h prothelp.h

helpdemo.exe:    helpdemo.obj wndproc.obj msgs.obj helpdemo.res
link4  helpdemo+wndproc+msgs,/align:16,/map/li,slibw slibca
helplib /NOE /co  , helpdemo.def
 rc helpdemo.res







[LISTING NINETEEN]


/*   **************************************
     FILE: helplib.act
     PURPOSE: example of specifying and loading
         the library at run-time instead of
         at link (link4). The code is written in
         Actor, an object-oriented language for
         developing MS-Windows applications.
     ************************************** */

/*   **************************************
     INITIALIZATION
     ************************************** */
/* define constants */
#define VSRUNTIME       300
#define CAVEATS         301
#define COOKBOOK        302
#define REF             303

#define A               500
#define B               501
#define C               502
!!

/* create a new C struct */
SCREEN := new(CStruct);
!!

init(SCREEN, #(
  int wScreen 1
  int wStart  1
  int wEnd    1
));
!!
/* initialize help library */
HelpLib := new(Library);            /* create new library               */
HelpLib.name := "HELPLIB.EXE";      /* set the file name of the library */
add(HelpLib, #SCREEN, 0, #(1));     /* add the names of the exported
                                       functions in the library that you
                                       will call. The '0' is the return
                                       type (0=int,1=long), and the
                                       '#(1)' lists the arguments - one
                                       long variable                    */
!!
/* load library */
load(HelpLib);
!!
/*   **************************************
     BODY
     ************************************** */
/* fill C Struct */
SCREEN[#wStart] := VSRUNTIME;
SCREEN[#wEnd] := REF;
SCREEN[#wScreen] := CAVEATS;
!!
/* call DLL to put up help dialog */
pcall(HelpLib.procs[#SCREEN], lP(SCREEN));
freeHandle(SCREEN);
!!
/*   **************************************
     CLEAN-UP
     ************************************** */
/* free the library */
free(HelpLib);
!!












Copyright © 1989, Dr. Dobb's Journal

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.