Writing DLLs for Windows Help
Part 2: Communicating with WinHelp
By Jim Mischel
This article first appeared in The WinHelp Journal, Summer 1995.
Download the listings for this article (whdll2.zip, 13KB)
In the last issue of The WinHelp Journal, I showed how you can write custom macros that you can call from your WinHelp files. Custom macros can be quite useful all by themselves, but if you want to make something really special, you have to write your DLL so that it will respond to the messages that WinHelp sends it. In this installment, we'll look closely at the WinHelp DLL interface and you'll see how to make your DLLs do even more.
The WinHelp DLL Interface
When WinHelp loads your DLL, it calls Windows' standard loader function, which locates the DLL on your hard drive, loads it into memory, and executes the DLL's startup code (normally the LibMain() function). Once Windows is done doing its thing, WinHelp regains control and looks into your DLL for an exported function called LDLLHandler(). The C prototype for LDLLHandler() is:
LONG FAR PASCAL _export LDLLHandler (WORD wMsg,
LONG lParam1, LONG lParam2);
As you saw in the last installment, your DLL doesn't have to contain one of these functions. However, if you want to be notified of WinHelp events, this function must exist in your DLL, and must be exported. LDLLHandler() is your DLL's "hook" into WinHelp, and receives messages when WinHelp performs specific functions.
If WinHelp finds LDLLHandler() in your DLL, WinHelp calls it and passes a DW_WHATMSG message in the wMsg parameter. In response to the DW_WHATMSG message, LDLLHandler() must return a value to WinHelp that describes the types of messages that you want WinHelp to send.
WinHelp Message Types
There are eleven different messages that WinHelp can pass to your LDLLHandler() in the wMsg parameter. These messages are broken out into five different message types, and one--DW_WHATMSG--doesn't belong to any group. The five different message types and their corresponding messages are shown in Table 1, and a more detailed description of each message is given in Table 2. Please note that all of the constants (DW_WHATMSG, DC_CALLBACKS, etc. are defined in the file DLL.H, which is supplied by Microsoft in the Help Authoring Guide).
Table 1 -- WinHelp message types
Table 2 -- The WinHelp messages that are sent to DLLs
What these messages allow you to do is hook into WinHelp and keep track of what's going on. Your DLL can then do some custom processing whenever a particular message is received. For example, a DLL that provides a table of contents view of a WinHelp file would want to update its display whenever a DW_ENDJUMP or DW_CHGFILE message is received, indicating that the user has moved to a new topic, or an entirely new file.
So, How do I do It?
The structure of LDLLHandler() is very similar to the structure of a standard window procedure--a big switch statement that has a case for each individual message. Once you get the structure down, the rest is pretty simple--just fill in the blanks.
When your DLL gets the DW_WHATMSG message, it constructs a return value by "ORing" together the values that correspond to the message types it wants to receive, and then returns that value to WinHelp. For example, if you want your DLL to receive all WinHelp message types, then you'd write:
return (DC_CALLBACKS | DC_ACTIVATE | DC_JUMP |
DC_MINMAX | DC_INITTERM);
And if you want to receive only the callbacks and initialization/termination messages, you'd write:
return (DC_CALLBACKS | DC_INITTERM);
With the exception of DW_INIT, the return value for all of the other messages should be TRUE if you want WinHelp to continue to send that message in the future, or FALSE if you don't want WinHelp to continue sending the message. The return value for DW_INIT should be TRUE if all initialization was performed successfully, of FALSE if an error occurred during DLL initialization. If DW_INIT returns FALSE, WinHelp will unload the DLL.
DLLEX.C (Listing 1) contains a very simple LDLLHandler() function that creates an event log that contains one line for every message received by the DLL. It also contains a custom macro--InitDLLex()--which doesn't do anything at all. This macro's sole purpose is to give your help files something to call so that WinHelp will load the DLL. Without this function, WinHelp would never know that you wanted to load the DLL, so the event log would never be written.
To create DLLEX.DLL, compile DLLEX.C in large memory model, and link using the supplied DLLEX.DEF (Listing 2). To test, create a simple one-topic help file that contains a call to the InitDLLex() macro in the [CONFIG] section of the help project file. For example, your [CONFIG] section might look like this:
After your help file loads, skip to other topics, move and size the window, switch out of WinHelp and back in, and load new WinHelp files by using the File|Open menu option. When you exit WinHelp, pull up the EVENT.LOG file into your favorite editor and examine the output.
WinHelp Internal Functions
There's not enough space to discuss WinHelp's internal functions in detail. Here's the quick rundown. The lParam1 parameter to the DW_CALLBACKS message is a pointer to a table of WinHelp internal functions that your DLL can use to obtain information about the current state of WinHelp, and also read and write WinHelp files. In all, there are 25 of these callback functions, of which 16 are documented by Microsoft. The other nine, the ones that let you modify WinHelp files, are undocumented but do appear to work. For more information on the documented functions, consult the Help Authoring Guide or my book, The Developer's Guide to WINHELP.EXE. The undocumented callbacks that let you modify WinHelp files are a fairly recent discovery, and are documented in the "Power Programming" column of PC Magazine, Volume 14 issues 13 and 15, 1995.