• The Old New Thing

    Why are only some of the Windows 7 system notification icons colorless?

    • 35 Comments

    André decided to play "gotcha" by noting that not all of the notification icons went colorless and wondered what the criteria was for deciding which ones to make colorless and which ones to make colorful.

    It's very simple: The design team generated colorless versions of the most commonly-seen notification icons. They didn't have the time to generate colorless versions of all notification icons, so they focused on the ones that gave them the most benefit.

    This is the standard tradeoff you have to make whenever you have finite resources. Eventually the marginal benefit of redrawing one more icon exceeds its marginal cost, at which point you stop. The marginal cost is measured not only in actual resources (designers can redraw only so many icons per day, and you have money to hire only so many designers) but also in opportunity cost (time spent redrawing icons to be colorless is time not spent on other design tasks).

    This is the same reason that not all icons in Windows XP were given the full-color perspective-view treatment. For example, nearly all of the icons in the Administrative Tools section are the old Windows 2000-style 16-color flat (or isometric) icons. The design team focused on the 100ish most commonly used icons and went to the effort of redrawing them in the Windows XP style. The more rarely-used icons were left in the old style because the cost of converting them did not merit the benefit.

    The same thing happened in Windows Vista, when the icon design changed yet again. The style became less stylized and more realistic, but not quite photorealistic, and the angle of presentation changed. The design team had the resources to convert the most commonly used icons, and the rest were left as they were.

    It's the Pareto Principle again. If you have finite resources (and who doesn't) you may find that you can get 80% of the benefit by doing only 20% of the work. And that leaves 80% of your capacity available to address some other problem.

  • The Old New Thing

    How do I turn off email reminders for my Windows Live calendar, and disable the birthday calendar while I'm at it?

    • 10 Comments

    While having lunch with my colleagues a few days ago, they complained that they get annoying email for all their calendar events.

    I mentioned, "So turn off the email notifications."

    "You can do that?" they replied, incredulous.

    Here's how you do it.

    First, go to calendar.live.com and sign in to your calendar.

    In the upper right corner of the screen, click on the gear icon.

    Sven Svensson
    👨

    From the drop-down menu, select Options. From the Options page, select the calendar you want to change.

    Edit your reminder and calendar settings
      Sven's calendar
      Birthday calendar

    In the Calendar settings, untick the checkboxes.

    Notifications
    Email notifications
    Get reminders for events and tasks
    Get daily agenda

    And then click Save to save your changes.

    My colleagues also noted "It used to mean something when somebody remembered your birthday. But now, everybody gets a notification on your birthday, and you get birthday wishes from random people you haven't talked to in years." Which led to the next complaint, "I just want to turn off the birthday calendar."

    No problem. Go back and click on the gear icon, and instead of selecting Options, look under Filter Calendars and untick the check box next to Birthday calendar.

    If you have a Windows Phone, you can remove it from your phone calendar by opening the Calendar app, opening the appbar, selecting settings, and then unticking the check box next to Birthday calendar.

  • The Old New Thing

    Customing the standard color-picker dialog

    • 11 Comments

    Today's Little Program does a little bit of customization of the Choose­Color dialog. We do this by, um, doing what the documentation says.

    For the color dialog, we take the template in color.dlg and modify it. Just to get our feet wet, we won't customize anything at all! This ensures that we have the basics down before we start trying anything fancy.

    Handy tip: Before trying to customize something, first write code that does it uncustomized. That way, you have a known working starting point.

    #include <windows.h>
    
    #include <colordlg.h>
    
    1 DIALOG LOADONCALL MOVEABLE DISCARDABLE 2, 0, 298, 184
    STYLE WS_BORDER | DS_MODALFRAME | WS_CAPTION | WS_POPUP | WS_SYSMENU |
          DS_3DLOOK
    CAPTION "Color"
    FONT 8 "MS Shell Dlg"
    BEGIN
        LTEXT           "&Basic colors:", -1, 4, 4, 140, 9
        CONTROL         "", COLOR_BOX1, "static",
                        SS_SIMPLE | WS_CHILD | WS_TABSTOP | WS_GROUP,
                        4, 14, 140, 86
    
        LTEXT           "&Custom colors:", -1, 4, 106, 140, 9
        CONTROL         "",  COLOR_CUSTOM1, "static",
                        SS_SIMPLE | WS_CHILD | WS_TABSTOP | WS_GROUP,
                        4, 116, 140, 28
    
        PUSHBUTTON      "&Define Custom Colors >>" COLOR_MIX, 4, 150, 138, 14,
                        WS_TABSTOP | WS_GROUP
    
        DEFPUSHBUTTON   "OK", IDOK, 4, 166, 44, 14, WS_GROUP | WS_TABSTOP
        PUSHBUTTON      "Cancel", IDCANCEL, 52, 166, 44, 14, WS_GROUP | WS_TABSTOP
        PUSHBUTTON      "&Help", pshHelp, 100, 166, 44, 14, WS_GROUP | WS_TABSTOP
    
        CONTROL         "", COLOR_RAINBOW, "static",
                        SS_SUNKEN | SS_SIMPLE | WS_CHILD, 152, 4, 118, 116
    
        CONTROL         "", COLOR_LUMSCROLL, "static",
                        SS_SUNKEN | SS_SIMPLE | WS_CHILD, 280, 4, 8, 116
    
        CONTROL         "", COLOR_CURRENT, "static",
                        SS_SUNKEN | SS_SIMPLE | WS_CHILD, 152, 124, 40, 26
    
        PUSHBUTTON      "&o", COLOR_SOLID, 300, 200, 4, 14, WS_GROUP
        RTEXT           "Color", COLOR_SOLID_LEFT, 152, 151, 20, 9
        LTEXT           "|S&olid", COLOR_SOLID_RIGHT, 172, 151, 20, 9
    
        RTEXT           "Hu&e:", COLOR_HUEACCEL, 194, 126, 20, 9
        EDITTEXT,       COLOR_HUE, 216, 124, 18, 12, WS_GROUP | WS_TABSTOP
    
        RTEXT           "&Sat:", COLOR_SATACCEL, 194, 140, 20, 9
        EDITTEXT,       COLOR_SAT, 216, 138, 18, 12, WS_GROUP | WS_TABSTOP
    
        RTEXT           "&amp;Lum:", COLOR_LUMACCEL, 194, 154, 20, 9
        EDITTEXT,       COLOR_LUM, 216, 152, 18, 12, WS_GROUP | WS_TABSTOP
    
        RTEXT           "&Red:", COLOR_REDACCEL, 243, 126, 24, 9
        EDITTEXT,       COLOR_RED, 269, 124, 18, 12, WS_GROUP | WS_TABSTOP
    
        RTEXT           "&Green:", COLOR_GREENACCEL, 243, 140, 24, 9
        EDITTEXT,       COLOR_GREEN, 269, 138, 18, 12, WS_GROUP | WS_TABSTOP
    
        RTEXT           "Bl&ue:", COLOR_BLUEACCEL, 243, 154, 24, 9
        EDITTEXT,       COLOR_BLUE, 269, 152, 18, 12, WS_GROUP | WS_TABSTOP
    
        PUSHBUTTON      "&Add to Custom Colors", COLOR_ADD, 152, 166, 142, 14,
                        WS_GROUP | WS_TABSTOP
    END
    

    Our resource file is just a copy of the original color.dlg file with no customizations. We stick a windows.h in front, and assign it a custom resource ID of 1. Let's see if we can display it.

    #define UNICODE
    #define _UNICODE
    #define STRICT
    #include <windows.h>
    #include <commdlg.h>
    
    int WINAPI wWinMain(
        HINSTANCE hinst, HINSTANCE hinstPrev,
        LPWSTR lpCmdLine, int nCmdShow)
    {
     COLORREF rgCustColors[16] = { 0 };
     CHOOSECOLOR cc = { sizeof(cc) };
     cc.hInstance = reinterpret_cast<HWND>(hinst);
     cc.lpTemplateName = MAKEINTRESOURCE(1);
     cc.Flags = CC_ENABLETEMPLATE;
     cc.lpCustColors = rgCustColors;
     if (ChooseColor(&cc)) {
      MessageBox(nullptr, TEXT("Thank you"), TEXT("Sample"), MB_OK);
     }
     return 0;
    }
    

    The hInstance member of the CHOOSE­COLOR structure is incorrectly declared as HWND, so we need to stick in a cast to keep everybody happy.

    Run this program, and... everything looks perfectly normal. Good! Now we can customize it.

    First, let's just add a message to the dialog.

    1 DIALOG LOADONCALL MOVEABLE DISCARDABLE 2, 0, 298, 198
    ...
    
        LTEXT           "Don't forget to smile!",
                        -1, 4, 166, 138, 14
    
        DEFPUSHBUTTON   "OK", IDOK, 4, 180, 44, 14, WS_GROUP | WS_TABSTOP
        PUSHBUTTON      "Cancel", IDCANCEL, 52, 180, 44, 14, WS_GROUP | WS_TABSTOP
        PUSHBUTTON      "&Help", pshHelp, 100, 180, 44, 14, WS_GROUP | WS_TABSTOP
    ...
    

    Rebuild the program and run it. Hey look, a happy message! Note that in order to fit the message in the dialog box, we had to make the dialog box taller and move some buttons out of the way.

    Just adding static text is nice, but it's not particularly interesting. So let's add a check box to the dialog too.

        AUTOCHECKBOX    "I remembered to s&mile",
                        1000, 4, 166, 138, 14, WS_TABSTOP
    

    In addition to remembering the color the user chose, we also want to remember whether they checked the box that says that they smiled. The documentation says that when the hook procedure receives a WM_INIT­DIALOG, the lParam points to the CHOOSE­COLOR dialog. We therefore have two options for passing extra data to the hook procedure.

    I'll use the traditional method for now. The lCust­Data is a pointer to a BOOL that receives the checkbox state on exit.

    UINT_PTR CALLBACK CCHookProc(
        HWND hdlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
      switch (uMsg)
      {
      case WM_INITDIALOG:
        {
          auto pcc = reinterpret_cast<CHOOSECOLOR*>(lParam);
          auto pfSmiled = reinterpret_cast<BOOL*>(pcc->lCustData);
          SetProp(hdlg, TEXT("SmileResult"), pfSmiled);
        }
        break;
    
      case WM_DESTROY:
        {
          auto pfSmiled = reinterpret_cast<BOOL*>(
               GetProp(hdlg, TEXT("SmileResult")));
          if (pfSmiled) {
           *pfSmiled = IsDlgButtonChecked(hdlg, 1000);
          }
          RemoveProp(hdlg, TEXT("SmileResult"));
        }
      }
      return 0;
    }
    
    int WINAPI wWinMain(
        HINSTANCE hinst, HINSTANCE hinstPrev,
        LPWSTR lpCmdLine, int nCmdShow)
    {
     COLORREF rgCustColors[16] = { 0 };
     BOOL fSmiled = FALSE;
     CHOOSECOLOR cc = { sizeof(cc) };
     cc.hInstance = reinterpret_cast<HWND>(hinst);
     cc.lpTemplateName = MAKEINTRESOURCE(1);
     cc.Flags = CC_ENABLETEMPLATE  | CC_ENABLEHOOK;
     cc.lpCustColors = rgCustColors;
     cc.lCustData = reinterpret_cast<LPARAM>(&fSmiled);
     cc.lpfnHook = CCHookProc;
     if (ChooseColor(&cc) && !fSmiled) {
      MessageBox(nullptr, TEXT("You forgot to smile."),
                 TEXT("Sample"), MB_OK);
     }
     return 0;
    }
    

    Now, the program displays a message if you selected a color but did not check the I remembered to smile box.

  • The Old New Thing

    Communication between moving vehicles during the narrow window of the late 1990s

    • 37 Comments

    The long holiday weekend in the United States means that there are probably going to be a lot of people on road trips.

    Back in the old days before mobile phones, if you had multiple cars traveling together on a long trip, you had to stay within visible range of each other so that you didn't get separated. And if the car at the end of the convoy needed to pull over to take a bathroom break or something, they needed to rush to the front of the group and pantomime through the window to the passengers in the lead car to tell them what they were going to do, and then everybody would pull over together.

    I still remember those days.

    Of course, nowadays, you'd just whip out your mobile phone and call the other people in the group. "Hey, we need to stop for a bathroom break. You can join us, or we'll just catch up with you down the road."

    There was a narrow window of a few years where WiFi hardware was generally available, if somewhat expensive (in the form of a PCMCIA PC Card for your laptop and a $1000 base station)¹ and mobile phone coverage along highways in less populated areas was spotty or nonexistent. That was the window of time during which I wrote a chat program for multi-car road trips.

    The idea was that we would establish an ad-hoc wireless network among the laptops, and the program would act as a peer-to-peer instant messaging program. We would be the world's fastest-moving WiFi hotspot. As long as the cars stayed within around 100 meters of each other, we would (presumably) still have connectivity. The program was robust to outages, and it could handle devices dynamically coming into or leaving communication range.

    With this technological contraption, we didn't have to make everybody stop and pull over in order to decide where to have lunch. We could just start an IM conversation and work it out while still moving. (But if we wanted to take a bathroom break, everybody had to stop; otherwise the cars would get out of range.)

    I over-engineered this program, designing it to handle chats that if printed out would require a roll of paper over 300 kilometers long. In other words, for a 300km trip, you would have to be sending instant messages fast enough that you could drive on the paper coming out of the printer. (Related.) Another way of doing the math was observing that the program could in theory handle a cross-country trip where people were sending 500 messages per second the entire time. (Well, except it would have run out of memory long before hitting its design limits.)

    This program met an even sadder fate than my in-car mp3 player. At least I got to use that program a few times. Mobile phone technology quickly improved to the point where the car chat program was no longer necessary. It was never used at all!

    ¹ I still have my $1000 base station packed in a box somewhere. I wouldn't be surprised if my my mobile phone now has a faster data plan than that thing.

  • The Old New Thing

    It rather involved being on the other side of this airtight hatchway: Surreptitious file access by administrator

    • 43 Comments

    A security report was received that went something like this:

    A user can bypass file sharing locks by opening a read-only handle to the physical volume containing the file in question. This allows the user to extract the contents of protected files by reading the corresponding sectors directly from the disk. Since this operation requires administrator access, any user with administrator access can extract data from files that are normally inaccessible due to file locks, such as the SAM database.

    Yes, that's right. An attacker who gains administrator privileges can extract data from any file on the computer.

    But so what? The attacker is already on the other side of the airtight hatchway. They already pwn your machine. That a pwned machine can be pwned is not really all that surprising.

    That some files are not accessible due to file locks is not a security measure. It is a consequence of, um, file access.

    Besides, once you gain administrator access, a much easier way to steal the SAM is to merely grab a backup copy.

    What, you can't find a backup copy?

    No problem.

    After all, you're the administrator. One of your job responsibilities is to maintain regular system backups.

    So create a backup of the SAM file. Of course the system will let you do this. It is your job after all.

    For example, you can use the Volume Shadow Service to create a volume snapshot, then mount the snapshot and extract the SAM file.

    Bingo, instant copy of the SAM database.

    Just doing your job.

  • The Old New Thing

    Redirecting the Favorites folder is technically legal, but you wouldn't like it

    • 47 Comments

    A customer liaison asked for assistance in debugging why Internet Explorer frequently stops responding at their customer's site. "We have tried a number of things like clearning the temporary Internet files, disabling add-ons, and resetting Internet Explorer settings. Please let me know if you can provide guidance based on the dump files provided below to indicate what could be causing Internet Explorer to hang here."

    The dump file showed 23 threads, and all of them seemed to be normal, except for one which looked like this:

    ntdll!KiFastSystemCallRet
    ntdll!ZwOpenFile+0xc
    kernel32!BaseDllOpenIniFileOnDisk+0x1ec
    kernel32!BaseDllReadWriteIniFileOnDisk+0x22
    kernel32!BaseDllReadWriteIniFile+0x154
    kernel32!GetPrivateProfileStringW+0x35
    kernel32!GetPrivateProfileSectionNamesW+0x18
    shell32!CPrivateProfile::_GetPrivateProfileAlloc+0x9e
    shell32!CPrivateProfile::GetSectionNames+0xa0
    ieframe!CINIPropSetStg::Load+0x74
    ieframe!CInternetShortcutPropertyStore::_CreateStoreFromFile+0x4e
    ieframe!CInternetShortcutPropertyStore::Load+0x22
    ieframe!CInternetShortcut::LoadFromFileW+0x39
    ieframe!CInternetShortcut::Initialize+0x17
    shell32!InitializeFileHandlerWithFile+0x2d
    shell32!CFileSysItemString::HandlerCreateInstance+0x29a
    shell32!CFileSysItemString::LoadHandler+0x91
    shell32!CFSFolder::_CreatePerInstanceDefExtIcon+0x7d
    shell32!CFSFolder::_CreateDefExtIcon+0xe9
    shell32!CFSFolder::s_GetExtractIcon+0x1b
    shell32!CFSFolder::_BindHandler+0x209
    shell32!CFSFolder::GetUIObjectOf+0x21
    shell32!GetExtractIconW+0x31
    shell32!_GetILIndexFromItem+0x52
    shell32!SHMapPIDLToSystemImageListIndex+0x37
    ieframe!OrderItem_GetSystemImageListIndex+0x187
    ieframe!CSFToolbar::_GetBitmap+0x2f
    ieframe!CSFToolbar::_OnGetDispInfo+0x34
    ieframe!CSFToolbar::_OnNotify+0x8e
    ieframe!CISFBand::_OnNotify+0x2c
    ieframe!CSFToolbar::OnWinEvent+0x89
    ieframe!_FwdWinEvent+0x1d
    ieframe!CBandSite::_SendToToolband+0x44
    ieframe!CInternetToolbar::_OnNotify+0x2e
    ieframe!CInternetToolbar::SizableWndProc+0x223
    user32!InternalCallWinProc+0x23
    user32!UserCallWinProcCheckWow+0x14b
    user32!SendMessageWorker+0x4b7
    user32!SendMessageW+0x7c
    comctl32!CCSendNotify+0xbfb
    comctl32!SendNotifyEx+0x63
    comctl32!CReBar::_WndProc+0x24f
    comctl32!CReBar::s_WndProc+0x2c
    user32!InternalCallWinProc+0x23
    user32!UserCallWinProcCheckWow+0x14b
    user32!CallWindowProcAorW+0x97
    user32!CallWindowProcW+0x1b
    comctl32!CallOriginalWndProc+0x1a
    comctl32!CallNextSubclassProc+0x3d
    comctl32!DefSubclassProc+0x46
    ieframe!CInternetToolbar::CITBandSite::s_RebarSubclassWndProc+0x5a
    comctl32!CallNextSubclassProc+0x3d
    comctl32!MasterSubclassProc+0x54
    user32!InternalCallWinProc+0x23
    user32!UserCallWinProcCheckWow+0x14b
    user32!SendMessageWorker+0x4b7
    user32!SendMessageW+0x7c
    comctl32!CCSendNotify+0xbfb
    comctl32!CToolbar::TBGetItem+0x2c
    comctl32!CToolbar::DrawButton+0x5e9
    comctl32!CToolbar::DrawToolbarH+0x1ad
    comctl32!CToolbar::TBPaintImpl+0xd5
    comctl32!CToolbar::TBPaint+0x18c
    comctl32!CToolbar::ToolbarWndProc+0xd2e
    comctl32!CToolbar::s_ToolbarWndProc+0x9b
    user32!InternalCallWinProc+0x23
    user32!UserCallWinProcCheckWow+0x14b
    user32!CallWindowProcAorW+0x97
    user32!CallWindowProcW+0x1b
    comctl32!CallOriginalWndProc+0x1a
    comctl32!CallNextSubclassProc+0x3d
    comctl32!DefSubclassProc+0x46
    ieframe!CSFToolbar::_DefWindowProc+0xb8
    ieframe!CISFBand::_DefWindowProc+0x75
    ieframe!CNotifySubclassWndProc::s_SubclassWndProc+0xb4
    comctl32!CallNextSubclassProc+0x3d
    comctl32!MasterSubclassProc+0x54
    user32!InternalCallWinProc+0x23
    user32!UserCallWinProcCheckWow+0x14b
    user32!DispatchClientMessage+0xda
    user32!__fnDWORD+0x24
    ntdll!KiUserCallbackDispatcher+0x2e
    user32!NtUserDispatchMessage+0xc
    user32!DispatchMessageWorker+0x38c
    user32!DispatchMessageW+0xf
    ieframe!CTabWindow::_TabWindowThreadProc+0x280
    kernel32!BaseThreadInitThunk+0xe
    ntdll!__RtlUserThreadStart+0x23
    ntdll!_RtlUserThreadStart+0x1b
    

    (Remember, you can get symbols for operating system binaries.)

    General debugging tip: If you see a really huge stack, that's a good sign that something interesting is going on. Boring stacks tend to be small.

    Furthermore, frames near the bottom of the stack tend to describe what the purpose of the thread is, whereas frames near the top of the stack tend to describe what the thread is actually doing right now. (Exercise: Why?)

    In this case, we see a stack that was probably created to manage a tab window (CTab­Window::_Tab­Window­Thread­Proc) and it's currently stuck in an I/O operation. You can then look at the file name to see what file is stuck.

    0:001> du 04cd6aac
    04cd6aac "\\server\share\abcdefg\Favorites\Mail.url"
    

    It looks like this user stored their Favorites on a network share that is not responding.

    The customer liaison replied,

    Thanks a lot for this information. Can you help me understand how do we tell that the dump indicates this file I/O is causing IE to hang? Having this information would help me better explain this to the customer.

    I wasn't sure how to respond to this. If you see a function with the words File and one of Open, Read, or Write in its name, there's a good chance that it opens, reads, or writes a file. You probably want to look to see what file is being opened, read from, or written to, because that may give a clue why the I/O operation is stuck.

    It turns out that this customer redirected the user's Favorites to a network location. The Internet Explorer folks tell me that this is not an explicitly supported scenario in the sense that they did not do any tuning to make this scenario work well, and frequent hangs are consequently not unexpected. If you redirect the Favorites to a network location, then you get what you get. And if that server frequently becomes unavailable, then what you get often sucks.

  • The Old New Thing

    What is the default size of the Recycle Bin, and how can an administrator control the size of the Recycle Bin?

    • 13 Comments

    A customer was setting up a file server to which they intended to redirect all their employees' documents. They were concerned about the amount of disk space used by all the Recycle Bins on that server.

    Is there a fixed default size for the Recycle Bin, or is it based on the size of the disk? Is there a way we can change the default size for the Recycle Bin?

    The customer is concerned that a user with a small quota on a large drive may end up filling their quota with Recycle Bin content and have no space left for their documents. For example, suppose you have a 1TB drive and each user has a 15 GB quota. If the Recycle Bin were based on disk size, and the Recycle Bin were set to use five percent of the disk, then that would give each user 5% × 1 TB = 51.2 GB of Recycle Bin, which is larger than their quota. Users can fill their Recycle Bin and have no room for documents!

    Fortunately, it doesn't work that way. The Recycle Bin calculations are always based on the disk quota, not the disk size. In the above example, each user's Recycle Bin would be 5% × 15 GB = 768 MB.

    Now as to what the default is, that's a trickier question.

    Up through Windows XP, the default Recycle Bin was ten percent of the user's quota on the underlying volume. Starting in Windows Vista, the algorithm was tweaked, and the default size is ten percent of the first 40 GB of quota, and five percent of any quota above 40 GB. (Note that future versions of Windows may tweak the defaults. This is provided for informational purposes, not contractual.)

    If you don't like the default, you can set an explicit maximum value by policy. If you are willing to live a little dangerously, you can dig under the covers and tweak values on a per-folder basis. Note that once you dig under the covers, you are in unsupported territory, so if it stops working (or starts misbehaving), then you're on your own.

  • The Old New Thing

    2014 mid-year link clearance

    • 18 Comments

    Another round of the semi-annual link clearance.

    James Mickens section

    January 2014: This World of Ours

    I have seen a video called “Gigantic Martian Insect Party,” and I have seen another video called “Gigantic Martian Insect Party 2: Don’t Tell Mom,” and I hated both videos, but this did not stop me from directing the sequel “Gigantic Martian Insect Party Into Darkness.”

    March 2014: To Wash It All Away

    Clearing the cache to fix a Web browser is like when your dad was driving you to kindergarten, and the car started to smoke, and he tried to fix the car by banging on the hood three times and then asking you if you could still smell the carbon monoxide, and you said, “Yeah, its better,” because you didn’t want to expose your dad as a fraud, and then both of you rode to school in silence as you struggled to remain conscious.

    And a recorded presentation: Computers are a Sadness, I am the Cure, which is hilarious because it's true, especially his final security recommendation.

  • The Old New Thing

    Getting the location of the Close button in the title bar, from Windows 2000 or Windows XP

    • 11 Comments

    Today's Little Program locates the × button in the corner of the window and displays a balloon tip pointing at it. We did this some time ago with the help of the WM_GET­TITLE­BAR­INFO­EX message, which is new for Windows Vista. But what if you don't have that message available, say, because you're running on Windows 2000 or Windows XP or (gasp) Windows 98?

    You can use the classic Accessibility interface IAccessible to enumerate the buttons in the title bar and see which one the window reports as the Close button.

    Let's take the program from last time and change the Get­Close­Button­Center function:

    #include <oleacc.h>
    #include <atlbase>
    
    BOOL GetCloseButtonCenter(HWND hwnd, POINT *ppt)
    {
     CComPtr<IAccessible> spacc;
     if (FAILED(AccessibleObjectFromWindow(hwnd, OBJID_TITLEBAR,
                       IID_PPV_ARGS(&spacc)))) return FALSE;
     CComQIPtr<IEnumVARIANT> spenum(spacc);
     if (!spenum) return FALSE;
     for (CComVariant vtChild; spenum->Next(1, &vtChild, nullptr) == S_OK;
          vtChild.Clear()) {
      CComVariant vtState;
      if (FAILED(spacc->get_accState(vtChild, &vtState))) continue;
      if (vtState.vt != VT_I4) continue;
      if (vtState.lVal & (STATE_SYSTEM_INVISIBLE |
                          STATE_SYSTEM_OFFSCREEN |
                          STATE_SYSTEM_UNAVAILABLE)) continue;
      long left, top, width, height;
      if (FAILED(spacc->accLocation(&left, &top, &width, &height,
                                    vtChild))) continue;
      POINT pt = { left + width / 2, top + height / 2 };
      if (SendMessage(hwnd, WM_NCHITTEST, 0,
                      MAKELPARAM(pt.x, pt.y)) == HTCLOSE) {
       *ppt = pt;
       return TRUE;
      }
     }
     return FALSE;
    }
    

    We obtain the IAccessible interface for the title bar and proceed to enumerate its children. For each child, we get its location, and then use the WM_NC­HIT­TEST message to determine programmatically what that location corresponds to. If the answer is "This is the Close button," then we found the button and report its center.

    Note that this once again highlights the distinction between WM_NC­MOUSE­MOVE and WM_NC­HIT­TEST. Hit-testing can occur for reasons other than mouse movement.

    Exercise: Why couldn't we use the IAccessible::get_accName method to figure out which button each child represents?

  • The Old New Thing

    Undefined behavior can result in time travel (among other things, but time travel is the funkiest)

    • 73 Comments

    The C and C++ languages are notorious for the very large section of the map labeled here be dragons, or more formally, undefined behavior.

    When undefined behavior is invoked, anything is possible. For example, a variable can be both true and false. John Regehr has a list of interesting examples, as well as some winners of the ensuing contest.

    Consider the following function:

    int table[4];
    bool exists_in_table(int v)
    {
        for (int i = 0; i <= 4; i++) {
            if (table[i] == v) return true;
        }
        return false;
    }
    

    What does this have to do with time travel, you ask? Hang on, impatient one.

    First of all, you might notice the off-by-one error in the loop control. The result is that the function reads one past the end of the table array before giving up. A classical compiler wouldn't particularly care. It would just generate the code to read the out-of-bounds array element (despite the fact that doing so is a violation of the language rules), and it would return true if the memory one past the end of the array happened to match.

    A post-classical compiler, on the other hand, might perform the following analysis:

    • The first four times through the loop, the function might return true.
    • When i is 4, the code performs undefined behavior. Since undefined behavior lets me do anything I want, I can totally ignore that case and proceed on the assumption that i is never 4. (If the assumption is violated, then something unpredictable happens, but that's okay, because undefined behavior grants me permission to be unpredictable.)
    • The case where i is 5 never occurs, because in order to get there, I first have to get through the case where i is 4, which I have already assumed cannot happen.
    • Therefore, all legal code paths return true.

    As a result, a post-classical compiler can optimize the function to

    bool exists_in_table(int v)
    {
        return true;
    }
    

    Okay, so that's already kind of weird. A function got optimized to basically nothing due to undefined behavior. Note that even if the value isn't in the table (not even in the illegal-to-access fifth element), the function will still return true.

    Now we can take this post-classical behavior one step further: Since the compiler can assume that undefined behavior never occurs (because if it did, then the compiler is allowed to do anything it wants), the compiler can use undefined behavior to guide optimizations.

    int value_or_fallback(int *p)
    {
     return p ? *p : 42;
    }
    

    The above function accepts a pointer to an integer and either returns the pointed-to value or (if the pointer is null) returns the fallback value 42. So far so good.

    Let's add a line of debugging to the function.

    int value_or_fallback(int *p)
    {
     printf("The value of *p is %d\n", *p);
     return p ? *p : 42;
    }
    

    This new line introduces a bug: It dereferences the pointer p without checking if it is null. This tiny bug actually has wide-ranging consequences. A post-classical compiler will optimize the function to

    int value_or_fallback(int *p)
    {
     printf("The value of *p is %d\n", *p);
     return *p;
    }
    

    because it observes that the null pointer check is no longer needed: If the pointer were null, then the printf already engaged in undefined behavior, so the compiler is allowed to do anything in the case the pointer is null (including acting as if it weren't).

    Okay, so that's not too surprising. That may even be an optimization you expect from a compiler. (For example, if the ternary operator was hidden inside a macro, you would have expected the compiler to remove the test that is provably false.)

    But a post-classical compiler can now use this buggy function to start doing time travel.

    void unwitting(bool door_is_open)
    {
     if (door_is_open) {
      walk_on_in();
     } else {
      ring_bell();
    
      // wait for the door to open using the fallback value
      fallback = value_or_fallback(nullptr);
      wait_for_door_to_open(fallback);
     }
    }
    

    A post-classical compiler can optimize this entire function to

    void unwitting(bool door_is_open)
    {
     walk_on_in();
    }
    

    Huh?

    The compiler observed that the call value_or_fallback(nullptr) invokes undefined behavior on all code paths. Propagating this analysis backward, the compiler then observes that if door_is_open is false, then the else branch invokes undefined behavior on all code paths. Therefore, the entire else branch can be treated as unreachable

    Okay, now here comes the time travel:

    void keep_checking_door()
    {
     for (;;) {
      printf("Is the door open? ");
      fflush(stdout);
      char response;
      if (scanf("%c", &response) != 1) return;
      bool door_is_open = response == 'Y';
      unwitting(door_is_open);
     }
    }
    

    A post-modern compiler may propagate the analysis that "if door_is_open is false, then the behavior is undefined" and rewrite this function to

    void keep_checking_door()
    {
     for (;;) {
      printf("Is the door open? ");
      fflush(stdout);
      char response;
      if (scanf("%c", &response) != 1) return;
      bool door_is_open = response == 'Y';
      if (!door_is_open) abort();
      walk_on_in();
     }
    }
    

    Observe that even though the original code rang the bell before crashing, the rewritten function skips over ringing the bell and just crashes immediately. You might say that the compiler went back in time and unrung the bell.

    This "going back in time" is possible even for objects with external visibility like files, because the standard allows for anything at all to happen when undefined behavior is encountered. And that includes hopping in a time machine and pretending you never called fwrite.

    Even if you claim that the compiler is not allowed to perform time travel,¹ it's still possible to see earlier operations become undone. For example, it's possible that the undefined operation resulted in the file buffers being corrupted, so the data never actually got written. Even if the buffers were flushed, the undefined operation may have resulted in a call to ftruncate to logically remove the data you just wrote. Or it may have resulted in a Delete­File to delete the file you thought you had created.

    All of these behaviors have the same observable effect, namely that the earlier action appears not to have occurred. Whether they actually occurred and were reversed or never occurred at all is moot from a compiler-theoretic point of view.

    The compiler may as well have propagated the effect of the undefined operation backward in time.

    ¹ For the record, the standard explicitly permits time travel in the face of undefined behavior:

    However, if any such execution contains an undefined operation, this International Standard places no requirement on the implementation executing that program with that input (not even with regard to operations preceding the first undefined operation).

    (Emphasis mine.)

    ² Another way of looking at this transformation is that the compiler saw that the else branch invokes undefined behavior on all code paths, so it rewrote the code as

    void unwitting(bool door_is_open)
    {
     if (door_is_open) {
      walk_on_in();
     } else {
      walk_on_in();
     }
    }
    

    taking advantage of the rule that undefined behavior allows anything to happen, so in this case, it decided that "anything" was "calling walk_on_in by mistake."

    Bonus chatter: Note that there are some categories of undefined behavior which may not be obvious. For example, dereferencing a null pointer is undefined behavior even if you try to counteract the dereference before it does anything dangerous.

    int *p = nullptr;
    int& i = *p;
    foo(&i); // undefined
    

    You might think that the & and the * cancel out and the result is as if you had written foo(p), but the fact that you created a reference to a nonexistent object, even if you never carried through on it, invokes undefined behavior (§8.5.3(1)).

    Related reading: What Every C Programmer Should Know About Undefined Behavior, Part 1, Part 2, Part 3.

    Update: Broke the &* into two lines because it is the lone * that is the problem.

Page 1 of 425 (4,249 items) 12345»