js' blog

ObjFW 98 SE: Bringing Objective-C to Windows 98 SE and NT 4.0
Created: 17.05.2020 20:34 UTC

Yesterday, I got the idea to port ObjFW to Windows NT 4.0. Considering the lowest supported Windows version so far was Windows XP, this seemed like it would not be too much work. However, the biggest problem was getting a toolchain that still supports Windows NT 4.0!

Just using an old MSYS2 on Windows XP (I had a Windows XP VM around anyway for writing IPX/SPX[1, 2] support) and copying the resulting binaries to Windows NT 4.0 did not work: It complained about not having AddVectoredExceptionHandler and RemoveVectoredExceptionHandler. Of course: According to MSDN, those were introduced in Windows XP. Grepping the GCC sources did not yield any results, but grepping the entire MinGW sources did: It's used in winpthread. And luckily, it's only used for thread names, so commenting out two lines and recompiling it was enough. winpthread is linked by GCC by default, because the exception handling runtime needs it - and we really want exceptions for ObjFW.

After the compiler no longer created binaries that had missing symbols on Windows NT 4.0, I got an OFInvalidFormatException loop: MinGW's implementation of asprintf seems to call into snprintf and expects it to return the number of bytes that would have been written if a sufficiently large buffer were provided. But on Windows NT 4.0, it only returns -1 if the buffer is too small. And trying to print the error of course calls into it again, hence the loop. Unfortunately, my own asprintf implementation relied on the exact same snprintf behavior - it's part of the C89 aleady, after all! So I had to work around it - easy enough. After that, I was able to get a "Hello world" using of_stdout and OFApplication:

Hello world with ObjFW on Windows NT 4.0

And a few minor changes later, all tests were running successfully:

All ObjFW tests running successfully on Windows NT 4.0

Well, not all of them: I had to disable sockets, as with them, something would try to load icmp.dll, which does not exist on Windows NT 4.0. I'll look into that another time.

Later that evening, I wanted to take things further and thought: If we have Windows NT 4.0 now, why not Windows 98 SE as well? This is more difficult, though: Windows has two versions of almost every API: An A version and a W version. A wrongly stands for ASCII, while W stands for wide. Meaning the A version takes all arguments in the native, locale-dependent codepage, while the W version always uses Unicode. The W versions were only introduced with Windows NT and ObjFW used the W versions exclusively. Since the Windows console is quite bad and needs some nasty hacks to be made bearable at all, it meant I could not even get a "Hello world" without porting those hacks to the A APIs first. So I added a fallback for when the W version returns ERROR_CALL_NOT_IMPLEMENTED (all W APIs return that error on Windows 95/98/ME), got a working "Hello world" and called it a day (unfortunately I forgot to take a screenshot, as it got quite late).

The next day, I decided that calling the W version first and only falling back to the A version if it fails with ERROR_CALL_NOT_IMPLEMENTED doesn't make a lot of sense: After all, there is never gonna be a non-NT version of Windows that supports Unicode. So I decided to add an API to detect if ObjFW is running on Windows NT and used that instead. While I already had a "Hello world" working, it was not one using OFApplication, but just using of_stdout in main. So in order to get that working, I ported OFApplication to the A APIs and finally got a proper "Hello world". So now it was time to port everything else to the A APIs and voilĂ , all tests are running successfully:

All tests running successfully on Windows 98 SE

Well, not all of them: For Windows 98 SE, in addition to disabling sockets, I also had to disable DLLs and threads. For DLLs, I have not figured out yet why it doesn't work, but for threads, I already know what it is and how to fix it (WaitForSingleObject does not support waiting for threads).

And the coolest thing: This now results in a binary that works on anything from Windows 98 SE to Windows 10, using all features as available!

I guess that's not too shabby for a weekend and I'll see that I get all the remaining issues fixed. And who knows, maybe ObjFW 3.11 for Workgroups is next? ;)