Creating DLLs Using Microsoft(R) QuickC(R) for Windows[TM]

Created: March 20, 1992

ABSTRACT

Dynamic link libraries (DLLs) are executable files for the MicrosoftÒ WindowsÔ graphical environment that offer many advantages over both standard executable files and static link libraries. This article explains what DLLs are, how you can benefit by using them, how they differ from standard executable files, and how Microsoft QuickCÒ for Windows simplifies the process of creating them.

WHAT IS A DLL?

A dynamic link library (DLL) is an executable module for the Microsoft Windows graphical environment that contains functions or resources that one or more applications can use. DLLs support stand-alone applications rather than being stand-alone applications. Nevertheless, in some cases, the DLL portion of an application is larger than the stand-alone portion. For example, the bulk of Windows consists of three DLLs: Kernel, which manages memory usage and execution of applications; graphics device interface (GDI), which manages the graphics functions; and User, which handles keyboard and mouse input and controls sizing and placement of windows on the screen.

DLLs are similar to static link object libraries in that they contain common functions that multiple applications can use (for example, the strlen function in the C run-time library). They thus save the programmer from rewriting these functions for each application.

DLLs differ from static link libraries in that they are linked to the EXE file dynamically at run time rather than statically at compile time. Consequently, the EXE file contains only the reference to the function. The object code for DLL functions resides in the DLL, which is a separate file. (At run time, the DLL file must be in the current directory, along the path, or in the Windows SYSTEM directory so that the Windows environment can find it.)

Advantages and Disadvantages of DLLs

DLLs offer a number of advantages. To begin with, they increase efficiency because applications share resources and code. For example, more than one application might use certain dialog boxes, bitmaps, or custom controls, or several database applications might use a database engine. Putting these commonly used resources and functions into a DLL eliminates “reinventing the wheel” for each application. In a multitasking system such as the Windows environment, applications can access a DLL simultaneously. And, when you update code, you update one file: the DLL. You need not update and rebuild all your applications.

DLLs also save space. They save disk space because the system needs only one copy of a function or a resource (as opposed to multiple applications each having a copy). In addition, DLLs reduce system memory requirements because they can be loaded and unloaded as needed. For example, within QuickCÒ for Windows, the compiler—which is a DLL—is loaded into memory only when the user needs to compile.

You can also use DLLs to convert an existing MS-DOSÒ application into a Windows application. If your MS-DOS application has its functions in a module separate from the user interface, you can compile the functions as a DLL and then write a Windows user interface in a separate module, making calls into the DLL as needed. Even if your MS DOS application has its functions and user interface in the same module, you can use this approach: Set compile-time flags on the user interface code so that these lines compile only when the program is being built as an MS-DOS executable. Either way, you get a Windows application in much less time than creating it from scratch.

Furthermore, if you want to continue to run the application under MS-DOS, you can use the same source code for both the MS-DOS and Windows environments. To run the program under MS-DOS, compile it as an MS-DOS executable; to run the program under Windows, compile it as a DLL.

On the other hand, DLLs also have certain disadvantages. To begin with, your applications are no longer self-contained when you use DLLs; additional execution modules must be in place at execution time. (That is, for the executable file to run, both the file and any DLLs it calls must be on your system.)

In addition, applications may be slightly slower, for two reasons. One reason is that, when some functions are in the executable file and others are in the DLL, the compiler and linker can’t optimize the calling sequence. As a result, the first application to call the DLL must load it from disk, which slows the application somewhat. However, this slowdown occurs only the first time the application uses the DLL; later uses are as fast as a single-module executable. The second reason that applications may be slightly slower is that, in DLLs, calls to functions that will be exported—that is, used by the executable module—have to be made as far calls rather than as near calls. The extra overhead this entails could produce slight slowdowns. (The reason for this far call requirement is explained in the “DLL Requirements” section.)

When to Use DLLs

We’ve already talked about three situations in which DLLs are helpful:

When more than one application uses resources or code

When you want to convert an MS-DOS application to a Windows application

When you want to do mixed-language programming

A fourth situation in which you can benefit from DLLs is when your high-level tool doesn’t do everything you want to do. For example, the Microsoft Visual BasicÔ modern programming system for Windows can’t access Windows application programming interfaces (APIs) that require callbacks, that is, that the Windows environment needs to access again later in the program. You may want a Visual Basic application to query the Windows environment to find out which other applications are currently running. To do so, you use the EnumWindows function. Because this function requires callbacks, you can’t access it directly through a Visual Basic application. But you can put the API call into a DLL and have the Visual Basic application call the DLL.

You can also use DLLs when working with an application’s macro language to customize or extend the application, and the macro language can’t do everything you want it to do. For example, if you are extending the Microsoft Excel spreadsheet with business graphics and database, you can put special functions or custom controls into a DLL and then use the Microsoft Excel macro language to call the DLL at the appropriate times.

DLL REQUIREMENTS

Writing a DLL is different from writing a standard program in that you need to add initialization and termination functions and write a short module definition file. But you also need to be aware of an important difference between DLLs and standard executable files: In DLLs, the data segment is different from the stack segment.

In standard executable files, the data segment and the stack segment are one and the same. As a result, applications can pass pointers between procedures without specifying whether the pointer is relative to the data segment or to the stack segment.

DLLs, however, use the stack segment of the task that calls them. Therefore, with DLLs, the data segment is different from the stack segment.

You can deal with this situation in two ways when writing DLLs:

1.Make all local variables static (that is, have them reference the data segment rather than the stack segment).

2.Use only far pointers.

If you’re converting an existing MS-DOS code to a DLL, be sure that functions that will be exported use one of these approaches.

Initialization

The DLL initialization function is called LibMain. This function, which the Windows environment automatically calls when the DLL is loaded, provides an entry point into the DLL. (Several applications can use the same DLL simultaneously, but LibMain executes only once, when the DLL is loaded, rather than when the application first references it.)

LibMain doesn’t have to do anything, but it needs to be present for the Windows environment to load the DLL. LibMain can, however, be used to accomplish certain tasks:

Register window classes, a task that involves describing the appearance of the window and pointing to the procedures for handling window-related messages. This task must be performed whenever an application puts a window on the screen.

Set initial values for DLL’s global variables.

LibMain may not perform any operation that will cause messaging, such as displaying dialog or message boxes or creating windows.

Termination

The function for terminating a DLL is known as a window exit procedure (WEP). This is the last procedure that the Windows environment calls before unloading the DLL (that is, Windows calls it when the remaining tasks no longer reference the DLL). It can be anywhere in the program but is generally placed at the start of the file, right after LibMain.

The purpose of the WEP is to clean up after a DLL. It can terminate the heap, free any memory allocated to the DLL, unhook any interrupts hooked by the DLL, and return any modified I/O ports to their original state.

You should be aware of three caveats regarding the use of WEPs.

First, do not use the WEP to perform any tasks other than those listed above. For example, don’t use a WEP to close a file or to write information to a file. Depending on how the DLL is loaded, the WEP may or may not be called.

When the DLL is loaded explicitly (that is, using the LoadLibrary function), it must be unloaded using the FreeLibrary function. Because the FreeLibrary function in turn calls the DLL’s WEP, the WEP is always called in cases of explicitly loaded DLLs.

But DLLs can also be loaded implicitly, by linking the DLL’s import library to the executable file. (The import library, a list of DLL functions imported by the executable file, is automatically generated by QuickC for Windows as part of the process of building the DLL.) In such cases, the DLL is loaded at the first point that the executable file calls a function listed in the import library. If another application needs the DLL after the first application is through with it, the DLL won’t be unloaded—and therefore the WEP won’t be called.

Because multiple applications can access DLLs, and because you have no way of knowing whether the other applications load your DLL implicitly or explicitly, it’s best not to use the WEP to do anything other than handle the cleanup tasks that must be performed before unloading the DLL.

Second, if an application calls multiple DLLs, you can’t assume the order in which the WEPs of these DLLs are called. It’s unlikely to be either the order in which the DLLs were loaded or the reverse of this order. DLLs are unloaded when they are no longer needed by any active applications. Because the user chooses which applications are active, predicting when a specific DLL will be unloaded is not possible.

Third, the WEP must reside in a code segment that is marked FIXED and PRELOAD in the DLL’s module definition file.

Module Definition File

A module definition (DEF) file is a separate file from the DLL and is read by the linker at link time. The module definition file:

Explains that the item being linked is a DLL, not an EXE.

Describes the functions that the DLL can export to other modules. (This information is also employed to create the import library, which the executable file uses to locate DLL functions.)

Describes the DLL’s behavior—that is, what the code will do, how the data is laid out, and the heap size.

USING QUICKC FOR WINDOWS TO BUILD AND DEBUG A DLL

Microsoft QuickC for Windows is an ideal environment for building DLLs. To begin with, QuickC for Windows automates the building process. You click on the project type (Windows DLL) and on Build, and QuickC for Windows calls the compiler and linker, builds the DLL, and even creates the import library (used by the executable file to locate DLL functions).

QuickC for Windows dramatically reduces the development time for creating a visual interface for your application (as may be the case, for example, when you move existing MS-DOS code to the Windows environment). Using the graphical tools provided with the product, you simply draw your visual interface. QuickC for Windows automatically generates the code for what you’ve drawn.

The QuickC for Windows integrated development environment lets you edit, compile, and debug in the same environment without saving your work and exiting as you move from one tool to another. Furthermore, because the development environment is Windows-hosted, you can experience all the benefits of working within a graphical environment while you’re creating applications for it.

QuickC for Windows also provides context-sensitive help. The Help system includes icons that help you navigate through the reference material, extensive indexes, cross-references, and hypertext links between related concepts.

The remainder of this section demonstrates how to build a DLL with QuickC for Windows. The example is based on SELECT (a DLL) and DEMO (an application that calls SELECT). Both files come with QuickC for Windows and are in the QCWIN\SOURCE\SELECT directory.

The purpose of SELECT is to let the user select areas of the screen—a routine that might be used in a paint program, for example. The program consists of four functions:

StartSelection: Begins the selection area (when the user clicks the mouse button)

UpdateSelection: Updates the selection area (as the user drags the mouse)

EndSelection: Ends the selection area (when the user releases the mouse button)

ClearSelection: Clears the selection area (when the user begins another selection)

The purpose of DEMO is to show how to manipulate the cursor to select a region of the screen. Note that DEMO loads the DLL implicitly rather than explicitly by means of a linked import library.

DEMO consists of four functions:

WinMain: Calls the initialization function; processes the message loop

DemoInit: Initializes window data and registers the window class

DemoWndProc: Processes messages for the parent window

About: Processes messages for the About dialog box

Writing the DLL

Suppose you already have the source code for the four functions in SELECT, and you want to use QuickC for Windows to turn the program into a DLL. The process consists of three steps:

1.Initiating the project.

2.Checking your source code to ensure that it doesn’t violate the DS != SS rule.

3.Writing LibMain.

Initiate a project

To start QuickC for Windows and initiate a project, select Project-Open, click the QCWIN\SOURCE\SELECT directory, and type the project name (for example, MYDLL) in the list box. Because MYDLL is a new project, a dialog box appears stating that a project called MYDLL.MAK doesn’t exist and asking whether you want to create such a project. Choose Yes.

Because you’re working with preexisting code, your next step is to specify the source files. (If you were creating the code from scratch, you would open a source window at this point and begin writing your code.) List (or create) three source files:

SELECT.H (the header file)

SELECT.C (the DLL source file)

SELECT.DEF (the module definition file)

Check your source code

Bring up SELECT.C (using the File-Open command), and check your code to ensure that it doesn’t treat the stack segment as equal to the data segment. If any near pointers to local variables are in the stack segment, either change them to far pointers or make all your local variables static.

SELECT.C deals with this issue by using only far pointers. For example:

lpSelectRect->right = ptCurrent.x;

lpSelectRect->bottom = ptCurrent.y;

Write a LibMain

The LibMain procedure in SELECT.C consists of two lines:

int FAR PASCAL LibMain (HANDLE hModule, WORD wDataSeg,WORD

cbHeapSize, LPSTR lps)

{

return TRUE;

}

(The Pascal reference specifies that the Microsoft Pascal calling convention is to be used. This calling convention is required for all functions called by the Windows environment.)

LibMain doesn’t do anything in this case: It simply names the parameters that Windows passes to the DLL and returns TRUE. But it needs to be there to allow the Windows environment to call the DLL.

The code above is the minimum requirement for a LibMain. If you want LibMain to perform additional actions (for example, register a window class or set initial values for global variables), add the appropriate code.

Writing a Module Definition File

Your next step is to write a module definition file to give the linker the information it needs to properly handle the DLL. This information is also accessed when creating the import library that the executable file uses to locate DLL functions.

Here is the module definition file for SELECT.C:

LIBRARY SELECT

EXETYPE WINDOWS

PROTMODE

CODE PRELOAD MOVEABLE DISCARDABLE

DATA PRELOAD SINGLE

SEGMENTS 'WEP_TEXT' FIXED PRELOAD

HEAPSIZE 1024

EXPORTS

WEP @1 RESIDENTNAME

StartSelection @2

UpdateSelection @3

EndSelection @4

ClearSelection @5

Only the LIBRARY line is mandatory because defaults exist for all the other lines. However, you should include all these lines; the defaults may not be appropriate for your situation.

Here is an explanation of each keyword in this file:

LIBRARY: Tells the linker to build the program as a DLL rather than as an EXE. The name in the LIBRARY statement must be in uppercase and must be the same as the name of the DLL file.

EXETYPE: Tells the linker that the program is a Windows DLL rather than a Microsoft OS/2Ò DLL.

PROTMODE: Tells the linker that the DLL runs in protected mode (and therefore can use extended memory).

CODE: Explains the nature of the code segments.

DATA: Explains the nature of the data segments.

SEGMENTS: Specifies the type of segment (fixed or discardable) in which the named functions are to be placed. Here, 'WEP_ TEXT' FIXED PRELOAD specifies that the WEP is to be placed in a fixed code segment (which is necessary to avoid its being discarded in low-memory conditions). If this line is not included in exactly this form, Windows might not be able to locate the WEP when it’s time to unload the DLL. (Even if your DLL uses the default WEP in the C run-time libraries, you must include this line.)

HEAPSIZE: Specifies the size of the heap.

EXPORTS: Specifies the functions to be exported to other modules. Functions may be listed in any order. The WEP line is mandatory and must read exactly as shown here. Again, this is true even if your DLL uses the default WEP in the C run-time libraries. The RESIDENTNAME keyword makes the name of the WEP function resident at all times, even in low-memory situations, so the Windows environment can always find the WEP when it needs to remove the DLL.

Building the DLL

Now you’re ready to build the DLL. First, select Options-Project. When the dialog box appears, select WIN DLL as the project type and then select Project-Build. QuickC for Windows reads the specified files, calls the compiler and linker, and builds the DLL. In doing so, it performs the following steps:

1.Runs the resource compiler (RCW.EXE) to compile any visual resources associated with the DLL, creating a RES file. (Visual resources, such as dialog boxes, icons, and fonts, are created separately from the DLL itself and stored in RC files.) If a DLL contains no visual resources—as is the case for SELECT.C—this step is skipped.

2.Compiles .C and .H files to create the OBJ file.

3.Reads the DEF file and links the OBJ file with the C run-time libraries to create a DLL file.

4.Runs RCW.EXE again—or for the first time if the DLL contains no visual resources. If visual resources are present, this step combines the RES file produced in Step 1 with the DLL file produced in Step 3 and alters the combined DLL file as needed to allow Windows to call it. If the DLL contains no visual resources, this step simply alters the DLL file created in Step 3 so that Windows can call it.

5.Creates an import library (SELECT.LIB) to tell the linker which functions are in the DLL.

Writing and Building the EXE File

The next step is to create and build the EXE file that calls the DLL. In this case, DEMO.C already exists, so we simply need to build it. For your own programs, you may need to write a source file first. Even if a higher-level program, such as Microsoft Excel or Visual Basic, will call your DLL, writing a C program to call the DLL is helpful in verifying that the DLL works. If you have problems calling the DLL, you’ll know that the problem is in the call from the higher-level program, not in the DLL itself.

If you’re converting an MS-DOS program to a DLL, you can use the graphical tools in QuickC for Windows to create a graphical user interface in much less time than if you wrote the code manually. You can then incorporate the graphical user interface into the EXE file that calls the DLL.

The steps for creating and building EXE files are generally similar to those for building DLLs. The only differences are:

In initiating the project, you list four files instead of three: DEMO.C (your source file), DEMO.H (the header file for this executable), DEMO.DEF (the module definition file for your EXE file), and SELECT.LIB (the DLL import library).

You don’t need a LibMain or a WEP in an EXE file.

The module definition file for an EXE uses the keyword NAME in the first line instead of LIBRARY and specifies the stack size as well as the heap size. (Because DLLs use the stack of the task that calls them, you don’t need to specify the stack size when writing module definition files for DLLs.)

When you build the EXE, you select the WIN EXE project type instead of WIN DLL.

Debugging the DLL

Your final step is to debug your DLL. (Note that you can’t perform this step until you’ve built both your DLL and your EXE, along with the associated module definition files.) The QuickC for Windows integrated development environment simplifies the debugging process and lets you move easily between debugging and editing functions.

To initiate a debugging session, select Run-Debug from the Options menu. If your current project is the EXE, a dialog box asks you to name any DLLs associated with the EXE. Fill in the name of the DLL (MYDLL.DLL). This tells QuickC for Windows to debug the DLL along with the EXE. If your current project is the DLL, a dialog box asks you to name the EXE that calls it.

Note:

If a Visual Basic executable file will call your DDL, specify VB.EXE as the calling program. QuickC for Windows loads Visual Basic, and you can then run the application that calls the DLL. Similarly, if a Microsoft Excel macro will call your DLL, specify EXCEL.EXE as the calling program.

You may also want to insert some breakpoints in your DLL—say, after LibMain is called and after StartSelection is called. To do so, use File-Open to open up the source code for SELECT.C, and then position the insertion point on the line where you want the breakpoint and press F9.

When you’re ready to start debugging, select Go from the Run menu. QuickC for Windows loads DEMO.EXE and runs it, loading the DLL at the appropriate point and breaking at your specified breakpoints.