Search code examples
assemblyabiprotected-mode80286

How to program in 16 bit protected mode with more than 64kb of data?


I want to write some code for the 16 bit protected mode, specifically a simple operating system with some programs. I know this sounds silly and it probably is, but I'm interested in understanding how to write programs under these constraints.

I'd like to know what kinds of conventions have been employed in the various operating systems working in 16 bit protected mode (e.g. OS/2 and Win 3.1). What ABI did they use? How are far pointers passed around? Were there multiple ABIs for different code models?

To clarify, I know what far pointers are and how they are used on the API level. What I'd like to know is how this works on assembly level. Are segments for far pointers passed on the stack? Are there any special conventions?


Solution

  • Most 16-bit protected mode APIs took far pointers as parameters. A far pointer is a 32-bit value containing both a 16-bit offset (low order word) and a 16-bit selector (high order word, selectors refer to segments). It's passed by value by putting it on the stack like any other parameter. Generally these pointers could only refer a region of memory up to 65536 bytes in size, but different far pointers could refer to different regions of memory allowing more than 64K of memory to be used.

    For example in the 16-bit Windows API (Win16) the function GetClientRect had the following documented interface:

    void GetClientRect(hwnd, lprc)
    
    HWND hwnd;    /* handle of window */
    RECT FAR* lprc;   /* address of structure for rectangle   */
    

    The symbol FAR was a macro that expanded to far keyword when using the 16-bit Windows API. Today this API function is documented as taking an LPRECT parameter, which is meant to be read as being "long (far) pointer to RECT". The symbol LPRECT is defined as a typedef of RECT * in the 32-bit and 64-bit Windows APIs. It would be a typedef of RECT far * when using the 16-bit API if that was still supported.

    There weren't separate APIs for different memory models (small, medium, compact, large), because of the use of the far keyword on pointers (and the function itself) the API was accessible from all memory models. The compiler would see that it took a far pointer and promote any near (16-bit) pointers as necessary.