> That was the whole point of it. It wasn't an "OS" per se; DOS was the OS. Windows was what a Linux-head would think of as a combination of an X server and window manager.
This seems like a very post-3.0 (i.e. 386-only) view of things—the 8086 and 286 versions of Windows were also fairly advanced memory allocator/overlay manager hybrids.
They parcelled out memory, compacted it to avoid external fragmentation (take that, dlmalloc!), expelled pieces that could be read back from executables, and discarded segments that the application programmer said could be recovered, as necessary. (Yet they couldn’t actually swap mutable memory, as far as I can tell. Why?) For data, they required your cooperation by only revealing addresses between *Lock and *Unlock calls and requiring you to store handle+offset pairs otherwise; for code, the 8086 kernel would reach into your stack and walk the frame pointer chains in order to redirect return addresses to swap-in thunks. (Maintaining LRU lists along the way in either case.) Things became better on the 286 when it could just hand out segment selectors and arrange for accesses to fault as required, but this is the problem statement that Windows 1.0, running on the 8086, set out for itself.
Now, none of this is impossibly difficult (although I shudder at the thought of doing it without good debugging tools), but it feels pretty damn OS-like to me. You might argue there isn’t much virtualization of hardware going on—except for RAM, CPU, display, keyboard, and mouse—but I’d say there is at least as much of it in Win16 as there is in DOS.
One would hope things would get easier on the 386. And then one gets to DPMI and VDMs and still wants to support 16-bit drivers that hooked BIOS calls as though that would help and now the system cannot interact with the user while it’s formatting a floppy[1].
> [T]he concurrency primitive wasn’t the “task” [but instead] the window. Until Windows 95, Windows was a multi-windowing OS. [... E]ach window participated separately in the global Windows event loop, up-to-and-including things like having its own set of loaded fonts, its own clipboard state, and its own interned strings table. In OOP terms†, your application was just a dead "class object", running no logic of its own save for one-time load and unload hooks.
That... doesn’t sound correct on the implementation level. If you’re an instance of a Win16 executable module, you own a copy of your executable image’s mutable data, you own your memory allocations (that are not marked GMEM_SHARE), you own a stack, you get all (“posted”, i.e. asynchronous) messages for all windows you (or your library dependencies) created and you ask the system to munge and route them to the message dispatch routines of the windows you created—or not, if you don’t want to.
Now, the overall effect is very much like what you described, and often it may feel that you could as well throw out all those tedious GetMessage-TranslateMessage-DispatchMessage loops and replace them with a standard implementation of a vat[2]. Then a puny message handler decides to hijack the whole thing and go into a modal dialog loop. And damned if I could describe what that means in terms of an object model.
(What would you say about Symbian? Now there’s a system that throws out processes and only runs objects. Except it doesn’t run inside of a DOS or anything; there’s a kernel and on top of that there are objects. Boom. ... I think?)
> The actual more interesting analogy is that Windows was essentially a (single-threaded, cooperatively-scheduled) actor system, where windows were the actors.
Yeah, that I’ll wholeheartedly agree with. Nevermind the drawing part, it even has separate synchronous sends (SendMessage) and asynchronous ones (PostMessage)! Of course, unlike E’s[3], these have completely insane interactions with the concurrency parts, especially once you get to Win32.
This seems like a very post-3.0 (i.e. 386-only) view of things—the 8086 and 286 versions of Windows were also fairly advanced memory allocator/overlay manager hybrids.
They parcelled out memory, compacted it to avoid external fragmentation (take that, dlmalloc!), expelled pieces that could be read back from executables, and discarded segments that the application programmer said could be recovered, as necessary. (Yet they couldn’t actually swap mutable memory, as far as I can tell. Why?) For data, they required your cooperation by only revealing addresses between *Lock and *Unlock calls and requiring you to store handle+offset pairs otherwise; for code, the 8086 kernel would reach into your stack and walk the frame pointer chains in order to redirect return addresses to swap-in thunks. (Maintaining LRU lists along the way in either case.) Things became better on the 286 when it could just hand out segment selectors and arrange for accesses to fault as required, but this is the problem statement that Windows 1.0, running on the 8086, set out for itself.
Now, none of this is impossibly difficult (although I shudder at the thought of doing it without good debugging tools), but it feels pretty damn OS-like to me. You might argue there isn’t much virtualization of hardware going on—except for RAM, CPU, display, keyboard, and mouse—but I’d say there is at least as much of it in Win16 as there is in DOS.
One would hope things would get easier on the 386. And then one gets to DPMI and VDMs and still wants to support 16-bit drivers that hooked BIOS calls as though that would help and now the system cannot interact with the user while it’s formatting a floppy[1].
> [T]he concurrency primitive wasn’t the “task” [but instead] the window. Until Windows 95, Windows was a multi-windowing OS. [... E]ach window participated separately in the global Windows event loop, up-to-and-including things like having its own set of loaded fonts, its own clipboard state, and its own interned strings table. In OOP terms†, your application was just a dead "class object", running no logic of its own save for one-time load and unload hooks.
That... doesn’t sound correct on the implementation level. If you’re an instance of a Win16 executable module, you own a copy of your executable image’s mutable data, you own your memory allocations (that are not marked GMEM_SHARE), you own a stack, you get all (“posted”, i.e. asynchronous) messages for all windows you (or your library dependencies) created and you ask the system to munge and route them to the message dispatch routines of the windows you created—or not, if you don’t want to.
Now, the overall effect is very much like what you described, and often it may feel that you could as well throw out all those tedious GetMessage-TranslateMessage-DispatchMessage loops and replace them with a standard implementation of a vat[2]. Then a puny message handler decides to hijack the whole thing and go into a modal dialog loop. And damned if I could describe what that means in terms of an object model.
(What would you say about Symbian? Now there’s a system that throws out processes and only runs objects. Except it doesn’t run inside of a DOS or anything; there’s a kernel and on top of that there are objects. Boom. ... I think?)
> The actual more interesting analogy is that Windows was essentially a (single-threaded, cooperatively-scheduled) actor system, where windows were the actors.
Yeah, that I’ll wholeheartedly agree with. Nevermind the drawing part, it even has separate synchronous sends (SendMessage) and asynchronous ones (PostMessage)! Of course, unlike E’s[3], these have completely insane interactions with the concurrency parts, especially once you get to Win32.
[1] http://bytepointer.com/resources/old_new_thing/20090102_002_...
[2] http://www.erights.org/elib/concurrency/vat.html
[3] http://www.erights.org/elib/concurrency/msg-passing.html