Tuesday, November 15, 2011

walk-through of RootWindow from scratch program

Last time I posted a port of Raymond Chen's scratch program. I mentioned that I had some goals for the design of the window library. One of those goals was to follow the intended window lifetime model. Most of the controls built into windows work by registering window classes that will allocate memory to store their state on the first message (usually WM_NCCREATE) and deallocate that state on the last message (usually WM_NCDESTROY). The window handle returned from CreateWindow is then used to send messages to get or set state.

In most existing libraries there is a single class with both the window handle and the state and an instance of the class is allocated and then passed to CreateWindow. Deallocation becomes challenging. If it is deallocated on WM_NCDESTROY then there is a risk that the object will be accessed after the destructor has run. In addition there is always the risk that delete could be called while the window is still trying to send messages.

Following the pattern used by the built-in controls results in a much safer lifetime model.

The following walkthrough will explain how each line contributes to the pattern:

Add a namespace where the implementation details of the window can be placed

namespace RootWindow {

This struct tag is the glue that is used to leverage ADL to stitch all the pieces of the window together.

struct tag {};

Adding a typedef for the Context just reduces typing. Notice the tag is used to make this Context unique to RootWindow.

typedef
  l::wnd::Context<tag>
    Context;

The struct window is allocated by the library on WM_NCCREATE to provide the runtime state for the window. in addition to the state it also provides a function for each window message it wishes to receive
The child member is there to mirror Raymond's scratch program as is the empty OnCreate.
OnPaint and OnPrintClient are in the base class of Raymond's scratch and I could have placed them in a common location as well, but I have not settled on the best place for them so they are here for now.

Notice that there is no message map. The library uses templates to detect which methods exist on struct window and only handle those. The rest go to DefWindowProc.

struct window
{
  l::wr::unique_close_window child;

  LRESULT OnCreate(const Context& , LPCREATESTRUCT) {
    return 0;
  }

  LRESULT OnSize(const Context& , UINT , int cx, int cy) {
    if (child) {
      SetWindowPos(
        child.get(), NULL,
        0, 0, cx, cy,
        SWP_NOZORDER | SWP_NOACTIVATE);
    }
    return 0;
  }

  LRESULT PaintContent(PAINTSTRUCT&) {
    return 0;
  }

  LRESULT OnPaint(const Context& context) {
    PAINTSTRUCT ps = {};
    BeginPaint(context.window, &ps);
    l::wr::unique_gdi_end_paint ender(
      std::make_pair(context.window, &ps));
    return PaintContent(ps);
  }

  LRESULT OnPrintClient(const Context& context, HDC hdc, DWORD) {
    PAINTSTRUCT ps = {};
    ps.hdc = hdc;
    GetClientRect(context.window, &ps.rcPaint);
    return PaintContent(ps);
  }

  LRESULT OnNCDestroy(const Context&) {
    PostQuitMessage(0);
    return 0;
  }
};

This declares a function that has no implementation. This function is used by the library to locate the window class using the tag struct. Since it is never called and is only referenced in a decltype expression there is no need for an implementation.

l::wnd::window_class_traits_builder<window>
window_class_traits(tag &&);

This function does need an implementation. The library calls this function to allow the window registration defaults to be changed. The library uses the tag to find this function.

void window_class_register(
  PCWSTR windowClass,
  WNDCLASSEX* wcex,
  tag &&) {
  wcex->style         = 0;
  wcex->hIcon         = NULL;
  wcex->hCursor       = LoadCursor(NULL, IDC_ARROW);
  wcex->lpszMenuName  = NULL;
  wcex->lpszClassName = windowClass;
}
}

This function is found using the tag as well. The library calls this function for each message that it dispatches. This function provides user-specified error handling. If this function does not catch exceptions the library will exit the process if an exception is thrown. This allows the library to be used in code that does not support exceptions as well (by skipping the try/catches).

template<typename Function, typename MessageTag>
void window_message_error_contract(
  Function && function,
  RootWindow::Context& ,
  MessageTag && ,
  RootWindow::tag &&)
{
  try {
    std::forward<Function>(function)();
  } catch (std::bad_alloc &&) {
  } catch (unique_winerror::exception &&) {
  } catch (unique_hresult::exception &&) {
  }
}

This typedef actually stitches all the previous pieces into the final type. the window_class is given the tag after which it uses the tag to search out all the pieces and build a WndProc that will create a struct window and start calling OnCreate etc..

typedef
  l::wnd::window_class<RootWindow::tag>
    RootWindowClass;

No comments:

Post a Comment