API WIN32: la clase de encapsulación de ventanas de Windows más simple [fácil de entender]

1 Elección de idioma de desarrollo

1.1 Elija C o C++ como lenguaje para desarrollar programas Win32

Después de decidir abandonar MFC y usar la API pura de Win32 para desarrollar programas de escritorio de Windows, todavía hay una opción de idioma, que es usar C++. Como superconjunto de C, C++ puede realizar todas las funciones que C puede realizar. De hecho, lo contrario también es cierto, C en sí mismo también puede completar la parte de la función que supera C++, pero puede requerir más líneas de código. Hasta donde yo entiendo,

  • Para proyectos grandes, es más seguro usar C puro para estructurar;
  • Para proyectos pequeños y medianos, C++ puede ser más conveniente y rápido. Como actualmente estoy trabajando en proyectos pequeños y medianos, decidí usar C++ como lenguaje principal de desarrollo.

1.2 Selección de conjuntos de características de C++

Después de decidir usar C++, hay otra elección crucial, y es la elección del conjunto de características de C++. C++ es demasiado complicado Además de admitir todos los modos de desarrollo de su antecesor C, también admite el desarrollo basado en objetos (OB), el desarrollo orientado a objetos (OO) y la tecnología de plantillas. Se puede decir que C++ es un lenguaje verdaderamente versátil, lo que también se traduce en la alta complejidad de C++. Usar diferentes modelos de desarrollo es equivalente a usar diferentes lenguajes de programación. En lo que a mí respecta, no tengo ninguna experiencia con la programación de plantillas de C++. Basado en experiencias y lecciones pasadas y mi dominio de C++, decidí:

  • Utilice modos de desarrollo orientados a objetos y basados ​​en objetos. Si ambos pueden realizar una función, se prefiere el basado en objetos. El punto de vista técnico inclinado a OB proviene de la experiencia de desarrollo de Object-C de Apple.
  • Trate de evitar la herencia múltiple, este punto de vista proviene de la experiencia de desarrollo de Java y .Net.
  • Estructuras de datos y contenedores, utilizando la biblioteca de plantillas estándar (STL) de C++, la programación de plantillas en sí es compleja, pero usar STL es muy fácil.

2 Clase de encapsulación del objeto de ventana de Windows

Para los programas de escritorio de Windows, el concepto de Ventana y Mensaje es el núcleo. Lo primero que hay que encapsular es la ventana, por ejemplo, MFC encapsula el objeto ventana con la clase CWnd. La razón por la que abandonamos MFC al principio fue porque era demasiado complicado y difícil de entender, por lo que la encapsulación de objetos de ventana básicos debe ser lo más simple posible.

2.1 Principios de encapsulación

El primer principio es "simple". Las funciones que se pueden realizar directamente mediante el uso de una API de Win32 nunca se volverán a empaquetar. Por ejemplo, la función MoveWindow() se puede usar para mover la ventana, y la función MoveWindow() con la misma función no debe aparecer en la clase. Hay muchas funciones repetitivas de este tipo en MFC. De hecho, solo es posible escribir un parámetro hwnd menos, pero agrega una capa adicional de llamadas. Solo quiero que el identificador HWND aparezca en todas partes y nunca lo oculte, porque este concepto es demasiado importante para Windows y los desarrolladores no deben ignorarlo cuando usan cualquier clase contenedora.

En segundo lugar, cuando varias tecnologías pueden realizar la misma función, debe preferirse la que sea fácil de entender. La "comprensibilidad" es más importante que la "eficiencia operativa".

2.2 Código fuente

Archivo de encabezado XqWindow.h

[cpp

  1. #pragma una vez
  2. #incluir <vector>
  3. clase XqVentana
  4. {
  5. público:
  6. XqWindow(HINSTANCIA hInst);
  7. ~VentanaXq();
  8. privado:
  9. HWND hWnd; // solo lectura externa, para garantizar la seguridad
  10. HINSTANCIA hInstancia;
  11. público:
  12. // devuelve el identificador del objeto de la ventana
  13. HWND GetHandle();
  14. // procesamiento de mensajes. Si se requiere el procesamiento predeterminado posterior, debe devolver 0; si se detiene el procesamiento posterior del mensaje, devuelve 1
  15. virtual int HandleMessage(HWND hWnd, UINT mensaje, WPARAM wParam, LPARAM lParam);
  16. privado:
  17. // procedimiento de ventana original
  18. estático LRESULT CALLBACK WndProc(HWND hWnd, UINT mensaje, WPARAM wParam, LPARAM lParam);
  19. privado:
  20. // colección de clases registradas
  21. static std::vector<void*> RegisterClassArray;
  22. público:
  23. // crear ventana
  24. vacío Crear ();
  25. };

Archivo de implementación XqWindow.cpp

[cpp

  1. #incluir "stdafx.h"
  2. #incluir "VentanaXq.h"
  3. std::vector<void*> XqWindow::registeredClassArray;
  4. // crear ventana
  5. vacío XqWindow::Crear()
  6. {
  7. wchar_t szClassName[32];
  8. wchar_t szTitle[128];
  9. vacío* _vPtr = *((vacío**)este);
  10. ::wsprintf(szClassName, L“%p”, _vPtr);
  11. std::vector<void*>::iterator it;
  12. for (it = RegisteredClassArray.begin(); it != RegisteredClassArray.end(); it++) // determina si la clase del objeto ha sido registrada
  13. {
  14. si ((*it) == _vPtr)
  15. romper;
  16. }
  17. if (it == RegisteredClassArray.end()) // si no está registrado, regístrese
  18. {
  19. //clase de ventana de registro
  20. WNDCLASSEX wcex;
  21. wcex.cbSize = sizeof(WNDCLASSEX);
  22. wcex.estilo = CS_HREDRAW | CS_VREDDRAW;
  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 = (BRUSH)(COLOR_WINDOW + 1);
  30. wcex.lpszMenuName = NULL;
  31. wcex.lpszClassName = szClassName;
  32. wcex.hIconSm = NULL;
  33. if (0 != ::RegisterClassEx(&wcex)) // agrega la clase registrada con éxito a la lista enlazada
  34. {
  35. registradoClassArray.push_back(_vPtr);
  36. }
  37. }
  38. // crear ventana
  39. si (esto->hWnd == NULL)
  40. {
  41. ::wsprintf(szTitle, L"nombre de clase de ventana (puntero de tabla virtual de C++): %p", _vPtr);
  42. HWND hwnd = ::CreateWindow(szClassName,
  43. szTitulo,
  44. WS_OVERLAPPEDWINDOW,
  45. 0, 0, 800, 600,
  46. NULO,
  47. NULO,
  48. hInstancia,
  49. (LPVOID) esto
  50. );
  51. si (hwnd == NULL)
  52. {
  53. esto->hWnd = NULL;
  54. mensaje wchar_t[100];
  55. ::wsprintf(msg, L"CreateWindow() falló: %ld", ::GetLastError());
  56. ::MessageBox(NULO, mensaje, L"Error", MB_OK);
  57. devolver;
  58. }
  59. }
  60. }
  61. XqWindow::XqWindow(HINSTANCE hInst)
  62. {
  63. esto->hWnd = NULL;
  64. este->hInstancia = hInst;
  65. }
  66. VentanaXq::~VentanaXq()
  67. {
  68. if ( this->hWnd!=NULL && ::IsWindow(this->hWnd) ) // Destruye el objeto ventana antes de que se destruya el objeto C++
  69. {
  70. ::DestroyWindow(esto->hWnd); // Dígale al sistema que destruya hWnd y envíe WM_DESTROY a wndproc
  71. }
  72. }
  73. HWNDXqWindow::GetHandle()
  74. {
  75. devuelve esto->hWnd;
  76. }
  77. // procesamiento de mensajes. Si se requiere el procesamiento predeterminado posterior, debe devolver 0; si se detiene el procesamiento posterior del mensaje, devuelve 1
  78. int XqWindow::HandleMessage(HWND hWnd, UINT mensaje, WPARAM wParam, LPARAM lParam)
  79. {
  80. devolver 0;
  81. }
  82. // procedimiento de ventana original
  83. LRESULT CALLBACK XqWindow::WndProc(HWND hWnd, UINT mensaje, WPARAM wParam, LPARAM lParam)
  84. {
  85. XqWindow* pObj = NULO;
  86. if (mensaje == WM_CREATE) // Cuando se reciba este mensaje, asigne el identificador del objeto de ventana al miembro del objeto de C++ y, al mismo tiempo, asigne la dirección del objeto de C++ al miembro del objeto de ventana
  87. {
  88. pObj = (XqWindow*)(((LPCREATESTRUCT)lParam)->lpCreateParams);
  89. pObj->hWnd = hWnd; // Obtenga HWND aquí, CreateWindow() aún no ha regresado.
  90. ::SetWindowLong(hWnd, GWL_USERDATA, (LONG)pObj); // Asociar HWND con el objeto C++ a través de USERDATA
  91. }
  92. pObj = ( XqWindow * )::GetWindowLong ( hWnd , GWL_USERDATA ) ;
  93. cambiar (mensaje)
  94. {
  95. caso WM_CREATE:
  96. pObj->HandleMessage(hWnd, mensaje, wParam, lParam);
  97. romper;
  98. caso WM_DESTROY:
  99. if (pObj != NULL) // En este punto, el objeto de la ventana se ha destruido y se notifica al objeto C++ configurando hWnd=NULL
  100. {
  101. pObj->hWnd = NULL;
  102. }
  103. romper;
  104. por defecto:
  105. pObj = ( XqWindow * )::GetWindowLong ( hWnd , GWL_USERDATA ) ;
  106. si (pObj != NULL)
  107. {
  108. if (pObj->HandleMessage(hWnd, message, wParam, lParam) == 0) // llamar a la función virtual de procesamiento de mensajes de la subclase
  109. {
  110. return DefWindowProc(hWnd, mensaje, wParam, lParam);
  111. }
  112. }
  113. demás
  114. {
  115. return DefWindowProc(hWnd, mensaje, wParam, lParam);
  116. }
  117. romper;
  118. }
  119. devolver 0;
  120. }

2.3 Ejemplo de uso

El uso básico es crear una clase TestWindow, heredar de XqWindow y luego rehacer la función virtual HandleMessage(). Todos los códigos de procesamiento de negocios deben llamarse en HandleMessage() Dado que esta función es una función miembro, puede usarla directamente para hacer referencia a los miembros del objeto de clase TestWindow. Un código de ejemplo es el siguiente:

PruebaVentana.h

[cpp

  1. #pragma una vez
  2. #incluir "VentanaXq.h"
  3. clase PruebaVentana :
  4. ventana Xq pública
  5. {
  6. público:
  7. TestWindow(HINSTANCIA);
  8. ~VentanaDePrueba();
  9. protegido:
  10. int HandleMessage(HWND hWnd, UINT mensaje, WPARAM wParam, LPARAM lParam);
  11. privado:
  12. // parte de datos comerciales
  13. int rectAncho;
  14. altura recta int;
  15. };

PruebaVentana.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; 
}

Parte de la llamada:

[cpp

  1. pTest = new TestWindow(theApp.m_hInstance);
  2. pTest->Crear();
  3. ::MostrarVentana(pTest->GetHandle(), SW_SHOW);
  4. ::ActualizarVentana(pTest->GetHandle());

resultado de ejecución:

2.4 Puntos técnicos

Esta clase XqWindow crea el paquete más pequeño para el objeto ventana y realiza principalmente la asociación entre la función de procesamiento de mensajes y el objeto C++. El diseño de la memoria es el siguiente:

Algunos puntos que necesitan ser explicados:

(1) Correspondencia uno a uno entre las clases de C++ y las clases de ventana. Dado que VC++ no habilita RTTI de manera predeterminada, y teniendo en cuenta la compatibilidad del código y la eficiencia operativa, no se recomienda habilitar RTTI. Sin la compatibilidad con RTTI, ¿cómo podemos distinguir todas las instancias de la misma clase de las instancias de otras clases en tiempo de ejecución? ¿Paño de lana? Aquí usamos el puntero de tabla virtual de C++, cada clase con funciones virtuales tiene su propia tabla virtual independiente, y este puntero de tabla virtual se almacena en cada instancia. Diferentes instancias de la misma clase comparten una vtable, por lo que esto nos da la oportunidad de distinguir la clase de C++ a la que pertenece el objeto. Por supuesto, esta técnica solo se puede utilizar en clases con funciones virtuales, para objetos de clases sin funciones virtuales, no hay tabla virtual. Para nuestro caso, la clase XqWindow tiene una función virtual HandleMessage, por lo que todas las demás subclases y nietos que heredan esta clase también tienen sus propias tablas virtuales.

Antes de RegisterClass(), primero juzgue si el puntero de la tabla virtual de la clase a la que pertenece el objeto C++ actual existe en la lista vinculada vptrAraay. De lo contrario, registre la clase de ventana y almacene el puntero de la tabla virtual en la lista vinculada vptrArray; si existe, use directamente la clase de ventana correspondiente al puntero de la tabla virtual.

Cabe señalar que la operación de obtener el valor del puntero de la tabla virtual del objeto no se puede realizar en el constructor XqWindow::XqWindow(), porque cuando se ejecuta esta función, el miembro del puntero de la tabla virtual del objeto C++ no se ha configurado para apuntar a la tabla virtual de la dirección de la clase derivada (porque aún no se ha llamado al constructor de la subclase). Por lo tanto, el valor del puntero de la tabla virtual debe obtenerse después de completar la construcción del objeto, por lo que no se puede llamar a Create() en el constructor XqWindow(). (Una vez puse Create() en XqWindow() para simplificar la llamada, ¡lo que resultó en los mismos punteros de tabla virtual para todos los objetos!)

(2) La relación entre los objetos de C++ y los objetos de ventana. Después de crear el objeto de C++, llamar a Create() es la única forma de vincularlo al objeto de ventana. Hasta que se destruya la ventana anterior, el objeto C++ ya no puede crear una nueva ventana, y llamar a Create() varias veces es inútil.

La vida útil del objeto C++ también es mayor que la vida útil de la ventana correspondiente; de ​​lo contrario, el uso del objeto C++ en el proceso de la ventana provocará un acceso ilegal a la memoria. La secuencia de vida de estos dos tipos de objetos es: Nacimiento del objeto C++: llamar a Create() para generar el objeto de la ventana: alguna razón, destrucción del objeto de la ventana: destrucción del objeto C++.

Para evitar que el objeto C++ se destruya antes que el objeto ventana, en el destructor de la clase XqWindow, primero destruya el objeto ventana a través de DestroyWindow(). Cuando se destruye el objeto de la ventana, hWnd del objeto de C++ también se establecerá en NULL para notificar la destrucción de la ventana del objeto de C++.

Para decirlo más vívidamente: el objeto C++ y el objeto ventana son monógamos, y la relación de marido y mujer solo puede ser viuda y no divorciada, y el objeto C++ tiene una vida larga, y el objeto ventana tiene una vida corta. Solo después de que un objeto de ventana muere, el objeto de C++ puede regenerar una nueva ventana. Y antes de que el objeto de C++ muera, el objeto de la ventana debe eliminarse y enterrarse con él.

(3) Referencias mutuas entre objetos de C++ y objetos de ventana. El objeto de C++ hace referencia al objeto de ventana a través de la variable miembro hWnd, y el objeto de ventana apunta al objeto de C++ a través del bloque de datos adicional GWL_USERDATA. Además, para capturar el mensaje WM_CRATE a tiempo y procesarlo en HandleMessage, la asignación del miembro hWnd de C++ no se realiza después de CreateWindow(), sino cuando la función del procedimiento de ventana original procesa el mensaje WM_CREAT. Esto está principalmente relacionado con el principio CreateWindow().

CrearVentana()

{

HWND hwnd = malloc(..);

Inicialice el objeto de la ventana;

WndProc(hwnd, WM_CRATE, ..); // La ventana ha sido creada en este punto

otras operaciones;

volver atrás;

}

Del mismo modo, el principio de DestroyWindow() es.

Destruir ventana (hwnd)

{

limpieza de objetos de ventana;

WndProc(hwnd, WM_DESTROY, ..); // La ventana ya no está visible en este momento

otras operaciones;

libre(hwnd);

}

2.5 Hay problemas

Aunque la clase XqWindow puede funcionar bien, existen algunos problemas:

(1) Dado que el objeto Ventana se refiere al objeto C++ por USERDATA, si otro código modifica este bloque de datos a través de SetWindowLong(hwnd, GWL_USERDATA, xxx), el programa fallará. Cómo prevenir este daño requiere más investigación.

(2) Use el puntero de la tabla virtual del objeto C++, y no existe un estándar claro para el diseño de memoria específico de este puntero. Una vez que el compilador VC++ modifique la ubicación de almacenamiento del puntero de la tabla virtual en el futuro, el programa tendrá problemas . Sin embargo, debido a consideraciones de compatibilidad binaria, es poco probable que VC++ haga tal cambio.

Supongo que te gusta

Origin blog.csdn.net/onebound_linda/article/details/130922852
Recomendado
Clasificación