December 1998
Download Dec98Win32.exe (4KB)
Jeffrey Richter wrote Advanced Windows, Third Edition (Microsoft Press, 1998) and Windows 95: A Developer's Guide (M&T Books, 1995). Jeff is a consultant and teaches Win32 programming courses (http://www.jeffreyrichter.com.
|
Q My application requires the use of several DLLs, which really hurts its load/initialization performance. I think I have done everything possible to improve load-time performance, including rebasing and binding my DLLs. I would really like to postpone loading my DLLs until they are actually needed. I know that I can accomplish this using explicit linking (calling LoadLibrary and GetProcAddress), but this means I would have to keep track of whether the DLL was already loaded and it would make coding more tedious. Isn't there an easier way to postpone loading DLLs until you actually call a function in them?
I am developing an application that needs to run on both Windows® 9x and Windows NT. My application checks to see if a certain process is running on the system. When my app runs on Windows NT, I use the PSAPI functions EnumProcessModules and GetModuleFileNameEx. For Windows 9x I use the Toolhelp functions CreateToolhelp32Snapshot, Process32First, Process32Next, and so on. When my application initializes, I call GetVersionEx to determine which OS my application is running on, and then call only the appropriate set of functions. My application compiles and links perfectly, but when I run it on Windows NT I get the following message: "The procedure entry point Process32Next could not be located in the dynamic link library KERNEL32.dll." Likewise, when I run my application on Windows 9x, I get a similar message regarding PSAPI.DLL. Can you tell me how I can get rid of such runtime errors?
|
The /Lib switch tells the linker to embed a special __delayLoadHelper function into your executable. The second switch tells the linker several things:
Error Conditions
|
Figure 1 Message Box |
Another problem that can occur is that __delayLoadHelper finds your DLL, but the function you're trying to call isn't in the DLL. This can happen if the loader finds an old version of the DLL. In this case, __delayLoadHelper raises another software exception and the same rules apply. The code in Figure 2 shows how to write the SEH code properly to handle these errors. While examining the code in Figure 2, you'll notice a lot of other stuff that has nothing to do with SEH and error handling. This code has to do with additional features available when you use delay-load DLLs. I'll describe these more advanced features shortly. If you don't use these features, you can delete this additional code. As you can see, the Visual C++ team has defined two software exception codes, VcppException(ERROR_ SEVERITY_ERROR, ERROR_MOD_NOT_FOUND) and VcppException(ERROR_SEVERITY_ERROR, ERROR_ PROC_NOT_FOUND), to represent the DLL module not found and the function not found. My exception filter function DelayLoadDllExceptionFilter checks for these two exception codes. If neither of these exception codes is thrown, my filter returns EXCEPTION_CONTINUE_ SEARCH, as any good filter should. (Never swallow exceptions that you don't know how to handle.) If one of these two exception codes is thrown, then the __delayLoadHelper function provides a pointer to a DelayLoadInfo structure containing some additional information. The DelayLoadInfo structure is defined in the DelayImp.H file in Visual C++ and is shown here: |
|
This data structure is allocated and initialized by the __delayLoadHelper function. As the function progresses through its work of dynamically loading the DLL and getting the address of the called function, it populates the members of this structure. Inside your SEH filter, the szDll member points to the name of the DLL attempting to be loaded, and the dlp member contains the function you attempted to look up. Since functions can be looked up via ordinal or name, the dlp member looks like this: |
|
If the DLL loaded successfully, but did not contain the desired function, then you might also look at the hmodCur member to see at what memory address the DLL loaded. Also, check the dwLastError member if you want to see what Win32 error caused the exception to be raised. For an exception filter this will probably be unnecessary because the exception code tells you what happened. The pfnCur member contains the address of the desired function. This will always be set to NULL in the exception filter because __delayLoadHelper couldn't find the address of the function.
Of the remaining members, cb is for versioning, pidd points to the table embedded in the EXE file that contains the list of delay-load DLLs and functions, and the ppfn member is the address where the function's address will go if the function is found. pidd and ppfn are used by the __delayLoadHelper function internally; they are for super-advanced use and it is extremely unlikely that you will ever have to examine or understand them.
Unloading a Delay-load DLL
|
|
The /Delay:unload linker switch tells the linker to place another table inside the file. This table contains the information necessary to reset the functions you've already called so that these functions call the __delayLoadHelper function again. When you call __FUnloadDelayLoadedDLL, you pass it the name of the delay-load DLL that you want to unload. The function then goes to the unload table in the file and resets all of the DLL's function addresses. Then __FUnloadDelayLoadedDLL calls FreeLibrary to unload the DLL.
Let me point out a couple of potential problems. First, make sure that you don't call FreeLibrary yourself to unload the DLL because the function's address will not be reset, causing an access violation the next time you attempt to call a function in the DLL. Second, when you call __FUnloadDelayLoadedDLL, the DLL name you pass should not include a path, and the letters in the name must be the same case as when you passed the DLL name to the /DelayLoad linker switch. If you don't comply, __FUnloadDelayLoadedDLL will fail. Third, if you never intend to unload a delay-load DLL, do not specify the /Delay:unload linker switch. Your executable file will be smaller. Finally, if you call __FUnloadDelayLoadedDLL from a module that was not built with the /Delay:unload switch, nothing bad happens; __FUnloadDelayLoadedDLL simply does nothing and returns FALSE.
Other Features
|
|
As you can see, this is a function data type and matches the prototype of my DliHook function. Inside DelayImp.LIB, the two variables are initialized to NULL, which tells __delayLoadHelper not to call any hook functions. So to have your hook function called, you must set either of these variables to your hook function's address. In my code, I simply add these two lines at global scope: |
|
As you can see, __delayLoadHelper actually works with two callback functions. __delayLoadHelper calls one to report notifications and the other to report failures. Since the prototypes are identical for both functions and the first parameter, dliNotify, tells you why the function is called, I like to make my life simpler by creating a single function and setting both variables to point to one function.
Wrap-up
Have a question about programming in Win32? Send your questions via email to Jeffrey Richter from his website at http://www.jeffreyrichter.com.
|
From the December 1998 issue of Microsoft Systems Journal.