WIN32 API —— the simplest Windows window encapsulation class [easy to understand]

1 Development language choice

1.1 Choose C or C++ as the language for developing Win32 programs

After deciding to abandon MFC and use pure Win32 API to develop Window desktop programs, there is still a choice of language, which is whether to use C++. As a superset of C, C++ can realize all the functions that C can realize. In fact, the reverse is also true. C itself can also complete the part of the function that C++ exceeds, but it may require more lines of code. As far as I understand,

  • For huge projects, it is more secure to use pure C to structure;
  • For small and medium-sized projects, C++ may be more convenient and faster. Since I am currently working on small and medium-sized projects, I decided to use C++ as the main development language.

1.2 Selection of C++ Feature Sets

After deciding to use C++, there is another crucial choice, and that is the choice of C++ feature set. C++ is too complicated. In addition to supporting all the development modes of its ancestor C, it also supports object-based development (OB), object-oriented development (OO), and template technology. It can be said that C++ is a truly versatile language, which also results in the high complexity of C++. Using different development models is equivalent to using different programming languages. As far as I am concerned, I have no experience with C++ template programming at all. Based on past experience and lessons and my mastery of C++, I decided to:

  • Use object-based and object-oriented development modes. If a function can be realized by both, object-based is preferred. The technical point of view inclined to OB comes from the development experience of Apple's Object-C.
  • Try to avoid multiple inheritance, this point of view comes from Java and. Net development experience.
  • Data structures and containers, using the C++ Standard Template Library (STL), template programming itself is complex, but using STL is very easy.

2 Encapsulation class of Windows window object

For Windows desktop programs, the concept of Window and Message is the core. The first thing that needs to be encapsulated is the window. For example, MFC encapsulates the window object with the CWnd class. The reason why we abandoned MFC at the beginning was because it was too complicated and difficult to understand, so the encapsulation of basic window objects must be as simple as possible.

2.1 Encapsulation principles

The first principle is "simple". The functions that can be realized directly by using a Win32 API will never be repackaged. For example, the MoveWindow() function can be used to move the window, and the MoveWindow() function with the same function should not appear in the class. There are many such repetitive functions in MFC. In fact, it is only possible to write one less hwnd parameter, but it adds an extra layer of calls. I just want the HWND handle to appear everywhere and never hide it, because this concept is too important for Windows, and developers should not ignore it when using any wrapper class.

Secondly, when the same function can be realized by multiple technologies, the technology that is easy to understand should be preferred. "Comprehensibility" is more important than "operating efficiency".

2.2 Source code

Header file XqWindow.h

[cpp

  1. #pragma once
  2. #include <vector>
  3. class XqWindow
  4. {
  5. public:
  6. XqWindow(HINSTANCE hInst);
  7. ~XqWindow();
  8. private:
  9. HWND hWnd; // external read-only, to ensure security
  10. HINSTANCE hInstance;
  11. public:
  12. // return window object handle
  13. HWND GetHandle();
  14. // message processing. If the subsequent default processing is required, it needs to return 0; if the subsequent processing of the message is stopped, it returns 1
  15. virtual int HandleMessage(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
  16. private:
  17. // original window procedure
  18. static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
  19. private:
  20. // registered class collection
  21. static std::vector<void*> registeredClassArray;
  22. public:
  23. // create window
  24. void Create();
  25. };

Implementation file XqWindow.cpp

[cpp

  1. #include “stdafx.h”
  2. #include “XqWindow.h”
  3. std::vector<void*> XqWindow::registeredClassArray;
  4. // create window
  5. void XqWindow::Create()
  6. {
  7. wchar_t szClassName[32];
  8. wchar_t szTitle[128];
  9. void* _vPtr = *((void**)this);
  10. ::wsprintf(szClassName, L“%p”, _vPtr);
  11. std::vector<void*>::iterator it;
  12. for (it = registeredClassArray.begin(); it != registeredClassArray.end(); it++) // determine whether the class of the object has been registered
  13. {
  14. if ((*it) == _vPtr)
  15. break;
  16. }
  17. if (it == registeredClassArray.end()) // if not registered, register
  18. {
  19. //register window class
  20. WNDCLASSEX wcex;
  21. wcex.cbSize = sizeof(WNDCLASSEX);
  22. wcex.style = CS_HREDRAW | CS_VREDRAW;
  23. wcex.lpfnWndProc = XqWindow::WndProc;
  24. wcex.cbClsExtra = 0;
  25. wcex.cbWndExtra = 0;
  26. wcex.hInstance = this->hInstance;
  27. wcex.hIcon = NULL;
  28. wcex.hCursor = ::LoadCursor(NULL, IDC_ARROW);
  29. wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
  30. wcex.lpszMenuName = NULL;
  31. wcex.lpszClassName = szClassName;
  32. wcex.hIconSm = NULL;
  33. if (0 != ::RegisterClassEx(&wcex)) // add the successfully registered class to the linked list
  34. {
  35. registeredClassArray.push_back(_vPtr);
  36. }
  37. }
  38. // create window
  39. if (this->hWnd == NULL)
  40. {
  41. ::wsprintf(szTitle, L"window class name (C++ virtual table pointer): %p", _vPtr);
  42. HWND hwnd = ::CreateWindow(szClassName,
  43. szTitle,
  44. WS_OVERLAPPEDWINDOW,
  45. 0, 0, 800, 600,
  46. NULL,
  47. NULL,
  48. hInstance,
  49. (LPVOID)this
  50. );
  51. if (hwnd == NULL)
  52. {
  53. this->hWnd = NULL;
  54. wchar_t msg[100];
  55. ::wsprintf(msg, L"CreateWindow() failed: %ld", ::GetLastError());
  56. ::MessageBox(NULL, msg, L"Error", MB_OK);
  57. return;
  58. }
  59. }
  60. }
  61. XqWindow::XqWindow(HINSTANCE hInst)
  62. {
  63. this->hWnd = NULL;
  64. this->hInstance = hInst;
  65. }
  66. XqWindow::~XqWindow()
  67. {
  68. if ( this->hWnd!=NULL && ::IsWindow(this->hWnd) ) // Destroy the window object before the C++ object is destroyed
  69. {
  70. ::DestroyWindow(this->hWnd); // Tell system to destroy hWnd and Send WM_DESTROY to wndproc
  71. }
  72. }
  73. HWND XqWindow::GetHandle()
  74. {
  75. return this->hWnd;
  76. }
  77. // message processing. If the subsequent default processing is required, it needs to return 0; if the subsequent processing of the message is stopped, it returns 1
  78. int XqWindow::HandleMessage(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
  79. {
  80. return 0;
  81. }
  82. // original window procedure
  83. LRESULT CALLBACK XqWindow::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
  84. {
  85. XqWindow* pObj = NULL;
  86. if (message == WM_CREATE) // When this message is received, assign the window object handle to the C++ object member, and at the same time assign the C++ object address to the window object member
  87. {
  88. pObj = (XqWindow*)(((LPCREATESTRUCT)lParam)->lpCreateParams);
  89. pObj->hWnd = hWnd; // Get HWND here, CreateWindow() has not returned yet.
  90. ::SetWindowLong(hWnd, GWL_USERDATA, (LONG)pObj); // Associate HWND with C++ object through USERDATA
  91. }
  92. pObj = ( XqWindow * )::GetWindowLong ( hWnd , GWL_USERDATA ) ;
  93. switch (message)
  94. {
  95. case WM_CREATE:
  96. pObj->HandleMessage(hWnd, message, wParam, lParam);
  97. break;
  98. case WM_DESTROY:
  99. if (pObj != NULL) // At this point, the window object has been destroyed, and the C++ object is notified by setting hWnd=NULL
  100. {
  101. pObj->hWnd = NULL;
  102. }
  103. break;
  104. default:
  105. pObj = ( XqWindow * )::GetWindowLong ( hWnd , GWL_USERDATA ) ;
  106. if (pObj != NULL)
  107. {
  108. if (pObj->HandleMessage(hWnd, message, wParam, lParam) == 0) // call the message processing virtual function of the subclass
  109. {
  110. return DefWindowProc(hWnd, message, wParam, lParam);
  111. }
  112. }
  113. else
  114. {
  115. return DefWindowProc(hWnd, message, wParam, lParam);
  116. }
  117. break;
  118. }
  119. return 0;
  120. }

2.3 Example of use

The basic usage is to create a TestWindow class, inherit from XqWindow, and then redo the virtual function HandleMessage(). All business processing codes must be called in HandleMessage(). Since this function is a member function, you can directly use this to refer to the members of the TestWindow class object. An example code is as follows:

TestWindow.h

[cpp

  1. #pragma once
  2. #include “XqWindow.h”
  3. class TestWindow :
  4. public XqWindow
  5. {
  6. public:
  7. TestWindow(HINSTANCE);
  8. ~TestWindow();
  9. protected:
  10. int HandleMessage(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
  11. private:
  12. // business data part
  13. int rectWidth;
  14. int rectHeight;
  15. };

TestWindow.cpp

[cpp

#include "stdafx.h" 
#include "TestWindow.h"

TestWindow::TestWindow(HINSTANCE hInst) :XqWindow(hInst) 
{  
	rectWidth = 300;  rectHeight = 200; 
}

TestWindow::~TestWindow() 
{

} 

int TestWindow::HandleMessage(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) 
{  
	PAINTSTRUCT ps;  
	HDC hdc;  
	switch (message)  
	{  
		case WM_PAINT:   
			 hdc = ::BeginPaint(hWnd, &ps);   
			 ::Rectangle(hdc, 0, 0, this->rectWidth, this->rectHeight);   
			 ::EndPaint(hWnd, &ps);   
			 return 1;  
		default:   
		break;  
	}  
	return 0; 
}

Call part:

[cpp

  1. pTest = new TestWindow(theApp.m_hInstance);
  2. pTest->Create();
  3. ::ShowWindow(pTest->GetHandle(), SW_SHOW);
  4. ::UpdateWindow(pTest->GetHandle());

running result:

2.4 Technical points

This XqWindow class makes the smallest package for the window object, and mainly realizes the association between the message processing function and the C++ object. The memory layout is as follows:

A few points that need to be explained:

(1) One-to-one correspondence between C++ classes and window classes. Since VC++ does not enable RTTI by default, and considering code compatibility and operating efficiency, it is not recommended to enable RTTI. Without RTTI support, how can we distinguish all instances of the same class from instances of other classes at runtime? Woolen cloth? Here we use the C++ virtual table pointer, each class with virtual functions has its own independent virtual table, and this virtual table pointer is stored in each instance. Different instances of the same class share a vtable, so this gives us the opportunity to distinguish the C++ class to which the object belongs. Of course, this technique can only be used in classes with virtual functions. For objects of classes without virtual functions, there is no virtual table. For our case, the XqWindow class has a HandleMessage virtual function, so all other subclasses and grandchildren that inherit this class also have their own virtual tables.

Before RegisterClass(), first judge whether the virtual table pointer of the class to which the current C++ object belongs exists in the vptrAraay linked list. If not, register the window class, and store the virtual table pointer in the vptrArray linked list; if it exists, directly use the window class corresponding to the virtual table pointer.

It should be noted that the operation of obtaining the object virtual table pointer value cannot be performed in the XqWindow::XqWindow() constructor, because when this function is executed, the virtual table pointer member of the C++ object has not been set to point to the virtual table of the derived class address (because the subclass's constructor has not been called yet). Therefore, the virtual table pointer value must be obtained after the object construction is completed, which is why Create() cannot be called in the XqWindow() constructor. (I once put Create() in XqWindow() to simplify the call, which resulted in the same virtual table pointers for all objects!)

(2) The relationship between C++ objects and window objects. After the C++ object is created, calling Create() is the only way to bind it to the window object. Until the old window is destroyed, the C++ object can no longer create a new window, and calling Create() multiple times is useless.

The lifetime of the C++ object is also greater than the lifetime of the corresponding window, otherwise the use of the C++ object in the window process will cause illegal access to memory. The life sequence of these two kinds of objects is: C++ object birth—call Create() to generate window object—some reason window object destruction—C++ object destruction.

In order to prevent the C++ object from being destroyed before the window object, in the destructor of the XqWindow class, first destroy the window object through DestroyWindow(). When the window object is destroyed, the hWnd of the C++ object will also be set to NULL to notify the destruction of the C++ object window.

To put it more vividly: the C++ object and the window object are monogamous, and the husband and wife relationship can only be widowed and not divorced, and the C++ object has a long life, and the window object has a short life. Only after a window object dies can the C++ object regenerate a new window. And before the C++ object dies, the window object needs to be killed and buried with it.

(3) Mutual references between C++ objects and window objects. The C++ object refers to the window object through the member variable hWnd, and the window object points to the C++ object through the GWL_USERDATA additional data block. In addition, in order to capture the WM_CRATE message in time and process it in HandleMessage, the assignment of the C++ member hWnd is not after CreateWindow(), but when the original window procedure function processes the WM_CREAT message. This is mainly related to the CreateWindow() principle.

CreateWindow()

{

HWND hwnd = malloc(..);

Initialize the window object;

WndProc(hwnd, WM_CRATE, ..); // The window has been created at this point

other operations;

return hwnd;

}

Similarly, the principle of DestroyWindow() is.

DestroyWindow(hwnd)

{

window object cleanup;

WndProc(hwnd, WM_DESTROY, ..); // The window is no longer visible at this time

other operations;

free(hwnd);

}

2.5 There are problems

Although the XqWindow class can work well, there are some problems:

(1) Since the Window object refers to the C++ object by USERDATA, if other code modifies this data block through SetWindowLong(hwnd, GWL_USERDATA, xxx), the program will crash. How to prevent this damage requires further research.

(2) Use the virtual table pointer of the C++ object, and there is no clear standard for the specific memory layout of this pointer. Once the VC++ compiler modifies the storage location of the virtual table pointer in the future, the program will have problems. However, due to binary compatibility considerations, it is unlikely that VC++ will make such a change.

Guess you like

Origin blog.csdn.net/onebound_linda/article/details/130922852