Like any other type of C application, dynamic-link libraries can contain multiple functions. Each function that other applications or libraries will use must be declared as FAR and must be listed in the EXPORTS section of the library's module-definition (.DEF) file. The module-definition file for this sample library is discussed further in Section 20.3.2, “Creating the Module-Definition File.”
/* MINDLL.C -- Sample DLL code to demonstrate minimum code */
/* needed to create a dynamic-link library. */
#include <windows.h>
int FAR PASCAL LibMain(HINSTANCE hinst,
WORD wDataSeg,
WORD cbHeapSize,
LPSTR lpszCmdLine)
{
.
. /* Perform DLL initialization. */
.
if (cbHeapSize != 0) /* if DLL data seg is MOVEABLE */
UnlockData(0);
return 1; /* initialization successful */
}
VOID FAR PASCAL MinRoutine(int iParam1,
LPSTR lpszParam2)
{
char cLocalVariable; /* local variables on stack */
.
. /* MinRoutine code goes here. */
.
}
int FAR PASCAL WEP(int nParameter)
{
if (nParameter == WEP_SYSTEM_EXIT) {
/* System shutdown is in progress. Respond accordingly. */
return 1;
}
else {
if (nParameter == WEP_FREE_DLL) {
/*
* DLL usage count is zero. Every application that had
* loaded the DLL has freed it.
*/
return 1;
}
else {
/* Value is undefined. Ignore it. */
return 1;
}
}
}
Source code for a dynamic-link library uses the WINDOWS.H header file in the same way application source code does. WINDOWS.H contains data-type definitions, application programming interface (API) entry-point definitions, and other useful parameter information.
The PASCAL declaration defines the parameter-passing and stack-cleanup convention for this function. This declaration is not required for dynamic-link functions, but its use results in slightly smaller and faster code and, therefore, is recommended. You cannot use the Pascal calling convention for functions with a variable number of parameters, or for calling C run-time functions. In such cases, the CDECL calling convention is required.
There are two parameters shown on the MinRoutine parameter list, but dynamic-link functions can have as few or as many parameters as are required. The only requirement is that pointers passed from outside the dynamic-link module must be long pointers.
You must include an automatic initialization function in your dynamic-link library. The initialization function performs one-time startup processing. Windows calls the function once, when the library is initially loaded. When subsequent applications load the library to use it, Windows does not call the initialization function; instead, it increments the library's usage count.
Windows maintains a library in memory as long as its usage count is greater than zero. If the count becomes zero, it is removed from memory. When an application reloads the library into memory, Windows will call the initialization function again.
Following are some typical tasks a library's initialization function might perform:
Registering window classes for window procedures contained in the library
Initializing the library's local heap
Setting initial values for the library's global variables
This initialization function is required in order to allocate the library's local heap. The local heap must be created before the library calls any local heap functions, such as LocalAlloc. While Windows automatically initializes the local heap for Windows applications, dynamic-link libraries must explicitly initialize the local heap by calling the LocalInit function.
In addition, you should include the following declaration in the initialization function:
extrn __acrtused:abs
This ensures that, if the library does not call any C run-time functions, it will be linked with the dynamic-link startup code in the Windows dynamic-link C run-time libraries (xDLLCyW.LIB).
Initialization information is passed in hardware registers to a library when it is loaded. Since hardware registers are not accessible from the C language, you must provide an assembly-language routine to obtain these values. The location and value of the heap information are as follows:
Register | Value |
DI | Identifies the library's instance handle. |
DS | Identifies the library's data segment, if any. |
CX | Contains the heap size specified in the library's .DEF file. |
ES:SI | Points to the command line (in the lpCmdLine member of the LoadModule function's lpvParameterBlock parameter). |
The Microsoft Windows 3.1 Software Development Kit (SDK) includes an assembly-language file, LIBENTRY.ASM, that you can use to create an initialization function for your dynamic-link library. The LibEntry function in this file is defined as follows:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; LIBENTRY.ASM
;
; Windows dynamic-link library entry routine
;
; This module generates a code segment called INIT_TEXT.
; It initializes the local heap if one exists and then calls
; the LibMain function, which should have the following form:
;
; BOOL FAR PASCAL LibMain(HANDLE hinst,
; WORD wDataSeg,
; WORD cbHeap,
; DWORD ignore); /* Always NULL - ignore */
;
; The result of the call to LibMain is returned to Windows.
; The C function should return TRUE if it completes initialization
; successfully; it should return FALSE if some error occurs.
;
; Note - The last parameter to LibMain is included for compatibility
; reasons. Applications that need to modify this file and remove
; the parameter from LibMain can do so by simply removing the two
; instructions marked with "****" in the following code.
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
include cmacros.inc
externFP <LibMain> ; the C routine to be called
createSeg INIT_TEXT, INIT_TEXT, BYTE, PUBLIC, CODE
sBegin INIT_TEXT
assumes CS,INIT_TEXT
?PLM=0 ; 'C'naming
externA <_acrtused> ; ensures that Win DLL startup code
; is linked
?PLM=1 ; 'PASCAL' naming
externFP <LocalInit> ; Windows heap initialization routine
cProc LibEntry, <PUBLIC,FAR> ; entry point into DLL
include CONVDLL.INC
cBegin
push di ; handle of the module instance
push ds ; library data segment
push cx ; heap size
push es ; always NULL; may remove ****
push si ; always NULL; may remove ****
; If we have some heap, then initialize it.
jcxz callc ; Jump if no heap specified
; Call the Windows function LocalInit to set up the heap.
; LocalInit((LPSTR)start, WORD cbHeap);
xor ax,ax
cCall LocalInit <ds, ax, cx>
or ax,ax ; Did it do it ok ?
jz error ; Quit if it failed
; Invoke the C routine to do any special initialization.
callc:
call LibMain ; Invoke the 'C' routine (result in AX)
jmp short exit ; LibMain is responsible for stack cleanup
error:
pop si ; Clean up stack on a LocalInit error
pop es
pop cx
pop ds
pop di
exit:
cEnd
sEnd INIT_TEXT
end LibEntry
You can find an assembled copy of this function in the file LIBENTRY.OBJ. You can use the LibEntry function to create a C-language initialization function. To use the LibEntry function unchanged, just add its filename, LIBENTRY.OBJ, to your link command line as follows:
link mindll.obj libentry.obj, mindll.dll,mindll.map /map,
mdllcew.lib libw.lib/noe/nod,mindll.def
LibEntry calls a FAR PASCAL function named LibMain. Your dynamic-link library must contain the LibMain function if you link the library with the file LIBENTRY.OBJ.
Following is a sample LibMain function:
int FAR PASCAL LibMain(HINSTANCE hinst,
WORD wDataSeg,
WORD cbHeapSize,
LPSTR lpszCmdLine)
{
.
. /* Perform DLL initialization. */
.
if (cbHeapSize != 0) /* if DLL data seg is MOVEABLE */
UnlockData(0);
return 1; /* successful installation; otherwise, return 0 */
}
LibMain takes four parameters: hinst, wDataSeg, cbHeapSize, and lpszCmdLine. The first parameter, hinst, is the instance handle of the dynamic-link library. The wDataSeg parameter is the value of the data-segment (DS) register. The cbHeapSize parameter is the size of the heap defined in the module-definition file. LibEntry uses this value to initialize the local heap. The lpszCmdLine parameter contains command-line information and is rarely used by dynamic-link libraries.
If you do not want the dynamic-link data segment to be locked, the call to UnlockData is necessary, because the LocalInit function leaves the data segment locked. UnlockData restores the data segment to its normal unlocked state.
If the dynamic-link library's initialization is successful, the library returns a value of 1. If the initialization is not successful, the library returns a value of 0 and is unloaded from system memory.
Note:
If you are writing the dynamic-link library entirely in assembly language, you must reserve the first 16 bytes of the dynamic-link data segment and initialize the area with zeros. If the dynamic-link module contains any C-language code, however, the C Optimizing Compiler automatically reserves and initializes this area.
Windows dynamic-link libraries typically include a termination function. A termination function, sometimes called an exit procedure, performs cleanup operations for a library before it is unloaded.
Libraries that contain window procedures that have been registered (by using the RegisterClass function) are not required to remove the class registration (by using the UnRegisterClass function); Windows does this automatically when the library terminates.
You should define the termination function as shown in the following example. In this example, a single argument is passed, nParameter, which indicates whether all of Windows is shutting down (nParameter==WEP_SYSTEM_EXIT), or just the individual library (WEP_FREE_DLL). This function always returns 1 to indicate success.
int FAR PASCAL WEP(int nParameter)
{
if (nParameter == WEP_SYSTEM_EXIT) {
/* System shutdown is in progress. Respond accordingly. */
return 1;
}
else {
if (nParameter == WEP_FREE_DLL) {
/*
* The DLL use count is zero. Every application that
* had loaded the DLL has freed it.
*/
return 1;
}
else {
/* Value is undefined. Ignore it. */
return 1;
}
}
}
The name of the termination function must be WEP, and it must be included in the EXPORTS section of the dynamic-link library's module-definition file. It is strongly recommended, for performance reasons, that the ordinal entry value and the RESIDENTNAME keyword be used, to minimize the time used to find this function. Since using the RESIDENTNAME keyword causes the export information for this function to stay in memory at all times, it is not recommended for use with other exported functions.