Monitoring Display Driver Interface Calls
by Mike Cooper
Have you ever wondered how to hook the calls going to the OS/2 PM display driver? Ever wish you could modify some of the calls directed at the display driver? If you have ever asked yourself these questions when trying to solve some OS/2 problem, this article is for you.
Several products exist that already take advantage of hooking the display driver for monitoring and redirecting output, including IBM's Distributed Console Access Facility (DCAF) and special needs products. DCAF mirrors screen output on remote terminals; the special needs products magnify the screen and voice assist a user.
To take advantage of the Presentation Manager/Engine Display Hooks (PM_ED_HOOKS) create a ring 2 conforming DLL. Place the DLL name in the OS2.INI file.
Upon bootup, the PM Graphics Engine will check the OS2.INI file for any modules that want to hook, or monitor, the calls made to the current display driver, that is, if the PM_ED_HOOKS entry exists. If the entry exists it will load the module associated with the entry. Set up the entry as follows:
OS2.INI FILE SETUP Application Name "PM_ED_HOOKS" Key Name module name Data fully qualified path and filename Example: APPLICATION "PM_ED_HOOKS" KEYNAME "PMDDHK" DATA "C:\OS2\DLL\PMDDHK.DLL"
OS/2's graphics engine also looks for the PM_ED_HOOKS entry in the OS2.INI file. If the entry is present, the system will load the special hook dynamic link libraries in first-in, first-load order. This means that the last loaded hook DLL will be the first DLL to receive the Device Driver Interface (DDI) calls. When the system loads the DLL, the Graphics Engine (GRE) will do a one-time init to the special hook module.
Your hook module must contain one export OS2_PM_DRV_ENABLE. The GRE will query this function's address and use this address for the subsequent initialization.
Note: OS2_PM_DRV_ENABLE is the same symbol that is at the heart of loading and initializing presentation drivers. This symbol is the entry point to several enabling subfunctions; however, the only one we are concerned with will be subfunction 0x0C.
The graphics engine will call this address with parameters subfunction 0x0C, P1 (current engine/display formats), and P2 (current display and engine dispatch tables). See the sample source code PMDDHK.C on your accompanying Developer Connection for OS/2 CD-ROM (in the "Source Code from The Developer Connection News" category).
LONG APIENTRY OS2_PM_DRV_ENABLE ( ULONG Subfunc, PFLAGS pParam1, PPARAM2 pParam2);
At this point, the hooking DLL can replace DDI entries with those of its own. If you want the system to continue running, store the current addresses before you overwrite them with your own. It is highly recommended that after you have completed your processing on the hooked function that you pass on the original parameters for whom they were intended.
GREENTRY()s and the Dispatch Tables
Let me digress for a second, and talk about how the WIN/GPI APIs map to the Device Driver Interfaces (DDIs). WIN/GPI functions call GRE functions that are mapped to nine macros: GRE32ENTRY2() through GRE32ENTRY10(). These entry points exist in a layer called the GRE dispatcher. The dispatcher sends the function request to the intended device's processing function. That is, the dispatcher calls the function in the device's dispatch table that is associated with the function.
The dispatcher evaluates the function number associated with the GRE32Entry macro, maps the GRE function to the GRE macro, and adds two parameters to the end of the function: a zero long and a function number. The zero long is used for storage of the handle device driver context (HDDC). It is retrieved from the device context associated with the HDC. The function number contains information used for determining the dispatch table to use, whether the function requires an HDC, and which function pointer to call in the dispatch table.
The dispatch table is an array of pointers to functions (PFN). The last two digits in the function number represent the index in the array of the table of function addresses. See Figure 2.
***************************************************************** WinDrawText(hps, cChars, pchStr, pRctl, lFC, lBC, flOptions) Calls GreCharStringPos(hdc, . . . pAttrs) Maps To Gre32Entry10(hdc, ...., pAttrs, 0L, 0x7036) 0 x 7 --> Determines which table the Gre will use for dispatching 0 --> Determines if the Gre needs a HDC 3 --\ 6 --/ Function index *****************************************************************
Figure 2. Table of function addresses
Hook the Display Driver Table
The easiest way to hook the table entries is to allocate a PFN array yourself, then save the indexed addresses that you want to redirect into your array. Then simply replace the PFNs in the dispatch table that were passed to you during the OS2_PM_DRV_ENABLE subfunction 0x0C. Now, whenever the dispatcher indexes to one of your entries, it will call your function first.
The sample code provided on The Developer Connection for OS/2 CD-ROM (in the "Source Code from The Developer Connection News" category) is a very simple illustration to show how to set up one of these hook modules. This sample only hooks out one function that you can modify. Try inserting a DosBeep() in My32CharStringPos just to see how this works.
The main file, PMDDHK.C, contains the OS2_PM_DRV_ENABLE function. In this function, we handle subfunction 0x0C; on every other subfunction, we just return. (This is the only subfunction that a hooking module will ever see.) Next, we check to see if the current display driver is 16-bit or 32-bit model. This sample will only work with 32-bit presentation display drivers. (See the "Hints and Considerations" for what you would have to do to support 16-bit and 32-bit drivers.) So if we make it that far, the next step is to save the current CharStringPos() address. A special note here is that the storage area for your saved function addresses must be accessible to all processes (in shared data). The hooked DLL will run on every process that is updating the screen, not just the process that the hooked DLL was initialized on. Lastly, we overlay the current CharStringPos function address with our own.
In PMDDHK32.C, we actually process the function that we hooked during OS2_PM_DRV_ENABLE. This is where the meat of your functions will reside. Three main items to keep in mind here are: 1) you are a DLL, watch out for trying to access instance data across processes; 2) watch out for objects that are process owned (such as bitmaps and device contexts (HDCs)); and 3) make sure you always pass on control to the system. In the sample code, we call back the CharStringPos() address that we stored earlier; the rest of the system can proceed normally.
I hope this information helps solve some of your display driver problems and gives you another perspective on the graphics engine. Besides implementing special needs products, the PM_ED_HOOKS have helped me to debug, code, and collect performance numbers. The sample source code has been provided--have fun with it!
Hints and Considerations
Because current products use these hooks quite differently, following is a list of hints for your consideration.
16-Bit and 32-Bit Display Drivers
As mentioned, you could be running on a system with a 16-bit display driver. If so, the second parameter on the 0x0C enable call has the fDriver32-bit reset to FALSE. This means that the driver dispatch table is a set of 16:16 far pointers. You must replace these entries with 16-bit FAR PASCAL function addresses. Of course, this requires that you either provide both 32-bit and 16-bit functions or a set of thunk routines. A good solution is to make a 32-bit presentation driver a prerequisite.
Windows applications running on the PM desktop (seamless applications) use a modified Windows driver and share updating of the PM desktop with the PM display driver. This means that the PM_ED_HOOKS do not provide hooks for updating Windows applications. You will have to use other methods to monitor Windows applications.
When hooking string output to the screen, you should be aware that AVIO character output goes through a different channel than the CharStringPos() function. AVIO uses GreCharStr() and GreCharRect() that provide a full CGA text buffer as one of their parameters.
The Direct Interface Video Extensions (DIVE) use the GreEscape() functions for acquiring and deacquiring a black hole on the screen. Multimedia applications write directly to the screen's flat frame buffer. There is no way to hook the writes from a multimedia application. You can hook GreEscape(), which queries the multimedia support and effectively turns off the support. If the current driver does not support the DIVE extensions, multimedia applications will go through the PM-supported path.
Enabling and Disabling
In order for hooking DLLs to be enabled, the OS2.INI file must be setup before bootup. If this file is changed after system startup, you must reboot the system. You can delete this entry after bootup, but you must not delete the hook DLL until after boot because the function addresses will remain active in the display dispatch chain.
Reprint Courtesy of International Business Machines Corporation, © International Business Machines Corporation