htm=articulo/wthunk.htm ok articulo/wthunk.htm El procedimiento de ventana en una clase C++

El procedimiento de ventana en una clase C++


Existe un problema asociado al uso de C++ para programar bajo Windows que es conocido para todo aquel que haya intentado desarrollar su propia biblioteca de clases: El API de Windows está pensado para ser usado en C.

La conexión entre el procedimiento de ventana y la instancia del objeto C++ que encapsula esa ventana, se realiza normalmente reservando un espacio extra al crear la clase de ventana para guardar el puntero this asociado a cada instancia.

Al Registrar la clase:

	WNDCLASS wc;
	.............
	wc.cbWndExtra  = 4;
	RegisterClass(&wc);

Al crear una ventana de esta clase:

	SetWindowLong(hwnd, GWL_USERDATA, (DWORD)this);

El procedimiento de ventana hay que declararlo como método estático y al recibir cada mensaje hay que obtener el puntero this usando GetWindowLong para saber a qué instancia va dirigido el mensaje.

	CVentana *pThis = (CVentana*) GetWindowLong(hwnd, GWL_USERDATA);
	if(pThis){
		.....
	}

Este modo de resolver el problema es el más usado, sin embargo hay algunos problemas dificiles de solucionar que enmarañan y relentizan el código.

Pero hay otro modo de asociar una clase C++ con una ventana de Windows que es poco conocida, se trata de sustituir el procedimiento de ventana de cada instancia por un procedimiento (no estático) específico para instancia.

En un procedimiento de ventana normal, hay cuatro parámetros y el primero de ellos es el handle de la ventana

En un procedimiento no estático de una clase C++ el primer parámetro es un parámetro oculto al que nos referimos por medio de la palabra clave this.

Lo que vamos a hacer es crear, para cada instancia del objeto C++, la siguiente porcion de código:

   MOV [ESP+4], pThis  ; sustituye el HWND por el valor this
   JMP NewProc         ; y ejecuta la funcion

Dado que el valor de this es diferente para cada instancia, hemos de crear esta porcion de código para cada instancia.

Para facilitar esta operacion tan poco intuitiva hemos creado una clase abstracta, bastará derivar de ella nuestra clase ventana (se puede usar herencia múltiple) y llamar al método InitThunk para inicializar la porción de código específica de esa instancia.

class CWinThunk{
protected:
	virtual LRESULT CALLBACK WindowProc(UINT, WPARAM, LPARAM)=0;
	DWORD InitThunk(void* pThis){
		//  MOV [ESP+4], pThis
		*(DWORD*)(_thunk + 0) = 0x042444C7;
		*(DWORD*)(_thunk + 4) = (DWORD)pThis;
		//  JMP  proc	//salta a WindowProc
		*(BYTE*) (_thunk + 8) = 0xe9;
		*(DWORD*)(_thunk + 9) = **(DWORD**)this - ((int)_thunk + 13);
		return (DWORD) _thunk;
	}
private:
	char _thunk[13];
};

Ahora vamos a desarrollar una clase CVentana con capacidad para crear ventanas y para encapsular ventanas ya existentes.

Dependiendo del tipo de ventana, el procedimiento de ventana, en el caso de los mensajes que no vayamos a procesar, deberá acabar con una llamada a DefWindowProc (para ventanas creadas por nosotros) o bien a CallWindowProc (para ventanas subclasificadas).

Veamos primero una clase que solo sirve para subclasificar ventanas:

class CVentana: public CWinThunk  
{
protected:
	HWND     m_hWnd;
	WNDPROC  m_lpOldProc;
	LRESULT  CALLBACK WindowProc(UINT, WPARAM, LPARAM);
	LRESULT  DefWindowProc(UINT msg, WPARAM wp, LPARAM lp);
public:
	CVentana();
	~CVentana();

	BOOL Conectar(HWND hwnd);
private:

	void   _Conectar(HWND hwnd);
};
//--------------------------------------------------------------------
CVentana::CVentana()
{
	m_lpOldProc = NULL;
	m_hWnd      = NULL;
}
CVentana::~CVentana()
{
}

LRESULT CALLBACK CVentana::WindowProc(UINT msg, WPARAM wp, LPARAM lp)
{
	HDC			hdc;
	PAINTSTRUCT ps;
	RECT        rt;
	switch(msg){
	case WM_PAINT:
		hdc = BeginPaint(m_hWnd, &ps);
		GetClientRect(m_hWnd, &rt);
		DrawText(hdc, "Hola mundo !", -1, &rt, 
			DT_CENTER | DT_VCENTER | DT_SINGLELINE);
		EndPaint(m_hWnd, &ps);
		break;
	default:
		return DefWindowProc(msg, wp, lp);

	}

	return 0;
}

BOOL CVentana::Conectar(HWND hwnd)
{
	if(m_hWnd)  return FALSE;
	m_lpOldProc = (WNDPROC)::GetWindowLong(hwnd, GWL_WNDPROC);
	_Conectar(hwnd);
	return TRUE;
}
void CVentana::_Conectar(HWND hwnd)
{
	DWORD   dw;
	m_hWnd = hwnd;
	dw = InitThunk(this);
	::SetWindowLong(hwnd, GWL_WNDPROC, dw);
}

LRESULT  CVentana::DefWindowProc(UINT msg, WPARAM wp, LPARAM lp)
{
	return m_lpOldProc ? 
		::CallWindowProc(m_lpOldProc, m_hWnd, msg, wp, lp) :
		::DefWindowProc(m_hWnd, msg, wp, lp);
}

El método tradional (wc.cbWndExtra = 4) no se puede usar para subclasificar ventanas, sin embargo con este método es facil subclasificar una ventana.

Para una ventana de nueva creación todo es más complicado, sin embargo el código resultante sigue siendo más sencillo que el método tradicional.

Para crear una ventana necesitamos registrar la clase de ventana, llamar a CreateWindow, etc...

Además crearemos un procedimiento de ventana estático que solo se usará para procesar el primer mensaje. Está garantizado que el primer mensaje sucederá durante la llamada a CreateWindow (antes de que vuelva esta funcion).

Tambien tendremos un puntero estático que nos indicará cual es la instancia del objeto que se está creando (si se usan threads habría que añadir sincronización en el acceso a este puntero).

Al procesar el primer mensaje de windows, sustituiremos el procedimiento de ventana por el procedimiento específico para ese objeto.

//--------------------------------------------------------------------
class CVentana: public CWinThunk  
{
protected:
	HWND     m_hWnd;
	WNDPROC  m_lpOldProc;
	LRESULT  CALLBACK WindowProc(UINT, WPARAM, LPARAM);
	LRESULT  DefWindowProc(UINT msg, WPARAM wp, LPARAM lp);
public:
	static BOOL Registrar(HINSTANCE hInstance);
	CVentana();
	~CVentana();

	//Usa Conectar o Crear, una de las dos, una sola vez !
	BOOL Crear(HINSTANCE hInstance, int nCmdShow);
	BOOL Conectar(HWND hwnd);
private:
	void   _Conectar(HWND hwnd);
	static CVentana *pCreando;
	static LRESULT CALLBACK PrimerMensaje(HWND, UINT, WPARAM, LPARAM);
};
//--------------------------------------------------------------------
CVentana::CVentana()
{
	m_lpOldProc = NULL;
	m_hWnd    = NULL;
}

CVentana::~CVentana()
{

}
static const  char* ClaseVentana = "ClaseCVentana";

BOOL CVentana::Registrar(HINSTANCE hInstance)
{
	WNDCLASS wc;
	ATOM     a; 

	ZeroMemory(&wc, sizeof(WNDCLASS));

	if(GetClassInfo(hInstance, ClaseVentana, &wc)) 
		return TRUE;

	wc.style         	= CS_HREDRAW | CS_VREDRAW;
	wc.lpfnWndProc     = (WNDPROC)CVentana::PrimerMensaje;
	wc.cbClsExtra	   = 0;
	wc.cbWndExtra      = 0;
	wc.hInstance       = hInstance;
	wc.hIcon           = LoadIcon(NULL, (LPCTSTR)IDI_APPLICATION);
	wc.hCursor	   = LoadCursor(NULL, IDC_ARROW);
	wc.hbrBackground   = (HBRUSH)(COLOR_WINDOW+1);
	wc.lpszClassName   = "ClaseCVentana";

	a = RegisterClass(&wc);
	return a != 0;
}

BOOL CVentana::Crear(HINSTANCE hInstance, int nCmdShow)
{
	HWND hwnd;

	if(m_hWnd) return FALSE;

	Registrar(hInstance);

	pCreando = this;

	hwnd = CreateWindow(
			ClaseVentana, 
			"Probando CVentana", 
			WS_OVERLAPPEDWINDOW,
			CW_USEDEFAULT, 
			CW_USEDEFAULT, 
			CW_USEDEFAULT, 
			CW_USEDEFAULT, 
			NULL, 
			NULL, 
			hInstance, 
			NULL);

	if(hwnd){
	   ShowWindow(hwnd, nCmdShow);
	   UpdateWindow(hwnd);
	}

	return hwnd != NULL;
}


LRESULT CALLBACK CVentana::WindowProc(UINT msg, WPARAM wp, LPARAM lp)
{
	HDC			hdc;
	PAINTSTRUCT ps;
	RECT        rt;
	switch(msg){
	case WM_PAINT:
		hdc = BeginPaint(m_hWnd, &ps);
		GetClientRect(m_hWnd, &rt);
		DrawText(hdc, "Hola mundo !", -1, &rt, 
				DT_CENTER | DT_VCENTER | DT_SINGLELINE);
		EndPaint(m_hWnd, &ps);
		break;
	case WM_DESTROY:
		PostQuitMessage(0);
		break;
	default:
		return DefWindowProc(msg, wp, lp);

	}

	return 0;
}

//------------------------
BOOL CVentana::Conectar(HWND hwnd)
{
	if(m_hWnd)  return FALSE;
	m_lpOldProc = (WNDPROC)::GetWindowLong(hwnd, GWL_WNDPROC);
	_Conectar(hwnd);
	return TRUE;
}
//------------------------
CVentana * CVentana::pCreando;
//------------------------
LRESULT CALLBACK CVentana::PrimerMensaje(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
{
	pCreando->_Conectar(hwnd);

	return pCreando->WindowProc(msg, wp, lp);
}
void CVentana::_Conectar(HWND hwnd)
{
	DWORD   dw;
	m_hWnd = hwnd;
	dw = InitThunk(this);
	::SetWindowLong(hwnd, GWL_WNDPROC, dw);
}

LRESULT  CVentana::DefWindowProc(UINT msg, WPARAM wp, LPARAM lp)
{
	return m_lpOldProc ? 
		::CallWindowProc(m_lpOldProc, m_hWnd, msg, wp, lp) :
		::DefWindowProc(m_hWnd, msg, wp, lp);
}
//-----------------------------------------------------------------

Pra descargar el código fuente (en un proyecto Visual C++) , haz click aquí.


© info3@maicas.net