• The Old New Thing

    Why are the Compression and Encryption options check boxes instead of radio buttons?

    • 55 Comments

    Tanveer Badar asks why the file properties Advanced dialog shows two checkboxes (compression and encryption) even though NTFS supports only one or the other at a time. "Why not have two radio buttons instead of these silly check boxes?"

    Actually, if you want radio buttons, you'd need three, one to cover the "neither" case. Let's look at what those radio buttons would look like:

    Compress contents to save disk space
    Encrypt contents to secure data
    Neither compress nor encrypt

    This takes an implementation detail (that NTFS currently does not support simultaneous compression and encryption) and elevates it to the user interface, so that it can provide maximum confusion to the user. "What a strange way of exposing compression and encryption. If I want to turn on encryption, I should just check a box that says 'Encryption'."

    The current implementation (two check boxes) matches user expectations, but then says "Sorry, we can't do that" if the user picks a combination that is not currently supported. But who knows, maybe someday, NTFS will support simultaneously encryption and compression, and all that will happen is that the error message goes away. You don't have to redesign the property sheet (and invalidate all the training materials that had been produced in the meantime).

    Either way, it's a leaky abstraction that sucks, but at least the suckiness isn't shoved in the user's face.

  • The Old New Thing

    PLAY! A Video Game Symphony comes to Seattle

    • 18 Comments

    Competing with the Miss America pageant for your Saturday evening attention is the Seattle performance of PLAY! A Video Game Symphony, featuring music from a large number of video games. Presumably, the music will have something to do with the accompanying video being projected on large screens.

    The description of the concert omits any mention of whether this is a cosplay event. Just saying.

  • The Old New Thing

    Even the trees are falling for the media's lies

    • 2 Comments

    The White House is doing some renovating of its own (NYT, free registration required), in order to refurbish a section of the lawn that is used by television reporters so they can stand there with a nice picture of the White House behind them.

    But that's not why I found this story interesting.

    Go about halfway down the article: "The light and heat from the television lights, sometimes 14 hours a day, were confusing the trees", causing them to sprout leaves in winter and flower out of season.

    Even the trees are falling for the media's lies.

  • The Old New Thing

    Debug session: Why is an LPC server not responding?

    • 16 Comments

    A particular scenario was hanging, and the team responsible for the scenario debugged it to the point where they saw that their X component was waiting for their Y component, which was waiting for Explorer, so they asked for help chasing the hang into Explorer.

    The team was kind enough to have shared what they've learned so far:

    kd> !alpc /m 9c14d020
    
    Message 9c14d020
      MessageID             : 0x0274 (628)
      CallbackID            : 0xCCA5 (52389)
      SequenceNumber        : 0x00000016 (22)
      Type                  : LPC_REQUEST
      DataLength            : 0x0094 (148)
      TotalLength           : 0x00AC (172)
      Canceled              : No
      Release               : No
      ReplyWaitReply        : No
      Continuation          : Yes
      OwnerPort             : 82bb9db8 [ALPC_CLIENT_COMMUNICATION_PORT]
      WaitingThread         : 834553c0
      QueueType             : ALPC_MSGQUEUE_MAIN
      QueuePort             : 84646730 [ALPC_CONNECTION_PORT]
      QueuePortOwnerProcess : 846209c0 (explorer.exe)
      ServerThread          : 00000000 <-----------------------
      QuotaCharged          : No
      CancelQueuePort       : 00000000
      CancelSequencePort    : 00000000
      CancelSequenceNumber  : 0x00000000 (0)
      ClientContext         : 02a56b80
      ServerContext         : 00000000
      PortContext           : 0701ea20
      CancelPortContext     : 00000000
      SecurityData          : 962f89b8
      View                  : 00000000
    kd> !process 846209c0 0
    PROCESS 846209c0  SessionId: 1  Cid: 0804    Peb: 7fbac000  ParentCid: 0724
        DirBase: 3e546380  ObjectTable: 97195300  HandleCount: 1041.
        Image: explorer.exe
    

    Yikes, there is no thread signed up to service the request.

    I don't know much about ALPC, but I can fumble around. Fortunately, this is debugging and not rocket surgery, so you still get full points if you stumble across the answer by guessing.

    I decided to start guessing by looking at what the !alpc command can tell me.

    kd> !alpc -?
    
      !alpc /m MessageAddress
        Dumps the message at the specified address.
    
      !alpc /p PortAddress
        Dumps the port at the specified address.
    

    Well, I already saw what the result was for dumping the message, so I may as well dump the port.

    kd> !alpc /p 84646730
    
    ...
      8 thread(s) are registered with port IO completion object:
        THREAD 84658d40  Cid 0804.0888  Teb: 7fa7e000 Win32Thread: 8214a748 WAIT
        THREAD 8466a040  Cid 0804.08c4  Teb: 7fa74000 Win32Thread: 8214c800 WAIT
        THREAD 84659a00  Cid 0804.08ec  Teb: 7fa72000 Win32Thread: 82158d08 WAIT
        THREAD 8466c8c0  Cid 0804.08f0  Teb: 7fa6e000 Win32Thread: 82160420 WAIT
        THREAD 84671040  Cid 0804.0910  Teb: 7fa68000 Win32Thread: 8217c4e8 WAIT
        THREAD 8460d180  Cid 0804.099c  Teb: 7fa5e000 Win32Thread: 820bad08 WAIT
        THREAD 834278c0  Cid 0804.0c80  Teb: 7fa6b000 Win32Thread: 820b9620 WAIT
        THREAD 8345ad40  Cid 0804.0da0  Teb: 7fba9000 Win32Thread: 821c6d08 WAIT
    ...
    

    So it looks like there are eight threads signed up to process events on this port. (Is that what this means? I don't know, but I'm going to assume that it does, because this is debugging. Debugging is an exercise in optimism.) Let's see what they're doing.

    kd> .thread 84658d40;k
    Implicit thread is now 84658d40
      *** Stack trace for last set context - .thread/.cxr resets it
    ChildEBP RetAddr
    940ef394 80f1505f nt!KiSwapContext+0x19
    940ef3d0 80f184e0 nt!KiSwapThread+0x34b
    940ef3fc 80f163fc nt!KiCommitThreadWait+0x26f
    940ef46c 80f4d2df nt!KeWaitForSingleObject+0x459
    940ef4b4 80e20838 nt!KiSchedulerApc+0x298
    940ef4c8 00000000 hal!KfLowerIrql+0x2c
    
    [the others look the same]
    

    Well, I don't know what they're doing, but it looks like they're waiting for something. But one of the threads looks different:

    kd> .thread 84671040;k
    Implicit thread is now 84671040
      *** Stack trace for last set context - .thread/.cxr resets it
    ChildEBP RetAddr
    9415f864 80f1505f nt!KiSwapContext+0x19
    9415f8a0 80f184e0 nt!KiSwapThread+0x34b
    9415f8cc 80eb3d6e nt!KiCommitThreadWait+0x26f
    9415f934 810c0527 nt!KeWaitForMultipleObjects+0x4e3
    9415fbe4 810c0703 nt!ObWaitForMultipleObjects+0x2fd
    9415fd38 80ef113c nt!NtWaitForMultipleObjects+0xca
    9415fd38 77945e04 nt!KiFastCallEntry+0x12c
    07e2f1c4 779437f6 ntdll!KiFastSystemCallRet
    07e2f1c8 7515c136 ntdll!NtWaitForMultipleObjects+0xa
    07e2f34c 77752658 KERNELBASE!WaitForMultipleObjectsEx+0xee
    07e2f368 777fbe60 KERNEL32!WaitForMultipleObjects+0x19
    07e2f3d4 777fc5de KERNEL32!WerpReportFaultInternal+0x1a3
    07e2f3e8 777df654 KERNEL32!WerpReportFault+0x6d
    07e2f3f4 751e517c KERNEL32!BasepReportFault+0x19
    07e2f490 77a0f95a KERNELBASE!UnhandledExceptionFilter+0x1e0
    07e2f4a0 77a0fd4d ntdll!TppExceptionFilter+0x1b
    07e2f4b4 77a1c66b ntdll!TppWorkerpInnerExceptionFilter+0x13
    07e2fb34 77753278 ntdll!TppWorkerThread+0xa6092
    07e2fb40 779761a6 KERNEL32!BaseThreadInitThunk+0xe
    07e2fb80 77976152 ntdll!__RtlUserThreadStart+0x4a
    07e2fb90 00000000 ntdll!_RtlUserThreadStart+0x1c
    

    Ah, well that explains why Explorer isn't responding: It crashed on an unhandled exception! Windows Error Reporting is busy trying to generate a report.

    Now to see what the crash was. I don't know for sure, but I'm pretty confident that one of the parameters to Basep­Report­Fault is an EXCEPTION_POINTERS. Why am I confident of that? Because it would be hard to report the fault without it!

    kd> dd 07e2f3f4 l4
    07e2f3f4  07e2f490 77a0f95a 07e2f4e8 00000001
              ChildEBP RetAddr  Param1
    
    kd> dd 07e2f4e8 l2
    07e2f4e8  07e2f620 07e2f63c 
                     ^ ^
       ExceptionRecord ContextRecord
    kd> .cxr 0x07e2f63c
    eax=00000000 ebx=0451e2f8 ecx=e2af034f edx=77945e00 esi=00000000 edi=0451e2e0
    eip=1df7fc6a esp=07e2f920 ebp=07e2f938 iopl=0         nv up ei pl zr na pe nc
    cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010246
    contoso!ContosoPower::Disconnect+0xdd:
    001b:1df7fc6a 8b08  mov ecx,dword ptr [eax] ds:0023:00000000=????????
    

    Aha, Explorer crashed due to a null pointer crash in the Contoso­Power::Disconnect function.

    Passing the buck onward to Contoso, the report back was that this was a known issue, and a hotfix was available.

  • The Old New Thing

    I got an array with plenty of nuthin'

    • 24 Comments

    A customer reported a memory leak in the function PropVariantClear:

    We found the following memory leak in the function PropVariantClear. Please fix it immediately because it causes our program to run out of memory.

    If the PROPVARIANT's type is VT_ARRAY, then the corresponding SAFEARRAY is leaked and not cleaned up.

    SAFEARRAY* psa = SafeArrayCreateVector(VT_UNKNOWN, 0, 1);
    PROPVARIANT v;
    v.vt = VT_ARRAY;
    v.parray = psa;
    PropVariantClear(&v);
    
    // The psa is leaked
    

    Right now, we are temporarily working around this in our program by inserting code before all calls to PropVariantClear to free the SAFEARRAY, but this is clearly an unsatisfactory solution because it will merely result in double-free bugs once you fix the bug. Please give this defect your highest priority as it is holding up deployment of our system.

    The VT_ARRAY value is not a variant type in and of itself; it is a type modifier. There are other type modifiers, such as VT_VECTOR and VT_BYREF. The thing about modifiers is that they need to modify something.

    The line v.vt = VT_ARRAY is incorrect. You have to say what you have a safe array of. In this case, you want v.vt = VT_ARRAY | VT_UNKNOWN. Once you change that, you'll find the memory leak is fixed.

    The customer didn't believe this explanation.

    I find this doubtful for several reasons.

    1. While this would explain why the IUnknowns in the SAFEARRAY are not released, it doesn't explain why the SAFEARRAY itself is leaked.
    2. The SAFEARRAY already contains this information, so it should already know that destroying it entails releasing the IUnknown pointers.
    3. If I manually call SafeArrayDestroy, then the IUnknowns are correctly released, confirming point 2.
    4. The function SafeArrayDestroy is never called; that is the root cause of the problem.

    The customer's mental model of PropVariantDestroy appeared to be that it should go something like this:

    if (pvt->vt & VT_ARRAY) {
     switch (pvt->vt & VT_TYPEMASK) {
     ...
     case VT_UNKNOWN:
      ... release the IUnknowns in the SAFEARRAY...
      break;
     ...
     }
     InternalFree(pvt->psa->pvData);
     InternalFree(pvt->psa);
     return S_OK;
    }
    

    In fact what's really going on is that the value of VT_ARRAY is interpreted as VT_ARRAY | VT_EMPTY, because (1) VT_ARRAY is a modifier, so it has to modify something, and (2) the numeric value of zero happens to be equal to VT_EMPTY. In other words, you told OLE automation that your PROPVARIANT holds a SAFEARRAY filled with VT_EMPTY.

    It also happens that a SAFEARRAY of VT_EMPTY is illegal. Only certain types can be placed in a SAFEARRAY, and VT_EMPTY is not one of them.

    The call to PropVariantClear was returning the error DISP_E_BADVARTYPE. It was performing parameter validation and rejecting the property variant as invalid, because you can't have an array of nothing. The customer's response to this explanation was very terse.

    Tx. Interesting.
  • The Old New Thing

    Yet another experiment in motivating people to find and fix bugs

    • 11 Comments

    Everybody has probably heard about some project where management decided to motivate testers and programmers by rewarding testers for finding bugs and programmers for fixing them. In the absence of high ethical standards, this can devolve into the situation known to Dilbert fans as I'm gonna write me a new minivan.

    I experimented with this idea once, over a decade ago. Well, not exactly this idea, but a variation of it: For each bug in my code which I fixed, I paid the tester who found it.

    The risk here would be that I would intentionally resolve bugs as INVALID (or WONTFIX or anything other than FIXED) in order to avoid paying the penalty for fixing them, but I like to think that I made my decisions on their technical merits rather than based on my pocketbook. Since the tester saw the bug resolution, there was a degree of oversight: If I used bogus bug resolutions to avoid the bug fixing penalty, the tester would just reopen the bugs. The amount of the reward varied based on the quality of the bug, so that encouraged testers to focus on finding good bugs instead of weenie ones.

    I think I paid out a few dozen bug rewards. (It was a small project.)

  • The Old New Thing

    Why was HDS_FILTERBAR added to the common controls if nobody uses it?

    • 19 Comments

    Mike Dunn was curious about the intended purpose of HDS_FILTERBAR.

    The HDS_FILTERBAR style adds a row below the header control consisting of an edit control and a funnel icon. The funnel icon presumably represents a coffee filter, because after all, everybody in the world drinks coffee as much as people in Seattle. (Developers think they're so clever.)

    Mike points out that new features of the common controls were nearly always used by whatever version of Windows or Internet Explorer shipped that new version. The HDS_FILTERBAR style is a notable exception. What happened?

    I believe the HDS_FILTERBAR feature was originally intended for use by Active Directory; my guess is that dialogs like Find Computer would have taken advantage of it. For whatever reason, that feature was cut from Active Directory, which is why you didn't see anybody using it. However, the feature was cut after the code for the feature was already written and checked into the common controls under the style HDS_FILTERBAR.

    The Active Directory team either forgot to tell the Common Controls team, "Hey, you know that feature we asked you to write for us? Yeah, we don't need it after all," or they did, and the Common Controls team said, "Well, we already wrote it, and we don't want to take the risk that removing it won't introduce a bug, so we'll just leave it in. Maybe somebody else can find a use for it."

    The result was a feature in the header control that nobody used. And since nobody used it, I wouldn't be surprised if it's a little buggy. (We already know that it's more than little ugly.)

  • The Old New Thing

    Can CoCreateGuid ever return GUID_NULL?

    • 26 Comments

    A customer asked whether the Co­Create­Guid function can ever return GUID_NULL. Their code uses GUID_NULL for special purposes, and it would be bad if that was ever returned as the GUID for an object. "Can we assume that Co­Create­Guid never returns GUID_NULL? Or should we test the return value against GUID_NULL, and if it is equal, then call Co­Create­Guid and try again?"

    Some people started running Co­Create­Guid a bunch of times and observing that it was spitting out type 4 GUIDs, which will always have a 4 in the version field. Then other people started wondering whether the use of Algorithm 4 was contractual (it isn't). Then still other people went back to read the RFCs which cover UUIDs to see whether those documents provided any guidance.

    And then I had to step in and stop the madness.

    It is very easy to show that any UUID generator which generates GUID_NULL has failed to meet the requirement that the generated UUID be unique in space and time: If it's equal to GUID_NULL, then it isn't unique!

    The uniqueness requirement is that the generated GUID be different from any other valid GUID. And if it generated GUID_NULL, then it wouldn't be different from GUID_NULL! (And GUID_NULL is a valid GUID, specifically identified in RFC4122 section 4.1.7.)

    If you're so worried about Co­Create­Guid generating a duplicate GUID_NULL, why aren't you worried about Co­Create­Guid generating a duplicate IID_IUnknown or GUID_DEV­CLASS_1394 or any of the other GUIDs that have already been generated in the past?

    In other words, no valid implementation of Co­Create­Guid can generate GUID_NULL because the specification for the function says that it is not allowed to generate any GUID that has been seen before.

    One of my colleagues cheekily remarked, "And even if it did generate GUID_NULL for some reason, uniqueness would require that it do so only once! (So you should try to force this bug to occur in test, and then you can be confident that it will never occur in production.)"

  • The Old New Thing

    Private classes, superclassing, and global subclassing

    • 9 Comments

    In the suggestion box, A. Skrobov asks why it's impossible to superclass WC_DIALOG, but the example that follows is not actually superclassing.

    When I register my own class under this atom, and leave NULL in WNDCLASS.hInstance, Windows fills it in for me. Then I have two distinct classes registered: (0,WC_DIALOG) and (hMyInstance,WC_DIALOG), and DialogBox functions all use the first one.

    This question is a bit confused, since it says that the goal is to superclass the dialog class, but registering WC_DIALOG is not superclassing.

    First, I'll refer everyone to this MSDN article which describes the various ways of manipulating a window class: Subclassing, superclassing, and global subclassing.

    To superclass the dialog class, you retrieve information about the class by calling GetClassInfo and then register a new class based on the original class. But you don't need to go to all that effort to superclass the dialog class, because you already know what you need to know: The number of extra bytes is DLGWINDOWEXTRA, and the dialog procedure is DefDlgProc. You can just register your superclass directly, as we saw last time.

    Superclassing is done by registering your custom class under a different name, and using that class name if you want to obtain the new behavior. On the other hand, the question about talks about registering a class under the same name as the original (namely, WC_DIALOG). This isn't subclassing, nor is it superclassing, nor is it even global subclassing.

    Before continuing the discussion, I'll first address the issue of leaving NULL in WNDCLASS.hInstance: The value NULL for the instance handle is not legal when registering a class. Each class is associated with a module instance, and NULL is not a module instance. The window manager autocorrects this mistake by registering the class under the module corresponding to the executable. This is the same special-case behavior you get if you call GetModuleHandle(NULL), so it's not something completely out of the blue. It looks like A. Skrobov is being confused by the window manager's attempt to do what you mean. So much for being helpful.

    Okay, back to the original problem. Recall that the HINSTANCE member of the WNDCLASS structure is used to specify the class namespace. If you register a class against the handle of the current executable, then in order to create a window with that class, you need to create it with that same instance handle.

    Now we can put all the pieces together: Registering the class with WNDCLASS.hInstance = NULL is autocorrected to registering it with WNDCLASS.hInstance = GetModuleHandle(NULL), which places the class in the window class namespace of the current module. This is a separate class from the system dialog class, which is registered against GetModuleHandle(TEXT("USER32")). The two are registered against different modules, so they live independent lives. They just happen to have the same name.

    As we learned a few years ago, the instance handle you pass to the CreateWindow (or related) function is used to look up the window class, and as we also learned, the HINSTANCE you pass to the DialogBox (or related) function is used to look up the template as well as to create the frame window. The class name comes from the template, and if you didn't specify an explicit class in your template, then the dialog manager will use WC_DIALOG.

    You now have all the pieces necessary to understand what is going on. When you register the class against your executable's instance, you need to use that same instance when creating the dialog box so that your private class is found instead of the global one.

    To show how this all fits together, I've written a little program which registers a private class which happens to have the name WC_DIALOG and then uses it to create a dialog box.

    // scratch.rc
    #include <windows.h>
    
    // A pointless dialog box, for illustration only
    1 DIALOG 0,0,150,50
    STYLE DS_MODALFRAME | DS_SHELLFONT | WS_POPUP | WS_VISIBLE |
        WS_CAPTION | WS_SYSMENU
    CAPTION "Pointless"
    FONT 8, "MS Shell Dlg"
    BEGIN
        DEFPUSHBUTTON "Cancel",IDCANCEL,50,18,50,14
    END
    
    // scratch.cpp
    #include <windows.h>
    
    LRESULT CALLBACK
    SuperDlgProc(HWND hwnd, UINT uiMsg, WPARAM wParam, LPARAM lParam)
    {
      switch (uiMsg) {
      case WM_ERASEBKGND:
        return DefWindowProc(hwnd, uiMsg, wParam, lParam);
      }
      return DefDlgProc(hwnd, uiMsg, wParam, lParam);
    }
    
    INT_PTR CALLBACK
    DlgProc(HWND hwnd, UINT wm, WPARAM wParam, LPARAM lParam)
    {
      switch (wm) {
      case WM_INITDIALOG: return TRUE;
      case WM_CLOSE: EndDialog(hwnd, 0); return TRUE;
      }
      return FALSE;
    }
    
    int CALLBACK
    WinMain(HINSTANCE hinst, HINSTANCE hinstPrev,
            LPSTR pszCmdLine, int nShowCmd)
    {
      WNDCLASS wc;
      wc.style = 0;
      wc.lpfnWndProc = SuperDlgProc;
      wc.cbClsExtra = 0;
      wc.cbWndExtra = DLGWINDOWEXTRA;
      wc.hInstance = hinst;
      wc.hIcon = NULL;
      wc.hCursor = LoadCursor(NULL, IDC_ARROW);
      wc.hbrBackground = (HBRUSH)(COLOR_INFOBK + 1);
      wc.lpszMenuName = NULL;
      wc.lpszClassName = WC_DIALOG;
    
      if (RegisterClass(&wc))
        DialogBox(hinst, MAKEINTRESOURCE(1), NULL, DlgProc);
    
      return 0;
    }
    

    The dialog template is itself entirely unremarkable; it looks like any old dialog template.

    Our superclass takes the regular dialog box class and gives it a custom background color, namely COLOR_INFOBK.

    The program registers this private version of WC_DIALOG and creates a dialog box based on it. Since we passed the same HINSTANCE in the WNDCLASS.hInstance as we did to DialogBox, the lookup of the WC_DIALOG class will find our private version and use it instead of the global version.

  • The Old New Thing

    That doesn't sound like South Frisian to me

    • 4 Comments

    I dreamed that I was back in college taking a course in South Frisian, but I suspected something was up because the words didn't sound Germanic at all, and we were taught the words to a Christmas carol as Nom Yom Hear What I Hear?

    Also, because the course was taught by known prevaricator/exaggerator Robert Irvine.

Page 368 of 449 (4,481 items) «366367368369370»