January, 2006

  • The Old New Thing

    Converting between LCIDs and RFC 1766 language codes

    • 17 Comments

    Occasionally, I see someone ask for a function that converts between LCIDs (such as 0x0409 for English-US) and RFC 1766 language identifiers (such as "en-us"). The rule of thumb is, if it's something a web browser would need, and it has to do with locales and languages, you should look in the MLang library. In this case, the IMultiLanguage::GetRfc1766FromLcid method does the trick.

    For illustration, here's a program that takes US-English and converts it to RFC 1766 format. For fun, we also convert "sv-fi" (Finland-Swedish) to an LCID.

    #include <stdio.h>
    #include <ole2.h>
    #include <oleauto.h>
    #include <mlang.h>
    
    int __cdecl main(int argc, char **argv)
    {
     HRESULT hr = CoInitialize(NULL);
     if (SUCCEEDED(hr)) {
      IMultiLanguage * pml;
      hr = CoCreateInstance(CLSID_CMultiLanguage, NULL,
                            CLSCTX_ALL,
                            IID_IMultiLanguage, (void**)&pml);
      if (SUCCEEDED(hr)) {
    
       // Let's convert US-English to an RFC 1766 string
       BSTR bs;
       LCID lcid = MAKELCID(MAKELANGID(LANG_ENGLISH,
                            SUBLANG_ENGLISH_US), SORT_DEFAULT);
       hr = pml->GetRfc1766FromLcid(lcid, &bs);
       if (SUCCEEDED(hr)) {
        printf("%ws\n", bs);
        SysFreeString(bs);
       }
    
       // And a sample reverse conversion just for good measure
       bs = SysAllocString(L"sv-fi");
       if (bs && SUCCEEDED(pml->GetLcidFromRfc1766(&lcid, bs))) {
        printf("%x\n", lcid);
       }
       SysFreeString(bs);
    
       pml->Release();
      }
      CoUninitialize();
     }
     return 0;
    }
    

    When you run this program, you should get

    en-us
    81d
    

    "en-us" is the RFC 1766 way of saying "US-English", and 0x081d is MAKELCID(MAKELANGID(LANG_SWEDISH, SUBLANG_SWEDISH_FINLAND), SORT_DEFAULT).

    If you browse around, you'll find lots of other interesting functions in the MLang library. You may recall that earlier we saw how to use MLang to display strings without those ugly boxes.

    Update (January 2008): The globalization folks have told me that they'd prefer that people didn't use MLang. They recommend instead the functions LCIDToLocaleName and LocaleNameToLCID. The functions are built into Windows Vista and are also available downlevel via a redistributable.

  • The Old New Thing

    The first rule about knit club...

    • 7 Comments

    Knit Club (direct link), a student movie from Western Washington University, wherein each residence hall floor was given less than a week to produce a five-minute movie.

    Note: Not be confused with Pillow Fight Club (photos from the one-hour fight). Unfortunately, the original Pillow Fight Club page is gone, but the first rule of Pillow Fight Club was "Tell everyone about Pillow Fight Club".

  • The Old New Thing

    Don't underestimate the resourcefulness of people trying to be annoying

    • 17 Comments

    TechNet Magazine's January • February 2006 issue is now available, including a short article from me on the resourcefulness of people trying to be annoying. The issue also has an article on how to use the Volume Shadow Copy Service which I mentioned a few months ago. (And if you had read that posting from last September, you'll know why the Previous Versions Client is kept in a folder called "twclient". Try using that at a party to impress someone.)

  • The Old New Thing

    Taxes: Detecting session state changes, such as a locked workstation

    • 23 Comments

    Another developer tax is playing friendly with Fast User Switching and Terminal Services. When the workstation is locked or disconnected, you should turn off non-essential timers, minimize background activities, and generally send your program into a quiet state. If you already used the technique of painting only when your window is visible on the screen, then you get all of this for free, since a locked workstation and a disconnected session do not generate paint messages.

    If you have other activities that you need to scale back or shut down when the user has locked the workstation or disconnected, you can register to be notified when the state changes. Knowing about these state changes is also important so that you can tell when your display is local or remote. As we saw last time, drawing on Remote Desktop Connection is much slower than on a local display, since all the bitmaps need to be transferred over the network to the Remote Desktop client.

    Since locking a workstation and disconnecting a session remove the ability to use visual feedback to indicate our program's state, we'll use the speaker. Start with our new scratch program and make the following changes:

    #include <wtsapi32.h>
    
    LRESULT RootWindow::OnCreate()
    {
     WTSRegisterSessionNotification(m_hwnd, NOTIFY_FOR_THIS_SESSION);
     return 0;
    }
    
    LRESULT RootWindow::HandleMessage(
                              UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
     switch (uMsg) {
     ...
     case WM_WTSSESSION_CHANGE:
      switch (wParam) {
      case WTS_CONSOLE_DISCONNECT:
      case WTS_REMOTE_DISCONNECT:
      case WTS_SESSION_LOCK:
      case WTS_SESSION_LOGOFF:
       Beep(440, 250); break;
    
      case WTS_CONSOLE_CONNECT:
      case WTS_REMOTE_CONNECT:
      case WTS_SESSION_UNLOCK:
      case WTS_SESSION_LOGON:
       Beep(880, 250); break;
      }
      break;
     ...
    }
    

    In this program, we register for a session notification when we create our main window, and listen for the session change message in our window procedure. If we see one of the "going away" messages, we make a low beep; if we see one of the "coming back" messages, we make a high beep.

    Run this program and then lock the workstation or use Fast User Switching to switch away. You should be greeted by a low beep (though you may have to listen carefully if you have a sound associated with the action you performed because the low beep will be mixed in with it). When you switch back, you should hear a high beep.

    Of course, a real program would respond to the notifications by starting or stopping its background tasks. The purpose of this program was merely to show how to get the notifications in the first place.

  • The Old New Thing

    Taxes: Remote Desktop Connection and painting

    • 40 Comments

    An increasingly important developer tax is supporting Remote Desktop Connection properly. When the user is connected via a Remote Desktop Connection, video operations are transferred over the network connection to the client for display. Since networks have high latency and nowhere near the bandwidth of a local PCI or AGP bus, you need to adapt to the changing cost of drawing to the screen.

    If you draw a line on the screen, the "draw line" command is sent over the network to the client. If you draw text, a "draw text" command is sent (along with the text to draw). So far so good. But if you copy a bitmap to the screen, the entire bitmap needs to be transferred over the network.

    Let's write a sample program that illustrates this point. Start with our new scratch program and make the following changes:

    void Window::Register()
    {
        WNDCLASS wc;
        wc.style         = CS_VREDRAW | CS_HREDRAW;
        wc.lpfnWndProc   = Window::s_WndProc;
        ...
    }
    
    class RootWindow : public Window
    {
    public:
     virtual LPCTSTR ClassName() { return TEXT("Scratch"); }
     static RootWindow *Create();
    protected:
     LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam);
     LRESULT OnCreate();
     void PaintContent(PAINTSTRUCT *pps);
     void Draw(HDC hdc, PAINTSTRUCT *pps);
    private:
     HWND m_hwndChild;
    };
    
    void RootWindow::Draw(HDC hdc, PAINTSTRUCT *pps)
    {
     FillRect(hdc, &pps->rcPaint, (HBRUSH)(COLOR_WINDOW + 1));
     RECT rc;
     GetClientRect(m_hwnd, &rc);
     for (int i = -10; i < 10; i++) {
      TextOut(hdc, 0, i * 15 + rc.bottom / 2, TEXT("Blah blah"), 9);
     }
    }
    
    void RootWindow::PaintContent(PAINTSTRUCT *pps)
    {
     Draw(pps->hdc, pps);
    }
    
    LRESULT RootWindow::HandleMessage(
                              UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
     switch (uMsg) {
     ...
     case WM_ERASEBKGND: return 1;
     ...
    }
    

    There is an odd division of labor here; the PaintContent method doesn't actually do anything aside from handing the work off to the Draw method to do the actual drawing. (You'll see why soon.) Make sure "Show window contents while dragging" is enabled and run this program and resize it vertically. Ugh, what ugly flicker. We fix this by the traditional technique of double-buffering.

    void RootWindow::PaintContent(PAINTSTRUCT *pps)
    {
     if (!IsRectEmpty(&pps->rcPaint)) {
      HDC hdc = CreateCompatibleDC(pps->hdc);
      if (hdc) {
       int x = pps->rcPaint.left;
       int y = pps->rcPaint.top;
       int cx = pps->rcPaint.right - pps->rcPaint.left;
       int cy = pps->rcPaint.bottom - pps->rcPaint.top;
       HBITMAP hbm = CreateCompatibleBitmap(pps->hdc, cx, cy);
       if (hbm) {
        HBITMAP hbmPrev = SelectBitmap(hdc, hbm);
        SetWindowOrgEx(hdc, x, y, NULL);
    
        Draw(hdc, pps);
    
        BitBlt(pps->hdc, x, y, cx, cy, hdc, x, y, SRCCOPY);
    
        SelectObject(hdc, hbmPrev);
        DeleteObject(hbm);
       }
       DeleteDC(hdc);
      }
     }
    }
    

    Our new PaintContent method creates an offscreen bitmap and asks the Draw method to draw into it. Once that's done, the results are copied to the screen at one go, thereby avoiding flicker. If you run this program, you'll see that it resizes nice and smooth.

    Now connect to the computer via a Remote Desktop Connection and run it again. Since Remote Desktop Connection disables "Show window contents while dragging", you can't use resizing to trigger redraws, so instead maximize the program and restore it a few times. Notice the long delay before the window is resized when you maximize it. That's because we are pumping a huge bitmap across the Remote Desktop Connection as part of that BitBlt call.

    Go back to the old version of the PaintContent method, the one that just calls Draw, and run it over Remote Desktop Connection. Ah, this one is fast. That's because the simpler version doesn't transfer a huge bitmap over the Remote Desktop Connection; it just sends twenty TextOut calls on a pretty short string of text. These take up much less bandwidth than a 1024x768 bitmap.

    We have one method that is faster over a Remote Desktop Connection, and another method that is faster when run locally. Which should we use?

    We use both, choosing our drawing method based on whether the program is running over a Remote Desktop Connection.

    void RootWindow::PaintContent(PAINTSTRUCT *pps)
    {
     if (GetSystemMetrics(SM_REMOTESESSION)) {
      Draw(pps->hdc, pps);
     } else if (!IsRectEmpty(&pps->rcPaint)) {
      ... as before ...
     }
    }
    

    Now we get the best of both worlds. When run locally, we use the double-buffered drawing which draws without flickering, but when run over a Remote Desktop Connection, we use the simple Draw method that draws directly to the screen rather than to an offscreen bitmap.

    This is a rather simple example of adapting to Remote Desktop Connection. In a more complex world, you may have more complicated data structures associated with the two styles of drawing, or you may have background activities related to drawing that you may want to turn on and off based on whether the program is running over a Remote Desktop Connection. Since the user can dynamically connect and disconnect, you can't just assume that the state of the Remote Desktop Connection when your program starts will be the state for the lifetime of the program. We'll see next time how we can adapt to a changing world.

  • The Old New Thing

    The world's slowest RET instruction

    • 4 Comments

    Occasionally, somebody will ask

    I'm debugging a hang, and I see that many threads are stuck at a RET instruction. When I try to trace one instruction from that thread, the trace breakpoint never fires. It's as if the RET instruction itself is wedged! I've found the world's slowest RET instruction.

    (A common variation on this theme is that the thread in question is consuming 100% CPU... on a RET instruction?)

    Because what you see in that RET instruction is a thread that is executing in kernel mode. The kernel parked the user-mode side of the thread at a RET instruction, poised to execute once the kernel-mode side has returned. Which it hasn't yet.

    In order to see what is really going on with that thread, you have to drop into the kernel debugger. You might be able to make some educated guesses (also known as "invoke psychic powers") based on what you can still see on the user-mode side. For example, the RET could be returning back to a WaitForSingleObject call, which tells you that whatever this thread is waiting for hasn't happened yet.

    [While Raymond was on vacation, the autopilot stopped working due to a power outage. This entry has been backdated.]

Page 4 of 4 (36 items) 1234