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]
Copyright © 1989, Dr. Dobb's Journal<a name="0089_000d">
/***************************************************************************
* 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;
}
<a name="0089_000e"><a name="0089_000e">
<a name="0089_000f">
[LISTING TWO]<a name="0089_000f">
/***************************************************************************
* 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
<a name="0089_0010"><a name="0089_0010">
<a name="0089_0011">
[LISTING THREE]<a name="0089_0011">
/***************************************************************************
* 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
<a name="0089_0012"><a name="0089_0012">
<a name="0089_0013">
[LISTING FOUR]<a name="0089_0013">
/**************************************************************************
* 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);
}
<a name="0089_0014"><a name="0089_0014">
<a name="0089_0015">
[LISTING FIVE]<a name="0089_0015">
/*
* 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);
}
<a name="0089_0016"><a name="0089_0016">
<a name="0089_0017">
[LISTING SIX]<a name="0089_0017">
; 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
<a name="0089_0018"><a name="0089_0018">
<a name="0089_0019">
[LISTING SEVEN]<a name="0089_0019">
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
<a name="0089_001a"><a name="0089_001a">
<a name="0089_001b">
[LISTING EIGHT]<a name="0089_001b">
#
# 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
<a name="0089_001c"><a name="0089_001c">
<a name="0089_001d">
[LISTING NINE]<a name="0089_001d">
/**********************************************************************
* 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;
}
<a name="0089_001e"><a name="0089_001e">
<a name="0089_001f">
[LISTING TEN]<a name="0089_001f">
/**************************************************************************
* 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;
}
<a name="0089_0020"><a name="0089_0020">
<a name="0089_0021">
[LISTING ELEVEN]<a name="0089_0021">
/**************************************************************************
* 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);
}
<a name="0089_0022"><a name="0089_0022">
<a name="0089_0023">
[LISTING TWELVE]<a name="0089_0023">
/**************************************************************************
* 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 );
<a name="0089_0024"><a name="0089_0024">
<a name="0089_0025">
[LISTING THIRTEEN]<a name="0089_0025">
/*************************************************************************
* 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 */
<a name="0089_0026">
[LISTING FOURTEEN]<a name="0089_0026">
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
<a name="0089_0027"><a name="0089_0027">
<a name="0089_0028">
[LISTING FIFTEEN]<a name="0089_0028">
/**********************************************************************
* 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
<a name="0089_0029"><a name="0089_0029">
<a name="0089_002a">
[LISTING SIXTEEN]<a name="0089_002a">
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
<a name="0089_002b"><a name="0089_002b">
<a name="0089_002c">
[LISTING SEVENTEEN]<a name="0089_002c">
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
<a name="0089_002d"><a name="0089_002d">
<a name="0089_002e">
[LISTING EIGHTEEN]<a name="0089_002e">
#
# 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
<a name="0089_002f"><a name="0089_002f">
<a name="0089_0030">
[LISTING NINETEEN]<a name="0089_0030">
/* **************************************
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);
!!