Mapa de mensajes: Parte 1
Bien, un largo tiempo desde mi último tema. Trataré de actualizar los temas anteriores. Ahora, para redimirme un poco hablaré una implementación en C++ de lo que Microsoft denominó mapa de mensajes (tabla de métodos virtuales dinámica). La empresa lo hizo pensando principalmente en el manejo de ventanas, pero se puede aplicar (como la misma compañía hizo) a otras ideas.
Por ahora, describiré el tema pensando en las ventanas de Windows (posiblemente exagerando en este aspecto) pero el objeto es ofrecer una clase independiente del sistema operativo. Observación: el uso del lenguaje en este tema requiere de ciertos conocimientos avanzados. De todos modos se puede aprender un poco.
Empecemos por describir que son mapas de mensajes, primero, quien conozca el funcionamiento de las ventanas en Windows sabrá que estas se rigen por mensajes. Me refiero, tienes una ventana y entonces el usuario al pasar el ratón sobre ella, hacer clic o al presionar una tecla, se añade un mensaje a una cola de mensajes de la ventana referida (donde se guardan todos los mensajes que se deben procesar). Ahora la ventana tiene que procesar tal señal, puede ser en el momento que se envía o luego de procesar los mensajes que llegaron antes de ella. ¡Pero estamos en C/C++! ¿Quien realiza esta operación?
Bueno cuando creamos la ventana previamente debemos especificar una función que realiza tal operación, la idea básica de la función se puede representar como:
// RESULTADO es el tipo de retorno, PARAM el tipo de los datos
// dados como parámetros y VENTANA el tipo que identifica la
// ventana.
RESULTADO ProcesarMensaje(VENTANA vent, int mensaje, PARAM parametros) {
switch (mensaje) {
case MENSAJE_MOVIMIENTORATON:
// Procedimiento para cuando se mueve el ratón...
// parametros incluye la posición.
return 0;
case MENSAJE_OBTENERFUENTE:
// Retornar la fuente que se usa...
return Fuente;
case MENSAJE_OTROMENSAJE:
// Procesar otro mensaje...
}
return ProcesarPorDefecto(mensaje, parametros);
}
Se puede tener una idea del algoritmo. El sistema operativo envía a esta función un mensaje indicando la ventana misma, el mensaje mediante valores predefinidos y los parámetros respectivos.
Pero este método se encuentra bajo un paradigma totalmente estructurado (Windows data de los 80). Es decir, que pasa si tenemos algún dato perteneciente a la ventana. Podemos tener una variable global a la cual acceder, pero si tenemos varias ventanas del mismo tipo (como un cuadro de texto), no necesariamente podemos acceder a la misma variable.
Por obvias razones el sistema operativo ofrece ciertas funciones para obtener datos privados. Ahora, resulta que una ventana puede tener todo tipo de datos, desde grandes estructuras hasta un simple carácter. Lo más lógico es solo guardar una dirección de memoria a una estructura que guarde tales datos y cada vez que se llama a la función, obtener tal puntero. Ahora ya tenemos resuelto el problema… en C…
Bajo C++ y la programación orientada a objetos, este esquema es extraño y limitante. Pero, ¿qué podemos hacer? Podemos diseñar una clase basada en la lógica anterior, pero ¿y los mensajes?, ¡se sigue usando la misma función! ¿Qué tal si usamos una clase plantilla y métodos virtuales como a continuación?:
class PlantillaVentana {
protected:
virtual RESULTADO SeMovioElRaton(PARAM) {
return 0;
}
private:
static RESULTADO ProcesarMensaje(VENTANA vent, int mensaje, PARAM parametros) {
switch (mensaje) {
case MENSAJE_MOVIMIENTORATON:
PlantillaVentana * p = (PlantillaVentana *) ObtenerDatoPrivado(vent);
return p->SeMovioElRaton(parametros);
}
return ProcesadorPorDefecto(mensaje, parametros);
}
};
class NuestraVentana : public PlantillaVentana {
RESULTADO SeMovioElRaton(PARAM parametros) {
return 1;
}
};
Ciertamente funciona, pero ahora, cada método virtual se guarda en una tabla oculta que pertenece a la clase. Si tomamos en cuenta que cada método es un simple puntero, tomaría, cada uno, 4 bytes en un procesador de 32 bits, ¿Qué tal si son cientos de mensajes? (lo cual es así). La situación se torna peor en un sistema de 64 bits, con un doble de memoria usado. ¡Tendríamos cerca de un 1 Kbyte solo en métodos virtuales de los cuales tal vez no usamos más de 10 o 20!
Entonces, el mismo Microsoft tratando de solucionar este problema, incluyo un sistema de mapa de mensajes en su librería MFC que viene integrado a Visual C++ desde sus versiones tempranas. Desconozco de donde data tal idea.
Pero al fin, ¿en qué consiste? Bueno es una idea similar de las función virtuales, solo que se usa memoria solamente para los métodos necesarios. En otras palabras, una tabla de métodos virtuales dinámica (tal vez sea mejor nombre que mapa de mensajes). Debido a no tratar de decir que se corrompió el estándar de C++ para cumplir esta necesidad, se realizará en tiempo de ejecución.
Ahora vamos a presentar una implementación de la idea. Recordar que el concepto se puede aplicar a una serie de casos (programación paralela me viene a la cabeza) fuera lo del caso de las ventanas. Pero, ¿qué es lo quiero hacer?:
class PlantillaVentana : public MessageTarget {
private:
static RESULTADO ProcesarMensaje(VENTANA vent, int mensaje, PARAM parametros) {
switch (mensaje) {
case MENSAJE_MOVIMIENTORATON:
PlantillaVentana * p = (PlantillaVentana *) ObtenerDatoPrivado(vent);
return p->Trigger(MENSAJE_MOVIMIENTORATON, parametros);
}
return ProcesadorPorDefecto(mensaje, parametros);
}
};
class NuestraVentana : public PlantillaVentana {
RESULTADO SeMovioElRaton(PARAM parametros) {
return 1;
}
public:
NuestraVentana() {
Bind(MENSAJE_MOVIMIENTORATON, &NuestraVentana::SeMovioElRaton);
}
};
Casi no hay diferencia del último ejemplo de la primera parte. Y de hecho el objetivo simplemente radica en eliminar el uso de las tablas de métodos virtuales (y crear una propia). Pero digamos que el método tiene sus ventajas.
Ahora, voy a establecer una clase MessageTarget (Objeto del mensaje) como la encargada de manejar un conjunto donde residen tales mensajes y su función respectiva. Tal clase contendrá 2 funciones básicas: Bind() el cual guarda una relación entre el mensaje indicado y el método; y Trigger(), el cual busca por tal mensaje y ejecuta la función respectiva.
Primero, ¿dónde y como guardamos cada relación?. Bueno, pienso utilizar un simple vector para guardar los datos, para aquellos que no se sienten a gustos con la librería STL, pueden implementar su propio manejador de memoria. La idea es un simple conjunto, nada más.
Segundo, la implementación del método Bind(). Pasamos como parámetros lo que nos importa, el identificador del mensaje y la función que la maneja. El estándar de C++ tiene una extraña forma de declarar punteros a métodos. Es decir, para una simple función (que no pertenece a una clase):
// función int Funcion(int); // puntero int (* puntero)(int) = Funcion; // llamada (* puntero)(0);
Ahora, con una clase es más extraño aún:
// función int Clase::Funcion(int); // puntero int (Clase::* puntero)(int) = &Clase::Funcion; // objeto de la clase Clase obj, * pobj; // llamada (obj.*puntero)(0); (pobj->*puntero)(0);
El estándar no permite ni implícitamente ni explícitamente convertir un puntero a un método de una función a otro tipo. Me refiero a que no podemos compilar el siguiente código sin que nos dé un error:
void * puntero = (void *) &Clase::Funcion;
Parece que ninguno del comité quiere que nos metamos con esto… Entonces, lo que pienso usar es un pequeño truco, llámese hack:
union {
int (Clase::* fptr)(int);
void * vptr;
} f;
f.fptr = &Clase::Funcion;
Ahora, en f.vptr tenemos el puntero como void *.
Luego, quiero aclarar ciertas cosas cuando diseñe está idea. La primera es que los parámetros que acepta la función puede ser variado (como en MFC) pero por simplicidad y seguridad pienso utilizar uno solo, un simple void *. Una de las ventajas es que cualquier puntero, no constante, se puede convertir implícitamente a este tipo. Segundo, debido a que luego vamos a llamar a la función desde MessageTarget::Trigger() dejaré este puntero almacenado como un método de la clase MessageTarget en vez de void *. El tipo de retorno lo dejo a cargo del implementador. En mi caso, por ejemplo, uso un tipo HRESULT que viene incluido en las bibliotecas de Windows. En este caso lo dejaré como int. Entonces, quedamos con:
union {
int (Clase::* fptr)(int);
int (MessageTarget::* vptr)(void *);
} f = { &Clase::Funcion };
Lo anterior lo describo como ejemplo, en la práctica utilizaré una plantilla y varias de sus bondades, (lo del inglés es por costumbre). Lo siguiente es describir la estructura de datos y el vector:
typedef struct tagMESSAGE {
int nMessage;
int nDist;
int (MessageTarget::* pAddress)(void *);
} MESSAGE;
std::vector<MESSAGE> m_MessageMap;
Me referiré a nDist luego, por ahora nMessage guarda el identificador del mensaje y pAddress la dirección de la función. Además del vector, podríamos usar un mapa, el cual mediante búsqueda binaria, ofrecería un servicio muy eficiente en el caso de grandes cantidades de mensajes. En la parte 2 comenzaremos a exponer a Bind().
Posts Relacionados
- Mapa de mensajes: Parte 2 Como lo indiqué al final de la primera parte, aquí...
- Restaurar Cola de Mensajes QMAIL Desafortunadamente, el MTA de Kernel Error Qmail dejo de funcionar...
- Sobre punteros, memoria dinámica y otras hierbas alucinogenas. “La gestión manual de bloques de memoria en C es...



Muy interesante y me encanto el uso de la misma funcion para no tener que gastar recursos!
Usandoexcelente escrito muchas gracias
Gracias, estaba inspirado, lo aprovecharé en lo posible. Además, me gusto lo del generador de contraseñas, creo que me va a ser de ayuda.
Usando