February, 2013

  • The Old New Thing

    For the Nitpickers: Enhanced-mode Windows 3.0 didn't exactly run a copy of standard-mode Windows inside the virtual machine

    • 45 Comments

    Generally speaking, Enhanced-mode Windows 3.0 ran a copy of standard-mode Windows inside the virtual machine. This statement isn't exactly true, but it's true enough.

    Commenter Nitpicker objected, "Why are you threatening us with the Nitpicker's Corner for asking about this issue instead of explaining it once and linking it everywhere?"

    Okay, first of all, as far as I can tell, you're the first person to ask about the issue. So you can't say "Everybody who asks about the issue is threatened with the Nitpicker's Corner" because up until you made your comment, nobody ever asked. Okay, well, technically you can say it, because every statement quantified over the empty set is true. But it is equally true that, at the time you made your comment, that "Everybody who asks about the issue is awarded a new car." So it is not a meaningfully true statement.

    I haven't bothered explaining the issue because the issue has never been central to the main point of whatever article happens to bring it up. The statement is true enough for the purpose of discussion, and the various little corners in which the statement breaks down have no bearing on the original topic. Nitpickers would point out that you can't combine velocities by simple addition because of the laws of Special Relativity. Even when the situation under discussion takes place at non-relativistic speeds.

    As for the suggestion, "Explain it once and link it everywhere," you're assuming that I can even explain it once, that doing so is less work than just saying "not exactly true, but true enough," and that I would enjoy explaining it in the first place.

    If you don't like it, you can ask for your money back.

    Okay, I went back and dug through the old Windows 3.0 source code to answer this question. It took me about four hours to study it all, try to understand what the code was doing, and then distill the conclusions into this article. Writing up the results took another two hours. That's six hours I could've spent doing something enjoyable.

    The 16-bit Windows kernel was actually three kernels. One if you were using an 8086 processor, another if you were using an 80286 processor, and a third if you were using an 80386 processor. The 8086 kernel was a completely separate beast, but the 80286 and 80386 kernels shared a lot of code in common. The major difference between the 80286 and 80386 kernels was in how they managed memory, because the descriptor tables on the 80386 were a different format from the descriptor tables on the 80286. The 80386 memory manager could also take advantage of the new 32-bit registers.

    But the difference between the 80286 and 80386 kernels were not based on whether you were running Standard or Enhanced mode. If you're running on an 80386 processor, then you get the 80386 kernel, regardless of whether you're using Standard or Enhanced mode Windows. And since Enhanced mode Windows required an 80386 processor, the behavioral changes between Standard and Enhanced mode were restricted to the 80386 kernel.

    The 80386 kernel was designed to run as a DPMI client. It asked the DPMI host to take it into protected mode, then used the DPMI interface to do things like allocate selectors and allocate memory. If you ran Windows in Standard mode, then the DPMI host was a custom-built DOS extender that was created just for Standard mode Windows. If you ran Windows in Enhanced mode, then the DPMI host was the 32-bit virtual machine manager. Abstracting to the DPMI interface allowed a single 80386 kernel to run in both Standard and Enhanced modes.

    And in fact if you ran Enhanced mode Windows with paging disabled, then the code running in the 80386 kernel was pretty much the same code that ran if you had run the 80386 kernel under Standard mode Windows.

    One obvious place where the behavior changed was in the code to manage MS-DOS applications, because Enhanced mode Windows could multi-task MS-DOS applications, and Standard mode Windows could not.

    Another place where the behavior changed was in in the code to allocate more selectors: The attempt to retry after extending the local descriptor table was skipped if you were running under the Standard mode DOS extender, because the Standard mode DOS extender didn't support extending the local descriptor table.

    And another difference is that the Windows idle loop in Enhanced mode would issue a special call to release its time slice to any multi-tasking MS-DOS applications. (If you were running in Standard mode, there were no multi-tasking MS-DOS applications, so there was nobody to release your time slice to.)

    Another thing special that the 80386 kernel did was register with the virtual machine manager so that it could display an appropriate message when you pressed Ctrl+Alt+Del. For example, you saw this message if you hit Ctrl+Alt+Del while there was a hung Windows application:

    Contoso Deluxe Music Composer


    This Windows application has stopped responding to the system.

    *  Press ESC to cancel and return to Windows.
    *  Press ENTER to close this application that is not responding.
       You will lose any unsaved information in this application.
    *  Press CTRL+ALT+DEL again to restart your computer. You will
       lose any unsaved information in all applications.

    But all these differences are minor in the grand scheme of things. The window manager behaved the same in Standard mode and Enhanced mode. GDI behaved the same in Standard mode and Enhanced mode. Printer drivers behaved the same in Standard mode and Enhanced mode. Only the low-level kernel bits had to change behavior between Standard mode and Enhanced mode, and as you can see, even those behavior changes were relatively minor.

    That's why I said it was "true enough" that what was running inside the virtual machine was a copy of Standard-mode Windows.

  • The Old New Thing

    Once you know something can be done, doing it is much easier

    • 22 Comments

    Unfortunately, I don't remember the name of the star of this story, but I'm told that there was a notable mathematician who believed the Perfect Graph Conjecture to be false and spent many years trying to prove it one way or another.

    Meanwhile, another mathematician (presumably László Lovász) announced that he had found a proof (in the affirmative).

    Upon hearing the news that the open question had been resolved, the first mathematician was able to produce a proof within 90 minutes.

    Once again showing that it's much easier to do something once you know it can be done.

  • The Old New Thing

    How can I register my context menu command for all file types *except* one, or other complex conditionals?

    • 12 Comments

    We saw that you can register your context menu under * to make it apply to all files. But what if you want it to apply to all files except one? For example, your command might be "Convert to Widget" but you don't want it to appear for .widget files because that would be redundant.

    I mentioned how to do this as an afterthought in an earlier discussion of advanced context menu registration, but I'm going to elevate to its own topic because it's probably even more useful than the base article!

    Starting in Windows 7, you can conditionalize your context menu declaratively. This is explained in the documentation for context menus in the section Getting Dynamic Behavior for Static Verbs by Using Advanced Query Syntax. Let's try it out. Of course, I don't actually have a "Widgetizer" program, so I'll just use Calc.

    [HKEY_CLASSES_ROOT\*\shell\Widgetize]
    "AppliesTo"="NOT System.FileExtension:=.widget"
    
    [HKEY_CLASSES_ROOT\*\shell\Widgetize\Command]
    @="calc.exe"
    

    The Widgetize command appears only for files whose extension is not .widget.

    You have access to the shell property system and Advanced Query Syntax here, so you can create more complex conditionals. Here's how you can Widgetize only files that are smaller than 32KB, and ignore zero-byte files.

    "AppliesTo"="System.Size:1..32kb AND NOT System.FileExtension:=.widget"
    
  • The Old New Thing

    The curious pattern of pre-emptively rejecting the solution to your problem

    • 28 Comments

    A frustrating pattern that shows up occasionally in customer questions is the case where the customer poses a problem, and pre-emptively rejects the mechanism explicitly designed to solve that problem.

    How can we change the widget color without using IWidget::Set­Color?

    Um, the whole point of IWidget::Set­Color is to change the color of a widget. Why are you rejecting the mechanism whose sole purpose in life is to solve the very problem you are having?

    Usually, if you press hard enough, they will cough up the reason why they think they cannot use the solution specifically designed to do what they want. Various excuses tend to come up over and over.

    One excuse is the belief that the proposed solution does not work in a particular scenario. "We cannot use ACLs because they don't work on network volumes." Um, yes they do. Check it out.

    Or that the proposed solution doesn't fit their choice of technology. "We are programming in a language that does not support COM objects. We can only p/invoke to C-style APIs." Well, you can work around that problem by writing a helper DLL that exposes a C-style API, and implements it by calling the COM method.

    Or that the proposed solution violates some vague corporate policy. "We have a corporate policy that users cannot change widget colors, so the IWidget::Set­Color method returns E_ACCESS­DENIED. We're looking for a way around that policy." Okay, well, now that's something you need to take up with the people who establish your corporate policies. Don't come to us looking for ways to circumvent corporate policy.

    One time, the reason came from our own technical support staff: "We cannot write a C++ program that calls IWidget::Set­Color and provide it to the customer because we are not a developer support team. We are not allowed to send compiled binaries to the customer for liability reasons, and we generally do not send source code because our customers typically do not have the expertise or desire to install Visual Studio and the Platform SDK just to compile and run a five-line C++ program. (Did I mention that we are not a developer support team?) Can it be done from a batch file?"

    Yeah, how about this batch file:

     >changeColor.cs echo using System;
    >>changeColor.cs echo class Program {
    >>changeColor.cs echo public static void Main(string[] args) {
    >>changeColor.cs echo ...
    >>changeColor.cs echo }
    >>changeColor.cs echo }
    %windir%\Microsoft.NET\Framework\v4.0.30319\csc changeColor.cs
    changeColor blue
    

    Only half-joking.

    The non-joking answer is "The customer can take this information to a developer support team, or at least somebody who will write the program for them, if they don't know how to write a program themselves." Microsoft Consulting Services exists for this, but that is likely overkill for a five-line program.

  • The Old New Thing

    Optimizing the Chili's dining experience

    • 26 Comments

    Back in the days of Windows 95, one of my colleagues paid a visit to his counterparts over in the Windows NT team as part of a continuing informal engagement to keep the Windows NT developers aware of the crazy stuff we've been doing on the Windows 95 side.

    One particular time, his visit occurred in late morning, and it ran longer than usual, so the Windows NT folks said, "Hey, it's lunchtime. Do you want to join us for lunch? It's sort of our tradition to go to Chili's for lunch on Thursdays."

    My colleague cheerfully accepted their offer.

    The group were shown to their table, and the Windows NT folks didn't even look at the menus. After all, they've been here every week for who-knows-how-long, so they know the menu inside-out.

    When the server came to take the orders, they naturally let my colleague order first, seeing as he was their special guest.

    "I'll have a chicken ranch sandwich."

    The folks from the Windows NT team then placed their orders.

    "I'll have the turkey sandwich."

    "Turkey sandwich."

    "A turkey sandwich for me, please."

    Every single person ordered a turkey sandwich.

    After the server left, my colleague asked, "Why do you all order the turkey sandwich?"

    They explained, "We've been coming here for a long time, and we eventually figured out that, at least at this restaurant, the turkey sandwich takes the least time to prepare."

    What my colleague forgot to ask was, "Well, since I already ordered something else, I naturally screwed up your highly-optimized algorithm. So why didn't you order something else?"

    Note: I actually don't know who ordered first. I just made up that part of the story to make it funnier. (Note that I make up parts of other stories, too. I'm not a historian. I'm a storyteller.)

  • The Old New Thing

    Old trace logs in your dreams

    • 12 Comments

    I dreamed that I was archiving old data. Some came off an old CP/M floppy disc. Another was an IntelliTrace debug log from the 1980's. Back then, IntelliTrace recorded the history onto index cards, which were stored in trays like in a library card catalog. Made jumping back and forth really easy, but setting breakpoints was a pain.

  • The Old New Thing

    Obtaining the parsing name (and pidl) for a random shell object

    • 5 Comments

    The parsing name for a shell item is handy, because it lets you regenerate the item later. Actually, the pidl for the shell item is even better, because that is the official way of saving and restoring objects. It's the pidl that gets saved in a shortcut, and since shortcuts can be copied around from machine to machine, pidls must be transportable and forward compatible. (A shortcut file created on Windows XP needs to keep working on all future versions of Windows.)

    Here's a handy little tool for grabbing the parsing name and pidl for a random shell object. Start with our scratch program, and add in the Simple­Drop­Target class, with the following tweaks:

    public:
     SimpleDropTarget() : m_cRef(1) { /* g_ppr->AddRef(); */ }
     ~SimpleDropTarget() { g_ppr->Release(); }
    
    ...
     // *** IDropTarget ***
     STDMETHODIMP DragEnter(IDataObject *pdto,
        DWORD grfKeyState, POINTL ptl, DWORD *pdwEffect)
     {
      *pdwEffect &= DROPEFFECT_LINK;
      return S_OK;
     }
    
     STDMETHODIMP DragOver(DWORD grfKeyState,
       POINTL ptl, DWORD *pdwEffect)
     {
      *pdwEffect &= DROPEFFECT_LINK;
      return S_OK;
     }
    ...
    };
    

    We are not a COM local server, so we won't worry about managing our process reference. And we will accept anything that has a pidl, so we say that we will accept objects via linking. (The original code accepted by copying, which would have made us reject non-copyable objects.)

    Now we can hook these up to our scratch program.

    BOOL
    OnCreate(HWND hwnd, LPCREATESTRUCT lpcs)
    {
      g_hwndChild = CreateWindow(
          TEXT("edit"), nullptr, ES_MULTILINE |
          WS_CHILD | WS_VISIBLE | WS_TABSTOP,
          0, 0, 0,0, hwnd, (HMENU)1, g_hinst, 0);
      SimpleDropTarget *psdt = new(std::nothrow) SimpleDropTarget();
      if (psdt) {
        RegisterDragDrop(hwnd, psdt);
        psdt->Release();
      }
      return TRUE;
    }
    
    void
    OnDestroy(HWND hwnd)
    {
      RevokeDragDrop(hwnd);
      PostQuitMessage(0);
    }
    
    ...
        // Change CoInitialize and CoUninitialize to Ole
        if (SUCCEEDED(OleInitialize(NULL))) {
    ...
            OleUninitialize();
    

    Finally, we need to say what to do when the drop occurs.

    void AppendText(LPCWSTR psz)
    {
      SendMessageW(g_hwndChild, EM_REPLACESEL, 0, (LPARAM)psz);
    }
    
    void OpenFilesFromDataObject(IDataObject *pdto)
    {
      CComPtr<IShellItemArray> spsia;
      if (SUCCEEDED(SHCreateShellItemArrayFromDataObject(
                                      pdto, IID_PPV_ARGS(&spsia)))) {
        CComPtr<IEnumShellItems> spenum;
        spsia->EnumItems(&spenum);
        if (spenum) {
          for (CComPtr<IShellItem> spsi;
               spenum->Next(1, &spsi, nullptr) == S_OK;
               spsi.Release()) {
            CComHeapPtr<wchar_t> spszName;
            if (SUCCEEDED(spsi->GetDisplayName(
                         SIGDN_DESKTOPABSOLUTEPARSING, &spszName))) {
              AppendText(spszName);
              AppendText(L"\r\n");
            }
            CComHeapPtr<ITEMIDLIST_ABSOLUTE> spidl;
            if (SUCCEEDED(CComQIPtr<IPersistIDList>(spsi)->
                                                GetIDList(&spidl))) {
              UINT cb = ILGetSize(spidl);
              BYTE *pb = reinterpret_cast<BYTE *>
                              (static_cast<PIDLIST_ABSOLUTE>(spidl));
              for (UINT i = 0; i < cb; i++) {
                WCHAR szHex[4];
                StringCchPrintf(szHex, ARRAYSIZE(szHex),
                                L"%02X ", pb[i]);
                AppendText(szHex);
              }
              AppendText(L"\r\n");
            }
          }
        }
      }
    }
    

    When the drop occurs, we convert the data object into a shell item array, enumerate the items, and print the parsing name for the item as well as a hex dump of the pidl associated with the item.

    I guess we need some header files.

    #include <shlobj.h>
    #include <strsafe.h>
    #include <atlbase.h>
    #include <atlalloc.h>
    

    Run this program and drop the Recycle Bin onto it, say.

    ::{645FF040-5081-101B-9F08-00AA002F954E}
    14 00 1F 78 40 F0 5F 64 81 50 1B 10 9F 08 00 AA 00 2F 95 4E 00 00 
    

    This tells you two things. First, that if you want to generate the Recycle Bin from a parsing name, you can use that string that starts with two colons.

    var shell = new ActiveXObject("Shell.Application");
    var recycleBin = shell.Namespace(
          "::{645FF040-5081-101B-9F08-00AA002F954E}");
    var items = recycleBin.Items();
    for (var i = 0; i < items.Count; i++) {
     WScript.StdOut.WriteLine(items.Item(i));
    }
    

    Of course, there is a predefined enumeration for the Recycle Bin, so this was a bit of a waste. You could've just written

    var recycleBin = shell.Namespace(10);
    

    But this technique generalizes to other locations in the shell namespace that do not have a special shorthand value.

    The second thing the program tells you is that if you want to generate the Recycle Bin from a pidl, you can just use that chunk of bytes. Okay, that's not quite so interesting from a scripting point of view, but if you're manipulating pidls, this can be quite handy.

    We'll use this program a little bit in a few weeks, but at this point, it's just a "Little Program" for today.

  • The Old New Thing

    The annual sporting event involving a football that dare not speak its name and a digression into the sportsmanship of wasting time in nonproductive activity

    • 34 Comments

    I always wonder about people who are so protective of the name of their event that they don't even allow people to mention it by name. One of the most notorious examples is the organization which runs a major international gathering of athletes which takes place every four years (or every two years if you consider warm-weather sports and cold-weather sports). Another example is that you aren't allowed to refer to the championship game of the major professional American football league by its actual name without permission. You have to use some alternate phrasing like the big game. I propose that all media organizations which cover these types of events accept the event organizers' wishes and refuse to call it by its name. Or even simply refuse to cover it until they relax their rules.

    The alternate name the big game is itself confusing, since there are many things that go by that name. Even within the realm of American football, the phrase big game can also refer to the annual match between Stanford University and the University of California at Berkeley, whose final play in 1982 was particularly memorable.

    For those not familiar with the timekeeping rules of American football (which includes the Stanford band, it seems): American football is not a continuous-play game. The game is broken up into relatively short units known as plays. Typically, a whistle is blown to indicate that a play has ended. Between plays are much longer units of time called standing around doing nothing. If time runs out while a play is in progress, the play is allowed to run to completion, and the results of the play are valid. An analogue in the sport of basketball is the case where time runs out while the ball is in flight. The ball's trajectory is permitted to run to completion, and if it goes into the basket, the points count.

    The amount of standing around doing nothing is determined by the offensive team. Some teams employ a strategy known as not standing around doing nothing quite so much (technically known as the no-huddle offense), the goal of which is to deprive the defense of time to prepare for the next play.

    Some sports such as soccer (known to most of the world as the one true football accept no substitutes) have a penalty of the form not making an honest effort to advance the ball which is assessed if a team appears to be wasting time in nonproductive activity. One thing I find odd about American football is that wasting time in nonproductive activity is not only permitted by the rules, it is actively pursued as a tactic. You are allowed to waste up to 40 seconds of time (subject to other adjustments) before incurring a delay of game penalty, which more accurately should be named excessive delay of game. The result of this formalization of the concept of wasting time is that the amount of time which elapses between plays tends to be approximately 39.5 seconds. This is actually handy if you are watching a game that you previously recorded: When you hear the whistle which ends a play, you can hit the skip ahead 30 seconds button and skip over nearly all of the standing around doing nothing.

    The fact that maximum time wastage is completely normal and expected leads to the odd phenomenon of teams walking off the field before the game has ended: If the remaining time is less than 40 seconds and the team with possession of the ball is winning, then it assumed that the winning team will merely stand around doing nothing until time runs out. As a result, everybody just leaves immediately instead of standing around doing nothing waiting for the end-of-game whistle. (Indeed, if a team in this situation actually tries to play the game, it will probably be criticized for attempting to run up the score.)

  • The Old New Thing

    Psychic debugging: Why your IContextMenu::InvokeCommand doesn't get called even though you returned success from IContextMenu::QueryContextMenu

    • 20 Comments

    A customer was having trouble with their IContext­Menu implementation. They observed that their IContext­Menu::Query­Context­Menu method was being called, but when the user selected their menu item, IContext­Menu::Invoke­Command was not being called.

    Given what you know about shell context menus, you can already direct the investigation. I'll let you read up about it first, especially the part about composition, then we can see how much you've learned.

    Welcome back. (Okay, I know you didn't actually do the reading, but I'm welcoming you back anyway.)

    Your first theory as to why IContext­Menu::Invoke­Command is not being called is probably that they returned S_OK from IContext­Menu::Query­Context­Menu instead of MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_NULL, 1). That would explain the problem, because a return value of S_OK is equivalent to MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_NULL, 0), which means "I successfully added up to zero menu items starting at idCmd­First." When the user picks the menu item they added, the dispatcher will go looking for the corresponding composite menu component, and since they said that they used zero entries, they will naturally never be called, since they disavowed any responsibility for those items.

    "Nope, that's not it. We're returning MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_NULL, 1). Any other guesses?"

    Oh great, the customer is now playing Twenty Questions.

    "I'm going to pose a puzzle with almost no clues, and you get to propose solutions, and I'll say whether or not you're right."

    I don't know why customers play this game. Maybe they don't realize that asking for help via email is very different from asking for help face-to-face. In a face-to-face conversation, the answer to a question arrives within seconds, whereas in email it can take hours or days. This means that when the answer finally comes back, the person asking the question has to go back and read the conversation history thread to re-establish context.

    Or maybe they think they're going to be disclosing Top Secret Information to Microsoft if they share the code that they can't get to work. Trust me, we don't care about your Top Secret Algorithm for Beating the Stock Market With No Money Down. Go ahead and remove those from the code. We just want to see how you are interfacing with the shell. (And besides, you probably should be removing them anyway, since they are irrelevant and are not part of a minimal program that reproduces the problem.)

    What would your next guess be?

    "Perhaps you're adding your items with the wrong menu item ID." That would explain the problem, because returning MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_NULL, 1) means "I successfully added up to one menu item starting at idCmd­First." But if they didn't actually add it at idCmd­First, then when the user selects the item, it won't be in the range they claimed, and therefore the invoke won't get routed to them.

    "Your intuition is wrong again. Here's the code we're using."

    I can abide by the "Ha ha, you guessed wrong again!" because it at least prodded them into sharing some code. Not much code, mind you, but at least some.

    HRESULT SooperSeekrit::QueryContextMenu(
        HMENU hmenu,
        UINT indexMenu,
        UINT idCmdFirst,
        UINT idCmdLast,
        UINT uFlags)
    {
      UINT cItemsAdded = 0;
      if (!(uFlags & CMF_DEFAULTONLY) &&
          InsertMenuItem(hmenu,
                         indexMenu,
                         TRUE,
                         &globalMenuItemInfo)) {
        return MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_NULL, 1);
      }
      return MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_NULL, 0);
    }
    

    The next thing you should have noticed is that they never actually used the idCmd­First parameter. So how could they claim to be adding the items with the correct menu item ID if they ignore the variable that tells them what the correct menu item ID is?

    "Could you tell us more about the global­Menu­Item­Info variable? In particular, what value does it use for the wID member, and how do you make sure that it is equal to idCmd­First? It seems that you are missing some lines of code here:

    globalMenuItemInfo.fMask |= MIIM_ID;
    globalMenuItemInfo.wID = idCmdFirst;
    

    but perhaps there's something going on that we are missing."

    The customer cheerfully replied, "Oops, sorry, didn't notice that. Works great now, thanks!"

    I didn't bother to draw their attention to the fact that they lied when they responded to the question "Did you add the menu item with the correct ID?" with "Wrong again! BZZZT!"

    The point of today's story is that you, gentle reader, already know how to debug these types of issues. You just have to take what you know and apply it to the situation at hand. If you know how composite context menu dispatch works, then you can come up with failure modes in which the dispatcher fails to match up the menu item with the component.

    Exercise: The customer is still not out of the woods yet. What other bug remains in their IContext­Menu::Query­Context­Menu implementation?

Page 3 of 3 (29 items) 123