Creating Cross-Platform Help Files
Adam Ernst, cosmicsoft © 2005

Introduction

One of the things that has suffered most in the desktop environment is the help file. It's easy for developers to become frustrated with the arbitrary "standards" that Apple and Microsoft impose for their help systems and create their own help system--or worse, offer no help at all. However, with a few HTML tricks, it's easy to create completely cross-platform help files.

Paging through help docs is not an enjoyable experience (Microsoft's, especially, sound like they were written by a marketing guru with no programming experience at all). This document is the result of my gleanings from those sources. If you notice an error or omission, email me at cosmicsoft@cosmicsoft.net.

(Disclaimer: the help demos included are only guaranteed to work on recent versions of Mac OS X and Windows. If you want ironclad backwards compatibility, you'll have to do your own research; mostly it just involves building pages that work OK without a CSS parser, plus setting some creator codes for OS 9.)

Download Files

This tutorial is useless without the sample files I've prepared, so download them here. They contain only HTML, text, CSS, and some graphics.

Systems

Both Windows & Mac use HTML for help files. Both also use <META> tags to store info about the file.

On Mac, the only other addition is an "index" file that is used to store info for searches. The folder containing the HTML files and index is then stored in the application bundle and registered and launched with API calls.

On Windows, text-based files for the Table of Contents, Index, and project settings are created, and then all the files are compiled into a proprietary compiled help file (.CHM file) that is launched via custom API calls to open the help book. You must use the proprietary Microsoft HTML Help Compiler to create a .CHM file.

The main differences between the actual HTML pages on the two system are the <META> tags in the header and the style sheet used. The former is not a problem since you can include both in all pages; the Windows tags are ignored on Mac and vice-versa.

The second problem, that of CSS, is worked around by using JavaScript. Based on the presence of "MSIE" in the "navigator" environment variable in JavaScript, a reference to either the Mac-specific stylesheet or the Windows stylesheet is printed in the header.

Why not use CSS conditionals? They're a hack, while Javascript is fairly well recognized. JavaScript is supported on both browsers, although I'm not sure about Mac OS 8, 9, and early versions of 10.

Files

There is a small skeleton of files that must be present in the help folder to do the necessary magic. Herg's a description of each.

Creating Help Files

An example is included in the file "topictemplate.html". It shows how to create a basic document with related items links, a "task list" on Mac, and the header setup. Most of the items are self-explanatory; be sure to fill in the comma-separated keywords and description in the header, and change the page title in both places (the <TITLE> tag and the <h1> section).

For the related items links, the first array is an array of page filenames. These must include the ".html" part of the name. The second array is an array of file titles. You must fill in both, and both arrays must have the exact same number of elements.

Compiling Help Files

On Mac, you must first index the book using Apple's Help Indexer. This is included with Xcode and the Mac Developer Tools. Just drop your help book on to index the book; by default, it only indexes for Tiger, so if you want compatibility with Mac OS 8.6 and later, be sure to check the Preferences.

The complete help folder is simply dropped into the "Contents/Resources" directory of your bundled executable. (Ignore the Apple documentation about needing to put it in "/Contents/Resources/English.lproj" unless you want multiple localized help folders in the same executable.)

Then, add the following items to the Info.plist file in your program:

<key>CFBundleHelpBookFolder</key>
<string>PRODUCTNAME Help</string>
<key>CFBundleHelpBookName</key>
<string>PRODUCTNAME Help</string>

These are very important; without them it will not work. "PRODUCTNAME" must be the same as the name you used in your help HTML files (see the examples), and also the same as the help folder that was dropped into the Resources folder. Note that if you're using REALbasic, you must add these strings to every binary you compile, since RB doesn't let you modify the default Info.plist that it spits out.

On Windows, you must use the Microsoft HTML Help compiler to "compile" your help book. You can download the compiler from the MSDN site; just Google for it. After installing it (it's installed in Program Files, but without a Start menu icon), use it to open the ".hhp" file in your project. Press the compile button, and a compiled help file (".CHM") will be created. This compiled file should be shipped with your application. You can just launch it to open the help book, but it's recommended procedure to use the special API calls (described later).

Registering Help Files

This step is only necessary on Mac. Every time your application runs, you must call a special Help system function to register the help book (add it to the menu in Help Viewer and make the system aware of it).

The call you need is AHRegisterHelpBook. If you're a C/C++/ObjC developer, I'll let you figure out how to call it. If you're using REALbasic, here's a snippet, courtesy of the REALbasic-NUG archives, for registering your help book:

dim bundleRef as Integer
dim urlRef as Integer
dim FSRef as MemoryBlock
dim OSErr as Integer

#if debugBuild
  // Can't register help because the app isn't in a valid bundle
  Return
#endif

Declare Function CFBundleGetMainBundle Lib CarbonLib () as Integer
Declare Function CFBundleCopyBundleURL Lib CarbonLib (bundleRef as Integer) as Integer
Declare Function CFURLGetFSRef Lib CarbonLib (urlRef as Integer, fs as Ptr) as Boolean
Declare Function AHRegisterHelpBook Lib CarbonLib (fs as Ptr) as Integer
Declare Sub CFRelease Lib CarbonLib (address as Integer)

bundleRef = CFBundleGetMainBundle()
If bundleRef = 0 Then
  System.DebugLog "Help was not successfully registered. (-1)"
  Return
End if

urlRef = CFBundleCopyBundleURL(bundleRef)
If urlRef = 0 Then
  System.DebugLog "Help was not successfully registered. (-2)"
  Return
End if

FSRef = NewMemoryBlock(80)
If CFURLGetFSRef(urlRef, FSRef) then
  OSErr = AHRegisterHelpBook(FSRef)
  If OSErr <> 0 Then
    System.DebugLog "Help was not successfully registered. (OS error " + Str(OSErr) + ")."
  End if
Else
  System.DebugLog "Help was not successfully registered. (-3)"
End if

CFRelease urlRef
Important Note

To register your help book, you MUST have the appropriate items added to the .plist in your application bundle. Check back above to make sure you have them, and that the strings are the same as the name of the help folder and the names used in the HTML files.

Opening the Help Book

To show your help book, use the AHGotoPage function on Mac and the HtmlHelp function on Windows. Those declares are in CarbonLib and hhctrl.ocx, respectively.

Here's an example of how to use it, in REALbasic again. If you want to go to the home page (index.html on Mac and overview.html on Windows) pass "" (the empty string) as pagePath. You must include a reference to a parent window, since on Windows help is attached to a "parent window" managed by the OS.

In addition, you will need the Core Foundation classes from Thomas Reed, available at his website.

Sub ShowHelpTopic(pagePath as String, parentWindow as window)
  // Open the application's help book.
  
  #if TargetCarbon
    dim bundleRef as Integer   // CFBundleRef
    dim bookName as Integer    // CFTypeRef
    dim strRef, pagePathRef as CFString
    dim err, pagePathRefPtr as Integer
    
    Declare Function CFBundleGetMainBundle Lib CarbonLib () as Integer
    Declare Function CFStringCreateWithCString Lib CarbonLib (alloc as Integer, s as CString, encoding as Integer) as Integer
    Declare Function CFBundleGetValueForInfoDictionaryKey Lib CarbonLib (bundle as Integer, key as Integer) as Integer
    Declare Function CFGetTypeID Lib CarbonLib (cf as Integer) as Integer
    Declare Function CFStringGetTypeID Lib CarbonLib () as Integer
    Declare Function AHGotoPage Lib CarbonLib (bookName as Integer, path as Integer, anchor as Integer) as Integer
    
    bundleRef = CFBundleGetMainBundle()
    If bundleRef = 0 then
      msgbox "Error Loading Help."
      System.DebugLog "Unable to locate the help files. (-1)"
      Return
    End if
    
    strRef = new CFString("CFBundleHelpBookFolder")
    bookName = CFBundleGetValueForInfoDictionaryKey(bundleRef, strRef.GetReference)
    if bookName = 0 or CFGetTypeID(bookName) <> CFStringGetTypeID() then
      msgbox "Error Loading Help."
      System.DebugLog "Unable to locate the help files. (-2)"
      Return
    end if
    
    if pagePath = "" then
      pagePathRefPtr = 0
    else
      pagePathRef = new CFString(pagePath)
      pagePathRefPtr = pagePathRef.GetReference
    end if
    err = AHGotoPage(bookName, pagePathRefPtr, 0)
    if err <> 0 then
      msgbox "Error Loading Help."
      System.DebugLog "Unable to locate the help files. (-3)"
      Return
    end if
    
  #elseif TargetWin32
    
    Declare Function HtmlHelp Lib "hhctrl.ocx" Alias "HtmlHelpA" (hwnd as Integer, url as Ptr, command as Integer, data as integer) as Integer
    Declare Function GetDesktopWindow Lib "user32" () As Integer
    
    dim f as FolderItem
    f = GetFolderItem("PRODUCTNAMEHelp.chm")
    if f is nil then
      msgbox "Error loading help."
      return
    end if
    
    dim theHandle as Integer
    
    if parentWindow <> nil then
      theHandle = parentWindow.Handle
    else
      theHandle = GetDesktopWindow()
    end if
    
    if pagePath = "" then pagePath = "overview.html"
    
    dim theURL as MemoryBlock
    theURL = new MemoryBlock(LenB(f.AbsolutePath)+lenb(pagePath)+16)
    theURL.CString(0) = f.AbsolutePath+"::/"+pagePath
    
    if HTMLHelp(theHandle, theURL, 0, 0) = 0 then
      msgbox "Error loading help."
    end if
    
  #endif
End Sub
More

There is, of course, much more to it than just this. But hopefully this tutorial has you started on the path to creating truly cross-platform help files; the days of creating two separate help systems just to support platform-native appearances are over.

There are probably plenty of things I left out. So, if you notice an omission or error, email me at cosmicsoft@cosmicsoft.net to let me know; I'll update this page ASAP.

The information contained herein and the linked download files are given freely to the public domain. The files were originally copied from or based on Apple and Microsoft HTML help files, but there were no copyright notices on those files. You are free to repost, wikify, or otherwise redistribute the content on this site. Just let me know if you can. Thanks!

Adam Ernst, cosmicsoft