WTL process analysis
Welcome to my Personal Home http://www.noasia.net/taoen
A window is created to destruction, there are such a few main processes.
In WinMain
Registration window class
Create a window
Enter the message loop
In WNDPROC
Handle message
Now we have to dig out where to handle these things in the WTL, how to deal with it. first of all:
Where is WinMain?
WinMain is in the same CPP file as the engineering name. The name is called _twinmain
Int WinApi _twinmain (Hinstance Hinstance, Hinstance / * Hprevinstance * /, LPTSTR LPSTRCMDLINE, INT NCMDSHOW)
{
HRESULT HRES = :: Coinitialize (NULL);
// if you are running on nt 4.0 or higher you can use the following call instead to
// Make The Exe Free Threaded. This Means That Calls Come in on a random rpc thread.
// HRESULT HRES = :: CoinitializeEx (null, coinit_multithread);
Atlassert ("succeeded (hres));
// this Resolves ATL WINDOW THUNKING Problem When Microsoft Layer For Unicode (MSLU) IS Used
:: DefWindowProc (NULL, 0, 0, 0L);
AtlinitcommonControls (ICC_COOL_CLASS | ICC_BAR_CLASSES); // Add Flags to Support Other Controls
HRES = _Module.init (null, hinstance);
Atlassert ("succeeded (hres));
Int nret = Run (LPSTRCMDLINE, NCMDSHOW);
_Module.Term ();
:: Couninitialize ();
Return nret;
}
From this function, you can't see anything, basically substantially allocated in other functions. The other functions mention here are run (lpstrcmdline, ncmdshow); this function is written by our own, just above this _twinmain.
Run's role
INT Run (LPTSTR / * LPSTRCMDLINE * / = null, int ncmdshow = sw_showdefault)
{
CMessageloop theloop;
_Module.AddMessageloop (& Theloop);
CMAINFRAME WNDMAIN;
IF (WNDMAIN.CREATEX () == NULL)
{
ATLTRACE (_T ("Main Window Creation Failed! / N")));
Return 0;
}
WNDMAIN.SHOWINDOW (NCMDSHOW);
INT nret = theloop.run ();
_Module.RemoveMessageloop ();
Return nret;
}
From the name Messageloop and CreateEx, you can guess this Run is where you create a window and enter the message loop. and so
WinMain is necessary to initialize, the main work is in RUN
RUN Create a window and enter the message loop.
Creation of the window
It is easy to know that this section is created in the window.
CMAINFRAME WNDMAIN;
IF (WNDMAIN.CREATEX () == null) {
ATLTRACE (_T ("Main Window Creation Failed! / N")));
Return 0;
}
CMAINFRAME is defined in Mainfrm.h.
Class CMAINFRAME: PUBLIC CFRAMEWINDOWIMPL
It can be seen here to use more inheritance, which is a universal behavior. Mainly inherited in CfraMewindowImpl, and this is a template, the parameters provided are CMAINFRAME. It can be found later that this parameter is used in the base class for mandatory type conversion, as a downward conversion.
Creating a call is wndmain.createex (), which cannot be found in CMAINFRAME, naturally there is in its base class. This is createex (): in CFrameWindowImpl ():
HWnd createex (hwnd hwndparent = null, _u_rect = null, dword dwstyle = 0, dword dwexstyle = 0, lpvoid lpcreateparam = null)
{
Tchar Szwindowname [256];
Szwindowname [0] = 0;
:: LoadString (_Module.getResourceInstance (), T :: getWndclassInfo (). M_UCommonResourceId, Szwindowname, 256);
HMENU HMENU = :: loadmenu (_Module.getResourceInstance (), makeintresource (t :: getWndclassInfo (). M_ucommonResourceId);
T * pt = static_cast
HWND HWND = Pt-> Create (HWndParent, Rect, Szwindowname, DWStyle, DwexStyle, HMenu, LpCreateParam);
IF (hwnd! = null)
M_haccel = :: loadaccelerators (_Module.getResourceInstance (), makeintresource (t :: getWndclassInfo (). m_ucommonResourceId);
Return hwnd;
}
Wait, we have found a singular behavior here.
T * pt = static_cast
What is this, forced type conversion, and is based on type conversion of template parameters. Well, this is the simulation dynamic binding of the ATL development group. Using the base class to provide derived class as a template parameter, the mandatory type conversion is enforced when the function call is used to determine which function is called during compilation. This makes we can rewrite the function in the base class in the derived class and except for the cost of virtual functions. so
Pt-> Create (HWndParent, Rect, Szwindowname, DWStyle, DwexStyle, HMenu, LpCreateParam);
Calling is a Create function of the derived class, although the derived class does not rewrite this function, but you can do this and get flexibility.
Let's continue tracking this Create behavior, don't look for it, this function is at the top of CreateEx. Derived class has not been rewritten, the call is the version in the base class. HWnd create (hwnd hwndparent = null, _u_rect = null, lpctstr szwindowname = null, dword dWStyle = 0, dword dwexstyle = 0, HMENU HMENU = NULL, LPVOID LPCREATEPARAM = NULL)
{
Atom atom = t :: getWndclassInfo (). Register (& m_pfnsuperwindowproc);
DWStyle = T :: getWndStyle (DWStyle);
DWEXStyle = T :: getWndexStyle (dwexstyle);
IF (Rect.m_lprect == Null)
Rect.m_lpRect = & TBASE :: rcdefault;
Return CFrameWindowImplbase
}
Red labeled two important processes, a registration window class, a window created. First pay attention to the registration of the window class.
Window class with registration
T :: getWndclassInfo (). Register (& M_PFNSUPERWINDOWPROC);
This code completes the registration of the window class.
T is the parameter passed to the base class, that is, derived classes. So T is CMAINFRAME. T :: getWndClassInfo () indicates that the class's static function is called. So where is this function defined? We must notice this line in the CMAINFRAME definition:
Declare_frame_wnd_class (null, idr_mainframe)
Obviously, this is a macro (you look at there without a semicolon behind). So continue to search this macro definition
#define declare_frame_wnd_class (wndclassname, ucommonResourceId) /
Static cframewndclassinfo & getWndclassInfo () /
{/
Static CFrameWndClassInfo WC = /
{/
{SizeOf (Wndclassex), 0, StartWindowProc, /
0, 0, NULL, NULL, NULL, (HBRUSH) (Color_Window 1), NULL, WNDCLASSNAME, NULL}, /
NULL, NULL, IDC_ARROW, TRUE, 0, _T (""), UcommonResourceId /
}; /
Return WC; /
}
^ _ ^, I caught you. It is this macro to make a static function. This static function is a static variable of type CFraMewndClassInfo based on parameters and returns it. This static variable contains WNDCLASS information and some of the information you need to provide when you create a window. It's so
Register (& M_PFNSUPERWINDOWPROC);
The call is the MEMBER FUNCTION in CFrameWndClassInfo. So, we have to see the definition of CFraMewndClassInfo: Class CframeWndClassInfo
{
PUBLIC:
WNDCLASSEX M_WC;
LPCTSTR M_LPSZORIGNAME;
WndProc PWNDPROC;
LPCTSTR M_LPSZCURSORID;
BOOL M_BSYSTEMCURSOR;
Atom m_atom;
TCHAR M_SZAUTONAME [5 SIZEOF (VOID *) * 2]; // sizeof (void *) * 2 is the number of digits% P Outputs
Uint M_UCommonResourceId;
Atom Register (WNDPROC * PPROC)
{
IF (m_atom == 0)
{
:: EntercriticalSection (& _ Module.m_cswindowcreate);
IF (m_atom == 0)
{
Hinstance hinst = _Module.getModuleInstance ();
IF (m_lpszorigname! = null)
{
Atlassert (PPROC! = Null);
LPCTSTR LPSZ = m_wc.lpszclassname;
WndProc Proc = m_wc.lpfnwndproc;
WNDCLASSEX WC;
wc.cbsize = sizeof (wndclassex);
// Try Process Local Class First
IF (! :: getclassinfoex (_module.getmoduleInstance (), m_lpszorigname, & wc))
{
// Try Global Class
IF (! :: getclassinfoex (null, m_lpszorigname, & wc))
{
:: LeavecriticalSection (& _ Module.m_cswindowcreate);
Return 0;
}
}
Memcpy (& M_WC, & WC, SIZEOF (WNDCLASSEX));
PWndProc = m_wc.lpfnwndproc;
m_wc.lpszclassname = lps;
m_wc.lpfnwndproc = proc;
}
Else
{
M_wc.hcursor = :: loadcursor (m_bsystemcursor? null: hinst, m_lpszcursorid);
}
m_wc.hinstance = hinst;
m_wc.style & = ~ cs_globalclass; // We don't register global classes
IF (m_wc.lpszclassname == NULL)
{
WSPrintf (m_szautoname, _t ("ATL:% P"), & m_wc);
m_wc.lpszclassname = m_szautoname;
}
WNDCLASSEX WCTEMP;
Memcpy (& WCTEMP, & M_WC, SIZEOF (WNDCLASSEX));
m_atom = (atom) :: getClassInfoEx (m_wc.hinstance, m_wc.lpszclassname, & wctemp);
IF (m_atom == 0)
{
IF (M_UCOMMONRESOURCEID! = 0) // use it if not zero {
M_wc.hicon = (hic) :: loadimage (_Module.getResourceInstance (), makeintresource (m_ucommonresourceid), Image_ICON, 32, 32, LR_DEFAULTCOLOR);
M_wc.hiconsm = (hicon) :: loadImage (_Module.getResourceInstance (), makeintresource (m_ucommonresourceid), image_icon, 16, 16, lr_defaultcolor;
}
m_atom = :: registerclassex; & m_wc
}
}
:: LeavecriticalSection (& _ Module.m_cswindowcreate);
}
IF (m_lpszorigname! = null)
{
Atlassert (PPROC! = Null);
Atlassert (PWndProc! = Null);
* pproc = pwndproc;
}
Return m_atom;
}
}
Don't take a lot of seven-eight-tunda, the key part is m_atom = :: registerclassex (& m_wc); obvious, this sentence completed the registration of the true window class. And use the m_atom tag to be registered. With regard to the registration of the window class, we must also pay attention to a key point, that is the address of WndProc. Out of the way, it is STARTWINDOWPROC. Ok, here, the registration of the window class has been completed. the following:
Creation of the window
CframeWindowImplbase
This is the definition of this function:
HWnd Create (HWND HWNDPARENT, _U_RECT RECT, LPCTSTSTSZWINDOWNAME, DWORD DWSTYLE, DWORD DWEXSTYLE, _U_MENUORID MENUORID, Atom Atom, LPVOID LPCREATEPARAM)
{
Atlassert (m_hwnd == null);
IF (atom == 0)
Return NULL;
_Module.addcreateWnddata (& m_thunk.cd, this);
IF (menuorid.m_hmenu == null && (dwstyle & ws_child))
Menuorid.m_hmenu = (hmenu) (uint_ptr) this;
IF (Rect.m_lprect == Null)
Rect.m_lpRect = & TBASE :: rcdefault;
HWND hWnd = :: CreateWindowEx (dwExStyle, (LPCTSTR) (LONG_PTR) MAKELONG (atom, 0), szWindowName, dwStyle, rect.m_lpRect-> left, rect.m_lpRect-> top, rect.m_lpRect-> right - rect.m_lpRect -> Left, Rect.m_lpRect-> Bottom-Rect.m_lpRect-> Top, hwndparent, menuorid.m_hmenu, _Module.getModuleInstance (), lpcreateparam); atlassert (m_hwnd == hwnd);
Return hwnd;
}
IF (atom == 0)
Return NULL;
Check if the window class is registered correctly. Then the creation work of CreateWindowex substance. The parameter window in it is (LPCTSTR) (long_ptr) makelong (atom, 0). So here, the window class name is not used. The Atom returned when the registration window class is used as the corresponding function. This is very different from MFC practices.
So far, the window class has been registered and created a window. Let's go in RUN:
INT Run (LPTSTR / * LPSTRCMDLINE * / = null, int ncmdshow = sw_showdefault)
{
CMessageloop theloop;
_Module.AddMessageloop (& Theloop);
CMAINFRAME WNDMAIN;
IF (WNDMAIN.CREATEX () == NULL)
{
ATLTRACE (_T ("Main Window Creation Failed! / N")));
Return 0;
}
WNDMAIN.SHOWINDOW (NCMDSHOW);
INT nret = theloop.run ();
_Module.RemoveMessageloop ();
Return nret;
}
Message loop
AddMessageloop and RemoveMessageloop hook the theloop to the module (program) object or remove.
The core of the problem now is the processing of the message loop. THELOOP.Run (); let's see the definition of CMessageloop's Run:
Int run ()
{
Bool bdoidle = true;
INT NIDECOUNT = 0;
BOOL BRET;
For (;;)
{
While (! :: PeekMessage (& M_MSG, NULL, 0, 0, PM_NOREMOVE) && bdoidle)
{
IF (! OnIdle (NidleCount ))
BDOIDLE = FALSE;
}
Bret = :: getMessage (& M_MSG, NULL, 0, 0);
IF (BRET == -1)
{
Atltrace2 (Atltraceui, 0, _t (":: getMessage returned -1 (error) / n"));
Continue; // Error, Don't Process
}
Else if (! BRET)
{
ATLTRACE2 (Atltraceui, 0, _T ("cMessageloop :: run - exiting / n"));
Break; // wm_quit, EXIT Message Loop
}
IF (! PretranslateMessage) {
:: TranslateMessage (& M_MSG);
:: DispatchMessage (& M_MSG);
}
IF (ISIDEMESSAGE))
{
BDOIDLE = True;
Nidlecount = 0;
}
}
Return (int) m_msg.wparam;
}
Very simple, use PeekMessage to determine if there is a message that needs to be processed, and then performs regular translation and distribution of messages that need to be processed. Among them, there is an opportunity to handle free time.
Then the message loop has started, now I have to pay attention to where to deal with the message?
Message process
The front is a small dish, which is very clear. I have encountered a big problem here. We recall the WndProc recorded in WNDCLASS is StartWndProc. No matter how, the message is entered at the beginning is this function. Grab it, there is hope:
Template
LResult Callback CWindowImplbaset
StartWindowProc (HWND HWND, UINT UMSG, WPARAM WPARAM, LPARAM LPARAM)
{
CWindowImplbaset
Atlassert (PTHIS! = Null);
Pthis-> m_hwnd = hwnd;
Pthis-> m_thunk.init (pthis-> getwindowproc (), pthis);
WndProc Pproc = (WndProc) & (PTHIS-> m_thunk.thunk);
WndProc PoldProc = (WndProc) :: SetWindowlong (hwnd, gwl_wndproc, (long) pproc
#ifdef _Debug
// Check if somebody Has Subclassed US Already Since We Discard IT
IF (PoldProc! = StartWindowProc)
ATLTRACE2 (AtltraceWindowing, 0, _T ("Subclassing Through a hook discarded./n"));
#ELSE
PoldProc; // avoid unused warning
#ENDIF
Return PPROC (HWND, UMSG, WPARAM, LPARAM);
}
First let's see SetWindowlong, know what this is to do? Setwindowlong changes some of the basic properties of the window. GWL_WNDPROC indicates that the address of WndProc is to be changed. ^ _ ^, I know why I want to see this first. This step is to change WndProc to "correct". That is, PPROC.
WndProc Pproc = (WndProc) & (PTHIS-> m_thunk.thunk);
This is the function of WndProc will be changed after the startWindowProc is executed. PTHIS is taken from _Module. The information taken is recorded when the window is created. For clarity, no matter it, it is a CWindowImplbaset type pointer.
Now I have to understand that M_thunk is a scorpion. m_thunk defines a variable of CWndProctHunk in the base class of CWindowImplbaset. Let's see CWndProcthunk: Class CWndProcthunk
{
PUBLIC:
union
{
_Atlcreatewnddata cd;
_WndProcthunk Thunk;
}
Void Init (WndProc Proc, Void * Pthis)
{
#if defined (_m_ix86)
Thunk.m_mov = 0x042444c7; file: // c7 44 24 0C
Thunk.m_this = (dword) PTHIS;
Thunk.m_jmp = 0xE9;
Thunk.m_relproc = (int) proc - ((int) this sizeof (_wndprocthunk));
#elif defined (_M_ALPHA)
Thunk.ldah_at = (0x279f0000 | HiWord (PROC)) (Loword >> 15);
Thunk.ldah_a0 = (0x261F0000 | HiWord (PTHIS)) (Loword (PTHIS) >> 15);
Thunk.lda_at = 0x239c0000 | Loword (Proc);
Thunk.lda_a0 = 0x22100000 | Loword (PTHIS);
Thunk.jmp = 0x6bfc0000;
#ENDIF
// Write Block from data cache and
File: // flush from instruction cache
FlushinstructionCache (GetCurrentProcess (), & Thunk, Sizeof (Thunk);
}
}
Horror, actually appeared. Basic ideas are to prepare a machine code through init, and then put the address of the machine code as a function address. This machine code does two things, one is to replace the WndProc's HWND parameter to pTHIS, and the other is to jump to the true WndProc in the corresponding window.
PTHIS-> getWindowProc ()
This code returns to the place where the actual processing message is actually processed. Now look at this function:
Virtual WndProc getWindowProc ()
{
Return windowProc;
}
Template
LResult Callback CWindowImplbaset
WindowProc (HWND HWND, UINT UMSG, WPARAM WPARAM, LPARAM LPARAM)
{
CWindowImplbaset
// set a ptr to this message and save the old value
Msg msg = {pthis-> m_hwnd, umsg, wparam, lparam, 0, {0, 0}};
Const msg * Poldmsg = pthis-> m_pcurrentmsg;
PTHIS-> M_PCURRENTMSG = & msg;
// Pass to the message map to process
LRESULT LRES;
Bool Bret = Pthis-> ProcessWindowMessage (PTHIS-> M_HWND, UMSG, WPARAM, LPARAM, LRES, 0); // RESTORE SAVED VALUE for the CURRENT Message
Atlassert (pthis-> m_pcurrentmsg == & msg);
PTHIS-> M_PCURRENTMSG = POLDMSG;
// do the default processing if message was not handled
IF (! bret)
{
IF (UMSG! = WM_NCDESTROY)
Lres = pthis-> DefWindowProc (UMSG, WPARAM, LPARAM);
Else
{
// unsubclass, if nesered
Long PfnWndProc = :: getwindowlong (pthis-> m_hwnd, gwl_wndproc);
Lres = pthis-> DefWindowProc (UMSG, WPARAM, LPARAM);
IF (pthis-> m_pfnsuperwindowproc! = :: DefwindowProc &&: getWindowlong (pthis-> m_hwnd, gwl_wndproc) == PfnWndProc)
:: setWindowlong (pthis-> m_hwnd, gwl_wndproc, (long) pthis-> m_pfnsuperwindowproc);
// Clear Out Window Handle
HWND HWND = PTHIS-> M_HWND;
PTHIS-> M_HWND = NULL;
// Clean Up After Window Is Destroyed
PTHIS-> OnfinalMessage (hwnd);
}
}
Return Lres;
}
It can be seen that several twists and turns will eventually fall into the processWindowMessage of derived class. The simulated virtual function is also used here. There is also a question is that I didn't write processWindowMessage in CMAINFRAME? But you wrote
Begin_MSG_MAP (CMAINFRAME)
Message_handler (WM_CREATE, ONCREATE)
Command_id_handler (ID_APP_EXIT, ONFILEEXIT)
Command_id_handler (id_file_new, onfilenew)
Command_id_handler (ID_FILE_OPEN, ONFILEOPEN)
Command_id_handler (id_view_toolbar, onviewtoolbar)
Command_id_handler (id_view_status_bar, onviousstatusbar)
Command_id_handler (ID_APP_ABOUT, ONAPPABOUT)
CHAIN_MSG_MAP (CUPDATEUI
CHAIN_MSG_MAP (CFrameWindowImpl
END_MSG_MAP ()
These things are actually ProcessWindowMessage, they are macro.
#define begin_msg_map (theclass) /
PUBLIC: /
Bool ProcessWindowMessage (HWND HWND, UINT UMSG, WPARAM WPARAM, LPARAM LPARAM, LRESULT & LRESULT, DWORD DWMSGMAPID = 0) / {/
Bool bhandled = true; /
HWND; /
UMSG; /
WPARAM; /
LPARAM; /
LRESULT; /
Bhandled; /
Switch (dwmsgmapid) /
{/
Case 0:
#define message_range_handler (Msgfirst, MSGLAST, FUNC) /
IF (umsg> = msgfirst && umsg <= msglast) /
{/
Bhandled = true; /
Lresult = func (UMSG, WPARAM, LPARAM, BHANDLED); /
IF (bhandled) /
Return True; /
}
#define command_id_handler (ID, FUNC) /
IF (UMSG == WM_COMMAND && ID == Loword (WPARAM)) /
{/
Bhandled = true; /
Lresult = func (HiWord (WPARAM), LOWORD (WPARAM), (HWND) LPARAM, BHANDLED); /
IF (bhandled) /
Return True; /
}
#define chain_msg_map (thechainclass) /
{/
IF (ThechainClass :: ProcessWindowMessage (HWND, UMSG, WPARAM, LPARAM, LRESULT)) /
Return True; /
}
#DEFINE END_MSG_MAP () /
Break; /
DEFAULT: /
Atltrace2 (AtltraceWindowing, 0, _T ("INVALID Message Map ID), DWMSGMapID; /
Atlassert (false); /
Break; /
} /
Return False; /
}
At this point, everything understands. In fact, WTL is just an extension of the window part of the ATL, and most of the things analyzed here are ATL. And this part of the content is also described in detail in "ATL INTERNAL".
Welcome to my Personal Home http://www.noasia.net/taoen