abr 7, 2009

Enviado por en C/C++

Mapa de mensajes: Parte 2

Como lo indiqué al final de la primera parte, aquí tenemos a Bind():

template <class _Class, class _ParamType>
void MessageTarget::Bind(int nMessage, int (_Class::*function)(_ParamType *)) {
	union {
		int (_Class::* fptr)(_ParamType *);
		int (MessageTarget::* vptr)(void *);
	} f = { function }; // Ya tenemos al puntero para guardarlo.
	MESSAGE t = {
		/* nMessage = */	nMessage,
		/* nDist = */		0, // No viene al caso ahora
		/* pAddress = */	f.vptr;
		};
	m_MessageMap.push_back(t); // Añadimos la estructura
};

Simple ¿no? (cierto, no todo compilador acepta esto). Notar el parámetro de la función, lo definimos exactamente como su tipo, la razón es seguridad, ahí le estamos diciendo al compilador que verifique el tipo de función solicitado, entonces este mismo nos está diciendo, si compila, que la función sigue tal estructura, es decir retorna un int y tiene un solo parámetro que es un puntero (esa fue la razón por que use este tipo de funciones). También quisiera aclarar otra cosa, lo propuesto anteriormente añade al final del vector la relación, pero prefiero sustituir uno existente, en el caso de que ya se haya añadido, el por qué lo expondré luego.

template <class _Class, class _ParamType>
void MessageTarget::Bind(int nMessage, int (_Class::*function)(_ParamType *)) {
	union {
		int (_Class::* fptr)(_ParamType *);
		int (MessageTarget::* vptr)(void *);
	} f = { function }; // Ya tenemos al puntero para guardarlo.
	MESSAGE t = {
		/* nMessage = */	nMessage,
		/* nDist = */		0, // No viene al caso ahora
		/* pAddress = */	f.vptr;
		};
	for (std::vector<MESSAGE>::iterator it = m_MessageMap.begin(); it ¡= m_MessageMap.end(); ++it) // verificamos cada elemento para conocer si ya existe.
		if (it->nMessage == nMessage) {
			* it = t; // lo encontramos y por lo tanto reemplazamos.
			return;
		}
	m_MessageMap.push_back(t); // Añadimos la estructura
};

Bien, ya tenemos a Bind(), ahora solo nos queda Trigger(). Este es más simple, solo recorremos todo el vector y si encontramos la función, “presionamos el gatillo”.

int MessageTarget::Trigger(UINT message, void * params) {
	for (std::vector<MESSAGE>::iterator it = m_MessageMap.begin(); it ¡= m_MessageMap.end(); ++it)
		if (it->uMessage == message)
			return (this->*(it->pAddress))(params);
	return 0;
}

Aquí está el porqué (son dos de hecho) no permito añadir varios manejadores para el mismo mensaje. El primero es ¿que se retorna?, si tenemos dos funciones ¿cual valor retornamos? El segundo es eficiencia, actualmente podemos aprovechar e incluir los mensajes más usados de primero de tal modo de ejecutarlos de una vez, no va a haber otro. Si se permitiese añadir elementos adicionales, estaríamos obligados a leer todos los elementos por cada mensaje. Tú puedes pasar el ratón una eternidad de veces sobre una ventana pero solo lo creas una vez. Particularmente utilizo un valor de retorno especial que indique cuando una función no se ha encontrado de tal modo de realizar operaciones por defecto.

Entonces, ¿listo? Ya tenemos las funciones y no hay mas nada que hacer, sino usarlo ¿cierto?

Bueno, resulta que cuando ejecutaba este código ocurrieron ciertos problemas con el acceso a memoria. Aquí viene el porqué:
En Trigger() se accede a this. Pero resulta que this es un puntero a MessageTarget, y no a la clase derivada. Pero, ¿cuál es el problema? Cuando vi como se administraba en la memoria noté la diferencia:

Ejemplo de la representación de la memoria en que se distribuyó ClaseDerviada y MessageTarget (caso especial)

Cuando estamos en MessageTarget, this solo se refiere a ella, si lo movemos varias posiciones antes obtenemos los datos de ClaseDerivada. Cuando llamamos a la función pensamos que la dirección es exactamente ClaseDerivada, entonces en ClaseDerivada cuando accedemos a una variable, en la realidad estamos accediendo al dato respectivo pero a partir de la posición de los datos de MessageTarget. Es decir:

assert((void *) (NuestraVentana *) this <= (void *) (MessageTarget *) this);

¿Cómo lo solucioné? Aquí entra nDist en acción, en ella guardamos la distancia en bytes entre ambas clases. Lógicamente se establece como:

nDist = (char *) (MessageTarget *) this - (char *) (NuestraVentana *) this;

Vamos a emplearlo en Bind() y en Trigger():

template <class _Class, class _ParamType>
void MessageTarget::Bind(int nMessage, int (_Class::*function)(_ParamType *)) {
	union {
		int (_Class::* fptr)(_ParamType *);
		int (MessageTarget::* vptr)(void *);
	} f = { function }; // Ya tenemos al puntero para guardarlo.
	MESSAGE t = {
		/* nMessage = */	nMessage,
		/* nDist = */		(char *) this - (char *) (_Class *) this,
		/* pAddress = */	f.vptr;
		};
	for (std::vector<MESSAGE>::iterator it = m_MessageMap.begin(); it ¡= m_MessageMap.end(); ++it) // verificamos cada elemento para conocer si ya existe.
		if (it->nMessage == nMessage) {
			* it = t; // lo encontramos y por lo tanto reemplazamos.
			return;
		}
	m_MessageMap.push_back(t); // Añadimos la estructura
};

int MessageTarget::Trigger(UINT message, void * params) {
	for (std::vector<MESSAGE>::iterator it = m_MessageMap.begin(); it ¡= m_MessageMap.end(); ++it)
		if (it->uMessage == message)
			return ((MessageTarget *) ((char *) this - it->nDist)->*(it->pAddress))(params);
	return 0;
}

Desconozco que dice el estándar con respecto a esto. Pero en el caso de que el compilador lo ordene al reverso, nDist siempre daría cero y por lo tanto es inofensivo. Esto es importante ya que luego mis códigos se empezaron a ordenar de este modo… Aún no se por qué…

Esto es lo que se podria denominar mala programación y por lo tanto el compilador no necesariamente tiene que prestarle atención a estos casos.

Conclusión

Esto lo hice para un proyecto en mis manos y me entretuve mientras realizaba este proceso. Realmente es muy simple la idea, y las funciones son muy cortas para toda la teoría expuesta, pero puede que les sea útil a algunos de los lectores. Sugerencias, críticas, correcciones u otros son siempre bienvenidos.

El código de la clase se puede obtener desde aquí: MessageTarget.h

    Posts Relacionados

    Dejar una respuesta

    Debes ser Alojarse para enviar un comentario.