Welcome to MSDN Blogs Sign in | Join | Help

The new scratch program

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.

Published Friday, April 22, 2005 7:00 AM by oldnewthing
Filed under:

Comments

# re: The new scratch program

Friday, April 22, 2005 9:08 AM by LarryOsterman
STA? Out of curiosity, why?

# re: The new scratch program

Friday, April 22, 2005 9:17 AM by oldnewthing

# re: The new scratch program

Friday, April 22, 2005 9:20 AM by Jonathan Payne
__super looks like a super (sorry) extension – do you know if there are any plans to standardize it?

# re: The new scratch program

Friday, April 22, 2005 9:24 AM by Ryan Heath
Cool, reminds me of Paul DiLascia's Windows++ ;)

# re: The new scratch program

Friday, April 22, 2005 9:29 AM by LarryOsterman
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?

# re: The new scratch program

Friday, April 22, 2005 9:30 AM by Serge Wautier
> 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 ;-)

# re: The new scratch program

Friday, April 22, 2005 9:41 AM by Matt Green
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.

# re: The new scratch program

Friday, April 22, 2005 10:00 AM by Michael Bundy
You've sold us out. I'm devastated.

# re: The new scratch program

Friday, April 22, 2005 10:25 AM by Mike Dunn
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.

# re: The new scratch program

Friday, April 22, 2005 10:26 AM by oldnewthing
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.

# re: The new scratch program

Friday, April 22, 2005 10:46 AM by carlso
> 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!

# re: The new scratch program

Friday, April 22, 2005 10:53 AM by oldnewthing
> Raymond, can you comment on the merits of this technique vs. the GetWindowLongPtr(..., GWLP_USERDATA) method?

I think you did an excellent job yourself.

# re: The new scratch program

Friday, April 22, 2005 11:29 AM by Matt Green
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.

# re: The new scratch program

Friday, April 22, 2005 12:17 PM by Joe
What happens if someone else sets our USERDATA?

# re: The new scratch program

Friday, April 22, 2005 12:23 PM by oldnewthing
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.

# re: The new scratch program

Friday, April 22, 2005 12:30 PM by Ryan Myers [MSFT]
<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.

# re: The new scratch program

Friday, April 22, 2005 12:48 PM by Ivo
Does anyone know how to use ATL-style thunks if HandleMessage is a virtual function as it is in the sample program?

# re: The new scratch program

Friday, April 22, 2005 1:50 PM by Anonymous Coward
How about adding some const correctness to your sample?

# re: The new scratch program

Friday, April 22, 2005 1:58 PM by Mike Dimmick
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.

# re: The new scratch program

Friday, April 22, 2005 2:06 PM by Jan
Using "private" instead of "public" makes this much safer:

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

Jan

# re: The new scratch program

Friday, April 22, 2005 3:06 PM by Matt Green
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. :)

# re: The new scratch program

Friday, April 22, 2005 3:36 PM by Me
Congrats. You just jumped the shark.

# No need to know your base class name. __super !

Friday, April 22, 2005 6:11 PM by Serge Wautier
Just one more question Raymond: Why didn't you tell me 10 years ago !

# re: The new scratch program

Friday, April 22, 2005 8:19 PM by Thierry Tremblay
Me think that "m_hwndChild" is never initialized anywhere.

# re: The new scratch program

Saturday, April 23, 2005 3:17 AM by Universalis
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.

# re: The new scratch program

Saturday, April 23, 2005 9:22 PM by teebee
oh no not you too Raymond... I feel so alone now

# re: The new scratch program

Saturday, April 23, 2005 10:07 PM by Tom_Seddon
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.

# re: The new scratch program

Saturday, April 23, 2005 11:02 PM by gel
I hate C++.

# re: The new scratch program

Sunday, April 24, 2005 8:47 AM by Tom_Seddon
*Everyone* hates C++! It's just that some people hate C more.

# re: The new scratch program

Sunday, April 24, 2005 10:51 AM by Mike
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++

# re: The new scratch program

Monday, April 25, 2005 3:57 AM by Paul Winwood
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.

# re: The new scratch program

Monday, April 25, 2005 5:02 AM by Anders
Yeah but GetProp is probably even slower than GetWindowLong

# re: The new scratch program

Monday, April 25, 2005 9:18 AM by AlisdairM
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.

# re: The new scratch program

Monday, April 25, 2005 5:37 PM by jeffdav
C++? You hippy.

# re: The new scratch program

Monday, April 25, 2005 11:44 PM by asdf
> 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.

# re: The new scratch program

Tuesday, April 26, 2005 5:57 AM by visitor
Hmm... Probably you are 10 (or more) years late here with C++.

# re: The new scratch program

Tuesday, April 26, 2005 10:11 AM by Brian Friesen
I for one am glad you're using C++. I only wish your co-workers at Microsoft were as into C++ as you are.

# Displaying the dictionary, part 1: Naive version

Monday, June 13, 2005 9:02 AM by The Old New Thing
Filling a listview with tens of thousands of items.

# What is the difference between WM_DESTROY and WM_NCDESTROY?

Tuesday, July 26, 2005 10:00 AM by The Old New Thing
One is sent at the start of the destruction process, the other at the end.

# What is the difference between WM_DESTROY and WM_NCDESTROY?

Tuesday, July 26, 2005 12:12 PM by The Old New Thing
One is sent at the start of the destruction process, the other at the end.

# What is the difference between WM_DESTROY and WM_NCDESTROY?

Tuesday, July 26, 2005 12:48 PM by The Old New Thing
One is sent at the start of the destruction process, the other at the end.

# When the normal window destruction messages are thrown for a loop

Wednesday, July 27, 2005 10:00 AM by The Old New Thing
Destroying a window that is already being destroyed leads to strange behavior.

# Rendering standard Windows elements

Monday, August 01, 2005 10:00 AM by The Old New Thing
Drawing various types of radio buttons.

# Rendering menu glyphs is slightly trickier

Tuesday, August 02, 2005 10:00 AM by The Old New Thing
Drawing the menu checkmark, as an example.

# Rendering menu glyphs is slightly trickier

Wednesday, August 03, 2005 12:01 PM by The Old New Thing
Drawing the menu checkmark, as an example.

# Why does my program run faster if I click and hold the caption bar?

Monday, February 20, 2006 10:00 AM by The Old New Thing
It means the program is spending too much time drawing to the screen and not enough time doing actual work.

# The Blog of Justice &raquo; GetWindowLongPtr

Friday, September 08, 2006 2:03 AM by The Blog of Justice » GetWindowLongPtr

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

Thursday, January 31, 2008 10:28 PM by fengzi_shen

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

# [PL] Stara miłość nie umiera czy kotlet odgrzewany? Rzecz o C++

Monday, March 03, 2008 12:09 PM by Only Human | Devoted to technology v.2.0

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

New Comments to this post are disabled
 
Page view tracker