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

Open Source

Inside the WinHelp() API Function


December 1996: Inside The WinHelp() Function

Inside the WinHelp() API Function

A cross-platform implementation

Paul Kissel

Paul, a developer for Willows Software, can be contacted at [email protected].


Since its inception, Windows has offered developers a standardized way of providing rich, online help for applications. While much has been written about the use of the WinHelp() API function and how to create a .HLP file, little information has been published about the relationship between WinHelp() and the WINHELP.EXE help-viewing program, or about the data file of a compiled .HLP help file.

Willows Software, the company I work for, offers a cross-platform development toolkit called TWIN APIW, a set of tools and libraries that extends the number of platforms upon which Windows API-based applications can be deployed. With the TWIN APIW, you can use a single set of source files to develop and deploy applications for Windows, UNIX, and MacOS.

To be comprehensive, TWIN APIW needed to provide the same degree of help support that Microsoft offers to Windows API-based applications. Consequently, Willows asked me to research and build a platform-independent implementation of the WinHelp() function and of the WINHELP.EXE help-file viewing program for inclusion in TWIN APIW. In this article, I'll discuss some of the information I discovered during my work.

Help-System Components

As Figure 1 illustrates, the Windows help system has two distinct parts: the WinHelp() function called by a Window application and WINHELP.EXE, the .HLP file-viewing program.

Typically, the help system shows the user a help topic for an application. The application calls WinHelp() and supplies it with the number of the help topic and the name of the help file in which the topic is located. WINHELP.EXE is then started and it displays the correct help information to the user.

Can We Talk?

How does WinHelp() tell WINHELP.EXE what information to display to the user? Ron Burk's "Undocumented Corner" article, entitled "Spying on WinHelp" (DDJ, June 1993), offers an explanation. Ron revealed that WinHelp() passes information to WINHELP.EXE using an undocumented window message called WM_WINHELP. Both WinHelp() and WINHELP.EXE register the message by calling the RegisterWindowMessage() API function. WinHelp() sends information to WINHELP.EXE in the WPARAM and LPARAM parameters of the message. WPARAM passes WINHELP.EXE the window handle of the application calling WinHelp(). LPARAM passes WINHELP.EXE a data structure filled with other help information, such as the help topic's number and the name of the help file to open. Ron also described the definition of the data structure and discussed how information is stored in it for each type of WinHelp() call. For more information about the data structure, run the sample application provided with this article, APITEST.EXE (available electronically).

Which is the Real Window?

Using Ron's article, I figured out how to translate and send WINHELP.EXE the information that is supplied to WinHelp(). But there were still a few puzzling questions, among them, understanding which help window should be sent the WM_ WINHELP message. If you've ever run the WINHELP.EXE program directly to view a .HLP file, you will have noted that WINHELP.EXE is a multi-instance application. You can run it over and over and each time it is started, it will create a new help window. So how does WinHelp() know which window it should send the WM_WINHELP message to? The answer is surprisingly simple and explains why the help system works the way it does.

You have probably noticed that the help window shown when you request help is actually a shared help window. If you are viewing an application's help file and then switch to another open application and ask for help, the existing help window is reset to display the help topic requested in the new application. In truth, there is only one help window created for and affected by any call to WinHelp(). I call this first type of help window the MS_WINHELP help window and the other type (the help window that is created when WINHELP.EXE is run) an MS_WINDOC help window. As I'll soon discuss, these are the names of the respective window types. You can create as many MS_WINDOC windows as you want and they will never be affected by any application's call to WinHelp().

From the user's perspective, the help windows appear and behave identically, but WinHelp() has some way to distinguish between the two so that it can send WM_WINHELP messages to the sole help window of type MS_WINHELP.

Getting a Handle On It

My first guess was that WINHELP.EXE was somehow telling WinHelp() the handle of the MS_WINHELP window that it created. WINHELP.EXE could store the window's handle in a global atom or call the WinHelp() function itself with some undocumented parameter that only it and WinHelp() know about. The problem with reverse engineering is that you can never be 100 percent sure how something may work. Often, the best thing to do is ask yourself how you would perform the task and try to think like the people who designed the software.

An undocumented parameter was not the right answer, however. Using documented Windows functions (such as those found in the TOOLHELP.DLL), I wrote an inspection program to view the window properties for a specific window. When I looked at the properties for each type of help window, I discovered that their only difference was that each window type had a unique class name--MS_WINHELP for the window type that only WinHelp() addresses, and MS_ WINDOC for the other type. I decided that WinHelp() probably uses this difference to identify which window is the current MS_WINHELP type window. Since there is only one window of this type at any one time, WinHelp() could simply call the documented FindWindow() function to get the MS_WINHELP window's handle. Supplied with the name of a class, FindWindow() will either return the handle of a window of that class or return NULL if no window of that class exists.

But how does WINHELP.EXE know that it should create a MS_WINHELP window when WinHelp() can't find one? And, since the normal operation of WINHELP.EXE is to create a visible help window of type MS_WINDOC when it is run, why doesn't it create one when it is started by WinHelp()?

Creating the MS_WINHELP Window

It's obvious that WinHelp() doesn't just run WINHELP.EXE when it can't find an MS_WINHELP window. If it did, then WINHELP.EXE would behave just as it does when a user starts the program. Somehow, WINHELP.EXE must know that it is being started by WinHelp(). Again, my first guess was that there was an undocumented command-line option to let WINHELP.EXE know that it was being started by WinHelp(). This time I was right. I replaced the real WINHELP.EXE program with a brain-dead program of the same name that did nothing but show the command-line data that was passed to it. Happily, I saw that the command-line argument "-x" was passed to WINHELP.EXE when it was started by WinHelp(). To check this, I started the real WINHELP.EXE program with the "-x" parameter, and it didn't create an MS_WINDOC window.

There was still the question of exactly when WINHELP.EXE creates the MS_WINHELP window for WinHelp(). Running WINHELP.EXE with the "-x" argument didn't cause the window to appear on the screen. I hypothesized that WinHelp() might be using an undocumented call to tell WINHELP.EXE to create an MS_WINHELP window. This time I was wrong. I found that, while WINHELP.EXE didn't seem to do anything when it was run by WinHelp(), it actually did create a hidden MS_WINHELP window that is unhidden when it receives its first WM_WINHELP message.

The Final Algorithm

With all of the basic questions answered, it was time to build a prototype of my WinHelp() function. The algorithm implemented in APITEST.EXE is as follows:

1.Call the FindWindow() function to find the handle of the MS_WINHELP help window.

2.If the FindWindow() function returns a valid window handle, proceed to step 3; otherwise, run WINHELP.EXE with the "-x" parameter using the WinExec() function and then go back to step 1.

3.Build a data structure containing the help information and send it and the handle of the application's window to the MS_WINHELP window via a WM_WINHELP message.

Conclusion

APITEST.EXE fills in any details that have not been covered in this article (the WM_

WINHELP message, for example), including my own implementation of the WinHelp() function. The program lets you enter the parameters for each type of WinHelp() call, then uses my platform-independent implementation of the WinHelp() function to send a WM_WINHELP message to the real WINHELP.EXE program included with Windows. To learn more about the parameters to use in the program, see the description of the WinHelp() function in the Windows SDK reference manual. In addition to APITEST.EXE, I'm providing the source files. The program was created in Microsoft Visual C++ 1.5; the files might not compile correctly using different compilers.

Knowing the relationship between the WinHelp() function and WINHELP.EXE program makes it possible for you to write an extended version of the WinHelp() function or even bypass WinHelp() altogether and send WM_WINHELP messages directly from your own application to the MS_WINHELP window. If you are interested in building an improved version of the WINHELP.EXE help-file viewer, this article will be helpful in making sure that your viewer can fulfill all of the needs of the WinHelp() function. Now you will just need to figure out the internal format of the .HLP file.

Developing for Portability

At Willows Software, we've identified what we believe to be the top seven implementation practices that lead to poor portability. By avoiding the "seven deadly sins" listed here, you can develop applications that can be ported to a wide variety of platforms.

Compiler issues. To ensure portable code, avoid using compiler-specific features that are not ANSI C/C++ standard. Microsoft compilers provide a number of these features. Each compiler feature should be researched to determine whether it is supported by ANSI C/C++ standard compilers. Examples of nonportable features include pragma statements, warning levels, model-size specifications (small and large), and platform-specific compiler extensions (near, far, and huge). To make these nonportable features portable, use a platform-specific header file to isolate platform dependencies.

DOS files have a carriage return/line feed at the end of each line and a control- Z at the end of the last line in source files. These may have to be removed on some platforms, and can usually be converted during the transfer (for example, use ASCII mode in ftp, not binary).

Microsoft compilers support the use of "//" to separate comments from code in the source code. This is not supported by ANSI C, although it is supported by ANSI C++. Use "/*" and "*/" to enclose a comment for ANSI C standard compliance.

Coding conventions. Hard-coded constants should not be used; use #define instead. For example, pointer sizes may differ on different platforms and the assumption should not be made that an int can be used to contain the pointer.

Function prototypes should be used to define functions before they are referenced in the source code. This will force the compiler to emit the correct code for arguments and return values. If this is not done, the compiler will use argument- and return-value size defaults that will be different for different platforms.

Use typedef to declare an identifier as a name for a type before it is referenced in the code. By being explicit, the compiler does not have to make any assumptions regarding type sizes. For application-specific objects that must be a precise size, create a complete set of unique typedefs. These application-specific data types and structures can be modified, minimally affecting the source code that uses them.

Data-structure padding. Never assume offset sizes in data structures. Different compilers pack and size structures differently. The best way to handle this is to use a macro wherever possible to ensure correct byte alignment. To eliminate alignment and packing issues, avoid casting a memory location for a specific structure member or component, or for a location within the structure member itself. Also, use the sizeof operator when referencing the size of a structure. Do not use hard-coded constant values for sizes.

Big/Little endian. Binary data may not have the same byte order as the platform on which the program is running. A portable set of routines or macros may be used to extract data by byte swapping as necessary.

Portable file formats. Files containing binary data are often a problem. The file may contain size and/or offset information appropriate on one platform, but not on another (due to data-type size changes and structure packing). For example, a structure within a data file may have been written on a platform where pointer or integer sizes are different from the target platform. This data can no longer be read directly into a memory data structure as the offsets and sizes will be different. Each element of the structure will have to be read into memory individually, processed, and placed into the memory data structure. This processing will also have to address any byte-ordering differences between the platforms.

Platform-specific filenames. Any use of embedded filenames must be examined for the inclusion of path separation characters and for upper/lowercase letters. An example of this is in #include directives. Any filename contained within quotes or angle brackets may be platform specific. To minimize problems, avoid the use of full or partial paths. Put only the filename itself within "" or <>.

Run-time issues. Use libraries that are supplied on the target platform to access platform-specific features. In addition, we recommend moving references to these features to a separate source file as a prelude to determining how to handle the features.

Figure 1: Help-system components.


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.