20050718

Difference between Windows and UNIX programming cultures

This post on Slashdot links to an article on comparison between UNIX and Windows programming cultures. However, it mostly talks of how the problem of usability is approached. I'd like to take a different tack, in the difference between the API of the two systems.

  • Windows APIs are huge. In the Microsoft world, everything seems to end up being part of the core OS services somehow. This has the advantage that you don't need to expect people to have such-and-such library. Or does it? Changes to what is the "core" between OS versions make compatibility somewhat nightmarish; you're never quite sure what libraries are there or not. Writing installers is a mess. MSI helps, but not if there's no MSI package for the libraries. Another side-effect of this is that Windows programmers are always learning a zillion new things. Win32 services. COM. COM+. .NET. DNA. TAPI. The list goes on and on. Many of those APIs do the exact same thing, so learning the new one is only needed because the old one becomes obsolete. It's hard to stabilize such a huge API.
    In contrast, the core UNIX APIs have been mostly unchanging for 30 years. You can write most application-level code without touching the newer calls; newer calls are mostly there because they provide better performance, and are needed in more specialized situations. There are a lot of third-party libraries; however, they're not part of core UNIX, and it's reasonable for UNIX programmers (though maybe not from the users' point of view) to expect the needed libraries to be installed. Like core UNIX APIs, those libraries tend to use rather stable technologies.
  • Core Win32 APIs have no consistent reporting. OK, this drove me up the wall when I was coding on that platform. Does the MoveWindow() return NULL or INVALID_HANDLE on error? How about CreateFile()? And what's up with the ridiculous conventions for WaitForMultipleObjects()? Sure, GetLastError() is there, but so many APIs set this (including, say, MessageBox()) that many programs end up reporting an error as "The operation completed succesfully". UNIX APIs tend to return ints, -1 on error with errno set, a positive integer otherwise. Period.
  • The C library in Windows is a mess. It's getting better recently, but people still use old Win98 boxen that don't have a decent libc installed. This, plus the annoying mishap with memory allocation (there are too many ways to allocate memory: GlobalAlloc() (deprecated), LocalAlloc() (deprecated), VirtualAlloc(), CoTaskMemAlloc(), malloc() and operation new in C++--and they all use a different heap!), makes writing interoperable DLLs a real mess. Contrast with UNIX, which tends to ensure that malloc works the same across all libc versions, and where upgrading your libc pretty much upgrades your whole system, and you'll see why I was pulling my hair trying to fix installation problems with the C library. Of course, .NET will solve all this... Just like Java is supposed to solve similar problems on UNIX. Well, not everyone wants to install 100+ MB of runtime code just to run your application...
  • Windows SendMessage() is stupid. Granted, with MFC and such, you don't need to look at it as much. But what's the big idea of passing two parameters of a known bit-width for every message? Why not pass a void* pointing to a different struct for each message? The result: huge pain when porting from Win16 to Win32, and another huge pain that will occur when porting from Win32 to Win64. No wonder they want to move to .NET. Compare to X-Window, which uses the void* approach, and you have to admit that SendMessage() and the WindowProc() conventions are mis-designed.
  • Some Windows services are strangely tied to physical windows. For instance, many COM calls don't work if there's no window and no message loop. This is documented, but it's a pain in the ass for multithreaded programming. Ditto for timers; IIRC there's no way portable to Win98 that lets you have a timer callback without a message loop. Compare to UNIX setitimer(2).
  • UNIX threading is a mess. This has improved somewhat in recent years, but I still run into problems. Linux and glibc are the big culprits there. They have changed their threading strategies several time, and each time a glitch appears, we get a finger-pointing match between the kernel and glibc team. This is annoying to say the least. At least one widely-distributed Linux distro (RedHat 9) exhibits severe problems under load, due to bugs in the glibc that are partly made worse by the JDK. In my view, threading should be a kernel service (and I'm not completely alone in this view--it seems the Linux kernel is moving more and more towards that model) and it should remain stable, dammit. Sure, you could do similar things with fork(), but that's not a reasonable approach with a GC runtime. In contrast, Win32 threading has been rock-solid for years. You can bitch a lot about their synchronization privitives (events are extremely easy to mis-use, and their overlapped IO is one of the most convoluted APIs I've had the displeasure of using, full of corner cases and with no easy way to cancel without introducing a lot of extra code), but at least, threads switch properly, semaphorses are locked properly, and that part of the API has been very stable.
  • UNIX C++ integration sucks. UNIX people seem to prefer C. So, there's no integration between signals and C++ structured exceptions. C++ runtimes are not versioned as carefully as the libc. And so on and so forth. Annoying, this. C++ remains a second-class citizen in UNIX for rather stupid reasons. At least, in Windows, exceptions work somewhat right (you need to mess with _set_se_handler() IIRC to get it standard compliant, though), and the C++ runtime is versioned together with the C runtime (then again, the C runtime's versioning's already messy...).

I'm sure I could go on, but these are the main thing that strike me. I don't know if this is useful to somebody.

Credentials: I've worked in a Windows shop for four years, writing Windows applications first in raw C, then in raw C++. I've seen Win16 (the horror! the horror!), the passage to Win32, COM using raw C++ as well as ATL, lots of newfangled APIs (pen API, new serial port interface in Win32, WinINet, etc) and had lots of headaches getting the stuff to work all the way down to Windows 95. Lately, I've been mostly writing Java applications for UNIX, but I've had the opportunity to write some C code on POSIX systems once in a while. I like to think I know what I'm talking about on those two APIs.

1 comment:

Harish Pulimi said...

Excellent post, please post more of these