Utilizing Hooks for Added Capabilities

From EDM2
Jump to: navigation, search

Written by Larry Salomon Jr.

Introduction

Beginning with the MVS operating system, user exits have existed in order to give those with a bit of programming skill the opportunity to change the default behavior for a particular event. Since then, these have evolved into system hooks, which provide the same ability; unfortunately, whether known as user exits or system hooks, there has been a lack of documentation on how to utilize these provisions from with OS/2 applications.

This article will discuss what hooks are, what the different types of hooks are, and the uses of some of the various types of hooks. Finally, we will look at a (new) version of Life Saver, which uses one of the hooks to implement a screen saver.

Going Fishing?

What is a hook? In the sport of fishing, a hook attaches the fishing line) to the fish so that the line follows the movement of the fish. In programming, the concept is similar - you use a hook to attach your application to an event so that your application knows whenever the event occurs. Why is this useful? Consider an application that needs to know when the system is rebooting, or needs to know whenever the PrintScreen key was pressed (regardless of who has the focus). These types of things can only be done with hooks.

OS/2 defines 17 types of hooks that can be set.

Send message event (HK_SENDMSG)
The hook is invoked whenever WinSendMsg() is called.
Input event (HK_INPUT)
The hook is invoked whenever WinPostMsg() is called, including - and especially - for all mouse and keyboard input.
Message filter (HK_MSGFILTER)
The hook is invoked during any Win function which enters a message loop, e.g. WinDlgBox(), WinMessageBox(), etc.
Journal record (HK_JOURNALRECORD)
The hook is invoked to record (journal) a message.
Journal playback (HK_JOURNALPLAYBACK)
The hook is invoked to play back a journaled message.
Help event (HK_HELP)
The hook is invoked whenever help is requested by the user or an application.
Library and procedure event (HK_LOADER)
The hook is invoked whenever WinLoadLibrary() or WinLoadProcedure() is called.
User message registration event (HK_REGISTERUSERMSG)
The hook is invoked whenever WinRegisterUserMsg() is called.
Message control event (HK_MSGCONTROL)
The hook is invoked whenever any of the following four functions are called: WinSetClassMsgInterest(), WinSetMsgInterest(), WinSetMsgMode(), and WinSetSynchroMode().
Find word event (HK_FINDWORD)
The hook is invoked whenever a word to be drawn by the WinDrawText() function is too long to fit within the bounding rectangle.
Code page change event (HK_CODEPAGECHANGED)
The hook is invoked whenever a message queue's code page changes.
Device context association event (HK_WINDOWDC)
The hook is invoked whenever a device context is associated with a window.
Window destroy event (HK_DESTROYWINDOW)
The hook is invoked whenever a window is destroyed.
Message filter event (HK_CHECKMSGFILTER)
The hook is invoked whenever WinGetMsg() or WinPeekMsg() is called.
Input insertion (HK_MSGINPUT)
The hook is used to insert mouse or keyboard messages similar to the journal playback hook.
System lockup event (HK_LOCKUP)
The hook is invoked whenever WinLockupSystem() is called.
System shutdown event (HK_FLUSHBUF)
The hook is invoked whenever the Ctrl-Alt-Del key sequence is pressed.

The user message registration event is generated by a function that is no longer documented. The WinRegisterUserMessage() function existed in the 1.3 Toolkit documentation but does no longer.

Two other hooks - HK_PLIST_ENTRY and HK_PLIST_EXIT - are also defined in pmwin.h but they do not exist within the system (as far as I know).

Hook Contexts

Some hooks can be defined as application-wide or system-wide; the difference is that an application-wide hook will be called whenever the event occurs within the application, while a system-wide hook will be called whenever the event occurs anywhere within the system. Because of this property of system-wide hooks, the corresponding hook procedure must reside within a DLL. This has other interesting connotations which we will see later.

Some hooks must be application-wide, while others must be system-wide. Such hooks do not have a choice.

Chaining Hooks

In reality, a hook is nothing more than a function that accepts one or more arguments. The function is called by the system, meaning that the function must be exportable and use the _System calling convention. Additionally, the system must be made aware of the hook's existance; it is registered via the function WinSetHook(). Similarly, when the hook is to be removed, the function WinReleaseHook() is called.

Figure 1) Before and after calling WinSetHook().

BOOL WinSetHook(HAB habAnchor,
                HMQ hmqQueue,
                LONG lHookType,
                PFN pfnHook,
                HMODULE hmModule);
BOOL WinReleaseHook(HAB habAnchor,
                    HMQ hmqQueue,
                    LONG lHookType,
                    PFN pfnHook,
                    HMODULE hmModule);

Both function take the following parameters:

habAnchor
Anchor block of the calling thread.
hmqQueue
Handle of the message queue to install the hook on. This can also be HMQ_CURRENT to specify the message queue of the calling thread, or NULLHANDLE if this is to be a system-wide queue.
lHookType
One of the HK_* constants (see the first section).
pfnHook
Pointer to the hook procedure.
hmModule
Handle to the DLL containing the hook procedure. This must be specified if hmqQueue is NULLHANDLE.

Functions for the various hook types are chained together, which allows for multiple hook functions to be installed for a particular hook type. The WinSetHook() call installs the hook function at the head of the chain.

Figure 2) Multiple hooks of the same type. The hook on top was installed last using WinSetHook().

This is important because some Win functions install hooks themselves, such as the WinCreateHelpInstance() function. Since the help hook installed by this function indicates to the system that no other hooks after it should be called, any help hooks you install before calling this function will never see any help events.

Two Examples

A Small Example

Consider the application provided as hooks.zip. It is nothing more than a dialog box with two hooks attached - the associate window HDC hook, and the destroy window hook. Whenever either hook is invoked, they beep if the window provided is a frame window.

BOOL EXPENTRY assocWindowHook(HAB habAnchor,
                              HDC hdcContext,
                              HWND hwndAssoc,
                              BOOL bAssoc)
//-------------------------------------------------------------------------
// This hook is invoked whenever an HDC is associated or disassociated with
// a window.
//
// Input:  habAnchor - anchor block of the thread in whose context the
//                     event occurred.
//         hdcContext - handle of the device context.
//         hwndAssoc - handle of the window associated/disassociated.
//         bAssoc - TRUE if hwndAssoc is associated with hdcContext.  FALSE
//                  otherwise.
// Returns:  TRUE if successful processing, FALSE otherwise.
//-------------------------------------------------------------------------
{
   CHAR achClass[256];

   WinQueryClassName(hwndAssoc,sizeof(achClass),achClass);

   if ((WinFindAtom(WinQuerySystemAtomTable(),
                    achClass)==LOUSHORT(WC_FRAME)) && bAssoc) {
      WinAlarm(HWND_DESKTOP,WA_NOTE);
   } /* endif */

   return TRUE;
}

BOOL EXPENTRY destroyWindowHook(HAB habAnchor,
                                HWND hwndDestroy,
                                ULONG ulReserved)
//-------------------------------------------------------------------------
// This hook is invoked whenever a window is destroyed.
//
// Input:  habAnchor - anchor block of the thread in whose context the
//                     event occurred.
//         hwndDestroy - handle of the window being destroyed.
//         ulReserved - reserved.
// Returns:  TRUE if successful processing, FALSE otherwise.
//-------------------------------------------------------------------------
{
   CHAR achClass[256];

   WinQueryClassName(hwndDestroy,sizeof(achClass),achClass);

   if (WinFindAtom(WinQuerySystemAtomTable(),achClass)==LOUSHORT(WC_FRAME))
{

      WinAlarm(HWND_DESKTOP,WA_ERROR);
   } /* endif */

   return TRUE;
}

Figure 3) Two simple hook procedures.

The hooks are installed, as we noted, using the WinSetHook() call and are released using the WinReleaseHook() call.

WinSetHook(habAnchor,
           HMQ_CURRENT,
           HK_WINDOWDC,
           (PFN)assocWindowHook,
           NULLHANDLE);
WinSetHook(habAnchor,
           HMQ_CURRENT,
           HK_DESTROYWINDOW,
           (PFN)destroyWindowHook,
           NULLHANDLE);
  :
  :
WinReleaseHook(habAnchor,
               HMQ_CURRENT,
               HK_WINDOWDC,
               (PFN)assocWindowHook,
               NULLHANDLE);
WinReleaseHook(habAnchor,
               HMQ_CURRENT,
               HK_DESTROYWINDOW,
               (PFN)destroyWindowHook,
               NULLHANDLE);

Figure 4) WinSetHook() and WinReleaseHook() calls.

Note that the hooks are local to the message queue for the thread. Also, note how the hook procedures are cast to the generic type PFN to avoid warnings/errors by the compiler.

A Large Example

Now, look at the application provided in life.zip. This is a screen saver which utilizes the input hook to determine when a period of inactivity elapses; actually, the word should be "expires" since the input hook is invoked when there is no inactivity. The strategy for the application is to start a timer using WinStartTimer() to count the number of seconds of inactivity. Whenever the input hook is invoked, it tells the application to reset this counter. Should the threshold be reached, the screen saver goes into effect.

There are a couple of interesting points to be made:

  1. The hook is a system-wide hook. Since this means it can be invoked from within the context of any process, it must reside in a DLL.
  2. Since the hook and the application need to share data but the DLL needs to access it from within any process' context, the data must be declared in the DLL and a pointer to it is kept by the application. Additionally, the DLL has the attributes DATA SHARED SINGLE specified in the .DEF file to indicate that all processes share the same copy of the DLL's data segment.

Summary

Hooks provide a way to code certain types of logic into your application, which could not be done (easily) in other ways. While they are very useful, care must be taken because, in the case of system-wide hooks, what you do affects the entire system. For example, in a send message hook, you wouldn't use WinSendMsg() to notify your application, or else you would have an endless loop. For all development involving hooks, one must be sure that the design is completely worked out, or you could spend fruitless hours debugging your hook...and these are not easy beasts to debug, either.

Probably the worst detriment during development is the lack of good documentation. Many of the hooks are scantily documented in pmwin.h at best, while others have incorrect parameters listed, making things even more difficult. If you have access to CompuServe, the best help you can get is from the IBMers that reside there, since they have been using many of these hooks since their first appearance.