• The Old New Thing

    Why doesn't the new Start menu use Intellimenus in the All Programs list?

    • 8 Comments

    Common request:

    I want to be able to turn on personalized menus (Intellimenus) when in XP Start Menu mode.

    Imagine if Intellimenus were enabled with the XP Start Menu.

    You use 5 apps; the rest are not used much. (Studies show that 5 is the typical number of unique applications users run on a regular basis. All the rest are rare.)

    Those 5 apps are on the MFU.

    You decide today you want to run some other app, one of those other apps that you run rarely.

    You click All Programs.

    You can't find the app because it got chevroned away. It got chevroned away because it's a rare app, by definition ... if it were a popular app it would be on the MFU already!

    If you are a naive user, you say "Hey, who uninstalled all my apps? It's missing from All Programs!" It's kind of a misnomer to call it "All Programs" when in fact it doesn't show all your programs.

    If you are an experienced user, you say, "Sigh, why do I have to keep clicking this chevron? The whole reason I'm going to 'All Programs' is that I want to run an app I haven't run in a long time, duh. The chevrons should be pre-expanded, save me a click!"

    In other words, if we had Intellimenus enabled on All Programs, it would just show you your MFU again, since the MFU and Intellimenus are both showing the same information, just in different ways. That's clearly pointless.

    Think of "All Programs" as a really big chevron. The MFU is the collapsed version. All Programs is the expanded version.

  • The Old New Thing

    You can read a contract from the other side

    • 8 Comments

    An interface is a contract, but remember that a contract applies to both parties. Most of the time, when you read an interface, you look at it from the point of view of the client side of the contract, but often it helps to read it from the server side.

    For example, let's look at the interface for control panel applications.

    Most of the time, when you're reading this documentation, you are wearing your "I am writing a Control Panel application" hat. So, for example, the documentation says

    When the controlling application first loads the Control Panel application, it retrieves the address of the CPlApplet function and subsequently uses the address to call the function and pass it messages.

    With your "I am writing a Control Panel application" hat, this means "Gosh, I had better have a function called CPlApplet and export it so I can receive messages."

    But if you are instead wearing your "I am hosting a Control Panel application" hat, this means, "Gosh, I had better call GetProcAddress() to get the address of the application's CPlApplet function so I can send it messages."

    Similarly, under the "Message Processing" section it lists the messages that are sent from the controlling application to the Control Panel application. If you are wearing your "I am writing a Control Panel application" hat, this means "Gosh, I had better be ready to receive these messages in this order." But if you are wearing your "I am hosting a Control Panel application" hat, this means "Gosh, I had better send these messages in the order listed."

    And finally, when it says "the controlling application release the Control Panel application by calling the FreeLibrary function," your "I am writing a Control Panel application" hat says "I had better be prepared to be unloaded," whereas your "I am hosting a Control Panel application" hat says, "This is where I unload the DLL."

    So let's try it. As always, start with our scratch program and change the WinMain:

    #include <cpl.h>
    
    int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev,
                       LPSTR lpCmdLine, int nShowCmd)
    {
      HWND hwnd;
    
      g_hinst = hinst;
    
      if (!InitApp()) return 0;
    
      if (SUCCEEDED(CoInitialize(NULL))) {/* In case we use COM */
    
          hwnd = CreateWindow(
              "Scratch",                      /* Class Name */
              "Scratch",                      /* Title */
              WS_OVERLAPPEDWINDOW,            /* Style */
              CW_USEDEFAULT, CW_USEDEFAULT,   /* Position */
              CW_USEDEFAULT, CW_USEDEFAULT,   /* Size */
              NULL,                           /* Parent */
              NULL,                           /* No menu */
              hinst,                          /* Instance */
              0);                             /* No special parameters */
    
          if (hwnd) {
            TCHAR szPath[MAX_PATH];
            LPTSTR pszLast;
            DWORD cch = SearchPath(NULL, TEXT("access.cpl"),
                         NULL, MAX_PATH, szPath, &pszLast);
            if (cch > 0 && cch < MAX_PATH) {
              RunControlPanel(hwnd, szPath);
          }
        }
        CoUninitialize();
      }
    
      return 0;
    }
    

    Instead of showing the window and entering the message loop, we start acting like a Control Panel host. Our victim today is access.cpl, the accessibility control panel. After locating the program on the path, we ask RunControlPanel to do the heavy lifting:

    void RunControlPanel(HWND hwnd, LPCTSTR pszPath)
    {
      // Maybe this control panel application has a custom manifest
      ACTCTX act = { 0 };
      act.cbSize = sizeof(act);
      act.dwFlags = 0;
      act.lpSource = pszPath;
      act.lpResourceName = MAKEINTRESOURCE(123);
      HANDLE hctx = CreateActCtx(&act);
      ULONG_PTR ulCookie;
      if (hctx == INVALID_HANDLE_VALUE ||
          ActivateActCtx(hctx, &ulCookie)) {
    
        HINSTANCE hinstCPL = LoadLibrary(pszPath);
        if (hinstCPL) {
          APPLET_PROC pfnCPlApplet = (APPLET_PROC)
            GetProcAddress(hinstCPL, "CPlApplet");
          if (pfnCPlApplet) {
            if (pfnCPlApplet(hwnd, CPL_INIT, 0, 0)) {
              int cApplets = pfnCPlApplet(hwnd, CPL_GETCOUNT, 0, 0);
              //  We're going to run application zero
              //  (In real life we might show the user a list of them
              //  and let them pick one)
              if (cApplets > 0) {
                CPLINFO cpli;
                pfnCPlApplet(hwnd, CPL_INQUIRE, 0, (LPARAM)&cpli);
                pfnCPlApplet(hwnd, CPL_DBLCLK, 0, cpli.lData);
                pfnCPlApplet(hwnd, CPL_STOP, 0, cpli.lData);
              }
            }
            pfnCPlApplet(hwnd, CPL_EXIT, 0, 0);
          }
    
          FreeLibrary(hinstCPL);
        }
    
        if (hctx != INVALID_HANDLE_VALUE) {
          DeactivateActCtx(0, ulCookie);
          ReleaseActCtx(hctx);
        }
      }
    }
    

    Ignore the red lines for now; we'll discuss them later.

    All we're doing is following the specification but reading it from the host side. So we load the library, locate its entry point, and call it with CPL_INIT, then CPL_GETCOUNT. If there are any control panel applications inside this CPL file, we inquire after the first one, double-click it (this is where all the interesting stuff happens), then stop it. After all that excitement, we clean up according to the rules set out for the host (namely, by sending a CPL_EXIT message.)

    So that's all. Well, except for the red parts. What's that about?

    The red parts are to support Control Panel applications that have a custom manifest. This is something new with Windows XP and is documented in MSDN here.

    If you go down to the "Using ComCtl32 Version 6 in Control Panel or a DLL That Is Run by RunDll32.exe" section, you'll see that the application provides its manifest to the Control Panel host by attaching it as resource number 123. So that's what the red code does: It loads and activates the manifest, then invites the Control Panel application to do its thing (with its manifest active), then cleans up. If there is no manifest, CreateActCtx will return INVALID_HANDLE_VALUE. We do not treat that as an error, since many programs don't yet provide a manifest.

    Exercise: What are the security implications of passing NULL as the first parameter to SearchPath?
  • The Old New Thing

    Why do I get a QueryInterface(IID_IMarshal) and then nothing?

    • 8 Comments
    A common problem when trying to get your new COM object off the ground is that you can't seem to be able to get it created. The object comes out of the class factory, and it gets a QueryInterface for IMarshal, and then nothing. What's going on?

    This is a sure sign that you didn't register your CLSID properly; most likely you forgot to set your threading model properly. (And if you fail to specify a threading model at all, then you get the dreaded "main" threading model.)

    If somebody tries to create a COM object from a thread whose model is incompatible with the threading model of the COM object, then a whole bunch of marshalling stuff kicks in. And if the marshalling stuff isn't there, then COM can't use your object.

    There is a long and very technical article in MSDN on COM threading models which has lots of scary-looking diagrams and tables. In particular, the second scary table in the "In-process servers: (almost) totally dependent on their clients" chapter lists all the combinations of thread models with object threading models, and what COM tries to do in each case.

    In particular, notice that if you have a (mistakenly marked) "main"-threaded object and somebody on any thread other than the main thread tries to create it, marshalling will try to kick in.

    So watch those threading models. The failure modes when you get them wrong are quite baffling.
  • The Old New Thing

    Raymond's comment policy

    • 8 Comments

    Okay, I was hoping it wasn't going to be needed but it takes only one bad apple...

    Here are the ground rules.

    • I reserve the right to edit, delete, or ignore any comment.
    • If I edit your comment in any significant way, I promise to make that fact clear in the edit. (Exception: Broken links and typos will be repaired without comment.)

    Things that increase the likelihood that your comment will be edited or deleted:

    • Offensive or abusive language or disrespectful behavior. (Insults count as disrespectful behavior. Examples of insulting words/phrases: "moron", "designed without adult supervision".)
    • Misrepresentation. (I.e., claiming to be somebody you're not.) If you don't want to use your real name, that's fine, as long as your "handle" isn't offensive, abusive, or misrepresentative.
    • Comment spamming. This includes posting multiple comments in rapid succession, even if they are different. If you are prone to spurts of creativity, collect them into batches and post them as a single comment.
    • Comments that are off-topic, particular when the discussion turns into a shouting match.
    • Comments that attempt to "out" a company/program/person. E.g., trying to guess the identity of a program whose name I did not reveal.
    • More generally, comments that identify a specific company, program, or person. You can talk about a program, but you have to call it "Program X" or something like that. The purpose is to discuss problems and solutions, not to assign blame and ridicule.
    • Comments that expose me to potential legal action. Examples: Disclosing any company's trade secrets, posting copyrighted source code, violating Microsoft's company guidelines.
    • Giving yourself a star. (This is another case of mispresentation.)

    If a wave of comment spam is under way, I may choose to moderate or even close comments until the problem subsides.

    More rules may be added later, but I hope it won't be necessary.

    Disclaimer: All postings are provided "AS IS" with no warranties and confer no rights. Opinions expressed are those of the respective authors. More legal stuff here.

    [31 May 2004: Add exceptions for broken link repair.

    2 Dec 2004: Add disclaimer and exception for fixing typos.

    13 Dec 2004: Add remark on temporary closure of comments during spam attacks.

    15 Mar 2005: Add remark for off-topic comments.

    20 Mar 2007: No "outing".

    25 Dec 2008: No disrespectful behavior.

    12 March 2009: Examples of insults.

    8 July 2010: Self-starring.]

  • The Old New Thing

    Why you should never suspend a thread

    • 8 Comments

    It's almost as bad as terminating a thread.

    Instead of just answering a question, I'm going to ask you the questions and see if you can come up with the answers.

    Consider the following program, in (gasp) C#:

    using System.Threading;
    using SC = System.Console;
    
    class Program {
      public static void Main() {
        Thread t = new Thread(new ThreadStart(Program.worker));
        t.Start();
        SC.WriteLine("Press Enter to suspend");
        SC.ReadLine();
        t.Suspend();
        SC.WriteLine("Press Enter to resume");
        SC.ReadLine();
        t.Resume();
      }
      static void worker() {
        for (;;) SC.Write("{0}\r", System.DateTime.Now);
      }
    }
    

    When you run this program and hit Enter to suspend, the program hangs. But if you change the worker function to just "for(;;) {}" the program runs fine. Let's see if we can figure out why.

    The worker thread spends nearly all its time calling System.Console.WriteLine, so when you call Thread.Suspend(), the worker thread is almost certainly inside the System.Console.WriteLine code.

    Q: Is the System.Console.WriteLine method threadsafe?

    Okay, I'll answer this one: Yes. I didn't even have to look at any documentation to figure this out. This program calls it from two different threads without any synchronization, so it had better be threadsafe or we would be in a lot of trouble already even before we get around to suspending the thread.

    Q: How does one typically make an object threadsafe?

    Q: What is the result of suspending a thread in the middle of a threadsafe operation?

    Q: What happens if - subsequently - you try to access that same object (in this case, the console) from another thread?

    These results are not specific to C#. The same logic applies to Win32 or any other threading model. In Win32, the process heap is a threadsafe object, and since it's hard to do very much in Win32 at all without accessing the heap, suspending a thread in Win32 has a very high chance of deadlocking your process.

    So why is there even a SuspendThread function in the first place?

    Debuggers use it to freeze all the threads in a process while you are debugging it. Debuggers can also use it to freeze all but one thread in a process, so you can focus on just one thread at a time. This doesn't create deadlocks in the debugger since the debugger is a separate process.
  • The Old New Thing

    An anecdote about improper capitalization

    • 8 Comments

    I've already discussed some of the strange consequences of case-sensitive comparisons.

    Joe Beda mentioned the Internet Explorer capitalization bug that transformed somebody's name into a dead body. Allow me to elaborate. You might learn something.

    This bug occurred because Internet Explorer tried to capitalize the characters in the name "Yamada" but was not mindful of the character-combining rules of the double-byte 932 character set used for Japanese. In this character set, a single glyph can be represented either by one or two bytes. The Roman character "A" is represented by the single byte 0x41. On the other hand, the characters "の" is represented by the two bytes 0x82 0xCC. (You will need to have Japanese fonts installed to see the "no" character properly.)

    When you parse a Japanese string in this character set, you need to maintain state. If you see a byte that is marked as a "DBCS lead byte", then it and the byte following must be treated as a single unit. There is no relationship between the character represented by 0xE8 0x41 (錢) and 0xE8 0x61 (鐶) even though the second bytes happen to be related when taken on their own (0x41 = "A" and 0x61 = "a").

    Internet Explorer forgot this rule and merely inspected and capitalized each byte independently. So when it came time to capitalize the characters making up the name "Yamada", the second bytes in the pairs were erroneously treated as if they were Roman characters and "capitalized" accordingly. The result was that the name "Yamada" turned into the characters meaning "corpse" and "field". You can imagine how Mr. Yamada felt about this.

    Converting the string to Unicode would have helped a little, since the Unicode capitalization rules would certainly not have connected two unrelated characters in that way. But there are still risks in character-by-character capitalization: In some languages, capitalization is itself context-sensitive. MSDN gives as an example that in Hungarian, "SC" and "Sc" are not the same thing when compared case-insensitively.

  • The Old New Thing

    Hello Sweden, you're on fire

    • 8 Comments

    (Geek talk resumes on Monday.)

    The longstanding tradition of Norwegians telling Swedish jokes and vice versa, was recently refueled by a border story. A Norwegian man called Sweden on his mobile phone to tell them they had a forest fire. Not only did the firefighters not know about the blaze, they didn't recognize the name of the place on fire.
    Mitt bidrag att försämra diplomatiska förbindelser mellan Sverige och dess grannländer...
  • The Old New Thing

    Other tricks with WM_GETDLGCODE

    • 8 Comments

    The WM_GETDLCODE message lets you influence the behavior of the dialog manager. A previous entry on using WM_GETDLGCODE described the DLGC_HASSETSEL flag which controls whether edit control content is auto-selected when focus changes.

    I was going to write a bit about the other flags, but it turns out that Knowledge Base Article 83302 already covers this, so I'll just call out some highlights.

    The DLGC_WANTMESSAGE flag is the most powerful one. It lets your control prevent the dialog manager from handling a message. So for example if you don't want ESC to dismiss the dialog box when focus is on a particular control but rather be delivered to the control itself, handle the WM_GETDLGCODE message and peek at the lParam. If it is a press of the ESC key, then return DLGC_WANTMESSAGE so the message will not be handled by the dialog manager.

    The DLGC_WANTCHARS, DLGC_WANTTAB and DLGC_WANTARROWS flags are just conveniences that save you the trouble of checking certain categories of messages.

    As always, consider the consequences of overriding default behavior. Doing so generally makes your program harder to use, since it goes against what people are accustomed to seeing in other programs.
  • The Old New Thing

    Christmas gift idea for your favorite glasses-wearing geek

    • 8 Comments

    Yes, I'm talking about Christmas gifts (or "winter solstice gifts" if you prefer) in July. I'm one of those people for whom buying Christmas gifts is a brain-wracking ordeal, and I'm always on the lookout all year round for the "perfect gift".

    Last Christmas, a friend of mine gave me a micro-fiber lens-cleaning cloth that comes in a pouch you can attach to a keychain. Example 1. Example 2. (I have no affiliation with those two sites; I just hunted around looking for a picture.) Best gift ever. I use it several times a day.

    My keychain has only four things on it: The aforementioned cleaning cloth, a USB thumb drive, my car key, and my house key. And I could do without the thumb drive and car key most days. But don't take away my cleaning cloth.

  • The Old New Thing

    The traffic gods are punishing me for bicycling

    • 8 Comments

    It happened again.

    The last time I participated in a bicycle ride that started in Seattle, I got stuck in highway traffic both going out and coming back. The 520 bridge was closed for inspection so everybody was taking the I-90 bridge instead. But traffic at the western terminal of I-90 was backed up because the Alaska Way Viaduct was closed for some sort of fundraiser walk-a-thon. And then, after the ride was over, I got stuck in traffic on the return trip as well because the Mariners baseball game had just let out, and that on top of all the traffic created by the 520 bridge being closed.

    This weekend, heading to the starting point for a light group ride through Seattle (which our group leader nicknamed "Crying over STP" since it coincided with the annual Seattle to Portland bike ride), there was a huge back-up on westbound highway 520 due to a multiple-car accident that closed both main lanes, forcing everybody to squeeze into the car pool lane. And then after the ride was over, I got stuck on the 520 bridge at around 4:10pm. The flashing lights were on, indicating that the drawbridge was open. I turned in to the Department of Transportation highway radio station and learned that the bridge was scheduled to be open from 4pm to 4:30pm for "boat fair traffic". But at least we knew how long we were going to be waiting, so I turned off the car engine, got out, and walked around the bridge. (It's not often that you get to walk on the 520 bridge. It didn't occur to me to ride my bicycle on the bridge.)

    When I got home, I tried to find any information on this scheduled closure, but turned up nothing. I guess you just had to know.

    But at least I won't get stuck in traffic next month when I head to Mercer Island to watch the Blue Angels perform, because I'm going to ride my bicycle there. We'll see whether the hills have returned.

    Postscript 1: And I hadn't known about the Elliot Bay Trail, which runs from the marina, through the trainyards, along the waterfront, to downtown Seattle. Very nice.

    Postscript 2: You can still see the after-effects of that accident on highway 520: The light barriers on the median near Hunts Point have all been scraped off!

    Postscript 3: We stopped for lunch at the Elliott Bay Marina, looking for the the sushi place that we dimly remembered, but it turns out that they no longer exist. (We had lunch at Maggie Bluff's Grill instead.) Sushi is a somewhat unorthodox bicycle food; I amused myself with the image of a support car driving up to a rider and handing over some unagi and maguro...

Page 380 of 450 (4,493 items) «378379380381382»