zdir - an OS/2 Family sdir20 Sep 2023
With the C runtime written, the next step is to write a real enough program to understand the API and verify that things work. I started with a DOS-OS/2 version of sdir since it's a tool I use a lot. This program needs to exercise a variety of pieces of the API, including DOS (to enumerate), VIO (to display) and KBD (to prompt for more input.)
The code is all compiled for an 8088, since it is designed to run in real mode DOS. Initially this seemed a bit strange - 8088 code that can run in protected mode? - but on reflection all of the programs included in Windows 3.0 could do the same.
Learning the family API showed a number of deficiencies. It may be that the API was later refined, and my opinions have the benefit of hindsight in seeing how NT handled these conditions.
Firstly, Win32 allows a console program to query a console buffer size distinct from window size. OS/2's console looks incapable of maintaining a scroll buffer, so it assumed these are the same thing, and only returns the buffer size. They're not quite the same, even in OS/2, since console windows regularly have scroll bars.
However NT can also run OS/2 programs from its unified console, which allows OS/2 programs to see things they wouldn't see on OS/2. From experimentation, NT limits the size of the buffer returned to 100 rows, which manages to be the worst of both worlds (larger than the window size but smaller than the buffer size), causing the application to be unable to position elements accurately or scroll correctly.
Win32 allows enumerations to return long and short file names. In OS/2 it looks like names that cannot be described as 8.3 are never returned to DOS applications. The DosFindFirst function can only return a single name, not a long/short pair. This is a bit amusing to me, because I always point out that the long/short pair behavior of Windows is not needed to run DOS programs - DOS programs would never generate a long name. This pairing is all about enabling DOS programs to consume files written by Win32 programs. In OS/2, this scenario wasn't supported, but DOS programs work fine.
In the OS/2 console, there's no equivalent to "set current color." Writing colored text requires a low level API that indicates the position of the text in the window. The cursor is not updated, the screen is not scrolled. Programs need to position these elements manually.
16 bit Windows supports the idea of "moveable" memory where memory can be allocated, then locked, which ensures it can't move and returns an address. When memory is unlocked, the system is free to move it around. This seems useful in real mode where no remapping exists, because the whole system is prone to fragmentation. Since OS/2 runs in protected mode, it could swap data without affecting virtual addresses, so didn't implement moveable memory.
What this overlooks is that Family API programs are real mode and have real mode problems. Moveable memory support on DOS would be useful not just for managing fragmentation but also to allow swapping to EMS or XMS. If I'm still writing Family programs in future, I'll probably build a "moveable" memory allocator which on OS/2 never moves.
Values such as file size are limited to 32 bit. In 1987 that's reasonable. More frustrating is disk free space, where the API seems to take pains to return multiple 32 bit values which are multiplied to form the result, allowing for sizes larger than 4Gb. To keep in the spirit of this support, ZDir can support free space up to around 4Tb, although I don't know if OS/2 could ever expose this to a 16 bit program.
The final result though is still quite functional. Although it's missing almost all of the features from the Win32 version, the OS/2 version is visually identical:
To prove this is really a program supporting OS/2 and DOS, here's what it looks like on OS/2:
And of course it's 16-bit, so it runs on OS/2 1.x too:
It's also possible to run on Phar-Lap's DOS extender. Note that Phar-Lap doesn't implement the DosQFSInfo API, so the program had to detect Phar-Lap and skip the step of reporting device free space:
One surprising aspect is the file size. A combined DOS-OS/2 program is more than twice the size of the OS/2 program alone. In theory the added code for this program should be an NE loader and translation functions for the API functions used by the OS/2 program. A functionally equivalent alternative is to generate a full DOS program and combine it with an OS/2 program as a stub program. This means the program logic exists in the file twice, but the NE loader is removed. For small programs, this is more efficient.
Next things I'd like to investigate:
- Provide my own OS/2 on DOS API library. ZDir already has two stub OS/2 functions on DOS, so this represents extending that library to cover the rest of the functions it uses. As of now it seems strange to have a program that can be freely compiled for OS/2 but requires a closed blob to build for DOS.
- From there, allow linking the two so the build output includes a DOS binary, an OS/2 binary, and a combined binary. Currently the only way to run a DOS binary is to include the OS/2 binary.
- Extend the DOS API layer. Since the OS/2 program must support long file names, it makes sense to support Win95/DOS 7 long file names for a DOS program too.
The code so far is available at https://github.com/malxau/os2api/.