August, 2012

  • The Old New Thing

    How do I customize how my application windows are grouped in the Taskbar?

    • 17 Comments

    Benjamin Smedberg wants to know how to customize the icon used in the Taskbar for applications that are grouped, when the application is a runtime for multiple applications. (This is the other scenario I hinted at last time.)

    Actually, customizing the icon is only part of what you want to happen when your application is a runtime. In that case, you really want each inner application to be exposed to the user as an entirely separate application. In other words, if your application is hosting Product A and Product B, you want the windows for Product A and Product B to group separately, have separate icons, maintain separate jump lists, all that stuff. Because from the user's point of view, they are separate programs. It just happens that under the covers, they're all being driven by a single EXE.

    In Windows, the concept of an application is captured in something called an Application User Model ID, or AppID for short. For backward compatibility, if your application does not provide an explicit AppID, the shell will autogenerate one based on your EXE name. Therefore, the starting point for AppIDs is that an AppID maps to an EXE. But once you start customizing your AppID, you can play with this default correspondence.

    All the information in this article came from the article Application User Model IDs (AppUserModelIDs) in MSDN.

    Okay, so suppose your application is really a runtime for other applications. What you need to do is assign a different AppID to each of the applications you are hosting. The mechanism for this is up to you. Your applications might explicitly provide a unique ID, or you may be able to infer one. For example, if you are Internet Explorer and your "applications" are pinned Web sites, you can use the URL of the site being pinned as the unique ID.

    You then get to take your unique IDs and create AppIDs for them. The format of an AppID is

    CompanyName.ProductName.SubProduct.VersionInformation
    

    where the Sub­Product is optional, and the Version­Information is present only if you want different versions of your app to be treated as distinct. (If you want an upgraded version to be a replacement for the old version, then omit the Version­Information so that the old and new versions use the same AppID.)

    Note that you have to be careful how you auto-generate your AppIDs, since the resulting AppID needs to be legal. For example, you cannot just take a URL and use it as the Sub­Product of an AppID. URLs contain embedded periods, which violates the overall format, and they can be longer than 128 characters and can contain spaces, both of which are also called out in the documentation as prohibited. Internet Explorer addresses this problem by using a hash of the URL as its Sub­Product rather than the full URL.

    You then assign this AppID to every window associated with the "application". You can do this for an entire process by calling SetCurrentProcessExplicitAppUserModelID, or you can do it on a window-by-window basis by setting the PKEY_AppUserModel_ID property.

    Okay, let's write a program that shows how a runtime for other applications can use AppIDs to control its treatment in the taskbar. Of course, our sample won't actually be a runtime for anything; the "applications" that it hosts will simply be icons.

    Start with the scratch program and make these changes:

    
    #include <shellapi.h>
    #include <shlobj.h>
    #include <strsafe.h>
    
    #define HOSTAPPID L"Contoso.Host"
    
    void SetProcessAppId(LPCWSTR pszTarget)
    {
      if (pszTarget[0]) {
        WCHAR szAppId[256];
        DWORD dwHash = 0;
        HashData((BYTE*)pszTarget, wcslen(pszTarget) * sizeof(WCHAR),
                 (BYTE*)&dwHash, sizeof(dwHash));
        StringCchPrintfW(szAppId, ARRAYSIZE(szAppId),
                         L"%s.hosted-%08x", HOSTAPPID, dwHash);
        SetCurrentProcessExplicitAppUserModelID(szAppId);
      } else {
        StringCchPrintfW(szAppId, ARRAYSIZE(szAppId),
                         L"%s.main", HOSTAPPID);
      }
    }
    
    int WINAPI wWinMain(HINSTANCE hinst, HINSTANCE hinstPrev,
                       LPWSTR lpCmdLine, int nShowCmd)
    {
      SetProcessAppId(lpCmdLine);
    
        ...
        ShowWindow(hwnd, SW_NORMAL);
    
        SetWindowText(hwnd, lpCmdLine);
        if (lpCmdLine[0]) {
          WCHAR szIcon[256];
          StringCchCopyW(szIcon, ARRAYSIZE(szIcon), ptszCmdLine);
          int iIcon = PathParseIconLocation(szIcon);
          if (iIcon == -1) iIcon = 0;
          HICON hico = ExtractIcon(hinst, szIcon, iIcon);
          SendMessage(hwnd, WM_SETICON, ICON_BIG, (LPARAM)hico);
        }
        ...
    }
    

    Our simple host program just hosts an icon. The path to the icon is passed on the command line in the form "path,id", and for good measure, we put the icon path in the caption so you can see how it groups.

    The real work happens in the SetProcessAppId function. If there is no command line, then we are running in standalone mode and set our Sub­Product to main. If we have a command line, then we hash it and use the hash to build our Sub­Product. I'm just using a four-byte hash with a simple has function; depending on how paranoid you are, you could use some other hash function, but make sure you can get the resulting AppID to fit into 128 characters. (This means that hex-encoded SHA512 is too big.)

    Once we figure out what our AppID is, we set it for the entire process by calling SetCurrentProcessExplicitAppUserModelID.

    Okay, let's take this program out for a spin. You can run it with the command lines

    scratch %windir%\explorer.exe,0
    scratch %windir%\explorer.exe,0
    scratch %windir%\explorer.exe,1
    scratch %windir%\explorer.exe,1
    

    to see four copies of the program, two with one icon and two with another. Observe that when they group in the taskbar, the icon for the group is preserved, and that the two sets of programs group separately.

    Note also that if you create shortcuts to your host program with a command line, you need to set the AppID in your shortcut, too. (Otherwise the shell won't know what the AppID of the resulting program will be, since you are setting it at runtime.)

    Note also that we did not need to register the application as a host process because we explicitly set an AppID in our application and in our shortcuts. (Or at least, we said that we would. I didn't actually do it.)

    Bonus reading: Developing for the Windows 7 Taskbar — Application ID.

  • The Old New Thing

    My colleague picked a good day to go out and catch a baseball game

    • 28 Comments

    I ran into one of my colleagues at the coffee stand and asked him how things were going. He said that Wednesday was his wife's birthday, and he asked her if she wanted to do anything special. "Let's catch a baseball game," she suggested.

    So off they went to watch a baseball game.

    Turns out they chose a good game to watch: Seattle Mariners pitcher Félix Hernández threw a perfect game. (I like how Wikipedia has a page dedicated to the 23rd perfect game in baseball history, but no page for the first perfect game in baseball history. Because Wikipedia covers pop culture prior to 2001 very differently from pop culture after 2001.)

    For some reason, the photo caption in the Associated Press article says that the Rays won the game. The Mariners can't get any respect. Their pitcher throws a perfect game and the AP still reports that they lost.

    Puzzle: Describe conditions under which a starting pitcher can throw nine complete perfect innings in a nine-inning game and not be credited with a perfect game.

    One possible answer: The pitcher for the home team pitches nine perfect innings. The game enters the bottom of the ninth scoreless. During the bottom of the ninth, the home team forfeits the game. According to the rules, the game ends immediately, and since the pitcher did not receive credit for a victory, a perfect game is not awarded.

    (For some reason, I enjoy thinking about these bizarre baseball scenarios.)

  • The Old New Thing

    What if my application is really two applications bundled into a single file, and I want them collected into two groups on the taskbar in Windows 7?

    • 14 Comments

    A customer wanted to prevent multiple copies of their program from being grouped on the taskbar. They didn't give an explanation why, but let's assume that they are doing this for honorable purposes rather than as a way to annoy the user. For example, maybe their program is really multiple applications bundled inside a single EXE file for convenience.

    The information you need to do this is in MSDN under Application User Model IDs, specifically in the Where to assign an AppUserModelID section. I'll assume you've read the guidance there, and I'm just going to dive into the implementation.

    Suppose our scratch program can serve both as a floor wax and as a dessert topping. It decides on the mode based on a command line switch.

    #include <shlobj.h>
    
    int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev,
                       LPSTR lpCmdLine, int nShowCmd)
    {
        MSG msg;
        HWND hwnd;
    
        g_hinst = hinst;
    
        if (!InitApp()) return 0;
    
        BOOL fDessert = strcmp(lpCmdLine, "-dessert") == 0;
        SetCurrentProcessExplicitAppUserModelID(fDessert ?
                L"Contoso.LitWare.DessertTopping" :
                L"Contoso.LitWare.FloorWax");
    
        if (SUCCEEDED(CoInitialize(NULL))) {/* In case we use COM */
            hwnd = CreateWindow(
                TEXT("Scratch"),                /* Class Name */
                fDessert ? TEXT("Dessert topping") : TEXT("Floor wax"),
                WS_OVERLAPPEDWINDOW,            /* Style */
                CW_USEDEFAULT, CW_USEDEFAULT,   /* Position */
                CW_USEDEFAULT, CW_USEDEFAULT,   /* Size */
                NULL,                           /* Parent */
                NULL,                           /* No menu */
                hinst,                          /* Instance */
                0);                             /* No special parameters */
    
        ...
    }
    

    Run this program a few times, some with the -dessert switch and some without. Observe that the dessert versions and non-dessert versions group separately.

    The next level of fancy-pants behavior is to give different AppIDs to different windows within a single process. You might do this if your combination floor wax/dessert topping program actually runs both modes inside the same process. Something like this:

    #include <shellapi.h>
    #include <propkey.h>
    #include <propvarutil.h>
    #include <shlobj.h>
    
    int g_cWindows = 0;
    
    BOOL
    OnCreate(HWND hwnd, LPCREATESTRUCT lpcs)
    {
      ++g_cWindows;
      return TRUE;
    }
    
    void
    OnDestroy(HWND hwnd)
    {
      if (--g_cWindows == 0) PostQuitMessage(0);
    }
    
    HWND
    CreateTaskWindow(BOOL fDessert, int nShowCmd)
    {
      HWND hwnd = CreateWindow(
          TEXT("Scratch"),                /* Class Name */
          fDessert ? TEXT("Dessert topping") : TEXT("Floor wax"),
          WS_OVERLAPPEDWINDOW,            /* Style */
          CW_USEDEFAULT, CW_USEDEFAULT,   /* Position */
          CW_USEDEFAULT, CW_USEDEFAULT,   /* Size */
          NULL,                           /* Parent */
          NULL,                           /* No menu */
          g_hinst,                        /* Instance */
          0);                             /* No special parameters */
      if (hwnd) {
        IPropertyStore *pps;
        HRESULT hr = SHGetPropertyStoreForWindow(hwnd, IID_PPV_ARGS(&pps));
        if (SUCCEEDED(hr)) {
          IPropertyStore_SetValue(pps, PKEY_AppUserModel_ID,
                fDessert ?
                L"Contoso.LitWare.DessertTopping" :
                L"Contoso.LitWare.FloorWax");
          pps->Release();
        }
        ShowWindow(hwnd, nShowCmd);
      }
      return hwnd;
    }
    
    void
    OnChar(HWND hwnd, TCHAR ch, int cRepeat)
    {
        switch (ch) {
        case 'd': CreateTaskWindow(TRUE, SW_SHOWNORMAL); break;
        case 'f': CreateTaskWindow(FALSE, SW_SHOWNORMAL); break;
        }
    }
    
        HANDLE_MSG(hwnd, WM_CHAR, OnChar);
    
    int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev,
                       LPSTR lpCmdLine, int nShowCmd)
    {
        MSG msg;
        HWND hwnd;
    
        g_hinst = hinst;
    
        if (!InitApp()) return 0;
    
        BOOL fDessert = strcmp(lpCmdLine, "-dessert") == 0;
        // SetCurrentProcessExplicitAppUserModelID(...);
    
        if (SUCCEEDED(CoInitialize(NULL))) {/* In case we use COM */
            hwnd = CreateTaskWindow(fDessert, nShowCmd);
    
            // ShowWindow(hwnd, nShowCmd);
        ...
    }
    

    This time, instead of setting the application ID globally, we set it on a per-window basis. When you run this program, you can press "f" to open a new floor wax window or "d" to open a new dessert topping window. As before, observe that the two types of windows group separately.

    The last detail is setting the System.AppUserModel.ID property on the shortcuts used to launch these programs. You can do this from MSI by adding an entry to your Msi­Shortcut­Property table, or if you create your shortcuts programmatically, you do this by setting the property yourself:

     CComPtr<IShellLink> spsl;
     spsl.CoCreateInstance(CLSID_ShellLink);
     spsl->SetPath(TEXT("C:\\Path\\to\\scratch.exe"));
     CComQIPtr<IPropertyStore> spps(spsl);
     IPropertyStore_SetValue(spps, PKEY_AppUserModel_ID,
                             L"Contoso.LitWare.FloorWax");
     spps->Commit();
     CComQIPtr<IPersistFile>(spsl)->Save(L"LitWare Floor Wax.lnk", TRUE);
    

    Next time, we'll look at another reason you might want to customize how your application group on the taskbar in Windows 7.

  • The Old New Thing

    Charles Petzold's Programming Windows Sixth Edition has reached Release Preview

    • 11 Comments

    We interrupt this program for a word from Microsoft Press.

    A few months ago, I noted that Charles Petzold is coming back with another edition of Programming Windows, and you could have gotten in on the ground floor and ordered your copy for only $10 which included access to all prerelease versions.

    Well, the book has reached Release Preview, which means that the sticker price has increased to $30, so if you missed your chance earlier, you can at least get your order in before it goes up to $40 on August 28 and before it reaches its final price of $50 on November 15. And as with the people who acted quickly, your $30 order includes access to the Release Preview and the Final version.

  • The Old New Thing

    What happened to the Windows 2000 "Set default language setting for the system" button?

    • 3 Comments

    Last time, we looked at the evolution of the control panel settings which control the language groups for which Windows will install fonts, code page information, and other support collateral. This was in the context of a customer who was trying to migrate from Windows 2000 to Windows XP, and the customer wanted to convert their workflow to the new operating system.

    They made the appropriate changes, and... the problem was not fixed.

    At this point, the customer liaison decided to tell us what the actual problem was. (I don't know whether the information was originally withheld by the customer or the customer liaison, so I intentionally phrased the situation vaguely.)

    The customer's original workflow involved installing Bulgarian language support, and from your explanation it appears that Bulgarian language support is installed by default on Windows XP. But we still have the problem where if they run one of their internal programs, the text comes out incorrectly unless they also set the Language for non-Unicode programs to Bulgarian.

    It took two days before the penny dropped and I figured out what the customer's actual problem was and what they were trying to do.

    Even though the customer's original question was asking what happened to "selecting multiple languages from the Language settings for the system" section of the control panel, their actual question is "What happened to the Set default... button that appears below the list box?"

    The Set default... button moved to the Languages for non-Unicode programs section of the control panel.

    Selecting Bulgarian from the dropdown is equivalent to clicking the old Set default... button and picking Bulgarian.

    Back in 2000, some wiseguy named Michael Kaplan walked through the old Windows 2000 control panel and explained what each piece means.

    The customer liaison reported back that they could get their program to behave the way they wanted, but for only one language at a time. Reportedly, under Windows 2000, once they added support for Bulgarian and Slovak, they could use the program in both Bulgarian and Slovak mode. But in Windows XP, they have to pick one or the other. If they pick Bulgarian, then the Slovak mode comes out garbage, and vice versa.

    The customer's application is Web-based, so the problem may be in the way the program performed language negotiation with the server. But the customer hasn't done any actual debugging; they're just running around fiddling knobs hoping that one of them will magically solve the problem.

    What the customer really needs to do is start debugging: For example, they can capture a network trace of the communication between the client and the server on a Windows 2000 machine, and compare it to the trace they get on a Windows XP machine. If there are differences, that may guide the next level of investigation. (And if there aren't any differences, then the problem is strictly on the client.)

  • The Old New Thing

    What happened to the Windows 2000 "Language settings for the system" control panel?

    • 41 Comments

    In 2011, a customer had a question about migrating from Windows 2000 to Windows XP. (That's right, this customer was still using Windows 2000 in the year 2011.) Specifically, they noted that in Windows 2000, they can select multiple languages in the "Language settings for the system" portion of the Regional Options control panel, and they couldn't find the corresponding control panel setting in Windows XP.

    Regional Options
    General
    Numbers
    Currency
    Time
    Date
    Input Locales
     
    Settings for the current user
    Many programs support international settings for numbers, currencies, times, and dates. Set the locale in order to use the standard settings.
    Your locale (location):
    English (United States)
    Language settings for the system
    Your system is configured to read and write documents in multiple languages.
    Arabic
    Armenian
    Baltic
    Central Europe
    Cyrillic

    In Windows 2000, "Language settings for the system" provides the option to install support (such as code pages, keyboard layouts, and fonts) for various language groups. In Windows XP, the big list of language groups was reduced to three categories:

    • Basic (Baltic, Central Europe, Cyrillic, Greek, Turkish, Western Europe)
    • Complex (Arabic, Armenian, Georgian, Hebrew, Indic, Vietnamese, Thai)
    • East Asia (Chinese Simplified, Chinese Traditional, Japanese, Korean)

    The Basic category is always installed. To install the Complex or East Asia categories, use the "Supplemental language support" section of the Regional and Language Options control panel.

    Windows XP Regional and Language Options property sheet, with a section titled "Supplemental language support" with options "Install files for complex script and right-to-left languages (including Thai)" and "Install files for East Asian languages

    Someday, that customer might upgrade to Windows Vista, so I may as well answer the question right now. In Windows Vista and onward, things were simplified even more: All language groups are installed at all times. The dialog box went away completely since there were no options remaining.

    As it turns out, the customer's problem had nothing to do with language support. Of course, they didn't come out and describe the problem they were having; rather, they reduced the problem into multiple pieces, and then asked for help on one specific piece. They tried out a solution based on this new information, but it didn't solve the problem, because as it turns out, the Language settings for the system control panel was a red herring. If they had told us what their original problem was, we could have told them "But this setting will do nothing to solve your problem. What you really need is over there."

    Tomorrow, we'll look at the customer's actual problem. (So please don't try to guess or you'll ruin the surprise. I can't believe I had to write that.)

  • The Old New Thing

    How long does it take for a notification icon to stop appearing in the Notification Area Icons control panel?

    • 31 Comments

    A customer reported that even after uninstalling their application, the notification icon entry remains in the Notification Area Icons control panel.

    Yup, that's right. Explorer remembers the icon, even after the underlying program has been uninstalled, because you might have uninstalled it with the intention of immediately reinstalling it, so Explorer remembers the icon in case it comes back. But after one week, Explorer gives up and finally forgets about the icon. "It's been a week, and the user hasn't reinstalled the application. I'm going to give up waiting for it."

    The customer wanted to know how they could remove their icon immediately upon uninstall. They reported that having the icon remain in the Notification Area Icons control panel made it appear that the uninstall was unsuccessful.

    There is no documented mechanism for removing the icon (and the undocumented mechanisms destroy all the icon history, not just the icon history for your icon, so don't do that either). You'll just have to wait seven days for the icon to go away.

    (One possibility that was considered was to have the Notification Area Icons control panel check if the application is still installed before showing it on the list. This runs into problems, though, if the application resides on the network or removable media. It means that opening the Notification Area Icons control panel can stall on network I/O, generate security audits if you lost access to the network share, and spin up external media. Remember how much people hated it when Windows 95 spun up your CD-ROM drive the first time you clicked on the address bar?)

  • The Old New Thing

    Why am I in the Quake credits?

    • 13 Comments

    Anon wants to know why I am listed in the credits for the video game Quake under the "Special Thanks" section. "Were you an early tester/debugger?"

    I've never played a game of Quake in my entire life. I (and most of the rest of the Windows 95 team) played DOOM, but after a while, first-person-shooter games started giving me a headache. By the time Quake came out, I had already abandoned playing FPS games.

    I don't remember what it was that I did specifically, but it was along the lines of helping them with various technical issues related to running under Windows. At the time, I was a kernel developer, and the advice I gave was almost certainly related to memory management and swapping.

    Sorry it wasn't anything exciting.

  • The Old New Thing

    How did real-mode Windows implement its LRU algorithm without hardware assistance?

    • 29 Comments

    I noted some time ago that real-mode Windows had to do all its memory management without any hardware assistance. And yet, along the way, they managed to implement an LRU-based discard algorithm. Gabe is really interested in how that was done.

    As we saw a few months ago, inter-segment calls were redirected through a little stub which either jumped directly to the target (if it was in memory) or loaded the target (possibly discarding other memory to make room) before jumping to it. And we saw that the executable format had INT 3Fh instructions baked into it so that the Entry Table could be loaded directly into memory for execution.

    As it happens, Windows didn't take advantage of that feature, because it wanted to do more.

    When it came time to load the Entry Table, the loader did a little rewriting, converting each

        db  flags
        INT 3Fh
        db  entry_segment
        dw  entry_offset
    

    sequence into

        db  flags
        sar byte ptr cs:[xxx], 1
        INT 3Fh
        db  entry_segment
        dw  entry_offset
    

    where the xxx refers to a table of bytes in the Entry Table preallocated for this purpose, initialized to 1's.

    What is "this purpose"?

    Whenever anybody needed the address of an inter-segment function, instead of return the address of the int 3Fh, the kernel returned the address of the sar instruction. The sar instruction stands for shift arithmetic right, For a byte value, this means to shift the bits right one place, but keep the high-order bit the same.

    a b c d e f g h
    a a b c d e f g

    Okay, so what was the effect of sticking this little sar instruction at the start of every inter-segment call? Since the values in the table were initialized to 1, a right arithmetic shift changed the 1 to 0. Therefore, each time an inter-segment call was performed, the corresponding byte in the table was set to zero.

    Hooray, a software-implemented Accessed bit!

    Every 250 milliseconds, Windows scanned and reset the Access bits, using the data to maintain an LRU-list of all the segments in the system. That way, when it was time to discard some memory, it could discard the least recently used ones first.

    Today, a timer that runs continuously at 250ms would incur the wrath of the power management team. But back in the days of real-mode Windows, there was no power management. Like Chuck Norris, PCs ran at only one power level: Awesome.

    I continue to be amazed at how much Windows 1.0 accomplished with so little.

    [Raymond is currently away; this message was pre-recorded.]

  • The Old New Thing

    What is SysFader and why is it always crashing?

    • 23 Comments

    If you type SysFader into your favorite search engine, you'll find lots of hits from people asking, "What is SysFader, and why is it always crashing Internet Explorer?"

    SysFader: iexplore.exe - Application Error
    The exception unknown software exception (0xe06d7363) occurred in the application at location 0x7c812afb.

    When a program encounters a fatal error, the system crash dialog appears. And it needs to put somebody's name in the title of the dialog to indicate which application crashed. Sure, it has the process name (iexplore.exe), but it has this nagging feeling that it can do better. After all, not everybody will know that "AcroRd32.exe" is "The menu for my favorite restaurant that I was looking at in Adobe Acrobat Reader". So it goes looking for a window that belongs to the thread so it can steal the window's title and use that to help the user understand what it was that crashed.

    And if can't find any visible windows, it will go for invisible ones, on the theory that, "Well maybe the application crashed before it could show the window."

    Now let's see what happens when we apply this logic to SysFader.

    SysFader is a helper window used by Internet Explorer to perform fade-out animations. It really doesn't do much, but it is a window, albeit an invisible one when there are no animations in progress.

    SysFader happens to run on a shared worker thread. If that worker thread is being borrowed by some other background task, and that background task crashes, then when the crash dialog appears and looks around for a window to put in the title, it says "Rats, I don't have any visible windows, but I do have this invisible one, so I'll go ahead and put that one in the title bar. Better than nothing."

    It's sort of the error-reporting version of the Politician's Fallacy:

    1. A window must be blamed.
    2. This is a window.
    3. Therefore, we must blame it.

    It's like your photo appearing in a newspaper article headlined Robbery at Woodgrove Bank, Suspect At Large, not because you're the suspect, but because you happen to have been in the building at the time of the robbery.

    Bonus chatter: You probably recognize the exception code as an unhandled C++ exception. Internet Explorer doesn't use C++ exceptions, so the exception most likely came from a plug-in.

    [Raymond is currently away; this message was pre-recorded.]

Page 2 of 3 (29 items) 123