• The Old New Thing

    Reading the word under the cursor via UI Automation

    • 12 Comments

    A while back, I showed how to use UI Automation to extract the text under the mouse cursor. Let's refine the program so it can extract the word under the cursor.

    The ability to extract subunits of text is provided by the Text­Pattern class. You can ask for the text around a specific point by asking Range­From­Point to create a zero-length range directly under the cursor, and then adjust the range to grab the text you want.

    Start with the first Little Program and make these changes:

    using System;
    using System.Windows;
    using System.Windows.Forms;
    using System.Windows.Automation;
    using System.Windows.Automation.Text;
    
    class Program
    {
     static Point MousePos {
      get { var pos = Control.MousePosition;
            return new Point(pos.X, pos.Y); }
     }
    
     public static void Main()
     {
      for (;;) {
       AutomationElement e = AutomationElement.FromPoint(MousePos);
       if (e != null) {
        object o;
        if (e.TryGetCurrentPattern(TextPattern.Pattern, out o)) {
         var textPattern = (TextPattern)o;
         var range = textPattern.RangeFromPoint(MousePos);
         range.ExpandToEnclosingUnit(TextUnit.Word);
         Console.WriteLine(range.GetText(-1));
        }
       }
       System.Threading.Thread.Sleep(1000);
      }
     }
    }
    

    We grab the element under the mouse and see if it supports Text­Pattern. If so, we ask the text pattern for a Text­Pattern­Range that corresponds to the position of the cursor. It's where the insertion point would go if you clicked there. Since the insertion point always goes between characters, the initial range has zero length. We expand it to the word enclosing the imaginary insertion point, then extract the text, then print it.

    Note that the Text pattern is typically supported only by editable text controls, so you will need to practice with Notepad or some other text editor.

    Just to show how annoying you can push things, let's secretly select the text under the cursor.

    using System;
    using System.Windows;
    using System.Windows.Forms;
    using System.Windows.Automation;
    using System.Windows.Automation.Text;
    
    class Program
    {
     static Point MousePos {
      get { var pos = Control.MousePosition;
            return new Point(pos.X, pos.Y); }
     }
    
     public static void Main()
     {
      for (;;) {
       AutomationElement e = AutomationElement.FromPoint(MousePos);
       if (e != null) {
        object o;
        if (e.TryGetCurrentPattern(TextPattern.Pattern, out o)) {
         var textPattern = (TextPattern)o;
         var range = textPattern.RangeFromPoint(MousePos);
         range.ExpandToEnclosingUnit(TextUnit.Word);
         // Console.WriteLine(range.GetText(-1));
         range.Select();
        }
       }
       System.Threading.Thread.Sleep(1000);
      }
     }
    }
    

    Once a second, the program autoselects the word under the cursor. This gets really annoying fast, but it's just a demonstration.

  • The Old New Thing

    What is this inconsistent heap state that MSDN warns me about during DLL_PROCESS_DETACH?

    • 23 Comments

    In the MSDN documentation for the Dll­Main entry point, MSDN notes:

    When handling DLL_PROCESS_DETACH, a DLL should free resources such as heap memory only if the DLL is being unloaded dynamically (the lpReserved¹ parameter is NULL). If the process is terminating (the lpvReserved parameter is non-NULL), all threads in the process except the current thread either have exited already or have been explicitly terminated by a call to the Exit­Process function, which might leave some process resources such as heaps in an inconsistent state. In this case, it is not safe for the DLL to clean up the resources. Instead, the DLL should allow the operating system to reclaim the memory.

    A customer wanted to know, "What is this inconsistent heap state that MSDN is talking about here?"

    The information is actually right there in the sentence. "... explicitly terminated by a call to the Exit­Process function, which might leave some process resources such as heaps in an inconsistent state."

    When you see text that says "X might lead to Y," then when you ask "What could lead to Y?" you might consider that it is X.

    Background reading: Quick overview of how processes exit on Windows XP, plus bonus electrification of critical sections and slim reader/writer locks.

    Okay, I'll give the customer the benefit of the doubt and assume that the question was more along the lines of "Why would termination by a call to the Exit­Process function lead to an inconsistent state?"

    Remember why Terminate­Thread is a horrible idea: It terminates the thread in the middle of whatever it is doing, not giving it a chance to restore consistency or otherwise provide for an orderly end to operations. The thread may have been in the middle of updating a data structure, which usually involves perturbing an invariant, then re-establishing it. If it was terminated before it could re-establish the invariant, you now have an inconsistent data structure.

    That's the sort of inconsistency the paragraph is talking about. If one of the threads terminated by Exit­Process was executing heap code at the moment it was terminated, then the heap may be inconsistent because the thread never got a chance to re-establish consistency. (The heap tries to detect that this has happened but all that does is transform one failure mode into another. The failure is still there.)

    Of course, the heap isn't the only resource that suffers from this problem. Any resource that is available to more than one thread is susceptible to this. It's just that the heap is a very popular shared resource, so it gets an extra mention in the documentation.

    ¹ Typo. Should be lpvReserved.

  • The Old New Thing

    Who is this rogue operative that filled my object with 0xDD, then sent me a message?

    • 21 Comments

    A failure occurred during stress testing, and the component team came to the conclusion that their component was actually the victim of memory corruption and they asked for help trying to see if there was anything still in memory that would give a clue who did the corrupting.

    /* static */ LRESULT CALLBACK CContoso::WndProc(
        HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
        CContoso *pThis = reinterpret_cast<CContoso *>
                               (GetWindowLongPtr(hwnd, GWLP_USERDATA));
        ...
        pThis->... // crash on first dereference of pThis
    

    According to the debugger, the value of pThis is a valid pointer to memory that is complete nonsense.

    0: kd> dv
               hwnd = 0xf0040162
               uMsg = 0x219
               ...
              pThis = 0x10938bf0
               ...
    0: kd> dt pThis
    Type CContoso*
       +0x000 __VFN_table : 0xdddddddd 
       +0x004 m_cRef           : 0n-572662307
       +0x008 m_hwnd           : 0xdddddddd HWND__
       ...
    

    The CContoso object was filled with the byte 0xDD. Who would do such a thing?

    There are a few clues so far, and if you're psychic, you may have picked up on their aura.

    But I had a suspicion what happened, so I dug straight into the code to check my theory.

    BOOL CContoso::StartStuffInBackground()
    {
     AddRef(); // DoBackgroundWork will release the reference
     BOOL fSuccess = QueueUserWorkItem(&CContoso::DoBackgroundWork, this, 0);
     if (!fSuccess) Release();
     return fSuccess;
    }
    
    /* static */ DWORD CALLBACK CContoso::DoBackgroundWork(void *lpParameter)
    {
     CContoso *pThis = static_cast<CContoso *>(lpParameter);
     pThis->DoThis();
     pThis->DoThat();
     pThis->Release();
     return 0;
    }
    

    So far, we have a standard pattern. An extra reference to the object is kept alive as long as the background thread is still running. This prevents the object from being destroyed prematurely.

    (Note that this object is not necessarily a COM object. It could be a plain object that happens to have chosen the names Add­Ref and Release for the methods that manipulate the reference count.)

    What people often forget to consider is that this means that the final release of the CContoso object can occur on the background thread. I mean, this is obvious in one sense, because they are adding the extra reference specifically to handle the case where we want to delay object destruction until the background thread completes. But what happens if that scenario actually comes to pass?

    CContoso::~CContoso()
    {
     if (m_hwnd != nullptr) DestroyWindow(m_hwnd);
     ...
    }
    

    As part of the destruction of the CContoso object, it destroys its window. But Destroy­Window must be called on the same thread which created the window: "A thread cannot use Destroy­Window to destroy a window created by a different thread."

    This means that if the final release of the CContoso object comes from the background thread, the destructor will run on the background thread, and the destructor will try to destroy the window, but the call will fail because it is on the wrong thread.

    The result is that the object is destroyed, but the window still hangs around, and the window has a (now dangling) pointer to the object that no longer exists.

    Since the window in question was a hidden helper window, the program managed to survive like this for quite some time: Since the program thought the window was destroyed, there was no code that tried to send it a message, and the normal system-generated messages were not anything the object cared about, so they all fell through to Def­Window­Proc and nobody got hurt. But eventually, some other stress test running on the machine happened coincidentally to broadcast the WM_SETTING­CHANGE message 0x0219, and when the object tried to check what settings changed, that's when it crashed. (That was one of the clues I hinted at above: The message that triggered the crash is 0x0219. This is a good number to memorize if you spend time studying stress failures because it is often the trigger for crashes like this where a window has been orphaned by its underlying object.)

    The root cause is that the object was treated as a free-threaded object even though it actually had thread affinity.

    One way to fix this is to isolate the parts with thread affinity so that they are used only on the UI thread. The one we identified is the destructor due to its use of Destroy­Window. So at a minimum, we could marshal destruction to the UI thread.

    LONG CContoso::Release()
    {
     LONG cRef = InterlockedDecrement(&this->m_cRef);
     if (cRef == 0)
     {
      if (m_hwnd == nullptr) {
        delete this;
      } else {
        PostMessage(m_hwnd, CWM_DESTROYTHIS, 0, 0);
      }
     }
     return cRef;
    }
    
    /* static */ LRESULT CALLBACK CContoso::WndProc(
        HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
        CContoso *pThis = reinterpret_cast<CContoso *>
                               (GetWindowLongPtr(hwnd, GWLP_USERDATA));
        ...
    
        case CWM_DESTROYTHIS:
         delete pThis;
         return 0;
        ...
    

    (The original code had better have been using an interlocked operation on Release because it was releasing from a background thread already.)

    If the final Release happens before we have a window, then we just destruct in-place, on the theory that if no window is created, then we are being destroyed due to failed initialization and are still on the original thread. Otherwise, we post a message to the window to ask it to destroy the object.

    Note that this design does have its own caveats:

    • Even if the final Release happens on the UI thread, we still post a message, even though we could have destroyed it inline.
    • Posting a message assumes that the message pump will continue to run after the object is released. If somebody releases the object and then immediately exits the thread, the posted message will never arrive and the object will be leaked.
    • Posting a message makes destruction asynchronous. There may be some assumptions that destruction is synchronous with the final release.

    As for the first problem, we could do a thread check and destruct in-place if we are on the UI thread. This would most likely solve the second problem because the exiting thread is not the one that will process the message. It will still be a problem if the background thread does something like

      Release();
      DoSomethingThatCausesTheUIThreadToExitImmediately();
    

    For the second problem, we could change the Post­Message to a Send­Message, but this creates its own problems because of the risk of deadlock. If the UI thread is blocked waiting for the background thread, and the background thread tries to send the UI thread a message, the two threads end up waiting for each other and nobody makes any progress. On the other hand, making the destruction synchronous would fix the third problem.

    Another approach is to push the affinity out one more step:

    /* static */ DWORD CALLBACK CContoso::DoBackgroundWork(void *lpParameter)
    {
     CContoso *pThis = static_cast<CContoso *>(lpParameter);
     pThis->DoThis();
     pThis->DoThat();
     pThis->AsyncRelease();
     return 0;
    }
    
    void CContoso::AsyncRelease()
    {
     PostMessage(m_hwnd, CWM_RELEASE, 0, 0);
    }
    
    /* static */ LRESULT CALLBACK CContoso::WndProc(
        HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
        CContoso *pThis = reinterpret_cast<CContoso *>
                               (GetWindowLongPtr(hwnd, GWLP_USERDATA));
        ...
        case CWM_RELEASE:
         pThis->Release();
         return 0;
    
        ...
    

    In this design, we make the asynchronicity explicit in the name of the function, and we require all background threads to use the asynchronous version. This design again assumes that the only reason the window wouldn't exist is that something went wrong during initialization before any background tasks were created.

    Unfortunately, this design also retains the original constraint that Release can be called only from the UI thread. That makes the object rather fragile, because it is not obvious that Release has such constraints. If you go this route, you probably should rename Release to Release­From­UI­Thread.

    If this object is a COM object, then another option is to use COM marshaling to marshal the IUnknown to the background thread and use IUnknown::Release to release the object. Since you used COM to marshal the object, it knows that Co­Uninitialize should wait for all outstanding references marshaled to other threads, thereby avoiding the "lost message" problem.

    Anyway, those are a few ideas for addressing this problem. None of them are particularly beautiful, though. Maybe you can come up with something better.

    (The component team fixed this problem by taking advantage of a detail in the usage pattern of the CContoso object: The client of the CContoso object is expected to call CContoso::Stop before destroying the object, and after calling CContoso::Stop, the only valid thing you can do with the object is destroy it. Furthermore, that call to CContoso::Stop must occur on the UI thread. Therefore, they moved the part of the cleanup code that must run on the UI thread into the Stop method. The object's background tasks already knew to abandon work once they detected that the object had been stopped.)

  • The Old New Thing

    Why was the replacement installer for recognized 16-bit installers itself a 32-bit program instead of a 64-bit program?

    • 34 Comments

    Even though 64-bit Windows does not support 16-bit applications, there is a special case for 16-bit installers for 32-bit applications. Windows detects this scenario and substitutes a 32-bit replacement installer which replicates the actions of the 16-bit installer. Commenter Karellen is horrified at the fact that the replacement installer is a 32-bit program. "You're writing a program that will run exclusively on 64-bit systems. Why not built it to run natively on the OS it's designed for? Why is this apparently not the "obvious" Right Thing(tm) to do? What am I missing?"

    Recall that a science project is a programming project that is technically impressive but ultimately impractical. For example it might be a project that nobody would actually use, or it attempts to add a Gee-Whiz feature that nobody is really clamoring for.

    But at least a science project is trying to solve a problem. This proposal doesn't even solve any problems! Indeed, this proposal creates problems. One argument in favor of doing it this way is that it is satisfies some obsessive-compulsive requirement that a 64-bit operating system have no 32-bit components beyond the 32-bit emulation environment itself.

    Because! Because you're running a 64-bit system, and running apps native to that system is just more elegant.

    Okay, it's not obsessive-compulsive behavior. It's some sort of aesthetic ideal, postulated for its own sake, devoid of practical considerations.

    Remember the problem space. We have a bunch of 32-bit applications that use a 16-bit installer. Our goal is to get those applications installed on 64-bit Windows. By making the replacement installer a 32-bit program, you get the emulator to do all the dirty work for you. Things like registry redirection, file system redirection, and 32-bit application compatibility.

    Suppose the original installer database says

    • Copy X.DLL file into the %Program­Files%\AppName directory.
    • Copy Y.DLL into the %windir%\System32 directory.
    • If the current version of C:\Program Files\Common Files\Adobe\Acrobat\ActiveX\AcroPDF.dll is 7.5 or higher, then set this registry key.

    If you write the replacement installer as a 32-bit program, then other parts of the 32-bit emulation engine do the work for you.

    • The environment manager knows that 64-bit processes get the environment variable Program­Files pointing to C:\Program Files, whereas 32-bit processes get Program­Files pointing to C:\Program Files (x86).
    • The file system redirector knows that if a 32-bit process asks for %windir%\System32, it should really get %windir%\SysWOW64.
    • The registry redirector knows that if a 32-bit process tries to access certain parts of the registry, they should be sent to the Wow­64­32­Node instead.

    If you had written the replacement installer as a 64-bit program, you would have to replicate all of these rules and make sure your copy of the rules exactly matched the rules used by the real environment manager, file system redirector, and registry redirector.

    Now you have to keep two engines in sync: the 32-bit emulation engine and the 64-bit replacement installer for 32-bit applications. This introduces fragility, because any behavior change in the 32-bit emulation engine must be accompanied by a corresponding change in the 64-bit replacement installer.

    Suppose the application compatibility folks add a rule that says, "If a 32-bit installer tries to read the version string from C:\Program Files\Common Files\Adobe\Acrobat\ActiveX\AcroPDF.dll, return the version string from C:\Program Files (x86)\Common Files\Adobe\Acrobat\ActiveX\AcroPDF.dll instead." And suppose that rule is not copied to the 64-bit replacement installer. Congratulations, your 64-bit replacement installer will incorrectly install any program that changes behavior based on the currently-installed version of AcroPDF.

    I don't know for sure, but I wouldn't be surprised if some of these installers support plug-ins, so that the application developer can run custom code during installation. It is possible for 16-bit applications to load 32-bit DLLs via a technique known as generic thunking, and the 16-bit stub installer would use a generic thunk to call into the 32-bit DLL to do whatever custom action was required. On the other hand, 64-bit applications cannot load 32-bit DLLs, so if the 64-bit replacement installer encountered a 32-bit DLL plug-in, it would have to run a 32-bit helper application to load the plug-in and call into it. So you didn't escape having a 32-bit component after all.

    And the original obsessive-compulsive reason for requiring the replacement installer to be 64-bit was flawed anyway. This is a replacement installer for a 32-bit application. Therefore, the replacement installer is part of the 32-bit emulation environment, so it is allowed to be written as a 32-bit component.

    Let's look at the other arguments given for why the replacement installer for a 32-bit application should be written as a 64-bit application.

    Because complexity is what will be our undoing in the end, and reducing it wherever we can is always a win.

    As we saw above, writing the replacement installer as a 64-bit application introduces complexity. Writing it as a 32-bit application reduces complexity. So this statement itself argues for writing the replacement installer as a 32-bit application.

    Because we can't rewrite everything from scratch at once, but we can create clean new code one small piece at a time, preventing an increase to our technical debt where we have the opportunity to do so at negligible incremental cost to just piling on more cruft.

    As noted above, the incremental cost is hardly negligible. Indeed, writing the replacement installer as a 64-bit application is not merely more complex, it creates an ongoing support obligation, because any time there is a change to the 32-bit emulation environment, that change needs to be replicated in the 64-bit replacement installer. This is a huge source of technical debt: Fragile coupling between two seemingly-unrelated components.

    And writing the replacement installer as a 32-bit application does not create a future obligation to port it to 64 bits when support for 32-bit applications is dropped in some future version of Windows. Because when support for 32-bit applications disappears (as it already has on Server Core), there will be no need to port the replacement installer to 64-bit because there's no point writing an installer for a program that cannot run!

    Writing the replacement installer as a 32-bit program was the right call.

  • The Old New Thing

    Staying cool is one of the skills of a corporate president

    • 12 Comments

    Some time ago, there was a mechanical problem with the heating/cooling system in our part of the building, and one of the senior managers in our group took the opportunity to tell a story of a one-on-one skip-level meeting he had with Steve Sinofsky.

    I'm sitting there in my office with Steve, and there was something wrong with the HVAC, because as the meeting progresses, it gets warmer and warmer, and eventually I'm sitting there sweating profusely, not exactly making the best impression on our group president. Steve, on the other hand, appears to be completely unaffected. It's sweltering in my office, but he's cool as a cucumber.

    It can't be more than five minutes after the meeting is over before a team of technicians swarms into my office to figure out why the heating system has gone berzerk.

    Steve must've whipped out his phone as soon as he left, called the Facilities desk, and said "Dude, there's something seriously wrong with the heating system over in room 1234. It's like an oven in there. You need to check it out." And since the request came from a corporate president, it got dispatched with high priority.

  • The Old New Thing

    Using thread pool cleanup groups to clean up many things at once

    • 10 Comments

    Today's Little Program demonstrates thread pool cleanup groups. When you associate a thread pool item with a cleanup group, you can perform bulk operations on the group. That can save you a lot of bookkeeping.

    Remember that Little Programs do little to no error checking.

    #include <windows.h>
    #include <stdio.h> // horrors! Mixing stdio and C++!
    
    VOID
    CALLBACK
    Callback(
        PTP_CALLBACK_INSTANCE Instance,
        PVOID                 /* Parameter */,
        PTP_TIMER             /* Timer */
        )
    {
        // Say what time the callback ran.
        printf("%p at %d\n", Instance, GetTickCount());
    }
    
    int
    __cdecl
    main(int, char**)
    {
        // Create an environment that we use for our timers.
        TP_CALLBACK_ENVIRON environ;
        InitializeThreadpoolEnvironment(&environ);
    
        // Create a thread pool cleanup group and associate it
        // with the environment.
        auto cleanupGroup = CreateThreadpoolCleanupGroup();
        SetThreadpoolCallbackCleanupGroup(&environ,
                                          cleanupGroup,
                                          nullptr);
    
        // Say what time we started
        printf("Start: %d\n", GetTickCount());
    
        // Ask for a one-second delay
        LARGE_INTEGER dueTime;
        dueTime.QuadPart = -10000LL * 1000; // one second
        FILETIME ftDue = { dueTime.LowPart, dueTime.HighPart };
    
        // Create ten timers to run after one second.
        for (int i = 0; i < 10; i++) {
            auto timer = CreateThreadpoolTimer(Callback,
                                               nullptr,
                                               &environ);
            SetThreadpoolTimer(timer, &ftDue, 0, 500);
        }
    
        // Wait a while - the timers will run.
        Sleep(1500);
    
        // Clean up the group.
        CloseThreadpoolCleanupGroupMembers(cleanupGroup,
                                           FALSE,
                                           nullptr);
    
        // Close the group.
        CloseThreadpoolCleanupGroup(cleanupGroup);
    }
    

    There is some trickiness in building the FILETIME structure to specify that we want to run after a one-second delay. First, the value is negative to indicate a relative timeout. Second, we cannot treat the FILETIME as an __int64, so we use a LARGE_INTEGER as an intermediary.

    When we create the ten timers, we associate them with the environment, which is in turn associated with the cleanup group. This puts all the timers into the cleanup group, which is a good thing, because we didn't save the timer handles!

    When it's time to clean up the timers, we use Close­Thread­pool­Cleanup­Group­Members, which does the work of closing each individual timer in the cleanup group. This saves us the trouble of having to remember all the timers ourselves and manually closing each one.

    For our next trick, comment out the Sleep(1500); and run the program again. This time, the timers don't run at all. That's because we closed them before they reached their due time. We let the cleanup group do the bookkeeping for us.

  • The Old New Thing

    Use GFlags to catch the silent killer (silent but deadly)

    • 22 Comments

    Suppose you have some process that is mysteriously dying and you can't figure out why. You think that some other process is doing a Terminate­Process but heck if you can figure out who that is.

    Starting in Windows 7 and Windows Server 2008 R2, the GFlags tool will let you catch these miscreants.

    On the Silent Process Exit tab, you enter the program you want to keep an extra eye on and check the box Enable Silent Process Exit Monitoring and select what you want to happen when one of these mysterious exits occurs. You can ask for an entry in the event log that identifies the killer, and you can ask for debugging minidumps to be created of both the killer and the victim.

  • The Old New Thing

    The SuspendThread function suspends a thread, but it does so asynchronously

    • 22 Comments

    Prologue: Why you should never suspend a thread.

    Okay, so a colleague decided to ignore that advice because he was running some experiments with thread safety and interlocked operations, and suspending a thread was a convenient way to open up race windows.

    While running these experiments, he observed some strange behavior.

    LONG lValue;
    
    DWORD CALLBACK IncrementerThread(void *)
    {
     while (1) {
      InterlockedIncrement(&lValue);
     }
     return 0;
    }
    
    // This is just a test app, so we will abort() if anything
    // happens we don't like.
    
    int __cdecl main(int, char **)
    {
     DWORD id;
     HANDLE thread = CreateThread(NULL, 0, IncrementerThread, NULL, 0, &id);
     if (thread == NULL) abort();
    
     while (1) {
      if (SuspendThread(thread) == (DWORD)-1) abort();
    
      if (InterlockedOr(&lValue, 0) != InterlockedOr(&lValue, 0)) {
       printf("Huh? The variable lValue was modified by a suspended thread?\n");
      }
    
      ResumeThread(thread);
     }
     return 0;
    }
    

    The strange thing is that the "Huh?" message was being printed. How can a suspended thread modify a variable? Is there some way that Interlocked­Increment can start incrementing a variable, then get suspended, and somehow finish the increment later?

    The answer is simpler than that. The Suspend­Thread function tells the scheduler to suspend the thread but does not wait for an acknowledgment from the scheduler that the suspension has actually occurred. This is sort of alluded to in the documentation for Suspend­Thread which says

    This function is primarily designed for use by debuggers. It is not intended to be used for thread synchronization

    You are not supposed to use Suspend­Thread to synchronize two threads because there is no actual synchronization guarantee. What is happening is that the Suspend­Thread signals the scheduler to suspend the thread and returns immediately. If the scheduler is busy doing something else, it may not be able to handle the suspend request immediately, so the thread being suspended gets to run on borrowed time until the scheduler gets around to processing the suspend request, at which point it actually gets suspended.

    If you want to make sure the thread really is suspended, you need to perform a synchronous operation that is dependent on the fact that the thread is suspended. This forces the suspend request to be processed since it is a prerequisite for your operation, and since your operation is synchronous, you know that by the time it returns, the suspend has definitely occurred.

    The traditional way of doing this is to call Get­Thread­Context, since this requires the kernel to read from the context of the suspended thread, which has as a prerequisite that the context be saved in the first place, which has as a prerequisite that the thread be suspended.

  • The Old New Thing

    If you wonder why a function can't be found, one thing to check is whether the function exists in the first place

    • 10 Comments

    One of my colleagues was frustrated trying to get some code to build. "Is there something strange about linking variadic functions? Because I keep getting an unresolved external error for the function, but if I move the function definition to the declaration point, then everything works fine."

    // blahblah.h
    
    ... other declarations ...
    
    void LogWidget(Widget* widget, const char* format, ...);
    
    ...
    
    // widgetstuff.cpp
    ...
    #include "blahblah.h"
    ...
    
    // some code that calls LogWidget
    void foo(Widget* widget)
    {
     LogWidget(widget, "starting foo");
     ...
    }
    
    // and then near the end of the file
    
    void LogWidget(Widget* widget, const char* format, ...)
    {
        ... implementation ...
    }
    
    ...
    

    "With the above code, the linker complains that Log­Widget cannot be found. But if I move the implementation of Log­Widget to the top of the file, then everything builds fine."

    // widgetstuff.cpp
    ...
    #include "blahblah.h"
    ...
    
    // move the code up here
    void LogWidget(Widget* widget, const char* format, ...)
    {
        ... implementation ...
    }
    
    // some code that calls LogWidget
    void foo(Widget* widget)
    {
     LogWidget(widget, "starting foo");
     ...
    }
    
    ...
    

    "I tried putting an explicit calling convention in the declaration, I tried using extern "C", nothing seems to help."

    We looked at the resulting object file and observed that in the case where the error occurred, there was an external reference to Log­Widget but no definition. I asked, "Is the definition of the function #ifdef'd out by mistake? You can use this technique to find out."

    That was indeed the problem. The definition of the function was inside some sort of #ifdef that prevented it from being compiled.

    Sometimes, the reason a function cannot be found is that it doesn't exist in the first place.

  • The Old New Thing

    Another way to make sure nobody sends you feedback

    • 28 Comments

    I wanted to report an issue about a problem with our building, let's say that the widget frobnicators were not working. I went to the internal Widget Frobnicators Web site, and it was very pretty, with an FAQ about the current supplier of widget frobnicators, where to look up more information about how the widget frobnicators work, how you can buy your own widget frobnicator for home use, and even how you can become a Widget Frobnicator Ambassador for your building. Widget Frobnicator Ambassadors receive additional information about widget frobnicators and get to participate in the widget frobnicator selection process, among other things.

    I didn't find a link on the Widget Frobnicator Web site that let me search for a Widget Frobnicator Ambassador in my building, or even a list of all Widget Frobnicator Ambassadors. However, I did find a link called Contact Us. Awesome. It is a mailto link addressed to the Widget Frobnicator Ambassadors distribution list.

    I composed a message explaining the problem I was observing and hit Send.

    The message was returned as undeliverable.

    "You do not have permission to send messages to this distribution list."

    Nice going, Widget Frobnicator team. Apparently you don't want to be contacted at all. Maybe it's a front for money laundering.

    (I was able to report the problem by other means, and it was resolved later that day, so everything worked out okay in the end, but as far as I can tell, the Widget Frobnicators Web site still provides no way for you to contact them.)

Page 4 of 446 (4,451 items) «23456»