• The Old New Thing

    On partially-constructed objects, additional remarks, not as interesting

    • 14 Comments

    Don't worry. Our long national nightmare of CLR week is almost over.

    I had originally planned to write an article about partially-constructed objects, but in the time since I queued up the topic (back in November 2005) to the time I got around to writing it up, I found that Joe Duffy had already written it for me!

    On partially-constructed objects

    Read it.

    Okay, here are some follow-up remarks.

    One place where people get caught out by partially-constructed objects is when they try to maintain a cache of objects (perhaps with a little bit of Weak­Reference action) and stash the objects into the cache before they are fully constructed:

    class SomeClass {
     public SomeClass(...) {
      cache.Add(this);
      AdditionalConstructionWork();
     }
    }
    

    If the Additional­Construction­Work takes an exception, then you end up with a partially-constructed object in your cache. (Mind you, you had one all along, but now it's a persistent condition as opposed to a transient one.)

    You might think to fix the problem by reordering the operations:

    class SomeClass {
     public SomeClass(...) {
      AdditionalConstructionWork();
      // add to cache only after construction ran to completion
      cache.Add(this);
     }
    }
    

    But that still doesn't work once you have derived classes:

    class Derived : SomeClass {
     public Derived(...) : base(...) {
      AdditionalConstruction(); // oops, what if this throws?
     }
    }
    

    The base constructor runs first, it successfully constructs the base object, and then puts it in the cache. And then the derived constructor runs and encounters an exception. You're back in the same boat with a partially-constructed object in the cache.

    You want to wait until the object is fully constructed because you add it to your cache.

    class SomeClass {
     static public SomeClass Create(...) {
      SomeClass c = new SomeClass(...);
      Register(c);
      return c;
     }
     protected static void Register(SomeClass c) { cache.Add(c); }
     protected SomeClass(...) { ... }
    }
    
    class Derived : SomeClass {
     static public Derived Create(...) {
      Derived d = new Derived(...);
      Register(d);
      return d;
     }
     public Derived(...) : base(...) { ... }
    }
    
  • The Old New Thing

    Using the MNS_DRAGDROP style: Dragging out

    • 8 Comments

    Windows 2000 introduced the MNS_DRAG­DROP menu style, which permits drag/drop operations in a menu. Nobody uses this style, probably because it's totally undiscoverable by the end-user. But I'll write a sample program anyway.

    Mind you, I knew nothing about the MNS_DRAG­DROP menu style until I started writing this entry. But I simply read the documentation, which says that if you set this style, you will receive WM_MENU­DRAG and WM_MENU­GET­OBJECT messages. The WM_MENU­DRAG message is sent when the user drags a menu item, so let's go with that first. The documentation says that you get information about the item that was dragged, and then you return a code that specifies whether you want the menu to remain up or whether you want it torn down.

    Simple enough. Let's do it.

    Start with the scratch program, add the function Get­UI­Object­Of­File and the class CDrop­Source, and change the calls to Co­Initialize and Co­Uninitialize into Ole­Initialize and Ole­Uninitialize, respectively. Next, define the menu we're going to play with:

    // resource header file
    #define IDM_MAIN 1
    #define IDC_CLOCK 100
    
    // resource file
    IDM_MAIN MENU PRELOAD
    BEGIN
        POPUP "&Test"
        BEGIN
            MENUITEM "&Clock", IDC_CLOCK
        END
    END
    

    Now we can add some new code to our scratch program. First, we add a menu to our window and enable drag/drop on it:

    BOOL
    OnCreate(HWND hwnd, LPCREATESTRUCT lpcs)
    {
     MENUINFO mi = { sizeof(mi), MIM_STYLE, MNS_DRAGDROP };
     return SetMenuInfo(GetMenu(hwnd), &mi);
    }
    
    // InitApp
     // wc.lpszMenuName = NULL;
     wc.lpszMenuName = MAKEINTRESOURCE(IDM_MAIN);
    

    For both dragging and dropping, we need a way to obtain the COM object associated with a menu item, so I'll put them in this common helper function:

    HRESULT GetMenuObject(HWND hwnd, HMENU hmenu, UINT uPos,
                          REFIID riid, void **ppvOut)
    {
     HRESULT hr = E_NOTIMPL;
     *ppvOut = NULL;
     if (hmenu == GetSubMenu(GetMenu(hwnd), 0)) {
      switch (GetMenuItemID(hmenu, uPos)) {
      case IDC_CLOCK:
       hr = GetUIObjectOfFile(hwnd, L"C:\\Windows\\clock.avi",
                                                 riid, ppvOut);
       break;
      }
     }
     return hr;
    }
    

    If the menu is our "Test" popup menu, then we know how to map the menu items to COM objects. For now, we have only one item, namely Clock, which corresponds to the C:\Windows\clock.avi¹ file.

    Now we can hook up a handler to the WM_MENU­DRAG message:

    #define HANDLE_WM_MENUDRAG(hwnd, wParam, lParam, fn) \
     (fn)((hwnd), (UINT)(wParam), (HMENU)(lParam))
    
    LRESULT OnMenuDrag(HWND hwnd, UINT uPos, HMENU hmenu)
    {
     LRESULT lres = MND_CONTINUE;
     IDataObject *pdto;
     if (SUCCEEDED(GetMenuObject(hwnd, hmenu, uPos,
                                     IID_PPV_ARGS(&pdto)))) {
      IDropSource *pds = new(std::nothrow) CDropSource();
      if (pds) {
       DWORD dwEffect;
       if (DoDragDrop(pdto, pds, DROPEFFECT_COPY | DROPEFFECT_LINK,
                      &dwEffect) == DRAGDROP_S_DROP) {
        lres = MND_ENDMENU;
       }
       pds->Release();
      }
      pdto->Release();
     }
     return lres;
    }
    

    This function is where the magic happens, but it's really not all that magical. We get the data object for the menu item being dragged and tell OLE to do a drag/drop operation with it. Just to make things interesting, I'll say that the menu should be dismissed if the user dropped the object somewhere; otherwise, the menu remains on the screen.

    Finally, we hook up the message handler to our window procedure:

    HANDLE_MSG(hwnd, WM_MENUDRAG, OnMenuDrag);
    

    And there you have it. A program that calls up a menu with drag enabled. If you drag the item labeled Clock, then the drag/drop operation proceeds as if you were dragging the clock.avi file.

    Next time, we'll look at the drop half of drag and drop.

    Footnote

    ¹ I hard-coded the clock.avi file for old time's sake. Yes, I know the file is no longer included with Windows. That'll teach people to use hard-coded paths!

  • The Old New Thing

    Star Trek meets The A-Team

    • 8 Comments

    Rumors swirl that there's a movie version of The A-Team in the works. If they haven't decided on the casting yet, here's an option they may have overlooked: The cast of Star Trek.

    (I like how in the clips used in the fake trailer, they use one of an episode where Kirk is old. And in Hollywood, making a character older means graying their hair. They never gain weight when they age, unlike the actors themselves.)

  • The Old New Thing

    In order to serve you bett... wait, it really is better: Fuel surcharges

    • 16 Comments

    Normally, the phrase In order to serve you better means that you're about to get screwed.

    Imagine my surprise to discover that United has stopped imposing a fuel surcharge for flights between Canada and the United States due to the decrease in fuel prices. But wait, that's only for flights between Canada and the United States. Flights within the United States appear to have the surcharge in place as usual.

    Hey, I've got an idea. How about getting rid of this surcharge nonsense and just raise the price of the ticket? Naw, that's just crazy talk.

    I can see the Calvin & Hobbes cartoon already. "Lemonade, 5¢ a glass (plus 25¢ water surcharge, 50¢ sugar surcharge, 75¢ rent reclamation fee, $1.50 Moe appeasement fee, 25¢ capital depreciation fee...)"

  • The Old New Thing

    Beverage Gas Division of Central Welding Supply

    • 19 Comments

    The other day, I saw a van which was labeled Beverage Gas Division of Central Welding Supply.

    This odd juxtaposition was created by the acquisition of Compressed Gas Western by Central Welding Supply in 2009.

    I sure hope they don't get their tanks confused.

  • The Old New Thing

    Apparently some people consider this a vacation; I consider it insane

    • 11 Comments

    One of the little cards that came in the STP information packet is an advertisement postcard for Epic Cycling Climbs of France. For $3299 (+airfare) you receive the "privilege" of cycling up the Alpe d'Huez and other notorious mountains.

    I'm sorry. If I'm going to go up l'Alpe D'Huez you're going to have to pay me, not the other way around.

    (Then again, I paid to ride my bicycle from Seattle to Portland, so obviously I'm a little insane already. I've already taken the first steps in progressive insanity; maybe they were just showing me where I was headed if I didn't watch my step. Sort of like those anti-smoking ads.)

  • The Old New Thing

    Microspeak: Engagement

    • 23 Comments

    Meetings are so passé. You no longer have a meeting with a customer; you have an engagement:

    I have a customer engagement tomorrow and they have a question surrounding Feature X.

    Note that this use of the phrase customer engagement is different from the process known as customer engagement. The process is an ongoing interaction, a long-term activity to build customer loyalty.

    The author of the above sentence is not using it in the process sense (because you don't have "a" customer engagement; rather, a meeting is one component of the overall process of customer engagement). Nope, the author is just trying to sound cool.

  • The Old New Thing

    2011 Q1 link clearance: Microsoft blogger edition

    • 11 Comments

    It's that time again: Linking to other Microsoft bloggers.

  • The Old New Thing

    How do I wait until all processes in a job have exited?

    • 10 Comments

    A customer was having trouble with job objects, specifically, the customer found that a Wait­For­Single­Object on a job object was not completing even though all the processes in the job had exited.

    This is probably the most frustrating part of job objects: A job object does not become signaled when all processes have exited.

    The state of a job object is set to signaled when all of its processes are terminated because the specified end-of-job time limit has been exceeded. Use Wait­For­Single­Object or Wait­For­Single­Object­Ex to monitor the job object for this event.

    The job object becomes signaled only if the end-of-job time limit has been reached. If the processes exit without exceeding the time limit, then the job object remains unsignaled. This is a historical artifact of the original motivation for creating job objects, which was to manage batch style server applications which were short-lived and usually ran to completion. The original purpose of job objects was to keep those processes from getting into a runaway state and consuming excessive resources. Therefore, the interesting thing from a job object's point of view was whether the process being managed in the job had to be killed for exceeding its resource allocation.

    Of course, nowadays, most people use job objects just to wait for a process tree to exit, not for keeping a server batch process from going runaway. The original motivation for job objects has vanished into the mists of time.

    In order to wait for all processes in a job object to exit, you need to listen for job completion port notifications. Let's try it:

    #define UNICODE
    #define _UNICODE
    #define STRICT
    #include <windows.h>
    #include <stdio.h>
    #include <atlbase.h>
    #include <atlalloc.h>
    #include <shlwapi.h>
    
    int __cdecl wmain(int argc, PWSTR argv[])
    {
     CHandle Job(CreateJobObject(nullptr, nullptr));
     if (!Job) {
      wprintf(L"CreateJobObject, error %d\n", GetLastError());
      return 0;
     }
    
     CHandle IOPort(CreateIoCompletionPort(INVALID_HANDLE_VALUE,
                                           nullptr, 0, 1));
     if (!IOPort) {
      wprintf(L"CreateIoCompletionPort, error %d\n",
              GetLastError());
      return 0;
     }
    
     JOBOBJECT_ASSOCIATE_COMPLETION_PORT Port;
     Port.CompletionKey = Job;
     Port.CompletionPort = IOPort;
     if (!SetInformationJobObject(Job,
           JobObjectAssociateCompletionPortInformation,
           &Port, sizeof(Port))) {
      wprintf(L"SetInformation, error %d\n", GetLastError());
      return 0;
     }
    
     PROCESS_INFORMATION ProcessInformation;
     STARTUPINFO StartupInfo = { sizeof(StartupInfo) };
     PWSTR CommandLine = PathGetArgs(GetCommandLine());
    
     if (!CreateProcess(nullptr, CommandLine, nullptr, nullptr,
                        FALSE, CREATE_SUSPENDED, nullptr, nullptr,
                        &StartupInfo, &ProcessInformation)) {
      wprintf(L"CreateProcess, error %d\n", GetLastError());
      return 0;
     }
    
     if (!AssignProcessToJobObject(Job,
             ProcessInformation.hProcess)) {
      wprintf(L"Assign, error %d\n", GetLastError());
      return 0;
     }
    
     ResumeThread(ProcessInformation.hThread);
     CloseHandle(ProcessInformation.hThread);
     CloseHandle(ProcessInformation.hProcess);
    
     DWORD CompletionCode;
     ULONG_PTR CompletionKey;
     LPOVERLAPPED Overlapped;
    
     while (GetQueuedCompletionStatus(IOPort, &CompletionCode,
              &CompletionKey, &Overlapped, INFINITE) &&
              !((HANDLE)CompletionKey == Job &&
               CompletionCode == JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO)) {
      wprintf(L"Still waiting...\n");
     }
    
     wprintf(L"All done\n");
    
     return 0;
    }
    

    The first few steps are to create a job object, then associate it with a completion port. We set the completion key to be the job itself, just in case some other I/O gets queued to our port that we aren't expecting. (Not sure how that could happen, but we'll watch out for it.)

    Next, we launch the desired process into the job. It's important that we create it suspended so that we can put it into the job before it exits or does something else that would mess up our bookkeeping. After it is safely assigned to the job, we can resume the process's main thread, at which point we have no use for the thread and process handles.

    Finally, we go into a loop pulling events from the I/O completion port. If the event is not "this job has no more active processes", then we just keep waiting.

    Officially, the last parameter to Get­Queued­Completion­Status is lpNumber­Of­Bytes, but the job notifications are posted via Post­Queued­Completion­Status, and the parameters to Post­Queued­Completion­Status can mean anything you want. In particular, when the job object posts notifications, it puts the notification code in the "number of bytes" field.

    Run this program with, say, cmd on the command line. From the nested cmd prompt, type start notepad. Then type exit to exit the nested command prompt. Observe that our program is still waiting, because it's waiting for Notepad to exit. When you exit Notepad, our program finally prints "All done".

    Exercise: The statement "Not sure how that could happen" is a lie. Name a case where a spurious notification could arrive, and how the code can protect against it.

  • The Old New Thing

    Why is the debugger telling me I crashed because my DLL was unloaded, when I see it loaded right here happily executing code?

    • 8 Comments

    A customer was puzzled by what appeared to be contradictory information coming from the debugger.

    We have Windows Error Reporting failures that tell us that we are executing code in our DLL which has been unloaded. Here's a sample stack:

    Child-SP          RetAddr           Call Site
    00000037`7995e8b0 00007ffb`fe64b08e ntdll!RtlDispatchException+0x197
    00000037`7995ef80 000007f6`e5d5390c ntdll!KiUserExceptionDispatch+0x2e
    00000037`7995f5b8 00007ffb`fc977640 <Unloaded_contoso.dll>+0x3390c
    00000037`7995f5c0 00007ffb`fc978296 RPCRT4!NDRSRundownContextHandle+0x18
    00000037`7995f610 00007ffb`fc9780ed RPCRT4!DestroyContextHandlesForGuard+0xea
    00000037`7995f650 00007ffb`fc9b5ff4 RPCRT4!ASSOCIATION_HANDLE::~ASSOCIATION_HANDLE+0x39
    00000037`7995f680 00007ffb`fc9b5f7c RPCRT4!LRPC_SASSOCIATION::`scalar deleting destructor'+0x14
    00000037`7995f6b0 00007ffb`fc978b25 RPCRT4!LRPC_SCALL_BROKEN_FLOW::FreeObject+0x14
    00000037`7995f6e0 00007ffb`fc982e44 RPCRT4!LRPC_SASSOCIATION::MessageReceivedWithClosePending+0x6d
    00000037`7995f730 00007ffb`fc9825be RPCRT4!LRPC_ADDRESS::ProcessIO+0x794
    00000037`7995f870 00007ffb`fe5ead64 RPCRT4!LrpcIoComplete+0xae
    00000037`7995f910 00007ffb`fe5e928a ntdll!TppAlpcpExecuteCallback+0x204
    00000037`7995f980 00007ffb`fc350ce5 ntdll!TppWorkerThread+0x70a
    00000037`7995fd00 00007ffb`fe60f009 KERNEL32!BaseThreadInitThunk+0xd
    00000037`7995fd30 00000000`00000000 ntdll!RtlUserThreadStart+0x1d
    

    But if we ask the debugger what modules are loaded, our DLL is right there, loaded as happy as can be:

    0:000> lm
    start             end                 module name
    ...
    000007f6`e6000000 000007f6`e6050000   contoso    (deferred)
    ...
    

    In fact, we can view other threads in the process, and they are happily running code in our DLL. What's going on here?

    All the information you need to solve this problem is given right there in the problem report. You just have to put the pieces together.

    Let's take a closer look at that <Unloaded_contoso.dll>+0x3390c entry. The address that the symbol refers to is the return address from the previous frame: 000007f6`e5d5390c. Subtract 0x3390c from that, and you get 000007f6`e5d20000, which is the base address of the unloaded module.

    On the other hand, the lm command says that the currently-loaded copy of contoso.dll is loaded at 000007f6`e6000000. This is a different address.

    What happened here is that contoso.dll was loaded into memory at 000007f6`e5d20000, and then it ran for a while. The DLL was then unloaded from memory, and later loaded back into memory. When it returned, it was loaded at a different address 000007f6`e6000000. For some reason (improper cleanup when unloading the first copy, most likely), there was still a function pointer pointing into the old unloaded copy, and when NDRS­Rundown­Context­Handle tries to call into that function pointer, it calls into an unloaded DLL, and you crash.

    When faced with something that seems impossible, you need to look more closely for clues that suggest how your implicit assumptions may be incorrect. In this case, the assumption was that there was only one copy of contoso.dll.

Page 378 of 431 (4,310 items) «376377378379380»