INF: Creating Dynamic-Linked Libraries Without Data Segments

ID Number: Q68565

3.00

WINDOWS

Summary:

Using dynamic-link libraries (DLLs) is a very flexible way to

centralize a body of program code. Many applications may use functions

from a DLL, while only one copy of the DLL code is in memory. This

contrasts with code that is statically linked into applications, which

requires each application to have its own copy of the shared code.

By default, each DLL has its own data segment, separate from that of

the calling applications. This default behavior precludes using some

of the standard Windows-application programming practices and C

run-time functions in a DLL. This article discusses techniques to

ensure that no data segment is created for a DLL.

More Information:

The most common reason to use a DLL that does not have its own data

segment is to ensure that the data segment (DS) register and stack

segment (SS) register are the same (DS == SS). A large majority of the

functions provided with the C run-time libraries expect that DS == SS.

Functions with this requirement have been removed from the

DLL-specific libraries included with the Windows Software Development

Kit (SDK).

DLLs that have their own data segment will use the stack segment of

the application that calls the DLL. This means that the data segment

and stack segment differ (DS != SS). This fact precludes using many C

run-time functions and any others that require DS and SS to be the

same. A DLL without a data segment will use both the data segment and

the stack segment of the calling application; therefore, DS == SS.

The clarity of the previous discussion is complicated by the fact that

some items within a DLL will declare their own data segment. The DATA

statement in the module definition (.DEF) file is the mechanism that

Windows uses to determine if the DLL has a data segment. If the .DEF

file declares DATA NONE, then Windows will assume that no data segment

exists for this DLL. Throughout this discussion, DLLs with a DATA NONE

statement in the appropriate .DEF file will be called DATA NONE DLLs.

Since Windows assumes that a DATA NONE DLL does not have a data

segment, a data segment that is created implicitly by the DLL code

itself is not loaded into memory and will not be visible by HEAPWALK

or other heap management utilities.

When a DATA NONE DLL does have a data segment, the DLL will use the

application's DS as it should; however, the offset into the data

segment is then calculated as being relative to the DLL's data

segment. Assigning any value to a variable declared in the DLL will

then overwrite any data in the application's data segment.

This type of anomaly may be difficult to track down. When the data

segment is built for the DLL, items are placed in the data segment

from bottom to top. For example, one integer variable requires two

bytes on the bottom of the DLL's data segment. Writing to this

variable will overwrite 2 bytes on the bottom of the calling

application's data segment. Since the size of an application's data

segment is often many thousands of bytes, this type of error may go

unnoticed and will most likely not cause a general-protection (GP)

fault.

These offending data segments are created by the C Compiler to store

global and static variables and static text. One symptom of an

unexpected data segment is that the Resource Compiler provides a

warning message similar to the following:

RC: Warning RW4002: Non-discardable Segment 2 set to PRELOAD

This warning is generated when a data segment has been created by the

C Compiler; however, flags have not been defined for that segment

(such as MULTIPLE or DISCARDABLE). The default setting for these

segments is PRELOAD SINGLE.

A DLL that uses the C run-time code references a global internal

variable __acrtused, which is declared as follows in LIBENTRY.ASM:

extern __acrtused:abs

Referencing this variable informs the linker that the DLL will use the

C run-time code. If the C run-time code is not required by the DLL,

this line should be removed to allow a truly DATA NONE DLL to be

created.

If the C run-time code is actually used by the DLL, __acrtused must be

declared. This will cause a data segment to be created; however, the

warnings concerning that data segment may be ignored.

However, if C run-time code is not being used in the DLL, __acrtused

can be removed, and the C run-time code can also be removed from the

DLL. The C run-time code is present in the standard import libraries

xDLLCyW (where "x" is a memory model, and "y" is "E" for emulator math

or "A" for alternate math). If the xNOCRTD libraries are used instead,

no C run-time code is linked into the DLL. In addition to changing the

library specification on the LINK command line, the /NOE option must

be specified.

To use the application's DS on a function-by-function basis, the

NODATA option can be used on exports. When the linker fixes up a DLL

entry point, it can be told to omit the code that changes DS on DLL

entry. This can be done on a per-export basis, allowing much greater

flexibility because a data segment can still exist, although it is

only only set for certain calls. An example of this type of EXPORT

statement is listed below (fragment of a .DEF file):

EXPORTS

MyExportName @145 NODATA

When "MyExportName" is called, it will not have the DS set to the

DLL's DS, instead it will retain the application's DS.