Holy cow, I wrote a book!
I found it amusing that somebody considered the fact that Microsoft employees can read my queued-up blog entries before the articles are published to be further evidence of Microsoft's evil essence as a monopoly.
Just for the record, this is not evidence of Microsoft's evil essence as a monopoly. Rather, it's evidence of Raymond's evil essence as a monopoly, because the monopoly on blog articles written by Raymond Chen that haven't yet been published belongs to me.
There are a few places in Windows where you are asked to enter your name in order to set up an account. Just for fun, I went through all the localized versions I could find and extracted the sample names. Some locales did not get around to translating all the strings. If the string was left untranslated (which can happen for LIPs), then I left the box blank. (Locales which did not translate either string have been omitted from the table.)
My reactions after the table.
Some observations:
Related: The Locales of Windows 7, all divvied up.
In Windows 2000, Explorer let you add properties like Summary and Author to nearly any file type. But when you view the files from a machine running Windows XP or later, those properties are lost. Where did they go?
Most file types do not have extensibility points for adding metadata. For example, every byte of a plain text files is devoted to text data; there is nowhere to put metadata like Author or Summary. In Windows 2000, the shell chose to store this extra information in NTFS alternate data streams (or more accurately, the shell chose to use the STGFMT_FILE storage format, which is implemented in terms of NTFS alternate data streams.) Storing the information in alternate data streams attaches the data to the file without affecting the file contents.
STGFMT_FILE
This was a clever idea, taking advantage of NTFS's ability to attach arbitrary data to a file, but it also had a serious problem: Alternate streams are not preserved by simple and common operations like sending the file by email, copying the file to a (FAT-formatted) USB thumb drive, uploading or downloading the file from a Web site, or burning the file to a CD. Basically, once the file leaves the comfortable confines of your local hard drive, there's a good chance that the metadata will be destroyed.
To avoid this problem, Windows XP switched to storing the metadata in the file contents itself. Doing this, however, requires support from the file format. Each file type can have registered for it a property handler which describes how to read and write properties for a file. (Windows itself comes with a few such handlers, such as for JPEG images and MP3 files, with more recent versions of Windows supporting more properties.) If no such property handler is registered, the shell will use structured storage, provided the file format is compatible with structured storage.
The data you added in Windows 2000 are still there. It's just that newer versions of Windows don't bother looking for them. (If you were sufficiently resourceful, you could write a program which opens the file in STGFMT_FILE mode, reads the properties, then reopens the file via the shell namespace and writes the properties back out.)
For lots of programming goodness about the shell property system, check out Ben Karas's blog, which I have been liberally linking to.
There are three functions which test very similar things, and sometimes applications pick the wrong one. Here's the rundown:
IsThemeActive
IsAppThemed
IsCompositionActive
How can you tell whether a particular button is an old-school button or a fancy new button? I don't know either.
Bonus emphasis: From the comments, it appears that people have confused "a window was created with the visual-styles-enabled version of the common controls library" with "themes are enabled". The two are independent concepts. All four combinations are possible. I thought I called this out in the article, but apparently I didn't call it out clearly enough.
WinMain is the conventional name for the user-provided entry point in a Win32 program. Just like in 16-bit Windows, where the complicated entry point requirements were converted by language-provided startup code into a call to the the user's WinMain function, the language startup code for 32-bit programs also does the work of converting the raw entry point into something that calls WinMain (or wWinMain or main or _wmain).
WinMain
wWinMain
main
_wmain
The raw entry point for 32-bit Windows applications has a much simpler interface than the crazy 16-bit entry point:
DWORD CALLBACK RawEntryPoint(void);
The operating system calls the function with no parameters, and the return value (if the function ever returns) is passed to the ExitThread function. In other words, the operating system calls your entry point like this:
ExitThread
... ExitThread(RawEntryPoint()); /*NOTREACHED*/
Where do the parameters to WinMain come from, if they aren't passed to the raw entry point?
The language startup code gets them by asking the operating system. The instance handle for the executable comes from GetModuleHandle(NULL), the command line comes from GetCommandLine, and the nCmdShow comes from GetStartupInfo. (As we saw before, the hPrevInstance is always NULL.)
GetModuleHandle(NULL)
GetCommandLine
nCmdShow
GetStartupInfo
hPrevInstance
NULL
If you want to be hard-core, you can program to the raw entry point. Mind you, other parts of your program may rely upon the work that the language startup code did before calling your WinMain. For example, the C++ language startup code will run global constructors before calling into WinMain, and both C and C++ will initialze the so-called security cookie used as part of stack buffer overrun detection. Bypass the language startup code at your peril.
Bonus chatter: Notice that if you choose to return from your entry point function, the operating system passes the return value to ExitThread and not ExitProcess. For this reason, you typically don't want to return from your raw entry point but instead want to call ExitProcess directly. Otherwise, if there are background threads hanging around, they will prevent your process from exiting.
ExitProcess
The game PowerPoint-Karaoke was invented in 2006 by Zentrale Ingelligenz Agentur. In this game, contestants are called upon to give a PowerPoint presentation based on a slide deck they have never seen. (The German spelling uses a hyphen between the two words. When "translated" into English, the hyphen is often omitted.)
At Microsoft, the term has been extended to refer to giving a presentation from slides prepared by somebody else, usually on short notice and therefore with little preparation.
Bob is out sick today, so I'll be giving the overview. Sorry for the PowerPoint Karaoke.
This is shorthand for "Sorry if this presentation is a bit clumsy, but I'm stepping in on short notice, and I'm not completely familiar with this slide deck."
In the context of PowerPoint presentations, an eye chart is a slide so dense with text that reading it is a test of visual acuity. The term is usually used as part of an apology for having created such a horrible slide in the first place.
More generally, the term eye chart refers to any presentation of data in a ridiculously small font. For example, over in the sales/marketing part of Microsoft, there are spreadsheets with titles like FY05 Sales Forecast Eye Chart. Here's what one of them might look like:
(The abbreviation Y-Y is being used correctly for once. Writing the program to generate all this fake data took far, far longer than writing the rest of this posting! It got a lot easier once I realized that, since this is just fake data, the totals don't have to add up.)
Giving the spreadsheet the title Eye Chart lets people know that this is the spreadsheet crammed with data to the point of information overload. If that's what you're looking for.
I wouldn't be surprised if these uses of the terms PowerPoint Karaoke and eye chart are also popular at other companies.
Bonus chatter: Last year, I was asked to give a repeat of a presentation I hadn't given in several months. I had only a little bit of time to prepare, and there were times where I lost my place and had to refer to my notes (which I thankfully remembered to keep). It's embarrassing to find yourself playing PowerPoint Karaoke to your own slide deck.
Once you make it possible to do something, you have to accept that you also made it possible to do something wrong.
When the window manager was originally designed, it made it possible for programs to override many standard behaviors. They could handle the WM_NCHITTEST message so a window can be dragged by grabbing any part of the window, not just the caption bar. They could handle the WM_NCPAINT message to draw custom title bars. The theory was that making all of these things possible permitted smart people to do clever things.
WM_NCHITTEST
WM_NCPAINT
The downside is that it also permits stupid people to do dumb things.
Changing the window procedure model from call DefWindowProc to get default behavior to return whether you handled the message wouldn't have helped. First of all, the handled/not-handled model is too restrictive: It requires you to do everything (handled) or nothing (not handled). There is no option to do a little bit. (Imagine if C++ didn't let you call the base class implementation of an overridden method.)
DefWindowProc
Doing a little bit is a very common pattern. The WM_NCHITTEST technique mentioned above, for example, uses the default hit-testing implementation, and then tweaks the result slightly:
WM_NCHITTEST
case WM_NCHITTEST: // call base class first lres = DefWindowProc(hwnd, uMsg, wParam, lParam); // tweak the result if (lres == HTCLIENT) lres = HTCAPTION; return lres;
How would you do this with the handled/not-handled model?
case WM_NCHITTEST: if (not handling this message would have resulted in HTCLIENT) { lres = HTCAPTION; handled = TRUE; } else { handled = FALSE; } break;
The trick about that bit in parentheses is that it requires the research department to finish the final details on that time machine they've been working on. It's basically saying, "Return not handled, then follow the message until handling is complete and if the final result is HTCLIENT, then fire up the time machine and rewind to this point so I can change my mind and return handled instead."
HTCLIENT
And even if the research department comes through with that time machine, the handled/not-handled model doesn't even solve the original problem!
The original problem was people failing to call DefWindowProc when they decided that they didn't want to handle a message. In the handled/not-handled model, the equivalent problem would be people returning handled = TRUE unconditionally.
handled = TRUE
BOOL NewStyleWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT& lres) { BOOL handled = TRUE; switch (uMsg) { case WM_THIS: ...; break; case WM_THAT: ...; break; // no "default: handled = FALSE; break;" } return handled; }
(Side note: The dialog manager uses the handled/not-handled model, and some people would prefer that it use the DefXxxProc model, so you might say "We tried that, and some people didn't like it.")
DefXxxProc
This topic raises another one of those "No matter what you do, somebody will call you an idiot" dilemmas. On the one side, there's the Windows should perform extra testing at runtime to detect bad applications school, and on the other side, there's the Windows should get rid of all the code whose sole purpose in life is to detect bad applications school.
In a discussion of commuting options (in which I was primarily an observer), the following exchange took place:
A: I would rather get kicked in the shins every morning than bike up the massive hill that sits between my apartment and main campus.
B: Have you tried an electric bike?
C: If you're going to eliminate pedaling, then you may as well go all the way and get a Segway.
B: Actually, I used to own a Segway. I was floored by the engineering achievement of creating a device that combined the speed benefits of walking with the exercise benefits of driving, and for just the cost of a used Honda!
Note, of course, that this is just one engineer's opinion. Some people at Microsoft are quite enamored of the Segway device.
Public Service Announcement: Today is Bike to Work Day. A number of commute stations will be set up along popular bike routes in the greater Seattle area to hand out bicycling information and snacks. And if you pass a guy creeping up the 520 hill at around 8:00am, that was probably me. Sorry I forgot to wave.
I covered the BeginBufferedPaint function in my 2008 PDC presentation, but one thing I didn't mention is that the buffered paint functions are very handy even if you have no intention of painting.
BeginBufferedPaint
Since the buffered paint functions maintain a cache (provided that you remembed to call BufferedPaintInit), you can use BeginBufferedPaint to get a temporary bitmap even if you have no intention of actually painting to the screen. You might want a bitmap to do some off-screen composition, or for some other temporary purpose, in which case you can ask BeginBufferedPaint to give you a bitmap, use the bitmap for whatever you like, and then pass fUpdateTarget = FALSE when you call EndBufferedPaint to say "Ha ha, just kidding."
BufferedPaintInit
BeginBufferedPaint
fUpdateTarget = FALSE
EndBufferedPaint
One thing to have to be aware of is that the bitmap provided by BeginBufferedPaint is not guaranteed to be exactly the size you requested; it only promises that the bitmap will be at least the size you requested. Most of the time, your code won't care (there are just pixels out there that you aren't using), but if you use the GetBufferedPaintBits function to obtain direct access to the bits, don't forget to take the stride into account.
GetBufferedPaintBits
CreateDIBSection
BOOL OnCreate(HWND hwnd, LPCREATESTRUCT lpcs) { BOOL fRc = FALSE; HDC hdcWin = GetDC(hwnd); if (hdcWin) { HDC hdcMem = CreateCompatibleDC(hdcWin); if (hdcMem) { const int cx = 200; const int cy = 200; RECT rc = { 0, 0, cx, cy }; BITMAPINFO bmi = { 0 }; bmi.bmiHeader.biSize = sizeof(bmi.bmiHeader); bmi.bmiHeader.biWidth = cx; bmi.bmiHeader.biHeight = cy; bmi.bmiHeader.biPlanes = 1; bmi.bmiHeader.biBitCount = 32; bmi.bmiHeader.biCompression = BI_RGB; RGBQUAD *prgbBits; HBITMAP hbm = CreateDIBSection(hdcWin, &bmi, DIB_RGB_COLORS, &reinterpret_cast<void*&>(prgbBits), NULL, 0); if (hbm) { HBITMAP hbmPrev = SelectBitmap(hdcMem, hbm); // Draw a simple picture FillRect(hdcMem, &rc, reinterpret_cast<HBRUSH>(COLOR_INFOBK + 1)); rc.left = cx / 4; rc.right -= rc.left; rc.top = cy / 4; rc.bottom -= rc.top; FillRect(hdcMem, &rc, reinterpret_cast<HBRUSH>(COLOR_INFOTEXT + 1)); // Apply the alpha channel (and premultiply) for (int y = 0; y < cy; y++) { for (int x = 0; x < cx; x++) { RGBQUAD *prgb = &prgbBits[y * cx + x]; BYTE bAlpha = static_cast<BYTE>(cx * x / cx); prgb->rgbRed = static_cast<BYTE>(prgb->rgbRed * bAlpha / 255); prgb->rgbBlue = static_cast<BYTE>(prgb->rgbBlue * bAlpha / 255); prgb->rgbGreen = static_cast<BYTE>(prgb->rgbGreen * bAlpha / 255); prgb->rgbReserved = bAlpha; } } // update the layered window POINT ptZero = { 0, 0 }; SIZE siz = { cx, cy }; BLENDFUNCTION bf = { AC_SRC_OVER, 0, 255, AC_SRC_ALPHA }; fRc = UpdateLayeredWindow(hwnd, NULL, &ptZero, &siz, hdcMem, &ptZero, 0, &bf, ULW_ALPHA); SelectBitmap(hdcMem, hbmPrev); DeleteObject(hbm); } DeleteDC(hdcMem); } ReleaseDC(hwnd, hdcWin); } return fRc; }
Pretty standard stuff. But let's convert this to use the buffered paint functions to take advantage of the buffered paint bitmap cache.
BOOL OnCreate(HWND hwnd, LPCREATESTRUCT lpcs) { BOOL fRc = FALSE; HDC hdcWin = GetDC(hwnd); if (hdcWin) { HDC hdcMem; // HDC hdcMem = CreateCompatibleDC(hdcWin); // if (hdcMem) { const int cx = 200; const int cy = 200; RECT rc = { 0, 0, cx, cy }; // BITMAPINFO bmi = { 0 }; // bmi.bmiHeader.biSize = sizeof(bmi.bmiHeader); // bmi.bmiHeader.biWidth = cx; // bmi.bmiHeader.biHeight = cy; // bmi.bmiHeader.biPlanes = 1; // bmi.bmiHeader.biBitCount = 32; // bmi.bmiHeader.biCompression = BI_RGB; RGBQUAD *prgbBits; BP_PAINTPARAMS params = { sizeof(params), BPPF_NOCLIP }; HPAINTBUFFER hbp = BeginBufferedPaint(hdcWin, &rc, BPBF_TOPDOWNDIB, ¶ms, &hdcMem); if (hbp) { int cxRow; if (SUCCEEDED(GetBufferedPaintBits(hpb, &prgbBits, &cxRow))) { // HBITMAP hbm = CreateDIBSection(hdcWin, &bmi, // DIB_RGB_COLORS, &reinterpret_cast<void*&>(prgbBits), // NULL, 0); // if (hbm) { // HBITMAP hbmPrev = SelectBitmap(hdcMem, hbm); // Draw a simple picture FillRect(hdcMem, &rc, reinterpret_cast<HBRUSH>(COLOR_INFOBK + 1)); rc.left = cx / 4; rc.right -= rc.left; rc.top = cy / 4; rc.bottom -= rc.top; FillRect(hdcMem, &rc, reinterpret_cast<HBRUSH>(COLOR_INFOTEXT + 1)); // Apply the alpha channel (and premultiply) for (int y = 0; y < cy; y++) { for (int x = 0; x < cx; x++) { RGBQUAD *prgb = &prgbBits[y * cxRow + x]; BYTE bAlpha = static_cast<BYTE>(cx * x / cx); prgb->rgbRed = static_cast<BYTE>(prgb->rgbRed * bAlpha / 255); prgb->rgbBlue = static_cast<BYTE>(prgb->rgbBlue * bAlpha / 255); prgb->rgbGreen = static_cast<BYTE>(prgb->rgbGreen * bAlpha / 255); prgb->rgbReserved = bAlpha; } } // update the layered window POINT ptZero = { 0, 0 }; SIZE siz = { cx, cy }; BLENDFUNCTION bf = { AC_SRC_OVER, 0, 255, AC_SRC_ALPHA }; fRc = UpdateLayeredWindow(hwnd, NULL, &ptZero, &siz, hdcMem, &ptZero, 0, &bf, ULW_ALPHA); // SelectBitmap(hdcMem, hbmPrev); // DeleteObject(hbm); } EndBufferedPaint(hpb, FALSE); // DeleteDC(hdcMem); } ReleaseDC(hwnd, hdcWin); } return fRc; } // changes to WinMain if (SUCCEEDED(BufferedPaintInit())) { // if (SUCCEEDED(CoInitialize(NULL))) {/* In case we use COM */ hwnd = CreateWindowEx(WS_EX_LAYERED, // hwnd = CreateWindow( ... BufferedPaintUnInit(); // CoUninitialize(); ...
We're using the buffered paint API not for buffered painting but just as a convenient way to get a bitmap and a DC at one shot. It saves some typing (you don't have to create the bitmap and the DC and select the bitmap in and out), and when you return the paint buffer to the cache, some other window that calls BeginBufferedPaint may be able to re-use that bitmap.
There are a few tricky parts here. First, if you're going to be accessing the bits directly, you need to call GetBufferedPaintBits and use the cxRow to determine the bitmap stride. Next, when we're done, we pass FALSE to EndBufferedPaint to say, "Yeah, um, thanks for the bitmap, but don't BitBlt the results back into the DC we passed to BeginBufferedPaint. Sorry for the confusion."
cxRow
FALSE
BitBlt
A less obvious trick is that we used BPPF_NOCLIP to get a full bitmap. By default, BeginBufferedPaint returns you a bitmap which is clipped to the DC you pass as the first parameter. This is an optimization to avoid allocating memory for pixels that can't be seen anyway when EndBufferedPaint goes to copy the bits back to the original DC. We don't want this optimization, however, since we have no intention of blitting the results back to the original DC. The clip region of the original DC is irrelevant to us because we just want a temporary bitmap for some internal calculations.
BPPF_NOCLIP
Anyway, there you have it, an example of using BeginBufferedPaint to obtain a temporary bitmap. It doesn't win much in this example (since we call it only once, at window creation time), but if you have code which creates a lot of DIB sections for temporary use, you can use this trick to take advantage of the buffered paint cache and reduce the overhead of bitmap creation and deletion.
Pre-emptive snarky comment: "How dare you show us an alternative method that isn't available on Windows 2000!"
There is no standard for process exit codes. You can pass anything you want to ExitProcess, and that's what GetExitCodeProcess will give back. The kernel does no interpretation of the value. If youw want code 42 to mean "Something infinitely improbable has occurred" then more power to you.
ExitProcess
GetExitCodeProcess
There is a convention, however, that an exit code of zero means success (though what constitutes "success" is left to the discretion of the author of the program) and a nonzero exit code means failure (again, with details left to the discretion of the programmer). Often, higher values for the exit code indicate more severe types of failure. The command processor ERRORLEVEL keyword was designed with these convention in mind.
ERRORLEVEL
There are cases where your process will get in such a bad state that a component will take it upon itself to terminate the process. For example, if a process cannot locate the DLLs it imports from, or one of those DLLs fails to initialize, the loader will terminate the process and use the status code as the process exit code. I believe that when a program crashes due to an unhandled exception, the exception code is used as the exit code.
A customer was seeing their program crash with an exit code of 3 and couldn't figure out where it was coming from. They never use that exit code in their program. Eventually, the source of the magic number 3 was identified: The C runtime abort function terminates the process with exit code 3.
abort