Joe received his PhD in 1975 in the area of compiler optimization. He is a Windows consultant and applications developer based in Pittsburgh, PA. His past experience has included computer graphics, document-processing software, operating-systems development, compiler development, CASE tooling, computer music, and real-time and embedded-systems development.
Back in the old days of DOS, we suffered with "RAM cram." We had large TSRs and device drivers that consumed massive amounts of lower 640K memory, leaving insufficient memory for running applications. Windows solved this by using VxDs to provide some of these capabilities and multitasking Windows apps to provide most of the others.
However, Windows has subjected us to a far worse problem: "PATH cram." How many times have you installed a new application, only to find out that it has added itself at the front of your PATH? If you remove it, the application doesn't run. And what happens when that miniscule 127-byte PATH limit is reached? You lose even more than with TSRs because the limit is so much lower.
A massive PATH means that the cost of loading a DLL or executable becomes incredibly high because each directory must be searched. If you spawn a DOS shell and type some nonexistent command, plenty of time can pass before the error message appears. With DOS 6.0 and higher (as well as other MS-DOS-compatible operating systems) this can be solved using separate, often empty configuration sections in the CONFIG.SYS. These set the %CONFIG% variable that can be tested in AUTOEXEC.BAT to select among several PATH statements. However, having to reboot between applications is not exactly user friendly. Alternately, all the necessary DLLs can be dumped into a single directory in the PATH, but this creates a directory full of incomprehensible files. Removing or updating a program then becomes a nightmare, particularly if DLLs are shared by several applications. This also results in "disk cram," in which each install dumps five or ten megabytes of DLLs into your Windows directory. The install procedures are often rather crude, and if there is not enough free space on the drive containing Windows, the install will usually refuse to attempt the installation.
Another disadvantage of PATH is that it allows one program to mask another; for example, if an application is delivered with a DLL, then exactly which DLL is invoked may depend upon whether you have added the new directory at the front or back of your PATH. If you have put it at the end of the PATH, then a DLL of the same name (but possibly with different interfaces!) found earlier in the PATH will be used, with potentially disastrous consequences. If you put the new directory at the front of your PATH, the program that previously used the DLL of the same name will now see the new DLL instead.
Typical DLLs first try the current directory, then the Windows and Windows\SYSTEM directories, then the directory containing the executable file, then the directories listed in the PATH, and finally the list of directories mapped in a network. Normally the DLL will be stored with the executable file and found before the PATH is searched; it is becoming more common, however, to have shared DLLs placed in a separate directory, forcing LoadLibrary to use the PATH. Thus, two identical systems loaded with the same executables might exhibit completely different behavior based solely upon the differences in their PATH variables. Fundamentally, the PATH mechanism is a poorly designed, inefficient abbreviation mechanism for translating from an unqualified program name to a particular instance of executable code. Its great charm is that it is easy to implement, which explains its survival.
My normal DOS approach was to have a minimal PATH and execute programs with .BAT files that either gave an explicit path to the executable or temporarily set the PATH to a new value. But the PATH must be set before you start Windows, so you cannot change it dynamically. DOS-based software should function correctly if invoked by an explicit directory path on the DOS command line, even if the user's PATH is empty. Trivial as this is to accomplish (use argv[0] to derive the home directory of the program), many commercial DOS applications fail if the program's directory is not in the PATH. For Windows, I use GetModuleFileName to obtain the program directory, and consequently never have to depend upon the PATH to find those DLLs, executables, or data files that would reside there. Finally, you can use an application-specific .INI file initialized during the setup to hold a section that locates other executables or DLLs: [programs] mumble.exe=d:\mumble\bin\mumble.exe.
Using LoadLibrary with an explicit path, followed by a series of GetProcAddress calls to initialize a series of pointers, makes it relatively easy to avoid requiring an implicitly loaded DLL. This eliminates the need for PATH; a simple macro makes the code look as if an implicitly loaded library were used.
All of these techniques can bulletproof an application from the vagaries of PATH; unfortunately, most commercial applications do not practice PATH-safe computing. For me, the breaking point came when I recently installed the OLE 2.0 (April 1993) SDK from the MSDN CD-ROM. It wanted not just one, but three new directories with long names in my PATH! This was simply impossible; the PATH is already too long to hold what is needed. I needed a way to avoid putting anything else in my PATH so that it could be found by Windows.
My solution to PATH cram is a program called "FreePath." It is designed to be loaded from the LOAD= line in your WIN.INI file, and it handles the PATH problem by simulating the effect of PATH without actually requiring new directories to be added to the PATH.
Key to making FreePath work is the ProcHook DLL presented in the article "Hook and Monitor Any 16-bit Windows Function with our ProcHook DLL," by James Finnegan in Microsoft Systems Journal (January 1994). Finnegan's DLL allows an application to provide a callback function which will intercept any selected Windows API call. A "hook" to this function is then set in the selected API call, allowing you to do anything with this API call, including calling the underlying API that had been hooked. Hooks are set by the SetProcAddress call, temporarily removed and restored by the ProcHook call, and permanently removed from the hook database by the SetProcRelease call. Table 1 provides relevant information about the ProcHook DLL. Hooks are implemented by actually modifying the code of the procedure to contain a JMP to either the hook handler or its instance thunk. To call the actual API call, you must replace the 5-byte JMP instruction with the original code sequence using ProcUnhook, then perform the call again; this time it will not be intercepted by the hook procedure. I hooked the LoadLibrary, LoadModule, and WinExec calls. The real work, as you'll see, is done in LoadModule.
The initial design was simple: My hook procedure would first try to load the module using the base LoadModule call. If LoadModule succeeded, it would rehook the callback and return the HINSTANCE value to the caller. If the call failed, I would then look at the filename that was passed in. If it were an absolute pathname, I would simply return the error code. However, if it were a simple name--such as FOOBAR.DLL, FOOBAR.EXE, or the like--I would find a corresponding complete path and try again. If this second attempt failed, I would return the error code of the first call; otherwise, I would return the newly obtained instance handle. This would successfully simulate the PATH without doing a search! As it turned out, the final implementation was much more complicated.
I had to decide where to store the path-mapping information--in the overused and much-abused WIN.INI file, in an application-specific .INI file, or in the "more-modern" registration database. The registration database has several potential advantages:
I could not find any good documentation on the "proper" use of the registration database (all existing documentation concentrates on its use for OLE servers), so I adopted some conventions. The first-level key is the name of my application, FreePath. Below this are names of some FreePath-related options, and under each option is a list of program name/pathname pairs.
An entry in the registration database is obtained by passing in a pointer to a string that looks like a directory string, for example, FreePath\Active\foobar.dll. The text string, which in my case is the full pathname, can be obtained by using the RegQueryValue API call. If I cannot find a full pathname to substitute in the registration database, I just return the error code of the LoadModule that failed.
I required that the mapping from a program name to a pathname be complete, rather than just a path to be prefixed; not only was this a bit simpler, but it also meant that the user could redirect to another DLL that had the same interface!
The code that locates a mapping is shown in Listing One . It uses a static variable for its scratch area so that it does not consume any more stack space than necessary for calls (see "Conserving Resources: Stack Space"). There is no way to tell how much stack is available when LoadModule is ultimately called, so the callback code should not be profligate of stack space (see Finnegan's article). The code simply forms the key "FreePath\Active" and opens the registration database for that key. If it finds the key, the code queries the subkey value, which is the filename of the module to be loaded--that is, it locates the value associated with FreePath\Active\filename. If the value is found, it is returned via the parameter pointer newfile.
Figure 1 is a sample of the registration database, as shown by regedit. Note that several other keys appear here. The distributed version lets you disable a definition (for example, for testing); disabled mappings are under the disabled key. You can also instruct FreePath to log any requests that generate errors or any requests for names which are not found; these are kept under the keys BadPath and NoPath, respectively. This can help determine why a load failed; for example, you may not realize that an executable needs a certain DLL; just turn on the error logging and the failing name will appear in the database! A simple pushbutton will clear these failure entries from the database, so you don't have to search through hundreds of two-week-old failure requests, or delete them one at a time.
The FreePath control panel is shown in Figure 2. This provides an application-specific editor for the registration database, and makes it easy to, for example, move a mapping from the Active to Disabled section, and back. The Display check boxes allow for selective display of information from the registration database, and the API check boxes allow for the API calls to be selectively enabled or disabled. A global Disable check box not only disables the actions, but completely removes the hooks set by ProcHook, leaving your system in its pre-FreePath condition.
I added the Performance section to determine FreePath's effectiveness. To improve its own performance, FreePath does not attempt to update this display when it is minimized; if it is visible, you see the numbers update in real time as modules are loaded. The counters are maintained internally even when the display is minimized (the overhead of a simple "++" operation is very small). When the icon is opened, the WM_SIZE handler posts a message to update the Performance display.
The Browse button brings up a file dialog and lets you locate a file to enter. It automatically sets the name to correspond to the filename part of the full pathname. You may also type a full pathname directly and FreePath will automatically fill in the filename.
The status of all the check boxes is stored in the FREEPATH.INI file in the Windows directory. When FreePath is started, its initial settings are taken from this initialization file. If you change any of the settings, the Save Now button will become active; clicking it will save the profile settings. Normally the profile is saved upon program exit, but since this program normally doesn't exit unless Windows shuts down successfully, I wanted to provide an option to guarantee that a particular configuration could be saved.
The Enable (alternatively Disable) button becomes active if a disabled or Active entry is selected from the list box, allowing the entry to be transferred between the two categories; Delete will delete a selection, and Add will add (or replace) an Active entry from the contents of the Name and Path input boxes.
Certain actions had to be performed for all callbacks, while others had to be performed for specific callbacks only. Rather than hardwire all the values for generic actions into the program, I simply constructed a table which pointed to individual entries; see Listing Two . The table contains a printable name (primarily for the debugging output), the address of the "real" procedure, a pointer to the handler procedure MakeProcInstance thunk, an entry for the hook argument to the ProcHook and ProcUnhook calls, the control id that enables the check box, and a set of Boolean flag values that indicate flag status.
The table is initialized using the code shown in Listing Three . I immediately ProcUnhook the hook set by SetProcAddress; if a hook fails to take, I make the check box that selects it invisible. (Initially, I had simply disabled the check box, but I found the distinction too subtle to detect, so I changed it to completely hide the box in question.) The use of MakeProcInstance, now largely obsolete because of smart callbacks, is absolutely mandatory for ProcHook (see Finnegan's article).
The callback table is initialized via a PostMessage call set up during the OnInitDialog handler. This is because WinExec does not return control to the calling application until the first Yield, typically implied by the GetMessage of the top-level message loop. Hooking the WinExec function before WinExec completes (in particular, the WinExec that launched FreePath) can cause a catastrophe. Finnegan recommends performing the hook initialization via a handler invoked from the top-level message loop via a PostMessage; see Listing Three.
Once the table is initialized, SetHooks establishes the settability of its hooks, after which the EnableHooks call actually places them; see Listing Four . Note that the m_API variables (m_LoadLibrary, for example) are variables maintained by the Microsoft Foundation Class (MFC) library to reflect the status of the check boxes; m_Disabled represents the state of the "disabled" check box that renders the program totally inactive, even to the point of removing its physical hooks.
The hooking is handled by callback procedures. Each API procedure to be intercepted has its own callback procedure as a hook handler. The handler's signature is the same as that of the API procedure. When a hook is set, any call to the API procedure will transfer control to its associated handler. The callback can do anything it wants, including calling the hooked API procedure. To prevent infinite recursion, the hook handler must first unhook the API procedure that calls it, then rehook it before returning.
The simplest callback is the WinExec callback, Free_WinExec, in Listing Five . Following the algorithm described, I unhook the procedure, perform certain actions, call the underlying "real" procedure, perform more actions, and return. In this case, I wanted to "activate" the LoadModule handler, whether the user had explicitly checked LoadModule as a trappable API call or not. If called from WinExec, its performance would be dictated by the WinExec check box, hence the assignment p_LoadModule.active = p_WinExec.active.
The LoadLibrary handler, Free_LoadLibrary, is a bit more complex because after reading Matt Pietrek's Windows Internals (Addison-Wesley, 1993), I was under the impression that LoadLibrary would be implicitly called to load related DLLs.
Pietrek correctly pointed out that LoadLibrary is just a shell around LoadModule and that WinExec is a wrapper around LoadModule. Consequently, I thought I could do everything just by hooking LoadModule. However, I realized that some users might not want everything redirected, so I gave the option of specifying which API calls were to be redirected. I mistakenly assumed, however, that LoadModule could call LoadLibrary to load any implicit libraries, which, of course, would call LoadModule. (The alleged relationships between the API calls are shown in Figure 3, and this seems to be supported by the pseudocode on page 259 of Pietrek's book.) This led me into a recursive situation--once I unhooked LoadLibrary to call the "real" LoadLibrary, I ran the risk of getting another call to LoadLibrary; because I had removed the hook, however, my callback would not see the call, and the real LoadLibrary would end up calling the real LoadModule. This complicated the implementation somewhat.
According to Pietrek, the "helper function" LMImports (called by LoadModule) calls LoadLibrary to load any related implicitly linked libraries. In fact, I discovered that it does not, and the true structure was inferred by setting a breakpoint at LoadModule and examining the callback stack from CodeView; see Figure 4.
The effects of this difference were nearly enough to kill the whole project. Fortunately, some of the code was salvageable because it handles a related problem, where the LibMain of a DLL explicitly calls LoadLibrary; see Listing Six .
The LoadLibrary handler works much like the WinExec handler: It is unhooked, and ultimately I call the underlying "real" LoadLibrary call. If the LoadModule check box wasn't selected on the user interface, it will not be active. But since I have to intercept LoadModule to complete LoadLibrary, I activate it by setting the active field TRUE. In addition, LoadModule might not actually have a hook set because Free_LoadModule unhooked itself to call LoadModule; if necessary, ProcHook is called to reset the LoadModule hook. If the hook was set active and the user doesn't want LoadLibrary mapping, we deactivate the LoadModule hook. After I call the "real" LoadLibrary routine, I reset the LoadModule.active state. If LoadModule has been hooked from within LoadLibrary, I unhook it and restore its active flag.
First, I'll illustrate Free_LoadModule's basic operation. Then I'll detail the consequences of the differences between the ideal implementation (Figure 1) and the real implementation (Figure 2), and aspects of subtler interactions such as SetErrorMode. The Free_LoadModule code and its associated helper function Call_LoadModule are in Listing Seven .
One little glitch was the error notification built into Windows. To handle this correctly, I had to add the SetErrorMode processing. The error-reporting dialog box that normally pops up when a load fails should be suppressed, but I must pop it up if I cannot remap the load request and if the prevailing error mode would have popped it up had FreePath not been handling the load operation. I must properly simulate the Windows behavior that would have occurred without FreePath. The handler and helper functions appear in Listing Twelve . For now, assume that Call_SetErrorMode is just SetErrorMode.
After setting the error mode, I unhook the LoadModule procedure, as expected. For now, ignore the pong-related code and the strange handling of the filename_stack and filename_ptr; these will be discussed later.
To avoid potential mutual recursion with LoadLibrary, if the LoadLibrary handler is unhooked I rehook it at this point (note that I will have to unhook it upon exit). Next, I perform the actual call on the base LoadModule function via Call_LoadModule. This is just a convenient way to package the pong-related code; substituting LoadModule for Call_LoadModule gives a good approximation of program execution. If the call to the base LoadModule succeeds, I increment a counter (for the Performance display on the FreePath control panel), notify the panel that an update is requested, clean up all the state I have modified, and return the instance handle to the caller. If the LoadModule call does not succeed, and LoadModule is "active," life becomes far more interesting_.
At this point, I am dealing with the "primary load" failure case. It is not clear exactly why the base LoadModule call failed. Consider the case of loading a program, TOP.EXE, which requires the implicit library DLL1.DLL. The DLL1.DLL library requires the loading of the implicit library DLL2.DLL. Therefore, when I get an error return, I don't know exactly why TOP.EXE failed to lead; either TOP.EXE, DLL1.DLL, or DLL2.DLL was not found. This is another use for the LoadModule_Depth counter: If the failure occurred at the immediate call to LoadModule, then the Failure_Depth will be zero because it is set to zero before the call to the base LoadModule and no recursive calls to LoadModule failed. In this case, I can attempt a retry. If, however, the Failure_Depth is nonzero, the failure must have occurred at a much lower level, because the LoadModule_Depth was stored at the time the failure occurred. I have already issued an error message for it, and retrying the operation with a mapped name will not help, so I just go directly to the failure exit.
Next, I check whether the path given was absolute or just an unqualified name. The code for RelativePath is in Listing Eight . I did not wish to implement a parser for pathnames, especially when the C library already had one; unfortunately, it was model specific. This meant that it wanted a char * pointer, which in medium model is 16 bits of DS-relative offset, whereas I had passed in an LPCSTR, a 32-bit FAR pointer. The solution was to copy the string to a local (near) variable and apply _splitpath. I also test whether the string length is acceptable and return FALSE if it is too long. If there is a drive or directory in the path, I assume that it is not mappable and return FALSE; otherwise it is mappable. While it is certainly possible to create mappings for fully qualified names, there are some problems with the backslash characters, and in any case, the point is to simulate the PATH environment, which only deals with unqualified names. (Note the use of static variables to avoid stack consumption.)
If the path is unqualified, I call GetMappedFile to map the file to a new name. If this fails, I take the failure exit. The code for GetMappedFile is shown in Listing One. If I find a mapping, I attempt to issue the base LoadModule call using the new filename. If this call fails, I increment a counter for the Performance display and take the error exit. If it succeeds, I increment a success counter for the Performance display.
In the exit code, I deal with more pong-related processing, unhook the LoadLibrary hook if it was set, rehook the LoadModule intercept if necessary, restore the error mode, and return. The desire to put the pong-related code in one place resulted in the helper function Call_LoadModule.
In the failure exit code, I examine the prevailing SetErrorMode state. If it is not SEM_NOOPENFILEERRORBOX, I reset the error mode to the prevailing error mode and issue a LoadModule call. This will force the standard (and expected) Windows error box to appear.
After the entire user interface was developed and the first-level functions were operational, I then tackled the LoadModule/LoadLibrary recursion problem. I came up with a brute-force solution, as follows:
My solution was to plant a ProcHook within the LoadModule code. Careful inspection of the code determined that the earliest feasible place was also at least five bytes into the code (and, of course, on an instruction boundary). I placed it at LoadModule+5. However, a JMP instruction would not transfer control properly to the handler. I therefore modified the hook instruction after the hook was set to be a CALL instruction. In order that ProcHook continue to work correctly, I restored the operation to a JMP instruction before unhooking it. In the handler, I modify the return address to point to the location where the hook was set. This implementation is void where prohibited by law.
Creating the pong handler required careful reading of the generated entry and exit code of the Free_Pong routine in Listing Ten . As I expected, the instruction-pointer portion of the far-return address was two bytes above SS:BP. I reset this value to be the pointer to the pong hook location, using the __asm insertion shown. The complete pong code outside Free_LoadModule is in Listing Eleven . Note the use of the undocumented AllocCStoDSAlias call, the same one Finnegan uses to map the selectors. AllocCStoDSAlias must be declared as extern "C" UINT WINAPI AllocCStoDSAlias(UINT); and the .DEF file must contain the declaration:
IMPORTS
AllocCStoDSAlias = KERNEL.170
The pong code is quite fragile and could, with a minor change in the code for LoadLibrary, seriously corrupt the system. Before I set the hook, I verify that the bytes found at the location where I place this internal hook are those that I expected. This code does not show that I have let the offset and signature to be specified by the .INI file, to allow for unforeseen circumstances. Such capabilities should not be used casually!
The first time I tried to load a complex system (Quattro Pro for Windows), Windows complained that it could not find one of its DLLs by popping up a MessageBox. This suggested that my code was suddenly failing for some unknown reason. Yet after I clicked on OK, Quattro Pro came right up, which it could not have done if the DLL had actually failed to load. In examining the log data I wrote with OutputDebugString, I saw that the first attempt to load it had indeed failed, and the second (mapped) attempt had succeeded. I added the SetErrorMode call so that (expected) failing load attempts would not notify the user. Of course, this behavior is also unacceptable if the FreePath remapping attempt fails or can not be attempted because no remapping was found. Therefore, in the failure exit, unless the user has externally set the error mode to SEM_NOOPENFILEERRORBOX, I simply re-issue the base LoadModule call. I could have done my own MessageBox, but that would lead to problems in internationalization; it would be confusing if some messages came up in the user's native language, and one that looked remarkably like a Windows message popped up in English. To avoid this, I force the underlying LoadModule code to issue the error.
This suppression of the error message introduces yet another problem: Suppose that TOP.EXE requires DLL1.DLL, and DLL1.DLL requires DLL2.DLL. I come in, set the error mode off, and load TOP.EXE successfully. TOP.EXE calls LoadModule to load DLL1.DLL, which also is successful; LoadModule is called again to load DLL2.DLL, which fails. By the time it gets to DLL2.DLL, I have already turned the error reporting off (at the highest level, when attempting to load TOP.EXE). LoadModule would typically have no history as to whether it was called from a user application or from an internal recursive call. Thus, I might fail to issue the error message because the error mode is incorrect. So I added two variables: LoadModule_Depth and the Prevailing_ErrorMode array. Note that I use the Prevailing_ErrorMode for the current LoadModule_Depth as the context in which I issue the LoadModule attempt that should generate the error message. Of course, now LoadModule must properly maintain the Prevailing_ErrorMode, so I set a ProcHook in SetErrorMode. This allows my own SetErrorMode handler, Free_SetErrorMode, to intercept the SetErrorMode calls and maintain the Prevailing_ErrorMode. This handler is in Listing Twelve.
The hooking of SetErrorMode rendered my code undebuggable, due to the interaction of CodeView with the SetErrorMode handler. I had made a minor error in the code which I didn't see immediately, so I brought up CodeView. I discovered that attempting to debug the code resulted in an infinite recursion entering Free_SetErrorMode. This seemed even less explicable than the bug I was looking for, which was that the SetErrorMode was not yet correctly maintained. In single-stepping, I discovered that I went into the infinite loop when I called ProcHook, which was even more confusing! I started single-stepping into ProcHook and got a fault in KRNL386.EXE in an _fmemcpy call. Suddenly, I remembered that one of the options to SetErrorMode is SEM_NOGPFAULTERRORBOX, which has the annotation "This flag should be set only by debugging applications that handle GP faults themselves." The code in ProcHook that sets the hook consists of two _fmemcpy calls; see Example 1.
While single-stepping, CodeView apparently calls SetErrorMode just before control returns to the user prompt. The internal failure occurred as I single-stepped across the first _fmemcpy. This has just changed the first instruction of SetErrorMode to a JMP, but the jump address, installed by the second _fmemcpy, has not yet been installed, which explains that failure. I wanted to rewrite ProcHook so that a single _fmemcpy would set the hook in one 5-byte transfer--that way, when CodeView called SetErrorMode before returning control to the user, a valid hook would have been set. But take a look at Listing Twelve. In Free_SetErrorMode, I call Call_SetErrorMode, which determines if the .set flag is TRUE and the hook pointer is not NULL. But since control has not yet returned to my code, I have not stored the result of ProcHook, so the value of the hook is NULL; I certainly would not have executed the next statement, so the value of the .set field would be FALSE. Thus, I would not call ProcUnhook, and the infinite recursion would happen. Now, all this would happen if I were single-stepping inside ProcHook itself, but it happens even when I am single-stepping my own code. Unfortunately, because I would have stored the .hook field, but not the .set flag, my test would again fail. Of course, I could set the .set flag first, but this would only be a partial solution. To fix this, I would have to actually test the first instruction of SetErrorMode to see if it was a JMP, and if so, unhook the handler before calling SetErrorMode. But this would work as long as I did not single-step into ProcHook and try to trace it; for complete success, I would have to modify ProcHook to set the hook in a single 5-byte transfer. I would also have to add an IsHooked call to tell me if the API is already hooked to me. These would be changes to the ProcHook library, and I did not wish to have a private, customized version of it. For now, I just avoid single-stepping into a ProcHook call on SetErrorMode, and set my breakpoint after I have successfully stored p_SetErrorMode.set.
When writing a program that attaches itself symbiotically into a system, it must not have a significant impact on the system's overall resource requirements or performance. I did not want to slow the system down by updating the Performance display if it was not necessary, or impose the additional stack space requirements for calling the update routine, as would be required for a SendMessage. Therefore, I used a PostMessage call to post an update request. The PostMessage queue size is usually very small and could overflow if a complicated module suite were loaded. Therefore, I set a flag after issuing the first PostMessage, and I issue no subsequent PostMessage calls until the message was handled and the flag was cleared. For performance reasons, the display need not be updated if the program is iconized, so I don't even bother with PostMessage when the window is iconized.
The interface here spans both C++ (in which the user interface is written) and pure C (in which the callbacks must be written), so I reflect some of the C++ state (from members of the class) into static C variables. One such variable is the window handle to which to post messages, C_var_PostWnd. If the window is iconized, I set it to NULL; if the window is restored, I set it to the window handle. The full posting code is shown in Listing Thirteen . The NotifyUpdate procedure is called from the callback for LoadModule; it does not attempt PostMessage if the C_var_PostWnd is NULL. Note that an update request is posted in the restoration of the window.
Generally, I have conserved stack space by making local variables static so that they are allocated in the program's DGROUP. However, in one case this was not possible: Keeping the local name of the mapped file required keeping a local copy in each recursive incarnation. I had initially done this in the obvious way, by allocating a local char[_MAX_PATH] array. Unfortunately, I would occasionally get a fatal stack overflow (which would crash all of Windows) when loading a complex program. Some careful investigation suggested that this might be caused by overconsumption of stack space. The only culprit I could find was the large char array that held the filename.
I therefore chose the implementation shown in Listing Seven. On each recursive entry, I store the current value of the filename_ptr in new_filename. After I fill in the new filename string in the location pointed to by new_filename, I increase filename_ptr by the length of the string plus one byte for the terminating NUL byte. When I exit the procedure, I reset filename_ptr to the value I stored in new_filename. This gives me a very compact filename stack. From a viewpoint of language purity this is outrageous; our languages are supposed to handle this for us. However, it reduces consumption of the application stack (SS:SP), a scarce resource over which I have no control, and increases consumption local DGROUP space, which is comparatively plentiful, essentially unrestricted, and over which I have complete control. I chose a large limit, MAX_PATH_DEPTH, on the length of the mapped strings, which in general should translate to several times that capability for realistic path lengths.
Note that I only use static temporaries in those procedures that can be called via the callbacks, where SS!=DS. For other uses, such as working with the user interface, the stack is our own, and SS==DS; the stack for the program is large enough to handle these cases, and there is no potential recursion, so large limits (such as 256 bytes) are acceptable.
Finnegan's ProcHook DLL is critical to this operation. It allows (nominally) any API call to be intercepted and routed to a handler. The binary code for PROCHOOK.DLL is provided electronically; see "Availability," page 3. The source code is available on CompuServe, Microsoft Download Service (206-936-6735), and several other services cited in MSJ.
A complete running version of FreePath is also available electronically in binary, along with the source for the critical subroutines shown in these listings. The package is being distributed as shareware, and registered users will get the complete source to FreePath and an online Help file.
Finnegan's ProcHook article discusses a very important limitation of ProcHook when it interacts with WinExec: Because WinExec does not get control back until after the WM_CREATE message, it is possible to get into unrecoverable situations if you attempt to ProcHook while in WinExec. This can be fixed by using PostMessage to trigger the hooking request, as my application does. If your handlers are in your main executable, it is critical that the code be compiled with the /Gw switch so that the call via the instance thunk will set DS properly.
I particularly want to thank James Finnegan, the author of ProcHook, without whose work this would not have been possible, and both the author and Microsoft Systems Journal for permission to distribute the ProcHook DLL with FreePath. I would also like to thank Matt Pietrek for answers and observations that helped me get this up quickly, and the many correspondents on CompuServe's MSLANG and MSMFC forums, especially the Microsoft engineers who have been helping me learn the MFC library.
NPHOOKCHILD SetProcAddress( FARPROC OriginalFunc, FARPROC NewFunc, BOOL exclusive) Sets up a hook to the function specified in OriginalFuncby redirecting the entry point to the function specified by NewFunc. The pointer specified by NewFunc must either be in a DLL, or it must be an instance thunk returned by MakeProcInstance. Smart callbacks will not work! The exclusive flag specifies whether this hook will be exclusive to the function. It returns a near pointer to an NPHOOKCHILD if successful, or NULL if not. BOOL SetProcRelease(NPHOOKCHILD hookptr) Permanently removes the hook specified by hookptr. Returns FALSE if successful, TRUE if not. BOOL ProcHook(NPHOOKCHILD hookptr) Rehooks the function specified by hookptr. The function should have been previously unhooked by ProcUnhook. It returns FALSE if successful, TRUE if not. BOOL ProcUnhook(NPHOOKCHILD hookptr) Temporarily unhooks the function specified by hookptr. Should be matched by a subsequent call to ProcHook. It returns FALSE if successful, TRUE if not.
Figure 1 Registration database editor.
Figure 2 FreePath control panel.
Figure 3 Alleged component relationships.
Figure 4 Actual component relationships.
// Change the first 5 bytes to JMP 1234:5678 (EA 78 56 34 12) _fmemcpy(lpJmpPtr++,&wJmp,1); _fmemcpy(lpJmpPtr, &npHookChild->lpfnNewFunc,4);
#define KEY_APPNAME "FreePath"
#define KEY_ACTIVE "Active"
BOOL CPromptDlg::GetMappedFile(LPCSTR filename, LPSTR newfile, int
newfile_len)
{
static char key[256]; // Dont eat stack, make this static
HKEY subkey;
lstrcpy(key, KEY_APPNAME);
lstrcat(key, "\\");
lstrcat(key, KEY_ACTIVE);
// At this point we have formed
// FreePath\KEY_ACTIVE
LONG retval = RegOpenKey(HKEY_CLASSES_ROOT, key, &subkey);
if (retval != ERROR_SUCCESS)
{ /* missing key KEY_ACTIVE */
RegCloseKey(subkey);
return FALSE;
} /* missing key KEY_ACTIVE */
else
{ /* has key KEY_ACTIVE */
LONG len = newfile_len;
// Ask for the value of
// FreePath\path\foo.dll
retval = RegQueryValue(subkey, filename, (LPSTR)newfile, &len);
if(retval != ERROR_SUCCESS)
{ /* failed */
RegCloseKey(subkey);
return FALSE;
} /* failed */
// We have found the mapped path
RegCloseKey(subkey);
return TRUE;
} /* has key KEY_ACTIVE */
}
typedef struct {
char * name; // Printable name
FARPROC proc; // "Real" proc address
BOOL enable; // do we want this hook set?
FARPROC handler; // Free_ handler for this proc
int id; // control ID that is associated with this entry
FARPROC callback; // MakeProcInstance(handler)
BOOL set; // Hook has been set
NPHOOKCHILD hook; // ProcHook magic cookie
BOOL active; // We are active
BOOL settable; // we can set this hook
} hook_entry;
//--------------------------------
// p_LoadLibrary
//--------------------------------
hook_entry p_LoadLibrary =
{"LoadLibrary", // Name
(FARPROC) LoadLibrary, // Address
FALSE, // enable
(FARPROC)Free_LoadLibrary, // local callback
IDC_LOADLIBRARY, // checkbox
NULL, // MakeProcInstance pointer
FALSE, // set
NULL, // hook
FALSE, // active
TRUE // settable
};
//--------------------------------
// p_LoadModule
//--------------------------------
hook_entry p_LoadModule =
{"LoadModule", // Name
(FARPROC) LoadModule, // address
FALSE, // enable
(FARPROC)Free_LoadModule, // local callback
IDC_LOADMODULE, // checkbox
NULL, // MakeProcInstance pointer
FALSE, // set
NULL, // hook
FALSE, // active
TRUE // settable
};
//--------------------------------
// p_WinExec
//--------------------------------
hook_entry p_WinExec =
{"WinExec", // Name
(FARPROC) WinExec, // address
FALSE, // enable
(FARPROC)Free_WinExec, // local callback
IDC_WINEXEC, // checkbox
NULL, // MakeProcInstance pointer
FALSE, // set
NULL, // hook
FALSE, // active
TRUE // settable
};
//--------------------------------
// p_Pong
//--------------------------------
hook_entry p_Pong =
{"Pong", // Name
(FARPROC) 0, // address
FALSE, // enable
(FARPROC)Free_Pong, // local callback
0, // checkbox (none corresponds to this)
NULL, // MakeProcInstance pointer
FALSE, // set
NULL, // hook
FALSE, // active
FALSE // settable
};
//--------------------------------
// p_SetErrorMode
//--------------------------------
hook_entry p_SetErrorMode =
{"SetErrorMode", // Name
(FARPROC) SetErrorMode, // address
FALSE, // enable
(FARPROC)Free_SetErrorMode, // local callback
0, // checkbox (none corresponds to this)
NULL, // MakeProcInstance pointer
FALSE, // set
NULL, // hook
FALSE, // active
FALSE // settable
};
hook_entry * hook_table[] = {
&p_LoadLibrary,
&p_LoadModule,
&p_WinExec,
NULL };
void CPromptDlg::InitProcTable()
{
int i;
for(i=0; hook_table[i] != NULL; i++)
{ /* initialize it */
hook_table[i]->callback = MakeProcInstance(hook_table[i]->handler,
AfxGetInstanceHandle());
hook_table[i]->hook = SetProcAddress((FARPROC)hook_table[i]->proc,
hook_table[i]->callback,
FALSE);
if(hook_table[i]->hook != NULL)
ProcUnhook(hook_table[i]->hook); // unhook immediately
hook_table[i]->set = FALSE;
// If the hook could not be set, do not show it as a possibility
// for being hooked (This used to be EnableWindow but the
// difference is too subtle to notice, so I just make it go away)
if(hook_table[i]->id != 0)
GetDlgItem(hook_table[i]->id)->ShowWindow(
(hook_table[i]->hook != NULL
? SW_SHOW
: SW_HIDE));
} /* initialize it */
}
#define UWM_INITIALIZE (WM_USER+1)
BOOL CPromptDlg::OnInitDialog()
{
// ...
PostMessage(UWM_INITIALIZE, 0, 0L);
// ...
}
BEGIN_MESSAGE_MAP(CPromptDlg, CDialog)
...
ON_MESSAGE(UWM_INITIALIZE, OnUserInitialize)
ON_MESSAGE(UWM_UPDATE, OnUserUpdate)
...
END_MESSAGE_MAP()
LONG CPromptDlg::OnUserInitialize(WPARAM wParam, LPARAM lParam)
{
InitProcTable();
SetHooks();
EnableHooks();
return 0;
}
void CPromptDlg::SetHooks()
{
p_LoadLibrary.active = p_LoadLibrary.enable = m_LoadLibrary;
p_LoadLibrary.settable = TRUE;
p_LoadModule.active = p_LoadModule.enable = m_LoadModule;
p_LoadModule.settable = TRUE;
p_WinExec.active = p_WinExec.enable = m_WinExec;
p_WinExec.settable = TRUE;
}
void CPromptDlg::EnableHooks()
{
int i;
for(i=0; hook_table[i] != NULL; i++)
{ /* check it */
if(!m_Disabled && !hook_table[i]->set)
{ /* set it */
if(hook_table[i]->hook != NULL && hook_table[i]->settable)
{ /* settable */
ProcHook(hook_table[i]->hook);
hook_table[i]->set = TRUE;
} /* settable */
} /* set it */
else
if(m_Disabled && hook_table[i]->set)
{ /* release it */
if(hook_table[i]->hook != NULL)
{ /* unsettable */
ProcUnhook(hook_table[i]->hook);
hook_table[i]->set = FALSE;
} /* unsettable */
} /* release it */
} /* check it */
}
HINSTANCE __export WINAPI Free_WinExec(LPCSTR filename, UINT cmdshow)
{
HINSTANCE inst;
BOOL LoadModule_active = p_LoadModule.active;
// Unhook the procedure
ProcUnhook(p_WinExec.hook);
p_WinExec.set = FALSE;
/*
Since WinExec eventually calls LoadModule, we want LoadModule to
do the mapping. If LoadModule is not active, we activate it so it
will do the remapping. Note that this is independent of the
LoadModule check box, which says that *all* LoadModule calls
will be mapped.
*/
p_LoadModule.active = p_WinExec.active;
// Now call the real, underlying version
inst = (HINSTANCE)WinExec(filename, cmdshow);
p_LoadModule.active = LoadModule_active;
// Rehook WinExec so we get called again
ProcHook(p_WinExec.hook);
p_WinExec.set = TRUE;
return inst; // return failure code
}
HINSTANCE __export WINAPI Free_LoadLibrary(LPCSTR filename)
{
HINSTANCE inst; // return instance
// Unhook the procedure and mark it as unhooked
ProcUnhook(p_LoadLibrary.hook);
p_LoadLibrary.set = FALSE;
/*
Note that LoadLibrary calls LoadModule, which may in turn call
LoadLibrary. If LoadLibrary is active we want name mapping, so
we have to make sure LoadModule is hooked. It might be unhooked
because it unhooked itself to call the *real* LoadModule which has
now called LoadLibrary.
*/
BOOL need_unhook_LoadModule = FALSE;
BOOL prev_active = p_LoadModule.active;
if(p_LoadLibrary.active)
{ /* we want LoadLibrary calls */
if(!p_LoadModule.set)
{ /* enable LoadModule hook */
ProcHook(p_LoadModule.hook);
need_unhook_LoadModule = TRUE;
// mark it has both hooked and active
p_LoadModule.set = TRUE;
p_LoadModule.active = TRUE;
} /* enable LoadModule hook */
} /* we want LoadLibrary calls */
else
{ /* we don't want LoadLibrary calls */
if(p_LoadModule.set)
{ /* disable LoadModule hook */
p_LoadModule.active = FALSE;
} /* disable LoadModule hook */
} /* we don't want LoadLibrary calls */
// Now call the real, underlying version
inst = LoadLibrary(filename);
// If we had hooked LoadModule to do the mapping, unhook it how
if(need_unhook_LoadModule)
{ /* was set */
ProcUnhook(p_LoadModule.hook);
// mark it as unhooked and restore its active flag
p_LoadModule.set = FALSE;
} /* was set */
// Restore the LoadModule.active flag to its incoming setting
p_LoadModule.active = prev_active;
// Reset the LoadLibrary hook
ProcHook(p_LoadLibrary.hook);
p_LoadLibrary.set = TRUE;
return inst; // return failure code
}
HINSTANCE Call_LoadModule(LPCSTR filename, LPVOID parms)
{
HINSTANCE inst;
// Unhook the procedure
if(p_LoadModule.set)
{ /* unset it */
ProcUnhook(p_LoadModule.hook);
p_LoadModule.set = FALSE;
} /* unset it */
// Now hook in the Pong hook (we "pong" the hooks between the ponghook
// and the LoadModule hook)
if(!p_Pong.set && p_Pong.hook != NULL)
{ /* set it */
PongHook();
p_Pong.set = TRUE;
} /* set it */
inst = LoadModule(filename, parms);
return inst;
}
#define MAX_LOADMODULE_DEPTH 25
static UINT Prevailing_ErrorMode[MAX_LOADMODULE_DEPTH];
static int LoadModule_Depth = 0;
HINSTANCE __export WINAPI Free_LoadModule(LPCSTR filename, LPVOID parms)
{
static int Failure_Depth = 0;
static char filename_stack[MAX_LOADMODULE_DEPTH * _MAX_PATH];
static char * filename_ptr = filename_stack;
HINSTANCE inst;
char * new_filename = filename_ptr;
// We first set it up so that if the "direct" load fails, we don't
// get an error message that would confuse and annoy the user.
// If a box was supposed to pop up, and we can't redirect the
// load, we will force it to pop up before we leave this procedure
LoadModule_Depth++;
UINT Old_ErrorMode = Call_SetErrorMode(SEM_NOOPENFILEERRORBOX);
// If this is the first call to LoadModule, either from the user
// or via WinExec or LoadLibrary, save the current error mode
// so we can issue error messages correctly
if(LoadModule_Depth < MAX_LOADMODULE_DEPTH)
Prevailing_ErrorMode[LoadModule_Depth] =
Prevailing_ErrorMode[LoadModule_Depth - 1];
// Prepare to maintain the error mode by SetErrorMode
BOOL must_unhook_SetErrorMode = !p_SetErrorMode.set;
if(!p_SetErrorMode.set && p_SetErrorMode.hook != NULL)
{ /* set it */
ProcHook(p_SetErrorMode.hook);
p_SetErrorMode.set = TRUE;
} /* set it */
/*
Here's a tricky bit.
LoadModule can cause LoadLibrary to be called, which in turn calls
LoadModule. But loading a DLL could cause loading of another DLL
and we want to keep the chain going all the way.
*/
// If the LoadLibrary mapping option is set, we make sure that
// LoadLibrary is hooked, and also that it is active
// This prepares us for half of the recursion
BOOL must_unhook_LoadLibrary = FALSE;
if(p_LoadLibrary.enable && !p_LoadLibrary.set)
{ /* set it */
ProcHook(p_LoadLibrary.hook);
p_LoadLibrary.set = TRUE;
must_unhook_LoadLibrary = TRUE;
} /* set it */
// Now call the *real* LoadModule, which may yet call LoadLibrary
// or it may call LoadModule recursively.
Failure_Depth = 0; // do we fail immediately or at lower level?
inst = Call_LoadModule(filename, parms);
if(inst < HINSTANCE_ERROR)
{ /* failed, do our special retry */
// If we are not "active", we just return the failure code
if(!p_LoadModule.active)
{ /* not active */
goto bad_exit;
} /* not active */
// If we failed at a lower level, don't retry the operation
if(Failure_Depth > 0)
{ /* already issued message */
goto bad_exit;
} /* already issued message */
if(!RelativePath(filename))
{ /* not relative path */
// It wasn't a relative path, we can't do anything
// so just undo what is done and return to the caller with
// the error code
goto bad_exit;
} /* not relative path */
// Try to find a mapping for the file
if(!GetMappedFile(filename, new_filename, _MAX_PATH))
{ /* no mapping found */
// We don't know why it failed, but we can't recover
// Log the "NoMap" entry for this one
RecordFailure(filename, KEY_NOMAP, NULL, -1);
goto bad_exit;
} /* no mapping found */
// We have now consumed some of our "filename stack". Update
// the filename stack pointer to point just beyond this:
filename_ptr += lstrlen(new_filename) + 1;
// We found a mapping for the top-level file. It almost certainly
// means that it was not on the path, so let's try to load it from
// the mapped name
// First, we have to unhook LoadModule which has been rehooked
// by the 'pong' hook, then rehook the ponghook:
HINSTANCE new_inst = Call_LoadModule(new_filename, parms);
if(new_inst < HINSTANCE_ERROR)
{ /* failed secondary load */
// This may have been due to a bad mapping, or a missing DLL
// at a lower level. Assume it is a bad mapping (most likely)
C_var_BadMap++;
NotifyUpdate();
RecordFailure(filename, KEY_BADMAP, new_filename, (int)new_inst);
goto bad_exit;
} /* failed secondary load */
// It loaded! Congratulate ourselves and return the instance handle
inst = new_inst;
// Indicate that our remapped load succeeded
C_var_Remaps++;
NotifyUpdate();
} /* failed, do our special retry */
else
{ /* direct load succeeded */
// Well! It worked the first time with the name we were given,
// either because it was already in the PATH or it was an absolute
// name. Makes no matter, record the success.
C_var_Direct++;
NotifyUpdate();
} /* direct load succeeded */
exit:
// Restore the filename stack pointer to its value when we came in
filename_ptr = new_filename;
if(p_Pong.set)
{ /* unhook Pong */
PongUnhook();
p_Pong.set = FALSE;
} /* unhook Pong */
// If we had hooked the LoadLibrary call, unhook it so it is back in
// its original state.
if(must_unhook_LoadLibrary && p_LoadLibrary.set)
{ /* unhook it */
ProcUnhook(p_LoadLibrary.hook);
p_LoadLibrary.set = FALSE;
} /* unhook it */
// Unhook SetErrorMode if we hooked it at this level
if(must_unhook_SetErrorMode && p_SetErrorMode.set)
{ /* unhook it */
ProcUnhook(p_SetErrorMode.hook);
p_SetErrorMode.set = FALSE;
} /* unhook it */
// Hook LoadModule back into the chain
if(!p_LoadModule.set)
{ /* re-set it */
ProcHook(p_LoadModule.hook);
p_LoadModule.set = TRUE;
} /* re-set it */
// Reset the error mode (if we haven't already)
Call_SetErrorMode(Old_ErrorMode);
// Decrement the depth count
LoadModule_Depth--;
// return handle or failure code
return inst;
bad_exit:
// This may look strange, but what we want to do is force the
// dialog box to come up if it would have on a straight call
// First, we set the depth based on the current LoadModule depth,
// including a depth limit
int depth = (LoadModule_Depth < MAX_LOADMODULE_DEPTH
? LoadModule_Depth
: MAX_LOADMODULE_DEPTH - 1);
Call_SetErrorMode(Prevailing_ErrorMode[depth]);
if(Prevailing_ErrorMode[depth] != SEM_NOOPENFILEERRORBOX)
{ /* force error message */
HINSTANCE err;
err = Call_LoadModule(filename, parms);
} /* force error message */
// Now record the failure depth so we can tell where we failed
Failure_Depth = LoadModule_Depth;
goto exit;
}
BOOL RelativePath(LPCSTR filename)
{
static char nearfile[_MAX_PATH];
static char drive[_MAX_DRIVE];
static char path[_MAX_DIR];
static char file[_MAX_FNAME];
static char ext[_MAX_EXT];
if(lstrlen(filename) > _MAX_PATH)
return FALSE;
lstrcpy(nearfile, filename);
_splitpath(nearfile, drive, path, file, ext);
return !(strlen(drive) > 0 || strlen(path) > 0);
}
IP instr disassembly 244: 45 inc bp 245: 55 push bp 246: 8bec mov bp,sp 248: 1e push ds 249: 1f pop ds <= Pong hook placed here 24a: 687502 push 0275 24d: 8b460a mov ax, [word ptr bp+0a] 250: 8b4e0c mov cx, [word ptr bp+0c] 253: e82f01 call 385
=============================================================================
Prolog code
=============================================================================
; void __export WINAPI Free_Pong()
; {
?Free_Pong@@ZCXXZ:
*** 000c0c 8c d8 mov ax,ds
*** 000c0e 90 xchg ax,ax
*** 000c0f 45 inc bp
*** 000c10 55 push bp
*** 000c11 8b ec mov bp,sp
*** 000c13 1e push ds
*** 000c14 8e d8 mov ds,ax
*** 000c16 b8 00 00 mov ax,OFFSET L21402
*** 000c19 9a 00 00 00 00 call FAR PTR __aFchkstk
*** 000c1e 56 push si
*** 000c1f 57 push di
=============================================================================
Epilog code
=============================================================================
; UINT off = OFFSETOF(p_Pong.proc);
*** 000cc5 a1 02 00 mov
ax,WORD PTR ?p_Pong@@3Uhook_entry@@A+2
*** 000cc8 8b 16 04 00 mov
dx,WORD PTR ?p_Pong@@3Uhook_entry@@A+4
*** 000ccc 89 46 fa mov WORD PTR -6[bp],ax
; __asm { // In some places in the world, doing this would be a capital
; // offense. Void where prohibited by law.
; mov ax, off
*** 000ccf 8b 46 fa mov ax,WORD PTR -6[bp]
; mov [word ptr BP+2],ax
*** 000cd2 89 46 02 mov WORD PTR 2[bp],ax
; }
; }
*** 000cd5 e9 00 00 jmp L20315
L20315:
*** 000cd8 5f pop di
*** 000cd9 5e pop si
*** 000cda 8d 66 fe lea sp,WORD PTR -2[bp]
*** 000cdd 1f pop ds
*** 000cde 5d pop bp
*** 000cdf 4d dec bp
*** 000ce0 cb ret OFFSET 0
static WORD pong_datasel; // DS: based selector for pong hook
static WORD pong_codesel; // CS: based selector for pong hook
void WritePong(unsigned char val)
{
unsigned char FAR * p;
p = (unsigned char FAR *)MAKELP(pong_datasel, OFFSETOF(p_Pong.proc));
*p = val; // change from JMP to CALL or back
}
void PongHook()
{
ProcHook(p_Pong.hook);
WritePong(0x9A); // JMP => CALL
}
void PongUnhook()
{
WritePong(0xEA); // => JMP
ProcUnhook(p_Pong.hook);
}
void __export WINAPI Free_Pong()
{
PongUnhook();
p_Pong.set = FALSE;
if(!p_LoadModule.set)
{ /* rehook LoadModule */
ProcHook(p_LoadModule.hook);
p_LoadModule.set = TRUE;
} /* rehook LoadModule */
// Now reset the return address pointer to point to the place we
// had set the PongHook
UINT off = OFFSETOF(p_Pong.proc);
__asm { // In some places in the world, doing this would be a capital
// offense. Void where prohibited by law.
mov ax, off
mov [word ptr BP+2],ax
}
}
// The following lines are added to CPromptDlg::InitProcTable:
p_Pong.proc = (FARPROC) ((char _huge *)p_LoadModule.proc + pong_offset);
pong_codesel = SELECTOROF((p_Pong.proc));
pong_datasel = AllocCStoDSAlias(pong_codesel);
// The following line is added before the program terminates
FreeSelector(pong_datasel);
UINT Call_SetErrorMode(UINT mode)
{
UINT oldmode;
BOOL set = p_SetErrorMode.set;
if(p_SetErrorMode.set && p_SetErrorMode.hook != NULL)
{ /* unset it */
ProcUnhook(p_SetErrorMode.hook);
p_SetErrorMode.set = FALSE;
} /* unset it */
oldmode = SetErrorMode(mode);
if(set && p_SetErrorMode.hook != NULL)
{ /* reset it */
ProcHook(p_SetErrorMode.hook);
p_SetErrorMode.set = TRUE;
} /* reset it */
return oldmode;
}
UINT __export WINAPI Free_SetErrorMode(UINT mode)
{
// Set the prevailing mode to be the one we want
int depth = (LoadModule_Depth < MAX_LOADMODULE_DEPTH
? LoadModule_Depth
: MAX_LOADMODULE_DEPTH - 1);
Prevailing_ErrorMode[depth] = mode;
int oldmode;
oldmode = Call_SetErrorMode(mode);
return oldmode;
}
static HWND C_var_PostWnd = NULL;
static BOOL C_var_Posted = FALSE;
void NotifyUpdate()
{
if(C_var_PostWnd != NULL && !C_var_Posted)
{ /* post it */
PostMessage(C_var_PostWnd, UWM_UPDATE, 0, 0L);
C_var_Posted = TRUE;
} /* post it */
}
LONG CPromptDlg::OnUserUpdate(WPARAM wParam, LPARAM lParam)
{
C_var_Posted = FALSE; // allow more posts to come thru
if(IsIconic())
return 0; // don't update iconic window
char val[20];
wsprintf(val, "%ld", C_var_NoMap);
c_NoMap.SetWindowText(val);
// ... other values formatted and displayed here
return 0;
}
void CPromptDlg::OnSize(UINT nType, int cx, int cy)
{
// If we have a minimized window, we don't want to spend any time
// updating its performance display, so NULL out the PostWnd used by
// the callbacks
if(nType == SIZE_MINIMIZED)
C_var_PostWnd = NULL;
else
C_var_PostWnd = m_hWnd;
if(nType == SIZE_MINIMIZED && m_Hide)
{ /* hide icon */
ShowWindow(SW_HIDE);
return;
} /* hide icon */
// Since it was not a minimzation request, suggest that we should update
// the performance display
PostMessage(UWM_UPDATE, 0, 0L);
// Now do usual WM_SIZE processing...
CDialog::OnSize(nType, cx, cy);
}
Copyright © 1994, Dr. Dobb's Journal