In my last post I talked about the reasons for changing the old 4NT / Take Command architecture. (Numerous Windows console limitations for 4NT, and problems in merging command line application i/o with a GUI command line in Take Command.)
My new plan reorganized things so 4NT (now renamed TCC) was solely responsible for the command line interpreter & batch file processing. Take Command would handle all of the GUI, including:
- Toolbars (the Take Command toolbar and the programmable tabbed toolbar)
- Status bar
- The command input window (primarily required for accessibility; more about this later)
- Mouse actions (text selection, context menus, etc.)
- Tabbed windows for the console applications (not just TCC, but any console app, including ones that write directly to the screen), and updating those windows with the contents of the appropriate hidden console window.
- Interprocess communication between the hidden console windows and other Take Command sessions. This would also allow TCC to query Take Command (and vice versa) for various information, such as the selections in the Folder and List View, window handles, etc.
- All keyboard I/O (which depending on the keystroke & any defined accelerator keys is redirected either to the hidden console window or to Take Command)
That was all fairly straightforward. The only thing left to do was to have Take Command start a hidden console session, and mirror the contents of its window to a Take Command tabbed window. That couldn’t be too hard.
My first idea was to use the Windows accessibility APIs. After all, they’re intended for just this sort of thing. But after a couple of weeks struggling to work around a number of issues, that approach had to be scrapped because of two unsolvable problems:
- The screen update notifications were not reliably delivered, and were occasionally delivered out of order.
- The notifications were slow. Really, really, slow.
Idea #2 was to use the kind of approach used in Console2 (a simple open-source tabbed windows interface for Windows console apps). This involves injecting a dll into every console process and then using interprocess communication to update the Take Command tab window with the contents of the console process’s window. This approach had one big advantage:
- The injected dll was running in the local console window’s process, so it could easily read the current contents of the console window.
And several fatal disadvantages:
- When you’re injecting code into another process, you’re now responsible for everything that happens in that process. So instead of only having to worry about supporting Take Command and TCC, I’d now have to worry about supporting every command line application, existing and future.
- It would be impossible to run DOS (16-bit) apps in a Take Command window. (Can’t do that anymore in Vista & Windows 7 anyway, but at the time I was still developing for XP and there were a fair number of users still running the occasional 16-bit app.)
- There would need to be separate 32-bit and 64-bit versions.
- A lot of antivirus apps will flag an app injecting code into other apps as a possible virus. (And not unreasonably!) I foresaw an unending stream of erroneous “bug” reports about Take Command being infected with a virus.
- Third-party apps injecting their buggy code into Take Command (and to a lesser degree 4NT / TCC) have been a support headache for me for several years. So I tend not to look favorably on the whole idea in the first place.
- The IPC between the console process and Take Command was slow. Not as slow as the accessibility notifications, but slow enough to highlight a significant slowdown running an application in Take Command compared to running it in a stand-alone Windows console.
So, throw away a few more weeks of work on idea #2, and on to idea #3. Windows unfortunately only allows allocating a single console per session, so I couldn’t simply allocate a new console for each new tabbed window, and switch between them. Trying to repeatedly allocate & free consoles also turned up a Windows bug that after a few hundred allocations / frees the console allocation API would no longer work. (Though I couldn’t really fault Microsoft for this one; I doubt it ever occurred to them that somebody would try doing something like this.)
By this time I was feeling a bit like Thomas Edison trying 10,000 different materials when he was inventing the light bulb. I had uncovered more than 70 Windows API bugs / documentation errors / undocumented “unfeatures” (take your pick) so far, and it seemed that Windows was going out of its way to prevent anyone from creating a viable console replacement.
Next: Idea #4 gets thrown against the wall …
Amazing explanation Rex!
Thanks for taking the time to write and post these history & design articles. I find them quite interesting.
I have to say I’m really enjoying this series of posts, in part because it’s nice to know I’m not the only Windows programmer who has beat his head against Microsoft’s stupidity for weeks at a time.