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