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 |
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
The reason why the
We use the
Observe that in the
class RootWindow : public Window { public: typedef Window super; ...
and use This program doesn't do anything interesting; it's just going to be a framework for future samples. |
Comments (49)
Comments are closed. |
STA? Out of curiosity, why?
http://support.microsoft.com/kb/287087
__super looks like a super (sorry) extension – do you know if there are any plans to standardize it?
Cool, reminds me of Paul DiLascia’s Windows++ ;)
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?
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.
You’ve sold us out. I’m devastated.
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.
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.
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.
What happens if someone else sets our USERDATA?
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.
Does anyone know how to use ATL-style thunks if HandleMessage is a virtual function as it is in the sample program?
How about adding some const correctness to your sample?
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.
Using "private" instead of "public" makes this much safer:
class RootWindow : public Window
{
private:
typedef Window super;
Jan
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. :)
Congrats. You just jumped the shark.
Just one more question Raymond: Why didn’t you tell me 10 years ago !
<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.
Me think that "m_hwndChild" is never initialized anywhere.
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.
oh no not you too Raymond… I feel so alone now
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.
I hate C++.
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++
*Everyone* hates C++! It’s just that some people hate C more.
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.
Yeah but GetProp is probably even slower than GetWindowLong
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.
C++? You hippy.
Hmm… Probably you are 10 (or more) years late here with C++.
> 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.
I for one am glad you’re using C++. I only wish your co-workers at Microsoft were as into C++ as you are.
Filling a listview with tens of thousands of items.
One is sent at the start of the destruction process, the other at the end.
One is sent at the start of the destruction process, the other at the end.
One is sent at the start of the destruction process, the other at the end.
Destroying a window that is already being destroyed leads to strange behavior.
Drawing various types of radio buttons.
Drawing the menu checkmark, as an example.
Drawing the menu checkmark, as an example.
It means the program is spending too much time drawing to the screen and not enough time doing actual work.
PingBack from http://blog.strafenet.com/2006/09/08/getwindowlongptr/
WM_DESTROY 和 WM_NCDESTROY 消息之间有什么区别?
Heh.. w ten weekend się zebrałem aby przyjrzeć się co też najnowsze środowisko Visual Studio.Net 2008