When Windows runs the first instance of a program, it loads the data segment and also loads one or more code segments. Windows also builds two fixed segments for program overhead. One segment contains information unique to each instance of the program, such as the command-line argument passed to the program and the program's current subdirectory. The other overhead segment, which is used for all instances of the program, contains a large chunk of the program's .EXE file header, including the entry table. The entry table already has 6 bytes of data for every far function in your program. Windows expands each entry into 8 bytes. These entries, called ”reload thunks,“ are small pieces of code that handle segment loading.
If the segment containing the far function has not yet been loaded into memory, the reload thunk for that far function looks like this:
SAR BY CS:[xxxx], 1
INT 3F 01 yyyy
The first statement is part of Windows' mechanism to determine whether a segment is a candidate for discarding. The second statement calls Interrupt 3FH, the software interrupt that loads into memory the appropriate segment containing the function.
If the segment containing the far function is present in memory, the reload thunk looks like this:
SAR BY CS:[xxxx], 1
JMP ssss:oooo
The second instruction is a far jump instruction. The ssss:oooo segment and offset address points to the beginning of the far function in the loaded code segment.
The references in your program's code to far functions are listed in the segment's relocation table. Windows must insert the addresses of the far functions into your code. The addresses that Windows uses for this are not the actual addresses of the far functions but rather the addresses of the reload thunks for the far functions. Because the reload thunk is in fixed memory, Windows doesn't need to make other changes to the code when moving the code segment that contains the far call or moving the code segment that the far call references. Windows needs to change only the ssss:oooo address in the reload thunk.
When the program calls a far function, it's actually calling the reload thunk. If the segment containing the far function is not currently in memory, the reload thunk executes the Interrupt 3FH loader that loads the segment into memory. Windows then alters the reload thunk to contain a JMP instruction to the function. Windows jumps back to the reload thunk, which then jumps to the actual function. When Windows discards a segment from memory, it changes the reload thunk from the JMP instruction back to the INT 3FH instruction. The next time a function in that segment is needed, the segment is reloaded.
The calls in your program to Windows functions are translated into far CALL instructions and are also listed in the segment's relocation tables. When Windows loads your code segment into memory, it also resolves the calls to Windows functions. For Windows functions in fixed code segments, Windows simply inserts the address of the Windows function into your code. Windows functions in moveable segments have their own reload thunks, and Windows inserts the addresses of these thunks into your code.
Windows also does something special with functions that have been listed in the EXPORTS section of the program's module definition file. Windows modifies the function prolog of these functions when loading the segment into memory. It replaces the first 2 bytes of every exported far function with NOP (no operation) instructions. The prolog now looks like this in memory:
NOP
NOP
NOP
INC BP
PUSH BP ; save incremented BP on stack
MOV BP, SP
PUSH DS ; save DS on stack
MOV DS, AX ; set DS to AX
SUB SP, x
Those two NOPs make a big difference. Now the prolog saves the original value of DS and sets DS to AX. When this function is called, AX must already be set to the data segment that the function must use. This change makes the exported function unusable for normal calls to the function. This is why you cannot call your window procedure (or any other exported function) directly from within your program.