Loading, Initializing, and Terminating a DLL

Bob Gunderson

Microsoft Developer Network Technology Group

Created: March 20, 1992

ABSTRACT

This article discusses loading, initializing, and terminating MicrosoftÒ WindowsÔ dynamic link libraries (DLLs). It covers the following topics:

When is a library loaded?

What is a library entry point (LibMain function)?

How is the entry point function limited?

What is a window exit procedure (WEP) and how is it used?

What are some workarounds for current problems with WEPs?

How is a WEP defined?

How are WEPs changed in Windows version 3.1?

LOADING A DLL

When a MicrosoftÒ WindowsÔ application executes, the Windows loader determines whether the application uses any dynamic link library (DLL) services and, if it does, loads the DLL early in the application initialization process, long before Windows calls the application’s WinMain function. When the DLL is loaded, Windows calls the library’s entry point.

The entry point is determined at link time and is placed in the “executable header” that the linker builds. For Windows applications, the entry point resolves to code that the language compiler provides, usually as part of the run-time library. Microsoft C defines the entry point to the __astart function, which is defined in the appropriate C run-time libraries (for example, SLIBCEW.LIB). The __astart function performs some initialization and then calls the familiar WinMain function. Microsoft C does not provide a standard entry point for DLLs, however. Windows calls the DLL entry point with parameters in registers, so an entry point function must be written in assembly language. You must declare the entry point function a far procedure and call it with the following register values:

DI Instance handle for the library. Any Windows function that takes an hInstance value, such as DialogBox, uses DI. When the library creates an object, such as a window, the object must use the instance handle passed in the DI register and not use the instance handle of the application calling the DLL. Most DLLs save this value in a global variable for later use.
DS Selector of the DLL’s data segment. When Windows calls the entry point function, the data segment contains all statics but does not have a local heap. If you want a local heap, the DLL must initialize it by calling the LocalInit function.
CX Requested size, in bytes, of the initial local heap. CX comes from the HEAPSIZE statement in the module definition file (DEF) for the DLL. When calling the LocalInit function to initialize the heap, pass a 0 for the pStart parameter, and pass the value of the CX register for the pEnd parameter. Doing so initializes the proper-size heap at the end of the current data segment.
ES:SI Some documentation states that a pointer to a command line string is passed in ES:SI. On the contrary, Windows always passes 0 in these registers.

The Microsoft Windows Software Development Kit (SDK) includes a copy of a sample library entry point function, which is fortunate for those with little or no assembly language experience. The sample, in the LIBENTRY.ASM file, initializes the local heap and then calls the LibMain function, which may be written in C.

The library entry point function must return a success code in the AX register. If all initialization completed successfully, the library entry point function returns a nonzero (TRUE) value. If initialization failed, the library entry point function returns a 0 (FALSE) value. If 0 is returned, Windows terminates the loading process and fails the execution of the application for which the library was being loaded.

When the DLL is successfully loaded, its usage count increments to 1. If a DLL to be loaded is already in memory, its usage count increments, but the library entry point is not called. Windows uses the usage count to determine when a library is no longer in use and should be unloaded.

An application can also explicitly load a DLL by using the LoadLibrary function.

DLL ENTRY POINT FUNCTION LIMITATIONS

Because a DLL’s entry point function is called early in the initialization process of an application, some limitations apply to what can be done in the entry point function. These limitations exist because the application’s message queue has not been created at the time the entry point is called. The message queue must exist before any code in the application or any code running on behalf of the application (such as the DLL entry point function) can perform any operation that generates messages. The most common mistake is to attempt to create a window or a dialog box in the entry point function. Because creating a window or a dialog box generates messages, do not call windows and dialog boxes at this point. Doing so generates a FatalExit function in the debugging version of the kernel. In addition, the DLL entry point function must not yield control to another application. Because tasking and messaging are interrelated, yielding without sending a message is difficult.

WHAT'S A WEP?

The window exit procedure (WEP) function resides in a DLL and is called when the DLL is unloaded. Windows version 3.0 introduced WEPs as a way for DLLs that directly manipulate the hardware (for example, hook interrupts or modify I/O ports) to do cleanup processing. For example, a DLL that hooks an interrupt directly needs to be able to unhook the interrupt before the interrupt service routine in the DLL is removed from memory. WEPs were never intended as a general purpose DLL termination facility. Unfortunately, the implementation details of WEPs were not well designed, and their limitations were not well documented, causing confusion among developers of Windows-based applications.

WEP Problems and Solutions

WEPs may encounter three main problems:

1.The DLL’s data segment may not be present when the WEP is called. If Windows encounters an error while the DLL is loading, the library fails to load. Unfortunately, the Windows kernel code can still call the DLL’s WEP, depending on how far along the load process was when Windows encountered the error. If the load fails before the DLL’s data segment is created, the DS value may be invalid when the WEP is called. The WEP code should use the LAR (local access rights) assembler instruction and verify that the selector is valid and present. The following code does this verification:

mov cx,wSeg ;get the selector

lar ax,cx ;get the access rights

jnz SEG_Bad ;if LAR fails, this is a bad selector

test ax,8000h ;is this segment present?

jz SEG_Bad ;no, selector is bad

2.When an error occurs during the library load process, Windows may call the WEP before calling the DLL’s entry point function. Serious problems can occur if the DLL is not anticipating this situation and is assuming that the entry point has been called. A DLL can work around this situation by defining a flag in its data segment that is initially set to 0 and then is set to 1 by the entry point function. The WEP, after verifying that the DS is valid, can test this flag to determine whether the entry point function has been executed.

3.In some circumstances, Windows may call the WEP on a stack that is too small to accommodate calls to any Windows function. A WEP can assume, however, that the current stack is a normal application stack if the data segment exists and if the library entry point has been called.

None of these problems happen when a library is explicitly loaded with the LoadLibrary function.

Building a DLL with a WEP

A WEP must be an exported far Pascal function. When it is declared in the EXPORTS section of the DEF file, it must have the RESIDENTNAME keyword associated with it. Here is a sample declaration for a WEP:

EXPORTS

WEP@1RESIDENTNAME

The RESIDENTNAME keyword places the export declaration into the resident portion of the exported name table. The name table contains a list of all exported functions in a DLL or in an application. The table is in two parts: a resident portion that remains in memory and a nonresident portion that can be discarded in low-memory situations. To unload the DLL, Windows calls the WEP by name. If the WEP is in the nonresident portion of the name table, Windows would attempt to reload it. Under low-memory conditions, the nonresident portion may fail to load, which generates a FatalExit. Windows can find the WEP without loading anything from disk if the RESIDENTNAME keyword is used.

All DLLs must have a WEP. If a WEP is not defined and is not located in the resident portion of the name table, Windows searches the nonresident portion, which causes serious problems in certain low-memory situations. Even if the WEP does nothing, it must be defined.

When unloading a DLL and calling a WEP, Windows must be able to call the WEP without loading the code segment from disk. Thus, the code segment that contains the WEP must be declared PRELOAD and FIXED in the DEF file. For example:

SEGMENTS

WEPSEG PRELOAD FIXED

Because fixed DLL code segments are page locked, the WEP should be the only code in the segment to avoid excessive amounts of code being page locked.

CHANGES IN WEPS FOR WINDOWS VERSION 3.1

1.When a module is unloaded, Windows version 3.1 calls all affected WEPs before discarding any DLL. In Windows version 3.0, if a DLL uses another DLL (that is, the other DLL is implicitly referenced), the DLL’s WEP function can be called after the associated DLL is freed. If the segment that contains the WEP is discarded while containing references to the associated DLL, the system crashes when the WEP’s segment is reloaded. This happens when Windows attempts to fix up the external references to the already freed DLL. In Windows version 3.1, all WEPs are called before any DLLs are freed. Thus, WEPs can reside in discardable segments in applications written explicitly for Windows version 3.1. In Windows version 3.1, a WEP can also call functions in other DLLs.

2.In Windows version 3.1, Windows calls a WEP only if the library entry point function has been called. The WEP code, for DLLs written specifically for Windows version 3.1, no longer needs to track this itself.

3.Windows version 3.1 ensures that the automatic data segment is allocated before the WEP is called. The WEP code can assume that DS is valid and that it contains the selector to the DLL’s data segment.

4.In Windows version 3.1, the WEP is never called more than once. Under rare circumstances, the WEP for a DLL can be called more than once in Windows version 3.0.

5.In Windows version 3.0, the USER module is notified that a DLL is freed before the WEP is called. Thus, any resources, such as registered classes, were freed before the WEP was called. In Windows version 3.1, the WEP is called before USER is notified.

6.Because the Microsoft Windows version 3.0 SDK documentation is unclear concerning the return value from a WEP, the WEP code in some existing DLLs doesn’t do a RETF 2. Windows version 3.1 saves and restores the value of SP around the call to the WEP.