Author: Zeeshan Amjad
Original link:
http://www.codeproject.com/atl/atl_underthehood_5.asp
Click here to download this article supporting source code
Introduction
Many people think that ATL is only used to write COM components, in fact, you can also use window classes in ATL to create a window-based application. Although you can convert MFC-based programs to ATL, it is too small for the UI (Transplement: User Interface) component in ATL. So, this requires you to write a lot of code yourself. For example, there is no document / view in the ATL, so you need yourself when you want to use it. In this article, we will explore some secrets on window classes, as well as secrets achieved by ATL technology. WTL (Window Template Library), although until the present (translation: This article is published in CodeProject on October 27, 2002) is not supported by Microsoft, but it hits a big step in making graphics applications. . WTL is based on ATL-based window classes.
Let us start from a classic Hello World program before you start discussing ATL-based programs. This program is completely written in SDK, and there are almost everyone in us, it is already familiar with it.
Program 66.
#include
Case WM_DESTROY: PostquitMessage (0); Break;} Return DefWindowProc (HWND, UMSG, WPARAM, LPARAM);} This program has no fresh things, which is displayed, and the Hello World is displayed in the window. ATL is an object-oriented development library, that is, you can use classes to do work. Let us try to do some of the same work, write some minor classes to make our work simpler. Ok, then we will write some classes to simplify your job - but should write these classes should follow one of the standards? In other words, how many classes need to be written, what is their relationship, and what kind of methods and attributes have. Here I don't intend to discuss the entire object-oriented theory, we just write a high quality library. In order to make my task, I grouped the associated API and put these associated APIs in a class. I put all the APIs of all handle windows in a class, and it can be associated with other APIs, such as fonts, files, menus, and more. So I wrote a small class and put all the first parameters for the API of the HWND in this class. That is, this class is simply a layer of packaging on the window API. My class name is zwindow, of course you can freely choose the name you like. This class is similar to this:
class ZWindow {public: HWND m_hWnd; ZWindow (HWND hWnd = 0): m_hWnd (hWnd) {} inline void Attach (HWND hWnd) {m_hWnd = hWnd;} inline BOOL ShowWindow (int nCmdShow) {return :: ShowWindow (m_hWnd, Ncmdshow);} inline Bool UpdateWindow () {return :: UpdateWindow (m_hwnd);}}
Here, I only packaged the current API you need. You can add all APIs to this class. The only advantage for this class is that you don't have to pass the HWND parameters like the API, this class itself will pass this parameter.
Hey, there is nothing special until now. However, what should our window callback function? Keep in mind that the first parameter of this callback function is also hwnd, so for our criteria, it should also be members in this class. So, I also added our callback function. Now, this class should be similar to this:
class ZWindow {public: HWND m_hWnd; ZWindow (HWND hWnd = 0): m_hWnd (hWnd) {} inline void Attach (HWND hWnd) {m_hWnd = hWnd;} inline BOOL ShowWindow (int nCmdShow) {return :: ShowWindow (m_hWnd, nCmdShow);} inline BOOL UpdateWindow () {return :: UpdateWindow (m_hWnd);} LRESULT CALLBACK WndProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {switch (uMsg) {case WM_DESTROY: PostQuitMessage (0); break } Return :: DefWindowProc (HWND, UMSG, WPARAM, LPARAM);}}; you need to provide this callback function address for a domain of WNDCLASS or WNDCLASSEX. Also, you need to assign a value like this after creating the zwindow class object: zwindow zwnd; wndclass wnd; wnd.lpfnwndproc = wnd.Wndproc; but when you compile the program, the compiler gives a mistake similar to this:
can not convert from 'long (__stdcall ZWindow :: *) (struct HWND__ *, unsigned int, unsigned int, long)' to 'long (__stdcall *) (struct HWND__ *, unsigned int, unsigned int, long) because you can not Transfer member functions as a callback function. why? Because the compiler will automatically pass a parameter to the member function, this parameter is a pointer to this class, or in other words is the THIS pointer. So this means that when you pass N parameters in a member function, the compiler will pass N 1 parameters, and the additional parameter is this pointer. This error message indicates that the compiler cannot convert member functions to a global function.
So, if we want to use the member function as a callback function, what should I do? If we tell the compiler, do not pass the first THIS pointer parameter, then we can use the member function as a callback function. In C , if we declare the member function as static, then the compiler will not pass the THIS pointer. This is the STATIC and non-Static member functions substantially different.
So, we can declare the WndProc in the Zwindow class as a Static member function. This technology can also be used in multithreading, such as when you want to use the member function as a thread function, you can use a Static member function as a thread function.
Below is an update program for the Zwindow class.
Program 67.
#include
While (GetMessage (& MSG, NULL, 0, 0)) {DispatchMessage (& MSG);} Return Msg.wParam;} This program is just simply demonstrating Zwindow usage, telling the truth, this class will not do anything special . It is just a layer of packaging for Windows API. The only advantage is that you don't need to pass HWnd parameters, but you have to enter the name of the object when calling the member function. For previous, you call the function like this:
ShowWindow (HWND, NCMDSHOW); now you can do this:
Zwnd.showWindow (ncmdshow); until now, this is not a significant advantage.
Let's take a look at how to handle window messages in WndProc. In the previous program, we only handle a function, which is WM_DESTROY. If you want to handle more messages, you can add more Case in the Switch statement. Let us modify WndProc to handle WM_PAINT. Just like this:
Switch (umsg) {case wm_paint: hdc = :: beginpaint (hwnd, & ps); :: getClientRect (hwnd, & review); :: DrawText (HDC, "Hello World", -1, & review, dt_center | dt_vcenter dt_singeline; :: EndPaint (hwnd, & ps); Break; Case WM_DESTROY: :: PostquitMessage (0); Break;}
This code is correct, it will show hello world in the window of the window. But why do you use BeGinPaint, getClientRect and Endpaint these API? Based on our standards, these APIs should be used as a member function of ZWindow - their first parameters are hwnd.
Because all these functions are non-Static functions. Also, you cannot call non-Static member functions in the Static member function. why? Because their difference is the THIS pointer, the non-Static member function has this pointer, and the Static function is not. If we pass a THIS pointer to the Static member function through some means, then we can call non-Static member functions in the Static member function. Let's take a look at the procedures below.
Program 68.
#include
STATICFUNNSTACFUN, so we can use the same technique here, that is, put the address of the ZWindow object in a global variable, then use this pointer to call the non-Static member function. Below is an updated version of the previous program, where we did not directly call the API. Program 69.
#include
} Return :: DefWindowProc (hWnd, uMsg, wParam, lParam);}}; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {char szAppName [] = "Hello world"; MSG msg; WNDCLASS wnd ; ZWindow zwnd; wnd.cbClsExtra = NULL; wnd.cbWndExtra = NULL; wnd.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH); wnd.hCursor = LoadCursor (NULL, IDC_ARROW); wnd.hIcon = LoadIcon (NULL, IDI_APPLICATION); wnd.hInstance = hInstance; wnd.lpfnWndProc = zwnd.WndProc; wnd.lpszClassName = szAppName; wnd.lpszMenuName = NULL; wnd.style = CS_HREDRAW | CS_VREDRAW; if {MessageBox (NULL, "Can not (RegisterClass (& wnd)!) register window class "," Error ", MB_OK | MB_ICONINFORMATION); return -1;} g_pWnd = & zwnd; zwnd.Create (szAppName," Hell world ", hInstance); zwnd.ShowWindow (nCmdShow); zwnd.UpdateWindow (); While (GetMessage (& MSG, NULL, 0, 0)) {DispatchMessage (& MSG);} Return Msg.wParam;}, we finally have this program that works. Now let us use object-oriented programming. If we call the function for each message, and make these functions become virtual functions, then we can call these functions after inheriting the Zwindow class. So, we can customize the default behavior of ZWindow. Now, WndProc is similar to this:
static LRESULT CALLBACK WndProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {ZWindow * pThis = g_pWnd; switch (uMsg) {case WM_CREATE: pThis-> OnCreate (wParam, lParam); break; case WM_PAINT: pThis-> Onpak; Case WM_DESTROY: :: PostquitMessage (0); Break;} Return :: DefWindowProc (HWND, UMSG, WPARAM, LPARAM);} Here, oncreate and onpaint are virtual functions. And, when we inherit a class from Zwindow, we can override all of these functions we want to customize. Here is a complete program that demonstrates the use of WM_Paint messages in derived classes. Program 70.
#include
Switch (UMSG) {CASE WM_CREATE: PTHIS-> Oncreate (WPARAM, LPARAM); Break; Case WM_Paint: Pthis-> Onpaint (WPARAM, LPARAM); Break; Case WM_DESTROY: :: PostquitMessage (0); Break;} Return: : DefWindowProc (hWnd, uMsg, wParam, lParam);}}; class ZDriveWindow: public ZWindow {public: LRESULT OnPaint (WPARAM wParam, LPARAM lParam) {HDC hDC; PAINTSTRUCT ps; RECT rect; hDC = BeginPaint (& ps); GetClientRect (& Rect); SetBkMode (HDC, Transparent); DrawText (HDC, "Hello World from Drive", -1, & Rect, Dt_Center | DT_VCenter | DT_SINGLINE); Endpaint (& PS); Return 0;}}; program output is a A "Hello World from Drive" message in the window. Before we use derived classes, you can say that everything is smooth. When we derive more than one class from Zwindow, the problem will happen. In this way, all messages will flow to the last inherited of Zwindow. Let's take a look at the following procedures.
Program 71.
#include
} Virtual LRESULT OnCreate (WPARAM wParam, LPARAM lParam) {return 0;} virtual LRESULT OnKeyDown (WPARAM wParam, LPARAM lParam) {return 0;} static LRESULT CALLBACK StartWndProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {ZWindow * PTHIS = g_pwnd; if (umsg == wm_ncdestroy) :: postquitMessage (0); switch (umsg) {copy wm_create: pthis-> oncreate (wparam, lparam); Break; Case WM_Paint: pthis-> onpaint (wparam, lparam ); break; case WM_LBUTTONDOWN: pThis-> OnLButtonDown (wParam, lParam); break; case WM_KEYDOWN: pThis-> OnKeyDown (wParam, lParam); break; case WM_DESTROY: :: PostQuitMessage (0); break;} return :: DefWindowProc (HWND, UMSG, WPARAM, LPARAM);}}; Class ZDRIVEWINDOW1: Public Zwindow {public: LRESULT ONPAI nt (WPARAM wParam, LPARAM lParam) {HDC hDC; PAINTSTRUCT ps; RECT rect; hDC = BeginPaint (& ps); GetClientRect (& rect); :: SetBkMode (hDC, TRANSPARENT); :: (hDC DrawText, "ZDriveWindow1", - 1, & rect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); EndPaint (& ps); return 0;} LRESULT OnLButtonDown (WPARAM wParam, LPARAM lParam) {:: MessageBox (NULL, "ZDriveWindow1 :: OnLButtonDown", "Msg", MB_OK); Return 0;}}; Class ZDRIVEWINDOW2: PUBLIC ZWINDOW {PUBLIC: LRESULT OnPAINT (WPARAM WPARAM, LPARAM LPARAM) {HDC HDC; PAINTSTRUCT PS; Rect Rect; HDC =
Beginpaint (& PS); getClientRect (& Re) ;: setBkmode (hdc, transparent); :: Rectangle (HDC, Rect.Left, Rect.top, Rect.right, Rect.Bottom); :: DrawText (HDC, "ZDRIVEWINDOW2 ", -1, & rect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); EndPaint (& ps); return 0;} LRESULT OnLButtonDown (WPARAM wParam, LPARAM lParam) {:: MessageBox (NULL," ZDriveWindow2 :: OnLButtonDown "," Msg ", MB_OK); return 0;}}; int WINAPI WinMain (hINSTANCE hInstance, hINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {char szAppName [] = "Hello world"; MSG msg; WNDCLASS wnd; ZDriveWindow1 zwnd1; ZDriveWindow2 zwnd2; wnd. cbClsExtra = NULL; wnd.cbWndExtra = NULL; wnd.hbrBackground = (HBRUSH) GetStockObject (GRAY_BRUSH); wnd.hCursor = LoadCursor (NULL, IDC_ARROW); wnd.hIcon = LoadIcon (NULL, IDI_APPLICATION); wnd.hInstance = hInstance; Wnd.lpfnwndproc = zwindow :: Startwn dProc; wnd.lpszClassName = szAppName; wnd.lpszMenuName = NULL; wnd.style = CS_HREDRAW | CS_VREDRAW; if {MessageBox (NULL, "Can not register window class", "Error", MB_OK | MB_ICONINFORMATION (RegisterClass (& wnd)!) ); return -1;} g_pWnd = & zwnd1; zwnd1.Create (szAppName, "Hell world", hInstance); zwnd1.ShowWindow (nCmdShow); zwnd1.UpdateWindow (); g_pWnd = & zwnd2; zwnd2.Create (szAppName, "Hello World ", hinstance, zwnd1.m_hwnd, ws_visible | ws_child | ES_MULTILINE, NULL, NULL, 0, 0, 150, 150);
While (GetMessage (& MSG, NULL, 0, 0)) {DispatchMessage (& MSG);} Return Msg.wparam;} The output of the program shows that no matter which window you click, you will pop up the same MessageBox.
No matter which window you click, you will get the same message box. This means that the message is not passed to the appropriate window. In fact, each window has its own window procedure, these window procedures handle all messages in the window. But here, we use the first window to use the second window's callback function, so we cannot handle the messages of the first window.
Now, our most important problem is to associate the window's callback function and the corresponding window. This means that hwnd should be associated with the corresponding derived class, so the message should be sent to the correct window. There are several ways to solve this problem, let's take a look at it.
First of all, I came up with a most obvious solution, we can easily realize it. The method is to create a global structure that stores HWND and the corresponding derived class. However, this method has two major problems. First, this structure will be more and large in the process of gradually joining the program; second, after the structure has become great, searching in this structure will definitely spend a lot of time.
The most important purpose of ATL is to make the procedure as small and fast as possible. Also, the above technique is not reached for both standards. This method is not only single, and it will occupy a large amount of memory in the case of a large number of windows.
Another possible solution is the use of WNDCLASS or WNDCLASSEX structures. There is also a question is why you don't have to use CBCLSEXTRA, but use CBWndextra? The answer is simple, CBClsextra stores additional bytes for each window class, while CbWndextra stores additional bytes for each window. Also, you may create multiple windows from a window class so that if you use Cbclsextra, then you can't distinguish between CBClsextra distinguish between different callback functions, because the window generated by these same window classes is this value is the same. The corresponding derived class address is then stored in CBWndextra.
This and method look better than the first, but it still has two problems. First, if the user wants to use CBWndextra, then he / she may cover the data used by the technology so that customers need to pay very attention when using CBWndextra, to prevent loss of information. So well, you can write in the documentation Do not use CBWndextra when you use your library, but there will still be a problem: this method is not very fast, once again violates the ATL rules --ATL should be as small as possible And fast.
ATL does not use any of these two methods, and the method used is called THUNK. Thunk is a small series of code and this term is used in different places. You may have heard two THUNKING:
Universal Thunking
Universal Thunking allows 32-bit functions to be called in the 16-bit code, which can be used under WIN 9X and WIN NT / 2000 / XP, also known as generic thunking.
General Thunking
General thunking allows the 16-bit function to be called in the 32-bit code, which can only be used in Win 9x because Win NT / 2000 / XP is a pure 32-bit operating system, so the function of calling 16 bits in 32-bit code is not logic. General Thunking is also known as Flat Thunking.
ATL does not use these two methods because you will not mix 16-bit and 32-bit code in ATL. In fact, ATL inserts a small piece of code to call the correct window process. Before studying ATL's Thunking, let's start with some foundation concepts. Please see the simple program below.
Program 72.
#include
Size of character = 1Size of integer = 4size of structure = 8 A sum of the size and one character should be 5 instead of 8. So let us change the program, add a member variable to see what will happen.
Program 73.
#include
Program 74.
#include
Address of Ch1 = 4683576Address of CH2 = 4683577Address of int = 4683580 This is due to structural and fellowship words. If you pay attention to observation, you can infer that each variable outside the structure is stored on an address that can be 4 completely, this is to improve the performance of the processor. Therefore, the structure here allocates 4 integral memory space, which is 4683576, CH1 and it has the same address. After the CH2 member is stored in this position, INT i is stored at a position 4683580. This location is not 4683578 because it cannot be divided by 4. What is the current problem? What is the location of 4683578 and 4683579? The answer is that if the variable is a local variable, then this is a spam value; if it is static or global variable, it is 0. Let's take a look at this program to better understand this. Program 75.
#include
The output of the AB ... 10 programs clearly indicates that there are garbage values in those spaces, just like the table below.
Now, what should we do if we don't want to waste those spaces? There are two options: or use the compiler switch / zp, or use the #PRAGMA statement before the declaration structure.
Program 76.
#include
SIZE OF STRUCTURE = 5
This means that there is no word to be aligned. In fact, ATL uses this technology to make Thunk. ATL uses a structure, this structure does not use word alignment, and this structure directly stores machine code of the microprocessor.
#pragma pack (push, 1) // Storage machine code structure Struct thunk {byte m_jmp; // JMP instruction operand DWORD M_RELPROC; // relative to JMP}; # prgma pack (pop) This type of structure is saved. Thunk code, it can be performed when not working. Let's take a look at the simple situation below, we will use Thunk to perform the functions we want to perform.
Program 77.
#include
· CallbackFun
INIT (Initialization Thunk)
· Get the THUNK address
· Execute Thunk
· Thunk code call staticfun
ATL also uses the same technique to call the correct callback function, but it has made a thing before calling the function. Now Zwindow has a virtual function ProcessWindowMessage, which does nothing in this class. However, each derived class of Zwindow needs to override it to handle your own message. The entire processing process is the same, and the virtual function of the Zwindow's derived class address is stored and the derived class is called, but now the name of WindowProc is StartWndProc. Here, ATL uses this technology to replace the HWND parameter to the THIS pointer. However, how is HWND, is we losing it? In fact, we have stored HWND in the Member variable of the Zwindow class.
To achieve this, ATL uses a larger structure than the previous program.
#pragma pack (push, 1) Struct_WndPROCTHUNK {DWORD M_MOV; // MOV DWORD PTR [ESP 0x4], PTHIS (ESP 0x4 IS HWND) DWORD M_THIS; BYTE M_JMP; // JMP WndProc DWORD M_RELPROC; // Relative JMP }; # prgma pack (POP) and, in initialization time, write an operation code "MOV DWORD PTR [ESP 4], PTHIS". Similar to this: void init (WndProc Proc, void * pthis) {thunk.m_mov = 0x042444c7; // C7 44 24 04 thunk.m_this = (dword) pthis; thunk.m_jmp = 0xe9; thunk.m_relproc = (int) Proc - ((int) This sizeof (_WndPROCTHUNK)); FlushinstructionCache (GetCurrentProcess (), & Thunk, Sizeof ());} and, after initializing the Thunk code, get the address of the THUNK and set a new callback function to the Thunk code . Then, the Thunk code will call WindowProc, but now the first parameter is not hwnd, in fact it is the THIS pointer. So we can convert it secure to zwindow * and call the ProcessWindowMessage function.
static LRESULT CALLBACK WindowProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {ZWindow * pThis = (ZWindow *) hWnd; if (uMsg == WM_NCDESTROY) PostQuitMessage (0); if (pThis-> ProcessWindowMessage (pThis-! > M_HWND, UMSG, WPARAM, LPARAM) RETURN :: DefWindowProc (PTHIS-> M_HWND, UMSG, WPARAM, LPARAM); ELSE RETURN 0;} Now, each window The correct window procedure can be called. The entire process is shown below:
Due to the length of code, the complete code of the program will be provided with this supporting set. I hope to continue to explore other secrets of ATL in the article after this series.