Writing DLLs for Windows Help

Part 1: Creating Custom Macros

By Jim Mischel

This article first appeared in The WinHelp Journal, Spring 1995

Download the listings for this article (whdll1.zip, 10KB)

Windows Help is a very powerful online documentation tool, and has a wide range of built-in features that make it possible for help developers to create effective help files. There are times, though, when the tools provided by WinHelp just aren't quite enough to provide the special look and feel that you want your help files to have. Fortunately, the WinHelp architects at Microsoft recognized this and provided an interface that gives developers the ability to extend WinHelp's functionality through Windows dynamic link libraries (DLLs).

WinHelp DLL functions fall into three broad categories:

  • Functions that implement custom macros
  • Functions that respond to WinHelp messages
  • Functions that implement embedded windows

The articles in this series will discuss custom macros and DLL functions that respond to WinHelp messages. Embedded windows are a completely separate subject which would require several installments on their own.

The articles in this series assume that you have some familiarity with C programming under Windows, and that you are able to create a Windows dynamic link library. The code examples have all been tested using Borland C++ version 4.02, and should be easily portable to other Windows development systems.

Custom WinHelp Macros

If you've done much Windows Help development, you're familiar with macros--built-in WinHelp functions that you can call from your help files to make WinHelp do any number of strange and wonderful things. BrowseButtons, for example, is a macro that causes WinHelp to place the >> and << browse buttons on the button bar. Other standard macros create and destroy buttons, modify menus, execute dialogs, and manipulate Help windows, among other functions. The documentation that came with your help development system should provide a good reference to the standard macros. A complete reference to all of the WinHelp macros, including undocumented macros, can be found in my book, The Developer's Guide to WINHELP.EXE.

As useful as they are, the standard WinHelp macros can't hope to implement every function that a developer might want to perform from within a help file. For example, you may have a custom piece of hardware that your help file must control. The standard help macros can't access this special hardware, but a custom macro written in a DLL could do so easily.

Creating a Custom Macro

Once you've figured out what you want a custom macro to do and what parameters you're going to pass to it, you need to write the C function that will implement the macro. If you've ever written a DLL in C, then you're already familiar with the process.

We're going to create three custom macros. They are:

Beep -- "Beeps" the speaker. This macro accepts no parameters and doesn't return a value.

DisplayMessage -- Displays a user-defined message in a Windows message box. This macro accepts a string parameter and doesn't return a value.

GetYesNo -- Displays a prompt in a Windows message box and waits for a Yes or No answer. This macro accepts a string and an integer as parameters, and returns 1 if the "Yes" button was pressed, or 0 if the "No" button was pressed.

The C prototypes for these three functions are:

void _far _pascal _export Beep (void);

void _far _pascal _export DisplayMessage (char far *Message);

int _far _pascal _export GetYesNo (char far *Question, int Default);

The actual source code for the three functions is shown in Listing 1, HELPMACS.C. The module definition file, HELPMACS.DEF, is shown in Listing 2, and the make script that's used to create the DLL (HELPMACS.MAK) is shown in Listing 3. To create the DLL using Borland C++, just enter the command "make -fhelpmacs" at the DOS prompt.

As you can see from Listing 1, there's nothing unique about a WinHelp DLL. Nothing in this DLL is WinHelp-specific, and the functions could easily be called by other Windows programs. In the next installment, you'll see that there are WinHelp-specific things you can do in a DLL, but those things need not concern us for now.

Granted, none of these functions is very complex. They don't really "do" anything, other than provide an example of how to create DLL functions that are callable from WinHelp. Once you've got the basics down, you can create WinHelp macros that perform more complex operations.

Registering the Macros with WinHelp

After you've compiled the DLL, you need to tell WinHelp where to find your custom macros. You do this by registering your macros with the RegisterRoutine help macro. RegisterRoutine tells WinHelp where to find the macros and what parameters the macros expect. The syntax for RegisterRoutine is:

RegisterRoutine ("dll-name", "function-name", "format-spec")

where dll-name is the name of the DLL that contains the custom macro, function-name is the name of the function (macro) within the DLL, and format-spec is a string that defines the number and types of parameters that are to be passed to and/or returned from the macro. You should note that function-name defines not only the name of the function in the DLL, but also the name by which the custom help macro is to be referred to when you call it. So, for example, the Beep() macro, which accepts no parameters, would be registered like this:

RegisterRoutine ("helpmacs.dll", "Beep", "")

The blank string ("") indicates that the macro doesn't accept any parameters or return a value.

The only tricky part about RegisterRoutine is getting the format-spec right. The valid format characters and their associated meanings are shown in Table 1.

Character Parameter Type C Variable Type
u An unsigned short integer unsigned short or WORD
U An unsigned long integer unsigned long or DWORD
i A signed short integer short int
I A signed long integer long
s A near (16-bit) string pointer (rarely used) near char * or PSTR
S A far (32-bit) string pointer far char * or LPSTR
v No type (rarely used) void
= Indicates that the previous character is the return value type N/A
  • Table 1 - RegisterRoutine Parameter Format Characters

  • Normally, you place RegisterRoutine macros in the [CONFIG] section of your help project file, so that the custom macros are registered when the help file is first loaded. For example, the [CONFIG] section of a help project file that uses our three example macros would look something like this:


    RegisterRoutine ("helpmacs.dll", "Beep", "")

    RegisterRoutine ("helpmacs.dll", "DisplayMessage", "S")

    RegisterRoutine ("helpmacs.dll", "GetYesNo", "i=Si")

    Note that RegisterRoutine doesn't actually execute the custom macros--it just defines them and tells WinHelp where to find them. In fact, RegisterRoutine doesn't even confirm that the specified DLL exists or that the macros exist in that DLL. You must attempt to execute a custom macro before WinHelp tries to find it.

    Calling the New Macros

    Once you've registered your new macros with WinHelp, you're free to call them just as you would call any other help macro. For example, to create a hot spot that "beeps" the speaker when it's selected, you would write this RTF code:

    Press to {\uldb beep.}{\v !Beep()}

    Similarly, you can display a message by executing the custom DisplayMessage macro, like this:

    {\uldb Hello, world}{\v !DisplayMessage("Hello, world")}

    The GetYesNo custom macro displays a message and waits for a Yes or No response from the user, which it then returns. You can test the return in your help file with the IfThen or IfThenElse standard macros. For example, to get a Yes or No answer, and then display a popup based on the returned response, you would write:

    {\uldb Yes or No?}{\v !IfThenElse(GetYesNo("Yes or No?", 0),

    "PopupId (qchPath, `YesAnswer'')", "PopupId (qchPath, `NoAnswer')")}

    This will prompt the user with the question, wait for a response, and then display the "YesAnswer" topic if the user pressed the "Yes" button, or the "NoAnswer" topic if the user pressed the "No" button. The second parameter to GetYesNo indicates which of the two buttons should be the default. If the parameter is 0, the "No" button is the default. Otherwise, the "Yes" button is the default.

    A sample topic file, TESTM.RTF, which illustrates the use of these three macros, is shown in Listing 4. The corresponding help project file, TESTM.HPJ, is shown in Listing 5. To create the test help file, execute the command "HC TESTM" at the DOS prompt.

    To test the file, make sure that you've created the DLL and the test help file as described above. Then, load the file TESTM.HLP in WinHelp and click on the hot spots to exercise the new macros.

    How WinHelp Locates DLLs

    If you get a "Routine not found" error message from Windows Help, then most likely WinHelp can't find the DLL. When you tell WinHelp to execute a custom macro, it looks in the following places, in this order, for the DLL:

    1. WinHelp's current directory (the current working directory)
    2. The MS-DOS current directory
    3. The user's Windows directory
    4. The Windows SYSTEM directory
    5. The directory containing WINHELP.EXE
    6. The directories listed in the PATH environment variable
    7. The directories specified in the [Files] section of WINHELP.INI

    If WinHelp can't find the DLL in any of these places, then it displays a "Routine not found" error message.

    When testing custom macros your best bet is to create a program item for the test help file, and set the Working Directory to the directory that contains the DLL. If you distribute help files that make calls to custom macros, you should install the DLLs that contain the macros into the user's Windows or Windows SYSTEM directory.

    What's Next

    Creating custom macros that are callable by Windows Help is a fairly straightforward process. The only tricky part is matching up the format-spec parameter to RegisterRoutine with the parameters that your help macro expects. Once you have that right, everything else falls into place.

    Although the help macros that we developed in this article aren't very useful, the techniques we discussed will allow you to create any number of useful macros that interact with the user or get information from the system. Next time, we're going to discuss the LDLLHandler function, and see how we can use it to make our DLLs respond to messages that are sent by Windows Help.