Hey, I'm Dave, Welcome to my Shop. Today in Dave's Garage, it's Part Three of the History
of Windows Task Manager. I'm Dave Plummer, it's original author, and I'm going to take you on
an exclusive guided tour of the never-before-seen original source code to the Windows Task Manager.
Welcome to Part 3! It's time to look at the actual Task Manager code to see if anything I did back
then is informative, instructive, or entertaining. And it's not just any code, but old school C++,
complete with virtual destructors and pointy edges and everything. Grab a helmet come along
for the ride. The code we're about to delve into represents the Windows Task Manager as it
stood about when I retired, right after Windows XP and Server 2003. I had not actually seen the
source code at all since then, until it came time to prepare for this presentation. I have a pretty
good memory for code. I thought I remembered most of Task Manager well enough that I would
only need to look up some finer points, but I was sorely mistaken. What I did remember was all true,
but it's much larger and more complicated than I pictured it in my memory. I thought we could
skim the whole thing and I'd give you some commentary on it, but at 22,703 lines of code,
that'd be about 350 printed pages. So that would hardly be possible or practical.
Now that my memory is suitably refreshed, my plan is to first give you the thousand-foot
view of the project as if you were a new member on the team, just getting your bearings. Once
you've got the big picture, we'll dive in and check out some of the highlights and details.
Believe it or not, Task Manager is the only time in my entire Windows career that I had
the opportunity to a create a new WinMain for an application in the product. Even ZipFolders was
a DLL extension that lived inside the Explorer's process. It was also one of the few times I wasn't
inheriting a bunch of legacy code. Task Manager was entirely fresh and new from ModuleEntry to
ExitProcess. Believe me when I say that's a rare opportunity, or at least in my experience it was!
Seen from high above, Task Manager is a standard Windows app, which means it has a main window and
a message pump. Within the main window is a tab control that in turn hosts a set of page objects,
such as the Performance Page and the Task Page and the User Page. The pages all inherit from a base
class, CPage, that provides surprisingly little commonality between the pages. There are seven
pure virtual functions that handle common actions such as being created, activated, updated, and
destroyed. Other than their preferred text title, the pages provide nothing back to their host,
and operate as little black boxes of their own, doing their drawing and so on without the help or
involvement of the main program. The only thing the main window provides to the pages is the
refresh interval event so that all pages update at the same time instead of independently.
The Process page is the most complicated of the set, coming it at about 5000 lines of code.
Or as IBM used to say "It's about 5 KLOCS". Most of that is related to the ability to enumerate,
interrogate, manage, and terminate processes. Task Manger updates every second or two,
but will actually update continually if you hold down the refresh key, which is F5. They've
throttled the refresh rate in the current Task Manager but the original had no such restriction.
I'd love to see how fast it could scroll today on modern hardware! In any event, for each and every
update frame each page refreshes all of its data. So, the performance page gets the CPU usage, the
process page enumerates all the processes, and so on. Each page then very selectively reviews that
data and compares it to the last frame so that it knows precisely what has changed and what has not.
It updates the view with a goal towards eliminating any unneeded drawing whatsoever.
The pages all use a common approach to keep track of what has changed in the system from
one update to the next. For example, for each and every process that is running, Task Manager knows
not only what all the current performance data is for that process, such as memory and thread
count and so on, but also which metrics have changed since the last frame and which have not.
That allows it to refresh only that process information that has actually changed from one
update frame to the next, and thereby avoid any unnecessary painting and potential flicker. That
should be enough of the big picture that we can actually jump into the Visual Studio Code editor
and have a look at the source. As we do so, keep in mind that other than going through it once to
jog my memory ahead of time, I'll be looking at it live in the editor with you, speaking
extemporaneously, making mistakes and incorrect recollections as I go, so please humor me a little
bit as we turn our attention to main.cpp, where it all begins, and where it all began. CODE Our
program begins at function named ModuleEntry, which is where the linker has been told to
tell the loader to start execution. The first thing it does is to call security init cookie.
Sounds devios, but it actually places a special cookie value at the end of fixed memory buffers
so that buffer overruns can be detected. If those cookies are ever missing or corrupted, it knows
immediately that memory has been damaged by a bug, and that it's a potential security hole.
You might be thinking a Windows program starts with WinMain, but there's actually additional work
that needs to go on before main. After all, if you think about it, how do your constructors run on
global or static objects if execution truly begins with WinMain? It's not magic, someone or something
must call that code. And that something is the CRT, or Compiler Runtime. It injects
into your code a table of function pointers that represent things such as your global constructors.
Normally the true entry point to your program is a stub function such as "Module Entry"
that does this work before calling WinMain. It's all behind the curtain, but because I
opted to save space by eliminating the CRT, I have to do it manually. Odds are this is the
first time you've ever seen how the sausage is made, and it's not pretty, so I'll keep it brief:
Right above our Module Entry are four linker sections that even the comment calls ridiculous,
so don't worry the details, but that's where our tables of function pointers will live. We
then have a function called init term that walks those tables and calls all of the initializers.
With that done it calls WinMain and execution of the program begins as normal.
The first thing WinMain does is to try to manage the single instance problem by looking for a
running instance of Task Manager. If it finds a candidate, it uses Send Message Timeout to
send a challenge code and wait for a response. If the right response comes back it activates that
existing instance and then exits. In all other cases a new Task Manager proceeds to launch.
You can see here my least favorite registry key - the one that optionally disables Task
Manager. If you've ever been denied Task Manager in a school or library or similar,
this is the policy line that the administrator has set in the system registry. In the normal case,
Task Manager now goes about its business of creating the task, process, performance, and other
pages. To be on the safe side, though, it first creates just the Task and Process pages. It then
checks memory and if memory is low, as in under 8 megabytes, it goes straight to work without
creating any of the fancy performance graphs or similar. You get a simpler task manager that
is still able to perform the process management that you likely need to fix whatever is wrong.
If you've just been through a college degree in computer science you are likely horrified by my
use of goto. After all, you've been told your entire life that goto is evil, and yet I'm here
to tell you that's not entirely true. Convoluted execution is evil, and spaghetti code is too, but
the use here is akin to raising an exception that takes execution to the cleanup label. There any
allocations are unwound. This approach is far more compact and easy to read and follow than a deeply
nested structure of conditionals that unwinds on its way back out. I've done it both ways and
after decades of consideration, I'm here to tell you that if you're not using actual exception
handlers, this is the cleanest way I've found to manage unwinding complex resource allocations,
at least without smart pointers. Because I was a young man, just two years out of school when I
wrote this, some of my comments are just a little too clever. In the cleanup case, this reads "Yes,
I could use virtual destructors, but I could also poke myself in the eye with a sharp stick. Either
way you wouldn't be able to see what's going on". I'm not entirely sure what I meant, but I think I
preferred tearing down the classes explicitly by name rather than in a loop where you don't know
from reading the code which class destructors are being called. This is sort of a pedantic approach
to making the code very obvious as to what it's doing, but not likely how I'd approach it today.
Live and learn. Task Manager places a high priority on responsiveness and never hanging,
but there are certain system calls that can still unfortunately block a long time. One such example
is the function for Exiting windows. Depending on the circumstances, it can take a long time
to return control to the caller. Because I do not want to block the main UI thread for task manager
at all, the solution is to spin up a separate thread and execute such APIs on a separate thread.
That way, if the API blocks the task manager application is still free to continue.
When the API finally returns that's when the exit windows functionality will be invoked.
You might have noticed the use of a class called C Privilege Enable. A privilege is like the right to
use the web cam or power off the system. It's good security practice not to enable any privileges
that you are not actively making use of, and to that end this class is just a helper I wrote that
allows me to enable a privilege that I need to use, but that will turn it back off automatically
as soon as we leave that function scope. As a brutal fallback, which may have seen be long
removed, I see that holding down the CONTROL key while selecting shutdown will pretty much
instantly flush buffers and power off the system right then and there. And Speaking of shutting
down the system, that's a good example of the two ways that an application can be written.
In the first case you provide a menu for shutting down and if the user lacks the privilege to do so,
they get an error message when they try. The better approach is to adjust the user interface to
reflect what they are able to do before they even try. So, for example, if the user doesn't have the
rights to hibernate the system then we shouldn't even offer them that choice. That's the approach
Task Manages takes, but as you can see checking on such things proactively can be complicated.
I'll just take a quick skim through the shutdown menu creation, but it will give you an idea of
how much work and forethought goes into even displaying a simple menu item sometimes. Task
Manager stores all of its settings in a C Options class. That class has an all-important ability for
resiliency, which is that it can restore itself to a useful factory state at anytime. Not just zeros,
but default options that work as if new. Whenever options are loaded everything is also validated
carefully. Imagine a case where you had a large monitor, parked task manager to the right side of
that screen, but switched to a smaller monitor before the next time you started your system.
This would break a lot of apps because the window is saved in a position that is now off screen.
That's the kind of thing that task manager validates against. You can force a reset to
defaults by holding down CONTROL, SHIFT, and ALT as Task Manager starts. If you can manage to get
those keys down before it loads its options, this bit of code will see those keys and force a reset
to defaults. Now let's turn our attention to task page dot CPP. a task is different from a process
in that a task is simply any top-level window with a caption bar and a few other characteristics.
To enumerate tasks properly, one must first enumerate all of the window stations.
Microsoft Windows allows for multiple users to be logged in at once whether through terminal
server sessions or the local console. Each session has its own window station. Each
window station can have an arbitrary number of desktops, and each desktop can have many windows.
To find all windows in the system you must enumerate at each of those levels.
One complication is that any thread that has created UI resources is then bound to that
windows station. That means our main thread, the one we are currently running on, is not
allowed the ability to jump around to different window stations and do enumerations in the first
place. That, in turn, means we must create a new thread that is free to roam and wait for it to
complete its work for us. Once the tasks are enumerated each is represented in memory by a C
task info structure. It contains attributes for the things you'd expect like window title, icon,
and so forth. To keep track of which columns of which tasks have actually changed from one refresh
pass to the next, a set of bitfields keeps track whenever an attribute is changed. By checking
the union of those bitfields you get an overall indication of whether anything has changed.
The process page is the most complicated of the pages. It follows the same general strategy as the
task page but has many more columns as processes have many more attributes than do tasks.
One frequent need that Task Manager has is displaying numbers, particularly large values with
commas. To be able to accommodate negative numbers in any number base and other such niceties,
it looks like my friend Brian added a function do to so. Building on the standard NT libraries,
which include a function called RTL Int 64 to Unicode String,
he naturally named it My Mo Betta Rtl Int 64 to Unicode String.
The process page does one thing that turns out to be essential to its mission of minimal painting:
it does a live merge sort of incoming process data with the existing list and marks only new rows or
changed columns as needing updating. Essentially, it takes the old list and the new list and matches
up what it can, deletes what it can't find in the new list, and adds new rows as they appear.
And finally, we'll take a quick look at the performance page. It's the
one with the charts and graphs and which has evolved the most in the last 20 years or so.
We've all seen the little graphs in Task Manager, so let's go look at some comfort code:
the code that draws the graph paper background. That way, whenever you see those little graphs,
you can picture and remember the code that renders them. It does exactly what you think it does:
It steps from top to bottom drawing horizontal lines every 12 pixels and then right to left to
draw the vertical lines. The only complication is that you need to shift the vertical lines
according to the current scroll position. To draw the actual graph, you can see it steps
through the last history values, up to 2000 deep in the original version, and does a simple MoveTo
and then LineTo to draw the jagged line chart. Yes, that's right, it's turtles all the way down.
Finally, it blits it from an offscreen bitmap onto the screen all at once. Why? You guessed it:
to prevent flicker. Well, that's probably a good place to end this high level overview of the code.
If there's sufficient appetite for more explanation or detail on the code, please
let me know in the video comments! BREAK Thanks for joining me here today in Dave's Garage! I'd
very much appreciate it if you enjoyed this look back at the Windows Task Manager, that you share
the video with someone you know that actually likes such things. Odds are if you liked it,
you know someone who'd never see it otherwise, and I would very much appreciate the kickstart!
It's a very narrow niche, obviously, so I'm entirely dependent on your shares and word
of mouth. Can a Task Manager video even go viral? Well, not without your help!
So text or email someone the link to the video, or you could click on the Share button in the
toolbar below the video, even if you've never clicked on it before. It won't bite,
it won't hurt, so give it a try. Consider it your personal growth and good deed for the day.
If you'd like to see future installments, do be sure to subscribe to Dave's Garage and to turn on
the bell icon and personal recommendations. If you found today's Task Manager tales informative
or entertaining, please leave me a like on the video. If you did not, consider sending me a
strong message by clicking on dislike twice. That will get my attention. And if you've got
any questions, fire away in the comments, I'll do my best to answer. Thanks, and I'll see you next
time here in Dave's Garage. And finally, just to be clear, I retired from Microsoft more than
15 years ago and while they graciously provided certain permissions that made this video possible,
I am not an official nor a spokesperson for them, and all opinions herein are mine only. Cheers!