Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

.NET

Undocumented Corner


JUN93: UNDOCUMENTED CORNER

UNDOCUMENTED CORNER

Spying on WinHelp

Ron Burk

Ron is the editor of Windows/DOS Developer's Journal and is working on a book entitled WinHelp for Programmers and Technical Writers. You can contact him via CompuServe (70302,2566), BIX (rlburk), or Internet ([email protected]).


Online help? What could possibly be undocumented about that? In Windows 3.1, Microsoft's WinHelp is a potentially marvelous facility that lets you attach macros, and any routine in a dynamic link library (DLL), to WinHelp menu items, buttons, and hotspots. For example, when a user clicks on a portion of a graphic in a Windows .HLP file, it can automatically invoke a function you've placed in a DLL.

The documentation for WinHelp appears in the Windows 3.1 SDK Programmer's Reference, Volume 4: Resources, Chapter 15 ("Windows Help Statements and Macros"); the WinHelp() function is documented in Volume 2: Functions. This documentation explains the basics of creating new macros from DLL functions with RegisterRoutine(), then attaching macros to buttons with ChangeButtonBinding(), attaching macros to menu items with ChangeItemBinding(), and so on. Pretty cool stuff for what most of us think of as just a simple online help facility. I've seen one company (FS Forth-Systeme in Breisach am Rhein, Germany) that's put together entire applications using just .HLP files and DLLs. No executable file!

Such power should make it possible to provide exciting help for Windows applications. But anyone who's ever tried to put together such sophisticated online documentation for a windows program has probably run into some annoying gaps in Microsoft's own documentation for WinHelp. For example, how do you let a user print or copy multiple topics at the same time? How do you declare DLL routines with return values? How does the WinHelp() function connect up with WINHELP.EXE, anyway?

This month's "Undocumented Corner" comes to us from Ron Burk, editor of the superb magazine, Windows/DOS Developer's Journal. If you've been reading W/DDJ, you know that Ron has in recent issues been putting together several big pieces of the WinHelp puzzle. The February 1993 W/DDJ has Ron's article "Automating Help Topic Extraction," which included a brief sidebar entitled "Undocumented WinHelp." The saga continued in the March 1993 W/DDJ, where Ron discussed "Automatic Help Topic Printing" (how to create help files that can print multiple topics: sounds simple, but it isn't). The source code from this second article is on CompuServe, in HLPPRN.ZIP in Library 7 (R&D Publications) of CLMFORUM. According to Ron, it is a fairly popular download item, since the question of how to print multiple topics comes up about once a week in the CompuServe WINSDK forum.

Dr. Dobb's readers are fortunate to have Ron contribute this month's "Undocumented Corner," where he uncovers yet more WinHelp undocumentedness. One important piece of information here is the that Microsoft's multimedia VIEWER.EXE provides essentially the same interface as WINHELP.EXE. Once you know this one little fact, you can use the better (albeit difficult-to-find) Viewer documentation to supplement the WinHelp documentation.

Ron also shows that WinHelp() communicates with WINHELP.EXE via a WM_WINHELP message (VIEWER.EXE uses a WM_WINDOC message). You won't find these messages listed in WINDOWS.H, or even in the chapter of Undocumented Windows devoted to messages. Why? Because these are registered window messages. Windows provides the capability of defining new system-wide messages: You pass RegisterWindowMessage() a string (such as "WM_WINHELP"), and it returns a message number. If someone has already registered that string, you get back the same message number. Hmm, sounds like a hash table. In fact, it is a hash table: Registered window messages are just atoms; you can use the ATOMWALK program from Undocumented Windows to find the WM_WINHELP atom in the USER's atom table. This means that registered window messages can be turned back into their original strings using the GetAtomName() function. As noted later, Borland's WinSight debugging tool takes advantage of this undocumented "implementation detail."

At the end of his article, Ron notes the crying need for someone to figure out the format of .HLP and .MVB files. We can only imagine the quality and quantity of third-party help tools we would have today if Microsoft would provide this information--or if some kind soul out there would reverse-engineer it. If you've figured out this or any other interesting aspect of Windows, DOS, NetWare, or whatever, please write to me via CompuServe (76320,302) or Internet ([email protected]).

--Andrew Schulman

Creating good online help for Windows applications is one of those things that seems to often fall through the cracks. Studying Microsoft's help compiler documentation is not a high priority for most programmers, and most technical writers cannot create quality help systems without some programming support. You can see the results on the Windows desktop--a handful of major applications supply context-sensitive online help complete with conceptual information, while many applications supply nothing more than a few lines of help text for each menu item; some applications offer essentially no online help at all. Some applications offer online help that sparkles with color and special effects, while most online help is black and white with only a few hypertext jumps to break up the monotony.

Software alone can't help you create quality online help, but software tools can make the task of creating online help less tedious for both programmers and writers. This article covers a bit of the history of the Windows help system and then shows how the undocumented WM_WINHELP message makes it easy to create a program to help debug an application's online help.

WinHelp History

Part of the apathy programmers exhibit toward online Windows help is no doubt due to the help system itself. Before Windows 3.1, Microsoft's scheme for online help was mechanical and allowed little room for programming creativity. With Windows 3.1, however, the help system blossomed and offered a new degree of programmability and extensibility. Windows 3.1 online help provides a simple set of macros that let you connect built-in actions (such as printing the current topic) to buttons, menu items, accelerator keys, and text or graphical hot spots. More importantly, Windows 3.1 lets you declare external DLL routines to augment the built-in macros, much as Visual Basic and WinWord's Word Basic provide access to external DLL functions.

Once you start exploring these new features, however, you quickly run into limitations of the help system and its documentation. For example, it would be handy if your help file could make decisions at run time, based on the return value of an external DLL function. Unfortunately, Microsoft's Windows 3.1 SDK documentation does not reveal how to do this. In fact, the Windows 3.1 help system has a great deal of functionality that is either only hinted at or not mentioned at all in the documentation Microsoft supplied to SDK programmers. Instead, Microsoft revealed these capabilities in, of all places, the Multimedia Development Kit (MDK).

The MDK manual that documents the Multimedia Viewer (which is used in Microsoft CD-ROM titles such as Cinemania) also documents, almost inadvertently, a variety of new functions in WINHELP.EXE. I say "inadvertently" because I found no place in the Viewer documentation that mentions that these features work the same in WINHELP.EXE as they do in VIEWER.EXE. However, it seems obvious that the Multimedia Viewer is constructed as an extension to the Windows 3.1 help system. The Viewer compiler adds some information not found in a normal help file (mainly, an index of all the words in your help text) and Viewer itself is a compatible replacement for WINHELP.EXE that provides an enhanced user interface. In fact, you can take almost any .HLP file, rename it to a .MVB file (the file extension that Viewer files use), and Viewer will display it with no problems!

The few programmers who purchased the MDK and had any interest in online help discovered that you can have WINHELP.EXE notify your DLL of a variety of useful events (such as when the user jumps to a different help topic or a different help file). This documentation also revealed that you can obtain a set of pointers to useful functions within WINHELP.EXE. The SDK documentation hints that you can add arbitrary files to your Windows help file (by listing them in the [BAGGAGE] section), but only the MDK's Viewer manual exposed the gory details of how you can use WINHELP.EXE's internal functions to perform I/0 to the help file's internal file system and actually read the baggage files you placed there when you compiled the help file. You can also create custom "embedded windows" in your help topics, within which you can perform animation, display 256-color bitmaps, or do anything else you can think of that requires a custom window.

All in all, the Multimedia Viewer manual is a goldmine of programming documentation for creating snazzy online help with WINHELP.EXE. The undocumented key to tapping this goldmine is the fact that WINHELP.EXE supports many of the same features as VIEWER.EXE.

At one time you really had to buy the MDK to uncover these Windows help system features; Microsoft Press published all the manuals in the MDK as standalone books--except for the Multimedia Viewer manual. The street price of the MDK is over $400.00, but you can now acquire this documentation less expensively. The Microsoft Developer's Network (MSDN) CD-ROM has a Help Authoring Guide that contains much of the information that first appeared in the Multimedia Viewer Developer's Guide, with WinHelp examples substituted for Viewer examples. (Incidentally, the MSDN CD-ROM itself is built using the Viewer.) Alternatively, if you have access to CompuServe, you can download the Help Authoring Guide, HAG.HLP in library 6 ("Unsupported Tools") from forum MSDNLIB.

WinHelp()

The Windows API provides a single function, WinHelp(), for managing your application's online help. WinHelp() starts up the help engine (WINHELP.EXE), if it isn't already running, and uses it to display topics in one or more help files. This function marks the dividing line between the programmer's job and the technical writer's job. Everything on the calling side (the application and the parameters it passes to WinHelp()) is the programmer's responsibility, while everything on the callee side (principally, what help text gets displayed) is the technical writer's responsibility. As the arbitrator between programmers and writers, it's no wonder that WinHelp() can be the focus of some exasperation during application development.

WinHelp() really performs several different functions, just as a window call-back procedure does. In fact, WinHelp()'s parameter list looks a bit like a window callback procedure. The first two arguments, a window handle and help-file name, are basically for identification; they identify the calling application and the help file to operate on. The third argument is an integer "command" that specifies the action WinHelp() should take, and the final argument is a long integer of "additional data" whose meaning depends on the command selected. Many variations are possible, but most applications interact with WinHelp() in a fairly simple way. Applications typically supply a menu selection that lets the user see the help file's index. Selecting that menu item usually results in a call like this:

WinHelp(hWnd, "myhelp.hlp",
HELP_CONTENTS, 0);

The user can then traverse the help file interactively without further help from the application.

A good application will offer context-sensitive online help. That requires that every menu item and dialog-box control have some associated help topic that explains its function. So long as the programmer assigns each such item a unique ID, this is fairly straightforward. In compiling the help file, the technical writer typically includes the same file of #defines that the programmer uses. The technical writer will also have to include a section that tells the help compiler which help topic to display for each of the #defines of interest. The application responds to a request for online help with a call something like this:

WinHelp(hwnd, "myhelp.hlp",
HELP_CONTEXT, CtrlId);

You can also associate help topics with keywords, and applications can request that the topic associated with a particular keyword be displayed. WINHELP.EXE has other, more esoteric features, but most applications do not get beyond the basics.

This is all well and good, but a good-sized application can have a great many items that require context-sensitive help, and getting them all correct can be tedious. Worse, WinHelp() provides little in the way of useful information when things go wrong. For example, if the programmer adds a menu item that the technical writer (and hence, the help file) doesn't know about, requesting context-sensitive help for that menu item produces the error message "Topic not found." Other kinds of mistakes can result in no feedback at all.

I decided that it would be nice to have a HelpSpy utility that displayed each call to WinHelp(), including the details of the parameters passed. That would give technical writers enough information when errors occurred that they could solve many problems without waiting for a programmer. The utility would also give programmers an easy way to verify that they were passing their parameters to WinHelp() correctly--some of the more esoteric WinHelp() commands have several fields of data to get right. A more peripheral benefit of a HelpSpy utility would be to allow you to spy on help files you did not create.

Looking for WinHelp() Messages

Unfortunately, there's no documentation on how WinHelp() executes and communicates with WINHELP.EXE. The most direct way to spy on WinHelp() would be to take over the function itself. Although it's possible to intercept any Windows function on-the-fly by patching its entry point, I decided to snoop around first and see exactly how WinHelp() communicates with WINHELP.EXE. WinHelp() could have been designed to communicate with WINHELP.EXE in a number of ways, but I started by looking at window messages. I used WinSight, a tool for spying on window messages that comes with Borland C++ 3.1. WinSight is based on Michael Geary's original Spy, which appeared in the 1987 special issue of BYTE on the IBM PC. WinSight displays the window-class names of the help windows that WINHELP.EXE creates as well as any messages they receive. Sure enough, WinSight showed that every time I issued a call to WinHelp(), the main help window received a registered message called WM_WINHELP.

Registered messages are a method Microsoft recommends for interapplication messaging. Each application that wants to communicate can call RegisterWindowMessage() with the same symbolic name and receive a unique message number that doesn't conflict with any existing window message numbers. The authors of WinSight apparently did a little reverse-engineering of their own to display the symbolic string associated with a registered window message, since the SDK does not document how to discover the strings associated with a given registered message number. [Editor's Note: As explained in Undocumented Windows, pp. 140-1, registered window messages are located in USER's atom table. Going backward from a registered message number to a string is simply a matter of calling GetAtomName() with USER's DS.] This feature of WinSight made my snooping easier--now I knew what string to pass to RegisterWindowMessage() to obtain the number of the window message I needed to intercept.

As WinSight showed the messages arriving, I watched the values for wParam and lParam. I knew that reverse-engineering the data should be fairly easy, since it had to contain all of the data I was passing to WinHelp(). Every call to WinHelp() includes a window handle and a pointer to the pathname for the help file, so I started by looking for these. It was easy to see that wParam was simply the window handle passed to WinHelp().

That left lParam for transmitting all of the rest of the WinHelp() parameter's data. At every call, the upper 16 bits of lParam seemed to be 0. About the only way I could see that 16 bits of data could transmit an arbitrary amount of data was if this was a handle to global memory. At that point, I put away WinSight and started writing code.

Hooking Messages

Windows makes it fairly easy to intercept window messages, even in other applications. I used SetWindowsHookEx(), passing it the WH_CALLWNDPROC option, as you can see in the finished program in Listing One (HELPSPY.C), page 169. The Microsoft Windows 3.1 SDK documentation claims that the hook function you pass to SetWindowsHookEx() "must be in a dynamic-link library." This is not true, but you do have to know what you are doing if you want to put the hook function inside an application instead of in a DLL.

For small applications, I don't go to the trouble to create a separate DLL just to contain a hook function. Also, I usually avoid using MakeProcInstance() in my Windows programs, since the latest crop of Windows compilers can nearly eliminate the need for it. Since the hook function will get called in the context of another application (WINHELP.EXE, in this case), I made sure the compiler-generated code that did not assume that the stack was in the default data segment (in other words, SS != DS). As with any exported function, the compiler had to generate prologue code to obtain the correct value of the local data segment (DS) for the hook function. I was not using MakeProcInstance(), so the correct value would not be passed in the AX register. This was a hook function, so the correct value could not be obtained from the SS register either. The only option remaining was to load the DS from DGROUP. Loading from DGROUP does prevent you from running more than one instance of HelpSpy, but I could not see any use in running more than one instance anyway.

Windows compiler options can be arcane. I try to make my code as vendor independent as possible, so I've written a simple program, CC.EXE (see "Availability," page 7), that provides a standard interface to the C/C++ compilers of Microsoft, Borland, and Symantec. Listing Two (page 169) shows the makefile that uses CC.EXE to create HELPSPY.EXE for any of the three vendors' compilers. CC.EXE handles creating a default module definition file and running rc to append the resource file to the executable.

Dumping WM_WINHELP

After I wrote code to hook the desired message, I wanted to test my theory that the lower 16 bits of lParam were a handle to global memory. Whenever my hook function received the registered message WM_WINHELP, it did a global lock on the lower 16 bits of lParam, and then unlocked it. This stub gave me a nice place to set a breakpoint in a debugger to dump the contents of the global memory. Figure 1 shows the hex dump I got from a call to:

  WinHelp():WinHelp(Window,   "C:\WIN3.1\APPS.HLP",   HELP_CONTENTS, 0);

Figure 1: Dump of WM_WINHELP global memory.

  WinHelp(Window, "C:\WIN3.1\APPS.HLP", HELP_CONTENTS, 0);

  0000: 23  0  3  0  0  0   0  0  0  0  0  0 10  0  0  0  #..............
  0010: 43 3a 5c 57 49 4e  33 2e 31 5c 41 50 50 53 2e 48 C:\WIN3.1\APPS.H
  0020: 4c 50  0 45 4c 50  2e 45 58 45  0  0  0  0  0  0 LP.ELP.EXE......
  0030:  0  0  0  0  0  0   0  0  0  0  0  0  0  0  0  0  ...............


  int Size = 0023h
  int Message = 3 (HELP_CONTENTS in WINDOWS.H)
  long Context = 0
  long Unknown = 0
  int PathOffset = 10h int StringOffset = 0
  char Path[] = "C:\WIN3.1\APPS.HLP"

By staring at Figure 1 and similar dumps, the format became obvious, and I started creating the structure I call HelpParams to describe it. There seemed to be some garbage at offset 23h, and the first word in the block was 23h, so I decided that the first word specified the length of the data structure (a common-enough practice for operating-system data structures) and I added an integer Size field to HelpParams. The value of the constant HELP_CONTENTS is 3 (see WINDOWS.H), which was precisely the value of the second word in the data structure, so I added an integer field called Message to HelpParams. The string containing the name of the help file started at offset 10h, which was also the value of the seventh word of the data structure, so I added a field called PathOffset to HelpParams.

Now the process became one of elimination. I passed in each of the various WinHelp() commands to see where the data was stored in the global memory array. You can see the final version of HelpParams in Listing One. Most of the messages are quite simple, but a few, such as HELP_SETWINPOS are more complex. In general, whenever a command required a variable-length structure or string, an offset to that structure or string appeared immediately after PathOffset, so I named that field StringOffset. Whenever a command required a help topic context number, that number (a long) appeared right after the help command, so I called that field Context.

That left a hole in my data structure, an integer that I named Unknown. I never saw it take on a value other than 0, so it seems likely this field is reserved for future use. The Context field was a little anomalous for commands that did not require a help topic context number. For some commands (HELP_COMMAND, HELP_PARTIALKEY, and HELP_SETWINPOS) it was always set to -1. Otherwise, it was 0. The exception was HELP_MULTIKEY, for which the Context field seemed to get set to more or less random values. In any case, I had found all of the data passed to WinHelp(), so I was ready to finish writing HelpSpy.

One way to quickly write small Windows applications is to use dialog boxes, and that is what I did with HelpSpy. Listing One (HELPSPY.C) shows the complete source, with the header file in Listing Three (HELPSPY.H), page 169, and the dialog-box definition in Listing Four (HELPSPY.RC), page 169. The main window is simply a dialog box whose entire client area is occupied by a list box. At initialization time, I store a handle to that list box in a global variable. When the hook routine receives a WM_WINHELP message, it produces a formatted dump of the message parameters and adds the resulting string to the list box. This lets you see the messages as they arrive and scroll back to look at the history of what has happened. Using a dialog box gave me a simple but useful user interface with very little coding. Figure 2 shows HelpSpy in action.

Earlier, I mentioned the close relationship between WinHelp and the Multimedia Viewer. In fact, you can use Viewer as the engine for your online help files. The MDK supplies a DLL with a function called "MVAPI" that provides almost exactly the same interface as WinHelp(). By also monitoring the registered message that Viewer uses for communication ("WM_WINDOC"), HelpSpy could work for Viewer as well, so I added a check for that message.

Conclusion

Most programmers know that users only read manuals as a last resort. That being the case, we need to invest our programming talent in online help as much as in any other part of the user interface. As we've seen in Windows, surprisingly, sometimes this will require discovering and depending upon undocumented information.

Microsoft documented the file format of WinHelp's ancestor (Microsoft's DOS help system) in the little-known manual, Microsoft Professional Advisor Library Reference. The format of Windows help files bears only some resemblance to DOS help files (both use a form of rich text format to describe the help text), and Microsoft continues to keep the new .HLP and .MVB formats proprietary despite regular requests from developers. The pressure to liberate the .HLP file format can only grow as more programmers discover the creative possibilities buried in WINHELP.EXE.



_UNDOCUMENTED CORNER_
edited by Andrew Schulman

"Spying on WinHelp"
by Ron Burk


[LISTING ONE]
<a name="01aa_000c">

/* helpspy.c - spy on WM_WINHELP messages, system-wide */

#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include "hookmsg.h"
#include "helpspy.h"

#define MAX_STRING      (256)


LRESULT CALLBACK _export HookProc(int, WPARAM, LPARAM);
BOOL CALLBACK _export DlgProc(HWND, UINT, WPARAM, LPARAM);

UINT        WM_WINHELP; /* a registered window message */
UINT        WM_WINDOC;  /* the one that Viewer uses    */
HWND        ListBox;    /* listbox to display messages */
HICON       MyIcon;

typedef struct  HelpParams {
    int     Size;
    int     Message;
    long    Context;
    long    Unknown;
    int     PathOffset;
    int     StringOffset;
    char    Path[1];
    }           HelpParams;
static  HHOOK   MsgHook;
#ifdef __BORLANDC__
    #pragma argsused
#endif
int PASCAL WinMain(HINSTANCE Me, HINSTANCE Previous,
             LPSTR lpszCmdLine, int nCmdShow)    {
    if(Previous) {
        MessageBox(NULL, "HelpSpy already running.", "HelpSpy", MB_OK);
        return 0;
        }
    MyIcon      = LoadIcon(Me, "HelpSpyIcon");
    WM_WINHELP  = RegisterWindowMessage("WM_WINHELP");
    WM_WINDOC   = RegisterWindowMessage("WM_WINDOC");
    MsgHook     = SetWindowsHookEx(WH_CALLWNDPROC, HookProc, Me, NULL);
    DialogBox(Me, "HelpSpyDialog", NULL, DlgProc);
    UnhookWindowsHookEx(MsgHook);
    return 0;
    }
/* HookProc - pass only WM_WINHELP messages to DumpWinHelp(). */
LRESULT CALLBACK _export HookProc(int Code, WPARAM Param1, LPARAM Param2) {
    void    DumpWinHelp(UINT Message, LPARAM Param2);
    typedef struct HMSG {
        LPARAM  lParam;
        WPARAM  wParam;
        UINT    message;
        HWND    hwnd;
        }   HMSG;
    HMSG *Message = (HMSG *)Param2;
    if(Message->message == WM_WINHELP || Message->message == WM_WINDOC)
        DumpWinHelp(Message->message, Message->lParam);
    if(Code < 0)

        CallNextHookEx(MsgHook, Code, Param1, Param2);
    return 0;
    }
/* DumpWinHelp() - dump a WM_WINHELP message. */
void    DumpWinHelp(UINT Message, LPARAM Param2) {
    char        String[MAX_STRING];
    char       *Command = "HELP_UNKNOWN";
    HGLOBAL     Handle  = (HGLOBAL)Param2;
    HelpParams  *Params = (HelpParams *)GlobalLock(Handle);
    switch(Params->Message) {
        case HELP_CONTEXT:      Command = "HELP_CONTEXT";       break;
        case HELP_CONTEXTPOPUP: Command = "HELP_CONTEXTPOPUP";  break;
        case HELP_CONTENTS:     Command = "HELP_CONTENTS";      break;
        case HELP_SETCONTENTS:  Command = "HELP_SETCONTENTS";   break;
        case HELP_KEY:          Command = "HELP_KEY";           break;
        case HELP_PARTIALKEY:   Command = "HELP_PARTIALKEY";    break;
        case HELP_MULTIKEY:     Command = "HELP_MULTIKEY";      break;
        case HELP_COMMAND:      Command = "HELP_COMMAND";       break;
        case HELP_SETWINPOS:    Command = "HELP_SETWINPOS";     break;
        case HELP_FORCEFILE:    Command = "HELP_FORCEFILE";     break;
        case HELP_HELPONHELP:   Command = "HELP_HELPONHELP";    break;
        case HELP_QUIT:         Command = "HELP_QUIT";          break;
        }
    sprintf(String, "%s(%s", (Message == WM_WINHELP)
        ? "WinHelp" : "MVAPI", Command);
    switch(Params->Message) {
        case    HELP_SETCONTENTS    :
        case    HELP_CONTEXT        :
        case    HELP_CONTEXTPOPUP   :
            sprintf(String+strlen(String), ",%ld)", Params->Context);
            break;
        case    HELP_KEY            :
        case    HELP_PARTIALKEY     :
        case    HELP_COMMAND        :
            sprintf(String+strlen(String), ",'%s')",
                    (char *)Params + Params->StringOffset);
            break;
        case    HELP_CONTENTS       :
        case    HELP_FORCEFILE      :
        case    HELP_HELPONHELP     :
        case    HELP_QUIT           :
            strcat(String, ")");
            break;
        case    HELP_SETWINPOS      :   {
            HELPWININFO *Info = (HELPWININFO *)
                    ((char *)Params + Params->StringOffset);
            sprintf(String+strlen(String),
                   ",x=%d,y=%d,dx=%d,dy=%d,wMax=%d,\"%s\")",
                       Info->x, Info->y, Info->dx, Info->dy,
                              Info->wMax, Info->rgchMember);
            break;
            }
        case    HELP_MULTIKEY       :   {

          MULTIKEYHELP *MultiKey    = (MULTIKEYHELP *)
                    ((char *)Params + Params->StringOffset);
          sprintf(String+strlen(String), ",'%c', '%.*s')", MultiKey->mkKeylist,
            MultiKey->mkSize-sizeof(UINT)-sizeof(BYTE), MultiKey->szKeyphrase);
            break;
            }
        default :
            strcat(String, ")");
        }
    GlobalUnlock(Handle);
    SendMessage(ListBox, LB_INSERTSTRING, 0, (LPARAM)(LPCSTR)String);
    }
/* DlgProc() - Store listbox handle in global variable. */
#ifdef __BORLANDC__
    #pragma argsused
#endif
BOOL CALLBACK _export DlgProc(HWND Dialog, UINT Message,
                             WPARAM Param1, LPARAM Param2) {
    if(Message == WM_INITDIALOG) {
        ListBox     = GetDlgItem(Dialog, ID_LISTBOX);
        return TRUE;
        }
    else if(Message == WM_PAINT && IsIconic(Dialog))
        {
        PAINTSTRUCT PaintInfo;
        BeginPaint(Dialog, &PaintInfo);
        DrawIcon(PaintInfo.hdc, 0, 0, MyIcon);
        EndPaint(Dialog, &PaintInfo);
        }
    else if(Message == WM_COMMAND)
        if(Param1 == IDOK || Param1 == IDCANCEL) {
            EndDialog(Dialog, 0);
            return TRUE;
            }
    return FALSE;
    }







<a name="01aa_000d">
<a name="01aa_000e">
[LISTING TWO]
<a name="01aa_000e">


APP     =helpspy
OBJ     =$(APP).obj
MODEL   =-ml!
#DEBUG   =-d
CCFLAGS =$(DEBUG) $(MODEL) -DSTRICT -wed

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

.c.obj :
    cc -c $(CCFLAGS) $*.c
$(APP).exe : $(OBJ) $(APP).res makefile
    cc $(DEBUG) $(MODEL) $(OBJ) $(APP).res
$(APP).res  $(APP).obj : $(APP).h






<a name="01aa_000f">
<a name="01aa_0010">
[LISTING THREE]
<a name="01aa_0010">

#include <windows.h>

#define ID_LISTBOX  101
#define MENU_HELP   201




<a name="01aa_0011">
<a name="01aa_0012">
[LISTING FOUR]
<a name="01aa_0012">

#include "helpspy.h"

HelpSpyDialog DIALOG 15, 131, 306, 41
STYLE WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX
CAPTION "HelpSpy"
BEGIN
    CONTROL "", ID_LISTBOX, "LISTBOX", LBS_NOTIFY | LBS_DISABLENOSCROLL |
        WS_CHILD | WS_VISIBLE | WS_VSCROLL, 2, 0, 304, 42

END
HelpSpyIcon ICON "helpspy.ico"












Copyright © 1993, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.