The new scratch program

Date:April 22, 2005 / year-entry #102
Tags:code
Orig Link:https://blogs.msdn.microsoft.com/oldnewthing/20050422-08/?p=35813
Comments:    49
Summary: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 #include #include #include #include ...

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.


Comments (49)
  1. STA? Out of curiosity, why?

  2. Jonathan Payne says:

    __super looks like a super (sorry) extension – do you know if there are any plans to standardize it?

  3. Ryan Heath says:

    Cool, reminds me of Paul DiLascia’s Windows++ ;)

  4. Ok, next stupid question… I understand that __super is similar to the C# Base keyword, but why use it?

    Is it to be able to derive RootWindow from something other than Window in the future?

  5. it’s just going to be a framework for future samples

    Or a sample framework ? Do you plan to cover frameworks architectures and issues ? I’d love that.

    Ha! Rats! No. You wrote it before : You know nothing to MFC ;-)

  6. Matt Green says:

    Is WM_NCCREATE the very first message that is sent to a window? If this is the case, then it makes ATL’s thunking pretty much unnecessary.

  7. Michael Bundy says:

    You’ve sold us out. I’m devastated.

  8. Mike Dunn says:

    Matt> ATL uses thunks because the designers didn’t want to use SetWindowLongPtr(GWLP_USERDATA). Doing so would be a barrier to people porting existing code that happened to already store important data in GWLP_USERDATA.

  9. If you ever copied, renamed, or reparented a class, you already know how handy __super can be. It’s the class hierarchy version of the ".." directory.

    We will see more of WM_NCCREATE in July.

  10. carlso says:

    ATL uses thunks because the designers didn’t want to use SetWindowLongPtr(GWLP_USERDATA). Doing so would be a barrier to people porting existing code that happened to already store important data in GWLP_USERDATA.

    Also, another motivation for ATL thunks is that you won’t pay the price for the GetWindowLongPtr() lookup on every message processed. Any comments on how fast GetWindowLongPtr(…, GWLP_USERDATA) is?

    If you’re unfamiliar with ATL thunks, it basically works like this: For each instance of a "C++ window object", you create a small piece of code at runtime (called a thunk). When the window is created, the actual WndProc is replaced by the thunk by calling the SetWindowLongPtr(…, GWL_WNDPROC, …) API.

    So, instead of calling the original WndProc, Windows will call your thunk instead. The job of the thunk is to replace the HWND parameter passed to the WndProc with the "this" pointer of the C++ class instance, and the thunk then jumps to the original WndProc. Because the thunk replaces the HWND with your this pointer, you code up your WndProc as accepting a "this" pointer instead of an HWND as the first parameter.

    More details can be found in the article "Thunking WndProcs in ATL" by Fritz Onion that appeared in C++ Report, March 1999: http://www.pluralsight.com/articlecontent/cpprep0399.htm

    Raymond, can you comment on the merits of this technique vs. the GetWindowLongPtr(…, GWLP_USERDATA) method?

    Thanks for another great blog!

  11. Raymond, can you comment on the merits of this technique vs. the GetWindowLongPtr(…, GWLP_USERDATA) method?

    I think you did an excellent job yourself.

  12. Matt Green says:

    One thing that I’ve wondered about the ATL thunks is how future-proof they are. Since they effectively step outside the bounds of the language and embed x86 ASM code into data members, aren’t they at risk for becoming obselete if x86 is ever abandoned? Although, admittedly, this is a pretty weak argument. The recent entries on the IA64 being a more strict architecture do bring these concerns back to the surface, however.

    I guess the nice thing about the ATL thunks is that the dirty work is done by maintainers at Microsoft, so conceivably we don’t have to worry about it as much.

  13. Joe says:

    What happens if someone else sets our USERDATA?

  14. Who else would that be? We control the both the window class implementation and the window class consumer. If I were writing a control intended for use by others then that would be a concern but that doesn’t apply here.

  15. Ivo says:

    Does anyone know how to use ATL-style thunks if HandleMessage is a virtual function as it is in the sample program?

  16. Anonymous Coward says:

    How about adding some const correctness to your sample?

  17. Mike Dimmick says:

    Matt: Yes, the ATL thunks need to be rewritten for each processor type.

    There’s some horrible nasty code in VS2005 Beta 2 for allocating the thunk from a pool of thunk blocks allocated from virtual memory (if on XP with NX enabled). See atlthunk.cpp if interested. I hope the OS team are aware that ATL is bumming around in the Process Environment Block! Although, since IE uses ATL windowing in some places, and presumably IE was modified to be NX-safe for XP SP2, perhaps they do know.

  18. Jan says:

    Using "private" instead of "public" makes this much safer:

    class RootWindow : public Window

    {

    private:

    typedef Window super;

    Jan

  19. Matt Green says:

    Err, Ryan, you’re aware I was discussing the ATL thunk implementation and not Raymond’s, correct? Your comments look like you were confusing the two. :)

  20. Me says:

    Congrats. You just jumped the shark.

  21. Just one more question Raymond: Why didn’t you tell me 10 years ago !

  22. <i>"One thing that I’ve wondered about the ATL thunks is how future-proof they are. Since they effectively step outside the bounds of the language and embed x86 ASM code into data members, aren’t they at risk for becoming obselete if x86 is ever abandoned?"</i>

    Uh, no ASM code being stored in data. Otherwise, you’d trip over NX protection.

    All he’s doing is casting a pointer to a class (Window *) to another pointer type (void *) and storing that somewhere. He then retrieves it, casts it back, and calls a method on it, which is perfectly legal.

    In fact, he’s even being careful to observe the C++ Standard rule that you cannot cast a data pointer to a function pointer or visa versa.

    If you eliminate the __super extension as he mentioned, as far as I can tell it’s perfectly valid C++. Not portable, since it uses Win32, but a valid program.

  23. Thierry Tremblay says:

    Me think that "m_hwndChild" is never initialized anywhere.

  24. Universalis says:

    ATL thunking sounds sort of like the good old CreateProcInstance from Windows 3.1. It’s a deficiency in C, really, that you have to use assembler to make this sort of thing work.

  25. teebee says:

    oh no not you too Raymond… I feel so alone now

  26. Tom_Seddon says:

    if you are still using C to write actual programs, there is a good reason that you are feeling lonely — most other people have joined the 21st century. Raymond is simply updating himself for the 1990s.

  27. gel says:

    I hate C++.

  28. Mike says:

    Ever had one of those moments where you suddenly realise you have been missing out on some crucial and amazingly useful programming ‘thing’.

    <windowsx.h>

    I never even knew this existed! Alas! Alas!

    and by the way, <3 C++

  29. Tom_Seddon says:

    *Everyone* hates C++! It’s just that some people hate C more.

  30. Paul Winwood says:

    Hmm, WinCreateWindow, shades of OS/2 Presentation Manager I think ;)

    On a serious note to avoid the problem of others using the GWLP_USERDATA you can always use SetProp/GetProp/RemoveProp.

  31. Anders says:

    Yeah but GetProp is probably even slower than GetWindowLong

  32. AlisdairM says:

    For all those wondering ‘why don’t ANSI/ISO standardise __super’, think about what it would mean in a class with multiple bases.

    It is very useful in single-inheritance hierarchies, and there are a lot of those around <g> but writing the private typedef has similar effect (and as you are in charge, works with multiple bases if you really want it to!) The main advantage of __super is that there is no risk of forgetting to update the typedef if you change your base class.

  33. jeffdav says:

    C++? You hippy.

  34. visitor says:

    Hmm… Probably you are 10 (or more) years late here with C++.

  35. asdf says:

    > For all those wondering ‘why don’t ANSI/ISO standardise __super’, think about what it would mean in a class with multiple bases.

    They would have to make an arbitrary decision whether to choose the first base, be illegal, or be a synonym for __super(0) which picks off the first base…

    Raymond, I noticed that the this pointer is implicitly converted to a void * in the call to CreateWindowEx but down below you reinterpret_cast the lpCreateParams to a Window *. This isn’t portable, you either have to change that to a static_cast (or C style cast) or do an explicit reinterpret_cast in the CreateWindowEx call.

  36. I for one am glad you’re using C++. I only wish your co-workers at Microsoft were as into C++ as you are.

  37. Filling a listview with tens of thousands of items.

  38. One is sent at the start of the destruction process, the other at the end.

  39. One is sent at the start of the destruction process, the other at the end.

  40. One is sent at the start of the destruction process, the other at the end.

  41. Destroying a window that is already being destroyed leads to strange behavior.

  42. Drawing various types of radio buttons.

  43. Drawing the menu checkmark, as an example.

  44. Drawing the menu checkmark, as an example.

  45. It means the program is spending too much time drawing to the screen and not enough time doing actual work.

  46. fengzi_shen says:

    WM_DESTROY 和 WM_NCDESTROY 消息之间有什么区别?

  47. Heh.. w ten weekend się zebrałem aby przyjrzeć się co też najnowsze środowisko Visual Studio.Net 2008

Comments are closed.


*DISCLAIMER: I DO NOT OWN THIS CONTENT. If you are the owner and would like it removed, please contact me. The content herein is an archived reproduction of entries from Raymond Chen's "Old New Thing" Blog (most recent link is here). It may have slight formatting modifications for consistency and to improve readability.

WHY DID I DUPLICATE THIS CONTENT HERE? Let me first say this site has never had anything to sell and has never shown ads of any kind. I have nothing monetarily to gain by duplicating content here. Because I had made my own local copy of this content throughout the years, for ease of using tools like grep, I decided to put it online after I discovered some of the original content previously and publicly available, had disappeared approximately early to mid 2019. At the same time, I present the content in an easily accessible theme-agnostic way.

The information provided by Raymond's blog is, for all practical purposes, more authoritative on Windows Development than Microsoft's own MSDN documentation and should be considered supplemental reading to that documentation. The wealth of missing details provided by this blog that Microsoft could not or did not document about Windows over the years is vital enough, many would agree an online "backup" of these details is a necessary endeavor. Specifics include:

<-- Back to Old New Thing Archive Index