The previous parts:
- The C7 Days: More tools, more problems | by Rico Mariani | Jul, 2026 | Medium
- The C6 Days: Tools, Memory, and Bugs | by Rico Mariani | Jul, 2026 | Medium
- My History of Visual Studio. [This was originally posted 10/5/2009… | by Rico Mariani | Medium (especially Part 1 which also deals with the time frame of this article)
Launch Video:
In this time period, everything happens in the shadow of Visual Basic. So let me start there…
Visual Basic came out in May of 1991. What a landmark. I mean, the thing was ridiculously good. It was ridiculously easy to get started and put together all kinds of cool-looking Windows apps that you could never have done before. I mean, you could have done them before, but it was a crazy amount of work to get this kind of a result, and now you could do it in minutes. All kinds of applications could be coded in just minutes. It was a fantastic offering, and I think it really changed the way people thought about development tools, certainly out-of-the-box development tools. This kind of thing was maybe previously discussed in the context of, you know, computer-assisted development, that kind of thing. But the idea that this would be a normal RAD tool, like, whoa. And that was May of ‘91.
C/C++ 7.0 didn’t come out until September of ’92. And we were looking at Visual Basic with wide eyes. And it was quite a thing for us to think about. Like, how are we going to put something in that ballpark? And I think despite the fact that Visual C++ was a great product, it didn’t hit the mark, at least not at first. Calling it Visual C++ was “aspirational marketing”.
But I don’t want to take away from the amazing work that people did on that product. The AppWizard was like nothing anyone had ever seen before, it allowed you to create really wicked-good C++ applications — at least document-centered ones — and get off the ground in minutes. And the output was readily customizable. The idea that you could just edit that output, tweak it however you needed to do, was itself a radical thing. I mean, maybe it had been done before. I’m sure someone had done it before. But putting it in everyone’s hands was a big deal. And ClassWizard was the thing that gave you the “visual” feel. That’s the “I can wire new handlers to buttons” thing; pick a control, pick a method, boom, message map, boom, function ready to write.
The best part about message maps and the message decoding techniques that we used to make this work was that, by and large, they were comparatively straightforward things that a person could understand in a few minutes. If you understood pointers to members, you could understand how a message map worked, and it sure beat the heck out of switch statements and Petzold-style Windows programming. You could code all day long and never have to deal with a window class or wParam or lParam and just, find yourself writing the code you needed to write to do your app. It’s pretty darn good.
OK, I could go on at great length about features, but I don’t think that’s the point of an article like this one. I want to talk about a few things that were going on in Visual C++ that are sort of invisible and yet very interesting and super hard.
OK, first, the tools. We can’t have different tools for the windowed environment. We have to have it be the case that whatever tools we’re going to use in the command line are going to be the same tools that we’re going to use in the windowed environment. And we didn’t want it to be the case that we had to have different versions of all the tools for all the different operating systems that we had to support. And at that point we had to support Windows NT and we had to support DOS (and notably not OS/2). So in order for this to work, we needed some kind of DOS extender that could let us write Windows NT binaries and run them under DOS using the high memory, and the various features of the processors that people typically had. And by that point, everyone had at least an i386. Most developers were packing a 486, and by the end of 1993 people were going to have Pentiums for the first time. OK, so DOS extenders were going to be very doable. And there was a good one.
Phar Lap had a nice little system called the TNT DOS Extender. Now this thing switched the processor into protected mode after the real-mode-style launch, and it could emulate all kinds of NT features. I mentioned this before because it came up in the context of some weird bugs that we ran into, but basically TNT meant that our regular 32-bit binaries, the ones that we were running on Windows NT, would work under DOS. That’s a fantastic situation to be in because it means you can spend a whole bunch of your time debugging all your regular problems under Windows NT and only rarely do you have to worry about issues in DOS. When we did run into issues in DOS where something was going on with the DOS extender, we could use NuMega SoftICE for the most part to get to the bottom of whatever was going wrong, and the Phar Lap guys were pretty responsive.
We had a pretty sweet licensing deal, and I don’t know anything about the business side of all this, but we were comfortable shipping the thing to our whole customer installation. I think they were pretty excited about having more people using the product and getting familiar with it. The net of all of this is that every command-line tool that was part of Visual C++ was running in the DOS-extended environment. And another really cool thing about this was that a DOS prompt inside of Windows could use that very same environment. So even if you opened a DOS window inside of Windows, the nested DOS still worked really well. And still let you run those 32-bit tools. Which is not a given. Remember, those DOS windows were not running in “Real mode,” but rather in “Virtual 386” mode and with Windows as the high memory provider.
OK, so the first behind-the-scenes miracle is this DOS extender that works in real mode and it works in virtual 86 mode and it can negotiate for memory with HIMEM.SYS or with Windows. It’s all very exciting. I’m sure nothing ever goes wrong there.
Now, let’s keep talking about tools. Keeping in mind this is Windows 3.1. One of the things an IDE has to do is run tools like NMAKE. I mean clearly, you can’t not run tools, and it’s not like you’re going to build a whole other set of tools that run in process with the IDE and do their output. You could do that when it was just Quick C, and there’s exactly one compiler and exactly one build system and, it only handles one or two files or whatever. But we needed this to be a real development environment that could run any kind of build that you might want and basically debug any native code at all. OK, so that meant running the normal command-line tools, the normal compiler, the normal linker, the normal Bscmake, the normal database creation system, the normal CVPACK. All that stuff had to happen. Well that sounds simple, right? You just launch the thing and, set its output to a pipe and capture the output and then display the output in a screen and maybe use a background thread or something like that to do the job. That sounds very straightforward. OK, but I just said that you should do a bunch of things that don’t exist on Windows 3.1. For instance, there’s no pipes. And, for seconders, there’s no threads. I mean, maybe that’s for firsters. There’s no background thread, there’s nothing.
In order to capture the output from the DOS box, you’re going to have to arrange for all console output that’s coming from DOS applications to somehow get captured in a way that Windows can see it, and the only way to do that was with a device driver. The combination of “wintee.exe” and “mmd.sys” was watching for console output, and then it would post messages periodically to Windows that had the text payload that’s being echoed to the screen, and then we could capture that and arrange for it to show up in the output window. And this is super important because you can’t really change the DOS environment. You don’t have redirection you can capture. And, importantly, the DOS window isn’t even visible! You don’t want that clunky DOS window to show up in the middle of a build, much less appear and disappear on the regular. So you create the DOS environment, with a hidden window and let it do its thing in there. You use the posted content messages to show compiler output as it happens so people can see what’s going on and even stop the build. And again, how do you stop the build from Windows? Well, you have to send a signal to DOS. And, how do you do that? Well, you have to have help from a device driver that’s going to be in the right context, because it’s not like you can do some INT 21h to stop the running process in DOS while you’re in the middle of Windows. The things in a virtual 386. You got about as much chance of catching that output as you do of catching the output of, say, a Hyper-V VM running Linux.
You can learn more about this if you want to spend some time Googling WINTEE.EXE and MMD.386. It’s a little bit bizarre that you have to invent an entire mechanism with device drivers to catch output from tools. Certainly the 32-bit operating system (WinNT) did not require this, and when we did the first version of 32-bit Visual C++, we didn’t have to do any such craziness. We just launched the process, captured the output in the usual ways with pipes, and we were able to display the output, no problem. Now, I say no problem but of course there were bugs and it took a while for this to all work properly in Windows 95, but by the time all was said and done and the bugs were fixed, it just worked the normal way you would expect it to. Windows 3.1 had no such features.
Now you may think to yourself, “OK, that’s probably the weirdest thing about Visual C++”, but we’re not even close. I’ve talked about soft-mode debugging before but I think maybe it’s worth going into it a little bit again here because it is the craziest thing about Visual C++.
The Visual C++ IDE provides debugging, GUI debugging, and you can debug any Windows app. And putting aside all the other things that have to happen in debugging, let’s just talk about breakpoints. Because if we talk about breakpoints, I think that’s enough for you to kind of get a sense that what I’m about to talk about is going on all the time. OK, normally when you hit a breakpoint you stop the process, give control to the debugger, and allow debugger commands to run to inspect the stopped process (the debuggee). It works like this in virtually every operating system you’ve ever heard of. But not Windows. Or at least not Windows 3.1. Why not?
Well, the 16-bit Windows products all used cooperative multitasking. And they were not multithreaded. There’s one thread. Exactly one thread. I mean, fair enough, there’s tricky bits for interrupts and device drivers and other wonkiness, but from the perspective of the GUI, there’s one thread.
Now, the reason that it looks like there’s multiple threads is that the apps are cooperating. When you’re done doing whatever you need to do, you yield, or you block waiting for another message, and then Windows can run something else. Every time you try to get another message is an opportunity for the operating system to swap in somebody else and let them run if they have a message waiting. It’s fabulous.
As an interview question I used to ask: “When you call SendMessage on Windows 16, a synchronous call, why is it that you know that it’s OK to call directly into the window proc at that exact moment?” In particular, couldn’t it be doing something else? What makes you think it’s so safe to just call the window proc? I mean, it’s SendMessage, it’s not even PostMessage, we’re synchronously and promptly calling the window proc. How could we possibly do that? Well, there’s an easy answer to this and that answer is “if that window proc was doing something already, your window proc couldn’t possibly be running to call SendMessage”. That window’s whole application has to be blocked on a yield or on a GetMessage, and by construction, if you’re blocked on a yield or a GetMessage, your window proc can be dispatched directly by the operating system and it is! You can just get a direct call and we know you’re doing nothing, and we know you’re doing nothing because if you were doing something, nobody else could be running to call SendMessage. So it has to be safe.
OK, but let’s think about that. There’s only one thread. If we hit a breakpoint, we can’t stop that thread. That stops everything. The thread has to keep running at least to let the debugger run, but the debugger is itself a GUI application, to say nothing of every other application like say “explorer”. So we have to keep all the apps running by pumping messages. But if we keep pumping messages and you move your mouse, say, over the window that belongs to the debuggee, what’s going to happen to those messages? The debuggee is supposed to be stopped. If you were to let it run, even processing an innocous WM_MOUSEMVE or whatever, arbitrary side effects could happen, and there’s no sort of rule that tells you that if you stop in this random place in the middle of the debuggee that you could even run more debugging code at all in any safe kind of fashion. The debuggee could be in the middle of a computation with any number of invariants temporarily broken or whatnot.
So the debuggee cannot run. And yet, from a Windows perspective, the debuggee must run. So what do we do? Well, we shim pretty much everything. For starters, you have to find every window that the debuggee opened, and you have to subclass them all dynamically. You do this so that if any of them receives a message, instead of running whatever the debuggee would normally run, you can do some sort of neutral, safe-ish kind of thing, like maybe run DefWindowProc. But sometimes that’s not enough (or it’s too much). Like if you receive DDE messages, you need to queue them up to deliver them later. Same with some COM messages. Some messages you should just respond with some sort of generic error to.
So it turns out there’s a complex mechanism that allows the debugger to field messages on behalf of the debuggee and magically have it not be totally broken. Hundreds of different message types with dozens of different rules for what you do for that kind of message. The easiest are things like “if the debuggee is asked to paint, you can just draw white”. And that’s not great, but it doesn’t cause a crash and a person can see that the debuggee isn’t really painting at that point. It does mean that it’s very difficult to debug paint problems with a GUI debugger. You could try to carefully make it so that the debuggee is in its own little part of the screen and its windows don’t overlap with anything. So it’s not going to get called to draw while it’s stopped. But that’s tricky. This way, if you get it mostly right, you’ll mostly be able to see what it’s doing, and you’ll sometimes be able to debug paint problems. If that doesn’t work, you have to switch to a hard-mode debugger like CVW or SoftICE.
But wowza. Breakpoints. Single stepping, step over, watch window. Keeping in mind the watch window can have arbitrary function calls in it that arbitrarily call back into the debuggee, that’s already, like, crazy hard. All of this has to work on the regular and it looks like nothing is happening, but every single step is a miracle.
Now, on top of all of this, of course you need a decent editor. You want syntax coloring, you want lots of cool editing features, you need to support all the languages and, you know, do localization, you need to be able to print, cut/copy/paste, and all the the other things. It’s a no-kidding-around GUI application. It’s… a lot.
C/C++ 7.0 wasn’t generally available until September of 1992, even though the launch was I think more like in March, so months earlier. We were already working on Visual C++ well before C7 finished. And we were starting from the basis of QuickC for Windows, “QCW.” But that product had a lot of simplifying assumptions, like just a single build type, for instance. Still, it was super helpful, and it had solved many of the debugging problems before we started, so that helped a lot. But of course, having solved many of the debugging problems left you with the many debugging problems that it hadn’t already solved.
Now, there’s a few more things things I’d like to say about the command line tools.
For starters, we did a lot of work to make them faster. We did a lot of work in the compiler. We did a lot of work in the linker. We did a lot of work in the debug info packer, CVPACK. It was in this version that PDB files started making their appearance big time. That allowed us to do shared type information across lots of object files, and that saved us tons and tons of disk operations. Just like precompiled headers had saved us from having to write a bunch of information into the OBJ files, PDB again let us share additional information. In some sense, the PDB file was pre-packing the type information.
This ended up being a huge innovation because, for instance, the checksums and fail-safes that were in the PDB files made them very suitable to find dynamically. So you could store them in bulk and download them from, say, a simple server and get that the info you needed on demand. You could still recover .PDB files because there was just enough information in the executable to uniquely identify the PDB file, even if you didn’t happen to remember the original path.
A ton of work went into making those files fast/small/safe. If you like, you can think of them as a very simple two-phase-commit database. The “database” has a bunch of streams in it, so it’s kind of like a .zip file in that it’s many files in one file. The streams are numbered, and stream 0 is a name table that has the names of all the other streams. So stream 0 is the directory, if you like. Streams can be replaced but not updated. So basically, you can re-emit all of (e.g.) stream one. There’s page mapping for the stream contents so that the overall write operation is atomic. Everything comes down to one last write which either succeeds or fails. And if that write doesn’t happen, the whole database is logically the way it was at the start of the run. And that’s important because people might hit Control-C in the middle of a build, and you don’t want to be worried about corrupting the PDB file because the linker was interrupted, for instance.
To make this all work, the compiler had to produce these files, the linker had to consume and merge them and re-emit PDB files. Actually most of the time when people think about PDB files, they think about the one that the linker makes, the one you download from (e.g.) a symbol server, but actually the ones that the compiler makes are just as important, and they contain partially reconciled debug information for some group of compilands. This is super helpful because, of course, it means much less work for the linker as well, because reading a bunch of PDBs is much faster than reading individual OBJ files to find debug information here and there. In a PDB you get much total data for the same content. That meant CVPACK also benefited. Much less to read, much less to pack, much less to write.
The real tour de force for CVPACK was we noticed that we were spending more time writing uncompressed debug information from the linker than we were actually packing it. So we bolted the packer into the linker, and instead of writing the debug info from the linker, the packer was changed so that it read the unpacked info right out of the linker’s memory and then wrote the packed information directly. That made a huge difference in terms of speed. To make this work without rewriting the packer, we created this in-memory directory of “where the output would have been written had it been written”, so that when the packer issued a seek, we would know which chunk of memory that was in. That little index basically made it possible to piecewise assemble the result of any read that the packer might call for and the packer was literally calling APIs that looked like seek and read, and so forth — it didn’t know that it wasn’t reading from a file.
All this put together meant for a really nice-looking toolchain. Maybe the best we had ever produced until that point. Nice 32-bit compilers and linkers with all the memory that they needed and that could take advantage of way more than 640K if you had it, which most people did at that point. They were well debugged because we could test them under Windows NT. And we invested in a bunch of performance fixes up and down the chain, some structural and some tactical. And on top of that we built a really nice resource editor, App Studio, which I barely mentioned. A really nice set of foundational classes — MFC 2.0 was really wicked good. And, of course, all the trickery needed to create an actual working IDE in an operating system that was cooperatively scheduled. A lot of this stuff basically goes unnoticed. And I guess that’s a good thing.