12.3 Considerations for Using 32-Bit Memory

As previously noted, Windows adheres to the segmented memory model. That is, all far pointers are in the form 16:16 consisting of a 16-bit segment selector, combined with a 16-bit offset within the segment. An application using the 32-bit registers of the 80386 or 80486 processor cannot directly call the Windows functions, because its far pointers are in the form 16:32 and Windows cannot work with the extra 16 bits in the offset portion of the address.

Because of this conflict, a Windows application cannot reside exclusively in a 32-bit segment. It must contain at least one 16-bit helper code segment through which it interacts with Windows (including WINMEM32.DLL). In other words, all calls to Windows functions must be made in the helper code segment. The helper segment contains the code that converts the 16:32 pointers in the 32-bit segment to the 16:16 pointers used by Windows functions. This segment also performs the same tasks for the application when the application makes calls to MS-DOS, to other DLLs, or to any other code that uses 16:16 pointers exclusively.

An important limitation on this helper segment is that it must not be discardable (although it can be movable). If the segment is discarded and a 32-bit segment attempts to access the segment, an indirect call into the Windows kernel module to reload the segment results. Because the source of this indirect call is not a 16-bit segment, the system might crash.

Another important consideration is that in writing your application you must not assume anything about the state of the 32-bit registers around 16:16 function calls. For instance, the Windows function calls preserve SI and DI registers, but they presently do not preserve ESI and EDI registers. If the application needs to preserve 32-bit registers around 16:16 function calls, it must explicitly push and pop the register values around the calls. If the 32-bit code segment that calls a Windows function (by means of the helper segment) needs ESI and EDI registers to be preserved when the Windows function returns, the helper segment must explicitly save the registers before making the actual Windows function call. The helper segment must then restore the registers when the Windows function returns.

This rule also applies to return values when a 32-bit segment indirectly calls a Windows function and the caller requires a 32-bit return value. The helper segment must explicitly set the high-order 16 bits of the return value when it moves it into the EAX register, as shown in the following examples:

movzx    eax,ax      ; unsigned return

movsx    eax,ax      ; signed return

All these considerations apply equally to calls to Windows DLLs, MS-DOS, and other 16-bit functions.