• The Old New Thing

    The new scratch program

    • 49 Comments

    I think it's time to update the scratch program we've been using for the past year. I hear there's this new language called C++ that's going to become really popular any day now, so let's hop on the bandwagon!

    #define STRICT
    #define UNICODE
    #define _UNICODE
    #include <windows.h>
    #include <windowsx.h>
    #include <ole2.h>
    #include <commctrl.h>
    #include <shlwapi.h>
    #include <shlobj.h>
    #include <shellapi.h>
    
    HINSTANCE g_hinst;
    
    class Window
    {
    public:
     HWND GetHWND() { return m_hwnd; }
    protected:
     virtual LRESULT HandleMessage(
                             UINT uMsg, WPARAM wParam, LPARAM lParam);
     virtual void PaintContent(PAINTSTRUCT *pps) { }
     virtual LPCTSTR ClassName() = 0;
     virtual BOOL WinRegisterClass(WNDCLASS *pwc)
         { return RegisterClass(pwc); }
     virtual ~Window() { }
    
     HWND WinCreateWindow(DWORD dwExStyle, LPCTSTR pszName,
           DWORD dwStyle, int x, int y, int cx, int cy,
           HWND hwndParent, HMENU hmenu)
     {
      Register();
      return CreateWindowEx(dwExStyle, ClassName(), pszName, dwStyle,
                      x, y, cx, cy, hwndParent, hmenu, g_hinst, this);
     }
    private:
     void Register();
     void OnPaint();
     void OnPrintClient(HDC hdc);
     static LRESULT CALLBACK s_WndProc(HWND hwnd,
         UINT uMsg, WPARAM wParam, LPARAM lParam);
    protected:
     HWND m_hwnd;
    };
    
    void Window::Register()
    {
        WNDCLASS wc;
        wc.style         = 0;
        wc.lpfnWndProc   = Window::s_WndProc;
        wc.cbClsExtra    = 0;
        wc.cbWndExtra    = 0;
        wc.hInstance     = g_hinst;
        wc.hIcon         = NULL;
        wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
        wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
        wc.lpszMenuName  = NULL;
        wc.lpszClassName = ClassName();
    
        WinRegisterClass(&wc);
    }
    
    LRESULT CALLBACK Window::s_WndProc(
                   HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
     Window *self;
     if (uMsg == WM_NCCREATE) {
      LPCREATESTRUCT lpcs = reinterpret_cast<LPCREATESTRUCT>(lParam);
      self = reinterpret_cast<Window *>(lpcs->lpCreateParams);
      self->m_hwnd = hwnd;
      SetWindowLongPtr(hwnd, GWLP_USERDATA,
                reinterpret_cast<LPARAM>(self));
     } else {
      self = reinterpret_cast<Window *>
                (GetWindowLongPtr(hwnd, GWLP_USERDATA));
     }
     if (self) {
      return self->HandleMessage(uMsg, wParam, lParam);
     } else {
      return DefWindowProc(hwnd, uMsg, wParam, lParam);
     }
    }
    
    LRESULT Window::HandleMessage(
                              UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
     LRESULT lres;
    
     switch (uMsg) {
     case WM_NCDESTROY:
      lres = DefWindowProc(m_hwnd, uMsg, wParam, lParam);
      SetWindowLongPtr(m_hwnd, GWLP_USERDATA, 0);
      delete this;
      return lres;
    
     case WM_PAINT:
      OnPaint();
      return 0;
    
     case WM_PRINTCLIENT:
      OnPrintClient(reinterpret_cast<HDC>(wParam));
      return 0;
     }
    
     return DefWindowProc(m_hwnd, uMsg, wParam, lParam);
    }
    
    void Window::OnPaint()
    {
     PAINTSTRUCT ps;
     BeginPaint(m_hwnd, &ps);
     PaintContent(&ps);
     EndPaint(m_hwnd, &ps);
    }
    
    void Window::OnPrintClient(HDC hdc)
    {
     PAINTSTRUCT ps;
     ps.hdc = hdc;
     GetClientRect(m_hwnd, &ps.rcPaint);
     PaintContent(&ps);
    }
    
    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();
    private:
     HWND m_hwndChild;
    };
    
    LRESULT RootWindow::OnCreate()
    {
     return 0;
    }
    
    LRESULT RootWindow::HandleMessage(
                              UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
     switch (uMsg) {
      case WM_CREATE:
       return OnCreate();  
    
      case WM_NCDESTROY:
       // Death of the root window ends the thread
       PostQuitMessage(0);
       break;
    
      case WM_SIZE:
       if (m_hwndChild) {
        SetWindowPos(m_hwndChild, NULL, 0, 0,
                     GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam),
                     SWP_NOZORDER | SWP_NOACTIVATE);
       }
       return 0;
    
      case WM_SETFOCUS:
       if (m_hwndChild) {
        SetFocus(m_hwndChild);
       }
       return 0;
     }
    
     return __super::HandleMessage(uMsg, wParam, lParam);
    }
    
    RootWindow *RootWindow::Create()
    {
     RootWindow *self = new RootWindow();
     if (self && self->WinCreateWindow(0,
           TEXT("Scratch"), WS_OVERLAPPEDWINDOW,
           CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
           NULL, NULL)) {
          return self;
      }
     delete self;
     return NULL;
    }
    
    int PASCAL
    WinMain(HINSTANCE hinst, HINSTANCE, LPSTR, int nShowCmd)
    {
     g_hinst = hinst;
    
     if (SUCCEEDED(CoInitialize(NULL))) {
      InitCommonControls();
    
      RootWindow *prw = RootWindow::Create();
      if (prw) {
       ShowWindow(prw->GetHWND(), nShowCmd);
       MSG msg;
       while (GetMessage(&msg, NULL, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
       }
      }
      CoUninitialize();
     }
     return 0;
    }
    

    The basic idea of this program is the same as our old scratch program, but now it has that fresh lemony C++ scent. Instead of keeping our state in globals, we declare a C++ class and hook it up to the window. For simplicity, the object's lifetime is tied to the window itself.

    First, there is a bare-bones Window class which we will use as our base class for any future "class associated with a window" work. The only derived class for now is the RootWindow, the top-level frame window that for now is the only window that the program uses. As you may suspect, we may have other derived classes later as the need arises.

    The reason why the WinRegisterClass method is virtual (and doesn't do anything interesting) is so that a derived class can modify the WNDCLASS that is used when the class is registered. I don't have any immediate need for it, but it'll be there if I need it.

    We use the GWLP_USERDATA window long to store the pointer to the associated class, thereby allowing us to recover the object from the window handle.

    Observe that in the RootWindow::HandleMessage method, I used the Visual C++ __super extension. If you don't want to rely on a nonstandard extension, you can instead write

    class RootWindow : public Window
    {
    public:
     typedef Window super;
     ...
    

    and use super instead of __super.

    This program doesn't do anything interesting; it's just going to be a framework for future samples.

  • The Old New Thing

    Sweden's Worst Driver

    • 19 Comments

    Swedish Television announced a new reality-based television program called Sweden's Worst Driver (Sveriges värsta bilförare). I've done a bad job of translating some excerpts:

    TV4 has gathered seven of Sweden's worst drivers and subjects them to a series of tests and challenges. Those who complete each exercise the best get a prize and leave the contest.

    Thus, whoever is the last one left in the television series, which begins on the 29th of April, "wins" and is the worst at the same time.

    ... "That there exist people who aren't so good at driving, I knew that already. But people as bad as this I didn't think existed," says [race driver and judge Richard Göransson].

    ... Sven-Ingvar "Snappe" Eriksson, 39, from Hammerdal in Jämtland was nominated by his wife Lena because he has crashed his car at least 25 to 30 times since he got his driver's license 22 years ago.

    Participants get to do several exercises, among them, yielding the right of way for unexpected traffic on forest roads, backing up with a trailer, parking in a tight spot on a hill, and getting out of a parking garage.

    This is sort of in the spirit of those makeover-type programs like What Not To Wear, *** Eye for the Straight Guy, where you ridicule somebody for their general lameness. But at least in those shows, after they make fun of you, they then try to help you be better! Even in the dreadful Queen for a Day, the humiliated "winner" at least got a prize. No such luck, it seems, for Sweden's worst driver.

    It's now a simple matter of time before this show reaches the United States.

  • The Old New Thing

    The Itanium's so-called stack

    • 6 Comments

    Last year I alluded to the fact that the Itanium processor has two stacks. The one that is traditionally thought of as "the stack" (and the one that the sp register refers to) is a manually managed block of memory from which a function can carve out space to use during its execution. For example, if you declare a local variable like

    TCHAR szBuffer[MAX_PATH];
    

    then that buffer will go on "the stack".

    But not all local variables are on "the stack".

    Recall that the Itanium has a very large number of registers, most of which participate in function calls. Consequently, many local variables are placed into registers rather than "the stack", and when a function is called, those registers are "squirreled away" by the processor and "unsquirreled" when the function returns. Where do they get squirreled? Well, the processor can often just squirrel them into other unused registers through a mechanism I won't go into. (Those still interested can read Intel's documents on the subject.) If the processor runs out of squirrel-space, it spills them into main memory, into a place known as the "register backing store". This is another stack-like chunk of memory separate from "the stack". (Here's Slava Oks artistic impression of the layout of the ia64's stacks.)

    As already noted, one consequence of this dual-stack model is that a stack buffer overflow will not corrupt the return address, because the return address is not kept on "the stack"; rather, it is kept in the "squirrel space" or (in the case of spillage) in the register backing store.

    Another consequence of this dual-stack model is that various tricks to locate the start of the stack will find only one of the stacks. Missing out on the other stack will cause problems if you think grovelling "the" stack will find all accessible object references.

    The Itanium architecture challenges many assumptions and is much less forgiving of various technically-illegal-but-nobody-really-enforced-it-before shenanigans, some of which I have discussed in earlier entries. To this list, add the "second stack".

  • The Old New Thing

    What is the DC brush good for?

    • 21 Comments

    The DC brush GetStockObject(DC_BRUSH) is a stock brush associated with the device context. Like the system color brushes, the color of the DC brush changes dynamically, but whereas the system color brushes change color based on the system colors, the color of the DC brush changes at your command.

    The DC brush is handy when you need a solid color brush for a very short time, since it always exists and doesn't need to be created or destroyed. Normally, you have to create a solid color brush, draw with it, then destroy it. With the DC brush, you set its color and start drawing. But it works only for a short time, because the moment somebody else calls the SetDCBrushColor function on your DC, the DC brush color will be overwritten. In practice, this means that the DC brush color is not trustworthy once you relinquish control to other code. (Note, however, that each DC has its own DC brush color, so you need only worry about somebody on another thread messing with your DC simultaneously, which doesn't happen under any of the painting models I am familiar with.)

    The DC brush is amazingly useful when handling the various WM_CTLCOLOR messages. These messages require you to return a brush that will be used to draw the control background. If you need a solid color brush, this usually means creating the solid color brush and caching it for the lifetime of the window, then destroying it when the window is destroyed. (Some people cache the brush in a static variable, which works great until somebody creates two copies of the dialog/window. Then you get a big mess.)

    Let's use the DC brush to customize the colors of a static control. The program is not interesting as a program; it's just an illustration of one way you can use the DC brush.

    Start, as always, with our scratch program, and making the following changes.

    BOOL
    OnCreate(HWND hwnd, LPCREATESTRUCT lpcs)
    {
      g_hwndChild = CreateWindow(TEXT("static"), NULL,
            WS_VISIBLE | WS_CHILD, 0, 0, 0, 0,
            hwnd, NULL, g_hinst, 0);
     if (!g_hwndChild) return FALSE;
     return TRUE;
    }
    
    HBRUSH OnCtlColor(HWND hwnd, HDC hdc, HWND hwndChild, int type)
    {
      FORWARD_WM_CTLCOLORSTATIC(hwnd, hdc, hwndChild, DefWindowProc);
      SetDCBrushColor(hdc, RGB(255,0,0));
      return GetStockBrush(DC_BRUSH);
    }
    
        HANDLE_MSG(hwnd, WM_CTLCOLORSTATIC, OnCtlColor);
    

    Run this program and observe that we changed the background color of the static window to red.

    The work happens inside the OnCtlColor function. When asked to customize the colors, we first forward the message to the DefWindowProc function so that the default foreground and background text colors are set. (Not relevant here since we draw no text, but a good thing to do on principle.) Since we want to override the background brush color, we set the DC brush color to red and then return the DC brush as our desired background brush.

    The static control then takes the brush we returned (the DC brush) and uses it to draw the background, which draws in red because that's the color we set it to.

    Normally, when customizing the background brush, we have to create a brush, return it from the WM_CTLCOLORSTATIC message, then destroy it when the parent window is destroyed. But by using the DC brush, we avoided having to do all that bookkeeping.

    There is also a DC pen GetStockObject(DC_PEN) which behaves in an entirely analogous manner.

  • The Old New Thing

    When people ask for security holes as features: Hiding files from Explorer

    • 40 Comments

    By default, Explorer does not show files that have the FILE_ATTRIBUTE_HIDDEN flag, since somebody went out of their way to hide those files from view.

    You can, of course, ask that such files be shown anyway by going to Folder Options and selecting "Show hidden files and folders". This shows files and folders even if they are marked as FILE_ATTRIBUTE_HIDDEN.

    On the other hand, files that are marked as both FILE_ATTRIBUTE_HIDDEN and FILE_ATTRIBUTE_SYSTEM remain hidden from view. These are typically files that involved in the plumbing of the operating system, messing with which can cause various types of "excitement". Files like the page file, folder configuration files, and the System Volume Information folder.

    If you want to see those files, too, then you can uncheck "Hide protected operating system files".

    Let's look at how far this game of hide/show ping-pong has gone:

    ShowHide
    1.Normal file
    2.Hidden file
    3."Show hidden files"
    4.Hidden + System
    5."Show protected
    operating system files"

    You'd think this would be the end of the hide/show arms race, but apparently some people want to add a sixth level and make something invisible to Explorer, overriding the five existing levels.

    At some point this back-and-forth has to stop, and for now, it has stopped at level five. Adding just a sixth level would create a security hole, because it would allow a file to hide from the user. As a matter of security, a sufficiently-privileged user must always have a way of seeing what is there or at least know that there is something there that can't be seen. Nothing can be undetectably invisible.

    If you add a sixth level that lets a file hide from level five, then there must be a level seven that reveals it.

  • The Old New Thing

    Project update: Voyage to Our Hollow Earth

    • 4 Comments

    In December 2003, I reported on Steve Currey's expedition to the hole at the top of the earth, which at the time was scheduled for June 26, 2005. But on May 6, 2004, the site rescheduled the trip for Jun 26, 2006 with no explanation. The reservation form reminds you that the 25% deposit is non-refundable.

    Far be it from me to suggest that these people are just stringing their loyal following along, pocketing the $4000 deposit, with no intention of actually mounting the expedition. That would be patently unfair of me. I'm certain there's a perfectly reasonable and honorable explanation for the delay.

  • The Old New Thing

    What is the HINSTANCE passed to CreateWindow and RegisterClass used for?

    • 10 Comments

    One of the less-understood parameters to the CreateWindow function and the RegisterClass function is the HINSTANCE (either passed as a parameter or as part of the WNDCLASS structure).

    The window class name is not sufficient to identify the class uniquely. Each process has its own window class list, and each entry in the window class list consists of an instance handle and a class name. For example, here's what the window class list might look like if a program has two DLLs, both of which register a class name "MyClass", passing the DLL's handle as the HINSTANCE.

    HINSTANCEClass name
    1.USER32.DLLStatic
    2.USER32.DLLButton
    3.USER32.DLLListbox
    4.USER32.DLLCombobox
    5.USER32.DLLEdit
    6.A.DLLMyClass
    7.B.DLLMyClass

    When it comes time to create a window, each module then passes its own HINSTANCE when creating the window, and the window manager uses the combination of the instance handle and the class name to look up the class.

    CreateWindow("MyClass", ..., hinstA, ...); // creates class 6
    CreateWindow("MyClass", ..., hinstB, ...); // creates class 7
    CreateWindow("MyClass", ..., hinstC, ...); // fails
    

    This is why it is okay if multiple DLLs all register a class called "MyClass"; the instance handle is used to tell them apart.

    There is an exception to the above rule, however. If you pass the CS_GLOBALCLASS flag when registering the class, then the window manager will ignore the instance handle when looking for your class. All of the USER32 classes are registered as global. Consequently, all of the following calls create the USER32 edit control:

    CreateWindow("edit", ..., hinstA, ...);
    CreateWindow("edit", ..., hinstB, ...);
    CreateWindow("edit", ..., hinstC, ...);
    

    If you are registering a class for other modules to use in dialog boxes, you need to register as CS_GLOBALCLASS, because as we saw earlier the internal CreateWindow call performed during dialog box creation to create the controls passes the dialog's HINSTANCE as the HINSTANCE parameter. Since the dialog instance handle is typically the DLL that is creating the dialog (since that same HINSTANCE is used to look up the template), failing to register with the CS_GLOBALCLASS flag means that the window class lookup will not find the class since it's registered under the instance handle of the DLL that provided the class, not the one that is using it.

    In 16-bit Windows, the instance handle did other things, too, but they are no longer relevant to Win32.

    A common mistake is to pass the HINSTANCE of some other module (typically, the primary executable) when registering a window class. Now that you understand what the HINSTANCE is used for, you should be able to explain the consequences of registering a class with the wrong HINSTANCE.

  • The Old New Thing

    News flash: Everybody has to pay income tax

    • 24 Comments

    NFL rookies are required to attend "How not to mess up your life like those other professional athletes" training. They learn about such things as sexual harassment, AIDS, common-law marriage, and, of course, taxes.

    Kendrell Bell, a Pittsburgh Steelers linebacker, tells of his great awakening to the verities of income tax: "I got a million-dollar signing bonus. But then I got the check, and it was only $624,000. I thought, Oh, well, I'll get the other half later. Then I found out that's all there was. I thought, They can't do this to me. Then I got on the Internet and I found out they can."

    Shocking! Football players have to pay income tax! Where will the injustice end?

    [Update: Yes, this is an inadvertent repeat. The link is better, though, not requiring a New York Times subscription.]

  • The Old New Thing

    Tweaking our computation of the interval between two moments in time

    • 6 Comments

    We can take our computation of the interval between two moments in time and combine it with the trick we developed for using the powers of mathematics to simplify multi-level comparisons to reduce the amount of work we impose upon the time/date engine.

    static void PrintAge(DateTime bday, DateTime asof)
    {
     // Accumulate years without going over.
     int years = asof.Year - bday.Year;
     if (asof.Month*32 + asof.Day < bday.Month*32 + bday.Day) years--;
     DateTime t = bday.AddYears(years);
    
     // Accumulate months without going over.
     int months = asof.Month - bday.Month;
     if (asof.Day < bday.Day) months--;
     months = (months + 12) % 12;
     t = t.AddMonths(months);
    
     // Days are constant-length, woo-hoo!
     int days = (asof - t).Days;
    
     SC.WriteLine("{0} years, {1} months, {2} days",
                  years, months, days);
    }
    

    Observe that we avoided a call to the AddYears method (which is presumably rather complicated because years are variable-length) by replacing it with a multi-level comparison to determine whether the ending month/day falls later in the year than the starting month/day. Since no month has 32 days, a multiplier of 32 is enough to avoid an overflow of the day into the month field of the comparison key.

  • The Old New Thing

    Computing the interval between two moments in time

    • 20 Comments

    Computing the interval between two moments in time is easy: It's just subtraction, but subtraction may not be what you want.

    If you are displaying time units on the order of months and years, then you run into the problem that a month is of variable length. some people just take the value relative to a base date of January 1 and extract the year and month counts.

    Unfortunately, this results in somewhat non-intuitive results. Let's illustrate with some examples. I'm going to write this in C# because it lets me focus on the algorithm instead of getting distracted by "oh dear how do I convert between SYSTEMTIME and FILETIME?" issues, and because it hightlights some new issues.

    // Remember, code in italics is wrong
    using System;
    using SC = System.Console;
    
    class Program {
     static void PrintAge(DateTime bday, DateTime asof)
     {
      TimeSpan span = asof - bday;
      SC.WriteLine(span);
     }
     public static void Main(string[] args) {
      DateTime bday = DateTime.Parse(args[0]);
      DateTime asof = DateTime.Parse(args[1]);
      if (bday > asof) { SC.WriteLine("not born yet"); return; }
      PrintAge(bday, asof);
     }
    }
    

    The two parameters to the program are the victim's birthday and the date as of which you want to compute the victim's age.

    Here's a sample run:

    > howold 1/1/2001 1/1/2002
    365.00:00:00
    

    Observe that the TimeSpan structure does not attempt to produce results in any unit larger than a day, since the authors of TimeSpan realized that months and years are variable-length.

    A naive implementation might go like this:

    static void PrintAge(DateTime bday, DateTime asof)
    {
     TimeSpan span = asof - bday;
     DateTime dt = (new DateTime(1900, 1, 1)).Add(span);
     SC.WriteLine("{0} years, {1} months, {2} days",
                  dt.Year - 1900, dt.Month - 1, dt.Day - 1);
    }
    

    Try it with some command lines and see what happens:

    > howold 1/1/2001 1/1/2002
    1 years, 0 months, 0 days // good
    > howold 1/1/2001 3/1/2001
    0 years, 2 months, 0 days // good
    > howold 1/1/2000 1/1/2001
    1 years, 0 months, 1 days // wrong
    > howold 9/1/2000 11/1/2000
    0 years, 2 months, 2 days // wrong
    

    Why does it say that a person born on January 1, 2000 is one year and one day old on January 1, 2001? The person is clearly exactly one year old on that day. Similarly, it thinks that November first is two months and two days after September first, when it is clearly two months exactly.

    The reason is that months and years are variable-length, but our algorithm assumes that they are constant. Specifically, months and years are context-sensitive but the algorithm assumes that they are translation-invariant. The lengths of months and years depend which month and year you're talking about. Leap years are longer than non-leap years. Months have all different lengths.

    How do you fix this? Well, first you have to figure out how human beings compute the difference between dates when variable-length units are involved. The most common algorithm is to declare that one year has elapsed when the same month and day have arrived in the year following the starting point. Similarly, a month has elapsed when the same numerical date has arrived in the month following the starting point.

    Mentally, you add years until you can't add years any more without overshooting. Then you add as many months as fit, and then finish off with days. (Some people subtract, but the result is the same.)

    Now you get to mimic this algorithm in code.

    static void PrintAge(DateTime bday, DateTime asof)
    {
     // Accumulate years without going over.
     int years = asof.Year - bday.Year;
     DateTime t = bday.AddYears(years);
     if (t > asof) { years--; t = bday.AddYears(years); }
    
     // Accumulate months without going over.
     int months = asof.Month - bday.Month; // fixed 10pm
     if (asof.Day < bday.Day) months--;
     months = (months + 12) % 12;
     t = t.AddMonths(months);
    
     // Days are constant-length, woo-hoo!
     int days = (asof - t).Days;
    
     SC.WriteLine("{0} years, {1} months, {2} days",
                  years, months, days);
    }
    

    Notice that this algorithm agrees with the common belief that people born on February 29th have birthdays only once every four years.

    Exercise: Explain what goes wrong if you change the line

     if (t > asof) { years--; t = bday.AddYears(years); }
    

    to

     if (t > asof) { years--; t = t.AddYears(-1); }
    
Page 367 of 439 (4,383 items) «365366367368369»