| Introducción a la programación C++ |
|
|
|
| Escrito por Elmessias | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| Viernes, 26 de Marzo de 2010 08:23 | |||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Introduccion a la programación C++ Introducción Soy consciente de que hay cientos de tutoriales en Internet que abarcan este tema incluso mucho mejor, de hecho este tutorial está basado en uno de Joseph "Ironblayde" Farrell, pero el principal objetivo de este tutorial es explicar al lector los conceptos básicos de la programación en Windows, de manera clara y sencilla sin entrar en explicaciones excesivamente técnicas. Aunque ya esté algo iniciado en la programación bajo este entorno, le recomiendo que lea este tutorial ya que le podría ayudar a aclarar algunos conceptos que suelen ser algo oscuros cuando se empieza. La estructura del fichero es la siguiente:
* La función WinMain() * Clases de Ventana * Creando una Ventana * Mensajes * Leer de la cola de mensajes * Enviar mensajes * Flujo del programa * Ejecutable y Código fuente * Conclusiones
La función WinMain() Al igual que los programas de C bajo MS-DOS comienzan su ejecución con la función main(), en Windows los programas comienzan con la función WinMain(). Una función WinMain() vacía tiene la siguiente forma:
a continuación explicamos cada uno de los parámetros pasados a la función: HINSTANCE hinstance: Es un handle a la instancia de nuestra aplicación. Es como un puntero :) HINSTANCE hPrevInstance: No debemos preocuparnos por este parámetro ya que está obsoleto. En antiguas versiones de Windows era un handle a la instancia de la aplicación que había llamado a la nuestra. La única razón de que aún se incluya es para asegurar la compatibilidad. LPSTR lpCmdLine: Es un puntero a una cadena que contiene los parámetros de la linea de comandos usados cuando se ha invocado al programa. Como puedes ver no hay ningun parámetro que nos diga cuantos parámetros hemos recibido así que lo tendremos que hacer por nosotros mismos. int CmdShow: Especifica como se abrirá la ventana principal de la aplicación. No es necesario utilizarlo, ya que se puede hacer de otras maneras. Los valores que toma comienzan por SW_. Como por ejemplo SW_SHOWNORMAL, SW_MAXIMIZE o SW_MINIMIZE. Clases de Ventana Cuando queremos crear una ventana en windows, debemos de hacerlo por medio de una clase, y a esta le pasamos los parámetros que definen las propiedades que queremos que tenga nuestra ventana. Por ejemplo, el icono, un menú (si tiene asociado uno), el nombre... Para hacer esto utilizamos la clase WNDCLASSEX, el sufijo de EX viene de EXTENDED ya que es una clase con algunas modificaciones respecto a la antigua clase de ventana (WNDCLASS), nosotros utilizaremos la primera. La estructura es la siguiente:
La descripción de cada uno de los parámetros: UINT cbSize: Este es el tamaño de la estructura en bytes. Siempre será sizeof(WNDCLASSEX) UINT style: Este es el estilo de la ventana, que viene definido por constantes con prefijos CS_. Podemos combinar estas constantes utilizando el operador | (OR).Algunos ejemplos son:
WNDPROC lpfnWndProc: Un puntero a la función CALLBACK que gestiona los mensajes que recibe la ventana. Si nunca has utilizado punteros a funciones, la dirección de la función es sencillamente el nombre de la función sin los parentesis, facil eh? :) int cbClsExtra: Información reservada para la clase que sinceramente tampoco le he encontrado ninguna utilidad. La inicializamos a 0. int cbWndExtra: Igual que la anterior pero reservada para la ventana. HANDLE hInstance: Instancia a la aplicación que está usando la ventana. Es uno de los parámetros de WinMain() HICON hIcon: Un handle al icono que representa el programa. Generalmente se suele inicializar por medio de la función LoadIcon(). El prefijo IDI_ representa iconos de Windows. Para cargar el icono genérico de Windows (hasta que aprendamos a cambiarlo) escribimos:
HCURSOR hCursor: Un handle al cursor que utiliza el ratón cuando están sobre nuestra ventana. Es cargado con la función LoadCursor(). Al igual que el anterior, se pueden cargar desde los recursos, pero mientras tanto utilizaremos la siguiente llamada para cargar el estandar:
HBRUSH hbrBackGround: Cuando la ventana recibe el mensaje de refrescar (repintar) Windows pinta el area de la ventana con un color sólido o "brush". El "brush" es definido por este parámetro. Los "brush" se pueden cargar por medio de GetStockObject(). Algunos ejemplos son: BLACK_BRUSH, WHITE_BRUSH... Por ahora utilizaremos:
LPCTSTR lpszMenuName: Si queremos crear una ventana que tenga un menu de tipo pull-down, este parametro especifica el nombre del mismo. Como por ahora no vamos a crear ninguno lo inicializamos a NULL. LPCSTR lpszClassName: Nombre de la clase, por ejemplo "Clase_Juego". HICON hIconSm: Es un handle al icono pequeño que aparece en la barra superior de la aplicación Se carga de la misma manera que el hIcon. Ahora que ya estamos algo más familiarizados con la estructura WNDCLASSEX vamos a rellenar cada uno de los parámetros: LPCTSTR lpszMenuName: Si queremos crear una ventana que tenga un menu de tipo pull-down, este parametro especifica el nombre del mismo. Como por ahora no vamos a crear ninguno lo inicializamos a NULL.
Como nota, explicar que cuando cargamos el "brush" hemos escrito (HBRUSH) delante de la llamada a la función GetStockObject(), hemos hecho esto porque esta función no solo se utiliza para cargar este tipo de dato sino que es mucho más general y devuelve un valor de tipo HGDIOBJ, por lo que lo tenemos que convertir a HBRUSH. Lo ultimo que tenemos que hacer es registrar nuestra nueva clase, para que Windows la pueda usar para crear una ventana esto se hace utilizando la función RegisterClassEx(). Solamente toma como parámetro la dirección de nuestra estructura. La llamada sería:
Creando una ventana Para crear una ventana lo "único" que tenemos que hacer es llamar a la función CreateWindowEx(), eso sí, toma bastantes parámetros :( Este es el prototipo de la función:
Lo primero de todo es comentar el valor de retorno de la función. Esta función devuelve un valor de tipo HWND que es un handle a la ventana. Esta valor es muy importante y debemos de guardarlo en alguna variable ya que es usado en un gran número de funciones de Windows. Los demás parámetros son:
DWORD dwExStyle: Las constantes usadas para este parámetro comienzan por WS_ES_ pero raramente utilizaremos alguna, así que lo inicializamos a NULL. LPCTSTR lpClassName: El nombre de la clase que hemos creado ("Clase de ejemplo") LPCTSTR lpWindowsName: El título que aparece en la barra de la aplicación. DWORD dwStyle: Especifica que tipo de ventana vamos a crear. Hay muchas constantes que podemos usar, comenzando por WS_, y pueden ser combinadas con el operador |. Algunas comunes so
La constate WS_OVERLAPPEDWINDOW es en realidad una combinación de otras constantes. Si lo que queremos poner en nuestra ventana son los botones Maximizar, minimizar, cerrar, etc... utilizaremos la anterior. Si queremos solo el titulo de barra y su borde utilizamos WS_OVERLAPPED. Si por el contrario no queremos nada de esto, utilizareoms la constante WS_POPUP que hará que inicialmente la ventana aparezca como un rectangulo negro, que generalmente es lo que se usa para aplicaciones a pantalla completa (videojuegos :D ). int nWidth, nHeight: Especifican la anchura y la altura (en pixels) de la ventana. HWND hWndParent: Es un handle a la ventana padre de la ventana que hemos creado. Para una ventana principal, se establece a NULL (Que especifica el escritorio). HMENU hMenu: Es un handle al menu que está asociado a la ventana. Si lo cargamos de los recursos utilizaremos la funcion LoadMenu(). Para nuestra ventanan no queremos menú así que lo ponemos a NULL. HINSTANCE hInstance: Instancia a la aplicación, usado en WinMain(). LPVOID lpParam: Este parámetro no es muy usual usarlo, sobre todo para videojuegos, se utiliza para crear cosas como interfaz de documento múltiplo. Así que otro NULL. :) Ahora lo que tenemos que hacer es rellenar la información y crear nuestra ventana :D
Se ha incluido la función CreateWindowsEx dentro de una sentencia if ya que si falla la creación de la ventana devueve NULL, con lo cual debemos de salir de la aplicación. Con todo esto ya hemos creado una ventana totalmente funcional, pero ahora necesitamos interactuar tanto con el usuario, como con el sistema operativo y las demas aplicaciones. Mensajes Cuando programas en MS-DOS no te tienes que preocupar acerca de otros programas que pueden estar ejecutandose ya que MS-DOS como todos sabemos no es un sistema operativo multitarea. Sin embargo cuando programamos en Windows si tenemos que considerar este dato. Por esta razón, y otrás más, Windows usa lo que se conoce como mensajes para comunicarse con las aplicaciones y decirles que es lo que tienen que hacer. Los mensajes pueden ser usados para muchos propósitos diferentes. Nos informan cuando una ventana se cambia de tamaño, se mueve o se cierra; nos dice cuando debemos refrescar la imagen; tambien pueden ser usados para controlar el movimiento del ratón y las pulsaciones, por ejemplo. La lista es bastante grande y como es obvio nuestro programa debería controlarlos. La manera de hacerlo es por medio de una función especial denominada CALLBACK. Las funciones CALLBACK son funciones que no son llamadas directamente desde tu código, sino que son llamadas externamente cuando se producen unas ciertas condiciones. El prototipo de una función manejadora de mensajes es:
El tipo LRESULT es usado comunmente en funciones de proceso de mensajes. HWND hwnd: Este es el handle de la ventana que ha enviado el mensaje que se está procesando. UINT msg: Este es el identificador del mensaje. Los valores de este parámetro son constantes que comienzan por el prefijo WM_ (Windows Message). A continuación se muestran algunos de los más importantes:
WPARAM wparam, LPARAM lparam: El valor de estos parámetros depende del tipo de mensajes enviado pero básicamente contienen el contenido del mensajes. Si se te ha pasado por la cabeza controlar cada uno de los mensajes que tu ventana puede recibir, sientate, relajate, respira un poco y sigue leyendo :) Seguramente haya muchos mensajes que no te interese controlar. Para este fin está la función DefWindowsProc(). Así podemos crear nuestro primer manejador de mensajes totalmente funcional y correcto (aunque algo inutil):
Al menos simple es ¿no?. Aunque generalmente nos interesa manejar algunos mensajes, para lo que incluimos el código necesario y devolvemos 0 para decirle al programa que ya hemos terminado de procesar el mensaje. Aqui se muestra un ejemplo de un manejador de mensajes que llama a un función de inicialización cuando se crea la ventana y llama al manejador por defecto para cualquier otro mensaje.
Seguramente tu manejador de mensajes comience con una sentencia SWITCHs para manejar de una manera más estructurada los diferentes mensajes que queremos tratar. Leer cola de mensajes No debemos olvidarnos de mirar en la cola de mensajes (donde todos los mensajes pendientes están almacenados) hay algo para nosotros. Si es así, hay algunas cosas que debemos hacer para que nuestro manejador procese los mensajes correctamente. La función que necesitamos es PeekMessage(). Este es el prototipo:
Parametros: BOOL (retorno): Devuelve TRUE si hay un mensaje esperando en la cola y FALSE si no. LPMSG lpMsg: Puntero a una variable de tipo MSG. Si hay un mensaje esperando esta variable contendrá la información del mensaje. HWND hWnd: Hande a la ventana de la que queremos comprobar la cola de mensajes. UINT wMsgFilterMin, UINT wMsgFilterMax: Los indices del primer y del último mensaje en la cola. Generalmente a nosotros nos interesará solo el primer mensaje, así que pondremos ambos parametros a 0. UINT wRemoveMsg: Generalmente solo toma dos valores: PM_REMOVE o PM_NOREMOVE. El primero se utiliza si queremos que el mensaje se elimine de la cola de mensajes una vez leido, y el segundo para que siga en la cola. Casi siempre utilizaremos PM_REMOVE. La primera función, como su nombre indice, hace una pequeña traducción del mensaje. La segunda invoca nuestro manejador de mensajes y le pasa la información apropiada de la estructura MSG. Y bien... !esto es todo lo que necesitamos!. Con cada iteración del bucle principal, si hay un mensaje esperando, llamamos a estas dos funciones y el manejador de mensajes se encarga del resto :) El código de ejemplo es:
Enviar mensajes Ya que estamos tratando el tema de los mensajes, tambien conviene saber como enviarlos manualemente. Para enviar mensajes manualmente hay dos opciones, utilizar la función PostMessage() o SendMessage(). Sus prototipos son muy parecidos:
Los parámetros son los mismos que utiliza nuestra función manejadora de mensajes. Lo único que debemos saber es la diferencia entre las dos funciones. PostMessage se usa cuando lo que queremos hacer es meter el mensaje que enviamos en la cola de mensajes de la ventana y que la lógica de nuestro programa se encarge de procesarlo (como hemos visto anteriormente). La función devuelve TRUE si se ha conseguido meter bien, o FALSE en caso contrario. SendMessage sin embargo lo que hace no es poner el mensaje en la cola sino que lo traduce e invoca directamente al manejador de mensajes de la ventana inmediatamente y no termina hasta que no se ha procesado el mensaje completamente. Por lo tanto esta función será usada para mensajes de prioridad alta que necesitan procesarse lo más rápido posible. Flujo del programa Como ya hemos comentado anteriormente la programación en Windows es diferente que bajo DOS ya que tenemos que encargarnos de los mensajes que recibimos y a la vez tener en cuanta la interacción con los demás programas. Veamos un ejemplo de flujo de programa en pseudo-codigo para ver más clara la diferencia:
Si estuvieramos en MS-DOS la función fadeout() la implementariamos de manera que cuando se llama, realiza un bucle en el que va oscureciendo la pantalla poco a poco (incluyendo un delay por ejemplo), cuando termina de oscurecer todo (negro), devuelve true. Y la función ComprobarEntrada() sería una función del tipo de la anterior que espera hasta que se pulse una tecla. Muy bien, esto es lo que se haría en MS-DOS, pero en Windows NO!! veamos porqué. El problema radica en que si lo hacemos como lo anterior, cuando se evalua actualizar a verdadero, se ejecutaría el codigo de fadeout(), CargarNuevoMapa() y fadeIn(), que supongamos que tarda 10 segundos, tiempo en el que no hemos procesado los mensajes que recibimos y tiempo más que suficiente para haber recibido muchos mensajes algunos como por ejemplo Cambiar de tamaño (deberiamos de cambiar el tamaño de la ventana para oscurecerla toda completa), Minimizar (Terminariamos con la función fadeout()), o Cerrar. Todo esto puede provocar resultados incorrectos, errores de protección general,... en definitiva nada bueno :( Lo que debemos hacer es ponernos como punto de partida que nuestro programa se ejecute a una velocidad de 30 FPS (frames per second) como mínimo, esto significa que el bucle principal se ejecuta 30 veces por segundo y en cada iteración del bucle principal mostramos un solo frame oscurecido (No todos como haciamos con la fadeout() de MS-DOS). Podéis descargar un código de ejemplo de la una ventana de aquí. |
|||||||||||||||||||||||||||||||||||||||||||||||||||||
| Última actualización el Martes, 30 de Marzo de 2010 22:33 |






