Mi primer programa en Win32
Comenzaremos con algo pequeño y veremos lo fácil que es utilizar directamente la API de Windows. Aprenderemos a mostrar un mensaje en pantalla y las características del código C++ en una aplicación en Windows. Para esto seguiremos estos 5 sencillos pasos:
1. Abrir el entorno de desarrollo (en mi caso Dev-C++)
2. Crear un nuevo proyecto Windows (ver apéndice B.1) que llamaremos Ejercicio1. Es importante hacerlo correctamente ya que si olvidas indicar que trabajamos en un proyecto Windows, no lograrás ejecutar el programa.
3. Agregar un Archivo C++ a nuestro proyecto (ver apéndice B.2) al que llamaremos Programa1.
4. Escribir el siguiente código en el archivo Programa1.cpp. Para ir adquiriendo confianza, te recomiendo transcribirlo completamente (sin hacer uso del Copy&Paste).
Programa1.cpp
#include
int WINAPI WinMain(HINSTANCE p1, HINSTANCE p2, LPSTR p3, int p4)
{
MessageBox(NULL, "¡Bienvenido a la programación Windows!", "Ejemplo", MB_OK);
return 0;
}
5. Generar el proyecto (ver apéndice B.3) y esperar un momento.
¡Increíble! Hemos sido capaces de mostrar un mensaje en pantalla completamente funcional como en la fig. 2.1.1. No estoy exagerando, ha sido un gran logro en tu aprendizaje. Lo que has hecho te hubiera tomado semanas enteras pero solo te ha tomado unos cuantos minutos gracias a que accedes a funciones de la API. Date cuenta que haz sido capaz de crear una ventana que cuenta con una barra de título (1), una etiqueta de texto (3) y dos botones (Aceptar 2 y Cerrar
4), el mensaje tiene buena apariencia, se puede mover por toda la pantalla, sin contar demás detalles, ¡esto pone interesante!.
fig. 2.1.1 Ejercicio1 ejecutándose.
En caso de haber errores: te mostrará un mensaje con una descripción (aunque a veces no sea de mucha ayuda). Todo varía dependiendo del tipo de compilador. El problema más común es que el compilador no este configurado para trabajar en un proyecto Windows y no encuentre la función main(). Al indicarle a nuestro entorno de desarrollo que trabajamos en un proyecto Windows, estas configuraciones necesarias se activan automáticamente (he ahí la importancia) y evitamos estos errores, te recomiendo visitar el apéndice B.
Otro posible error, que no te debería de ocurrir a estas alturas, es haber escrito mal el código. Verifica que has puesto los puntos y coma (;) en su lugar correspondiente, al igual que las llaves que abren y cierran ({ }), y no olvides que C++ distingue entre mayúsculas y minúsculas. Si este es tu caso, te recomiendo repasar algún manual de C++ y dejar este curso para otro momento.
2.2 #include
Esta es la primera línea de nuestro programa y nos servirá para incluir el archivo de cabecera windows.h que contiene todas las funciones, estructuras, macros y constantes numéricas que forman la API. Resumiendo, en todo proyecto es necesario incluir a windows.h para crear aplicaciones de Windows.
Si te ayuda, puedes verlo como un equivalente a las librerías estándar utilizadas en las aplicaciones de consola (ej. iostream).
El archivo de encabezado windows.h proporciona una ruta de acceso a más de mil declaraciones de constantes, declaraciones typedef y cientos de prototipos de funciones.
2.3 La función WinMain()
Ésta es la tercera línea de nuestro programa (contando la línea en blanco). En nuestras aplicaciones de consola utilizamos la función "main()" para indicar el punto de partida del programa. Esa función ya no resulta para aplicaciones Windows. WinMain() sustituye a la función "main()".
La función WinMain() es el punto donde inicia y termina la ejecución de una aplicación Windows. Al igual que el archivo de cabecera windows.h, todas las aplicaciones de Windows necesitan la función WinMain(). Esto es ventajoso ya que así podemos distinguir entre un programa diseñado para consola y un programa para Windows.
Recordemos como escribimos esta función:
int WINAPI WinMain(HINSTANCE p1, HINSTANCE p2, LPSTR p3, int p4)
{
...
}
Al finalizar la función WinMain(), se deberá devolver un número entero al sistema operativo, por lo que habremos de declararla como int.
La constante WINAPI, escrita entre el tipo de retorno y el nombre de la función, indica al compilador que la función WinMain() es la encargada de limpiar la pila y por el momento no necesitas comprender este hecho. Está definida como __stdcall dentro de windows.h (en windef.h). Puede suceder que leas algún otro código y encuentres que en lugar de WINAPI utilicen otras constantes como APIENTRY o PASCAL, son exactamente lo mismo. Lo que pasa es que todas ellas existen por cuestiones de compatibilidad de códigos (la evolución de las librerías, ¡por supuesto!), pero mi recomendación será siempre utilizar WINAPI que es la constante más reciente.
La función WinMain() cuenta con 4 parámetros. Con toda la intención del mundo y de manera didáctica, los parámetros (p1, p2, p3 y p4) tienen nombres extraños y nos dan una vaga idea de lo que almacenan (es una mala práctica que no debemos tener y que corregiremos más adelante).
Los dos primeros parámetros, p1 y p2, contienen un número único generado automáticamente por el sistema operativo al momento de ejecutar el programa (son del tipo HINSTANCE). El número de p1 nos sirve para referirnos a nuestra aplicación y diferenciarla de las demás. El número de p2 existe por cuestiones de compatibilidad con aplicaciones Win 3.x, pero por ahora basta decir que ya no se usa y que siempre guardará el valor cero (NULL).
El tercer parámetro p3 es un puntero hacia una cadena de caracteres terminada en cero (LPSTR o char*). En ella se guardarán los argumentos de secuencias de comandos indicados cuando se manda a ejecutar la aplicación (puede ser que sean administrados desde la consola, un acceso directo, el explorador de Windows u otro programa). Por ejemplo, si ejecutamos el programa desde la opción Menú Inicio / Ejecutar de esta forma: programa1.exe /AYUDA... la variable p3 contendrá la cadena "/AYUDA" (que podemos procesarla mediante código y darle un significado para decidir qué hacer con ella). Normalmente este parámetro contiene un NULL ya que al ejecutar el programa no se indican argumentos.1
El cuarto y último parámetro p4 es un número entero que representa alguna de las muchas constantes predefinidas de Windows, lo explicaré adelante en este capítulo. Este número indica la manera en que la ventana deba mostrarse, ya sea maximizada, minimizada, etc. Como ejemplo, este parámetro toma un valor cuando los accesos directos se configuran para abrir la ventana Maximizada o en algún otro estado en especial.
2.4 Acuerdos
Cuando diseñaron Windows, se utilizó una técnica especial para nombrar variables, constantes, clases, estructuras, etc: la Notación Húngara. Se le llama así en honor a su inventor, Charles Simonyi, de origen húngaro. Él propuso que cada nombre debe llevar un prefijo que identifique al tipo de dato, así por ejemplo, un programador cualquiera puede saber fácilmente el tipo de variable que se trata sin tener que buscar su declaración.
Aunque no es estrictamente necesario, es de mucha utilidad y lo veremos muy a menudo. Aquí va una lista de prefijos más utilizados para nombrar constantes y variables:
Prefijo Tipo de dato
b bool
c char (un solo caracter)
cb contador de bytes
d DOUBLE
fn función
h manipulador (handle)
l LONG
n int
p puntero
pt CPoint o POINT
str CString
sz cadena de caracteres terminada en cero (string with zero)
w WORD
Se utilizará a lo largo de todo el curso, así que no intentes aprenderte toda la tabla... mejor tómala de referencia y ya verás que con el tiempo identificarás casi inconscientemente su tipo. Generalmente se usa la primera letra o una abreviación del tipo de dato.
Ejemplos: nEdad es un número entero de alguna edad; hInstance es un manipulador de instancia (después veremos a que se refiere con "manipulador de instancia").
Se pueden realizar combinaciones mientras sea válido, por ejemplo, lp para long pointer o dw para double word. Así, lpszNombre indica que es un puntero largo que apunta a una cadena de caracteres terminada en cero y que contiene un nombre (aunque algunos optan por nombrarlo lpNombre para no hacerlo muy grande, lo correcto es ponerlo completo).
¡Alto!, ¿lp o puntero largo? en realidad es un simple puntero y ya. Antiguamente existían dos tipos de punteros: cortos (de 2 bytes) y largos (de 4 bytes). A partir de Windows 95 se usan solo los punteros de 4 bytes y ha quedado la costumbre de seguirles llamando largos.
Basados en esto, vamos a rescribir la función WinMain() como debería de ser:
int WINAPI WinMain(HINSTANCE hInstancia, HINSTANCE hInstanciaPrev,
LPSTR lpszLineaCmd, int nEstadoVentana)
{
...
}
2.5 Tipo de datos y constantes
En la programación en Windows existen tipos de datos nuevos. En nuestro programa anterior tuvimos la oportunidad de trabajar dos: HINSTANCE y LPSTR. Como ya te habrás dado cuenta, estos tipos no existen en la programación en C++ estándar. ¿Por qué complicar más las cosas? Es por una razón muy simple, si se realizan actualizaciones al sistema, los programadores no tienen que cambiar sus códigos ni aprender las nuevas reglas (en teoría solo se remplazan los archivos de cabecera). Los tipos de datos están definidos por instrucciones typedef o #define.
Otra cosa, es muy común que las funciones trabajen y regresen números para indicar algo en específico, un ejemplo, el cuarto parámetro de la función WinMain(), nEstadoVentana, tomará el valor de 2 para indicar que la ventana debe mostrarse minimizada o 3 para indicar que debe ser maximizada. Obviamente no se espera que memorices el significado de cada número para cada función, por lo que se ha creado una lista de constantes que explican por sí solas la utilidad de la información contenida en el valor. En nuestro primer programa ya hemos utilizado dos de esas constantes: NULL y MB_OK (ambas valen cero). Puedes utilizar los números en vez de las constantes, da exactamente lo mismo, aunque probablemente pierdas mucho tiempo tratando de entenderlos para realizar modificaciones.
Sabiendo esto y si así lo deseas, puedes escribir el código de la siguiente manera (los cambios en negrita):
#include
int __stdcall WinMain(HINSTANCE hInstancia, HINSTANCE hInstanciaPrev,
char* lpszLineaCmd, int nEstadoVentana)
{
MessageBox(0, "¡Bienvenido a la programación Windows!", "Ejemplo", 0);
return 0;
}
No lo recomiendo, pero sí lo puedes hacer. La desventaja es que el código será menos portátil y perderá legibilidad. Ahora tendremos que recurrir a la documentación de Windows para averiguar que significa el 0 en el cuarto parámetro de MessageBox(), a diferencia de utilizar su constante MB_OK que por lo menos te da una idea que se trata del botón Aceptar.
Vamos a estar continuamente manejando esas constantes numéricas. Volviendo al cuarto parámetro de la función WinMain(), no te diré que nEstadoVentana tomará el valor de 2 o 3 para indicar cómo se mostrará la ventana, en lugar de decirte eso, me referiré a que regresará valores SW_SHOWMINIMIZED o SW_SHOWMAXIMIZED, ¡más fácil!.
Si eres curioso puedes examinar los archivos de cabecera de windows.h, donde podemos encontrar sus siguientes declaraciones:
#define SW_SHOWNORMAL 1
#define SW_SHOWMINIMIZED 2
#define SW_SHOWMAXIMIZED 3
¿Complicado? Las constantes se distinguen porque están escritas en MAYÚSCULAS, tienen un prefijo generalmente de dos letras (SW_, CS_, WM_, etc) y siempre es una buena idea usarlas. El prefijo indica la categoría o su finalidad como por ejemplo, SW_ quiere decir Show Window (mostrar ventana). Con el tiempo irás memorizando las más comunes y de ahora en adelante sabrás que la documentación de Windows es un apoyo inseparable, como veremos en otro capítulo más avanzado, donde te diré cómo y dónde conseguirla.
2.6 La función MessageBox()
Nuevamente, si buscamos en el archivo winuser.h (dentro de windows.h) encontraremos el prototipo de esta función:
int WINAPI MessageBoxW(HWND,LPCWSTR,LPCWSTR,UINT);
#define MessageBox MessageBoxW
La función MessageBox() tiene 4 parámetros de los cuales ahora solo nos interesan los últimos 3: el segundo y tercer parámetro son punteros hacia cadenas de texto (LPCWSTR); el segundo indica el contenido del mensaje a mostrar en pantalla, y el tercero es un string que se mostrará en la barra de título del mensaje. El cuarto parámetro es un número entero (UINT) en el cual indicaremos el estilo del cuadro de mensaje.
Hasta ahora al cuarto parámetro le hemos indicado el valor MB_OK (que sabemos vale cero) porque ese número indica que quieres mostrar el botón Aceptar. Si al cuarto parámetro le indicamos otro número, podremos ser capaces de mostrar un ícono de error o de pregunta, además de mostrar varios botones en lugar de solo uno. Esto no se hace al azar, ya he explicado que contamos con constantes numéricas para indicar qué botones y qué ícono mostraremos en pantalla. Aquí va una pequeña lista de constantes numéricas ya declaradas en windows.h y que podemos combinar para mostrar diferentes mensajes:
// BOTONES
#define MB_OK 0 //botón "Aceptar"
#define MB_OKCANCEL 1 //botones "Aceptar" y "Cancelar"
#define MB_ABORTRETRYIGNORE 2 //botones "Anular", "Reintentar", "Omitir"
#define MB_YESNOCANCEL 3 //botones "Sí", "No" y "Cancelar"
#define MB_YESNO 4 //botones "Sí" y "No"
// ICONOS
#define MB_ICONINFORMATION 64 //ícono de información
#define MB_ICONEXCLAMATION 0x30 //ícono de alerta, signo de exclamación
#define MB_ICONERROR 16 //ícono de error, alto, tacha roja
#define MB_ICONQUESTION 32 //ícono de pregunta
Para combinar dos constantes (ejemplo MB_ICONERROR y MB_OKCANCEL) debes utilizar el operador OR numérico, es decir, las juntas por medio del "|" (MB_ICONERROR | MB_OKCANCEL ... o también ... 16 | 1 ... o inclusive ... 17). ¿Muy sencillo no?, es una técnica que se utilizará en muchas funciones API para fusionar los significados de dos o más constantes, veamos un pequeño ejemplo:
#include
int WINAPI WinMain(HINSTANCE hInstancia, HINSTANCE hInstanciaPrev,
LPSTR lpszCmd, int nEstadoVentana)
{
MessageBox(0,"¿Guardar cambios?", "Título", MB_YESNO | MB_ICONQUESTION);
return 0;
}
Nota aclaratoria: Hay ocasiones en que la suma de las constantes (MB_YESNO + MB_ICONQUESTION) rinden el mismo resultado que utilizando el operador OR numérico (MB_YESNO | MB_ICONQUESTION). Algunos programadores suman las constantes y les funciona, pero no es lo mismo y no debes seguir sus pasos. Para evitar errores, te recomiendo utilizar siempre el operador OR numérico "|", nunca sumes las constantes en tus códigos.
Supongo que a tu nivel deberías entender este "fenómeno". Con esto se busca que si por accidente se repite una constante, el resultado esperado no cambie (sabemos que MB_OKCANCEL | MB_OKCANCEL = MB_OKCANCEL), cosa que si se hubieran sumado hubiera resultado en otra cosa (MB_OKCANCEL + MB_OKCANCEL = MB_ABORTRETRYIGNORE ... sustituyendo... 1 + 1 = 2). ¡Utiliza |!
2.7 Ejercicios
1. Profundizando en la función MessageBox. Deberás mostrar un mensaje en pantalla, con la función MessageBox(), de manera que sea idéntico a la siguiente imagen (fig. 2.7.1):
fig. 2.7.1 Ejercicio de la función MessageBox().
2. Prueba de fuego. Esta es la prueba de fuego, supuestamente ahora eres capaz de crear y entender este programa:
Ejercicio1.cpp
#include
#include
int WINAPI WinMain(HINSTANCE hInstancia, HINSTANCE hInstanciaPrev,
LPSTR lpszLineaCmd, int nEstadoVentana)
{
if (strcmp(lpszLineaCmd, "PASS:Windows") == 0)
MessageBox(NULL, "¡Contraseña aceptada!",
"Bienvenida", MB_OK | MB_ICONINFORMATION);
else
MessageBox(NULL, "Contraseña incorrecta", "Error", MB_OK | MB_ICONERROR);
return 0;
}
Pues sucede que si generas un ejecutable de ese programa, vas a Menú Inicio / Ejecutar y tecleas su ruta junto con la contraseña, como en la fig. 2.7.1, recibirás un mensaje como en la fig. 2.7.1. ¿Cómo ves? apenas estamos calentando motores y ya eres capaz de implementar una sencilla restricción en tus programas.
Otros posibles errores, son no haber configurado el código para compilar para Windows o haber escrito mal el código, ¡cuidado! quiere decir que no has comprendido el capítulo de inicio a fin.
2.8 Resumiendo.
Ya sabemos que siempre hay que incluir el archivo de cabecera windows.h y que el punto de partida de un programa Windows es la función WinMain(), la cual tiene cuatro parámetros de quienes podemos deducir brevemente sus significados si hacemos uso la notación húngara:
Parámetro Significado
HINSTANCE hInstancia manipulador de instancia
HINSTANCE hInstanciaPrev manipulador de instancia previa
LPSTR lpszLineaCmd puntero de cadena terminada en cero que apunta a la línea de comandos
int nEstadoVentana un número entero para el estado de la ventana
Los nombres por sí mismos nos dan una gran referencia de su finalidad, aun sin embargo, estoy consciente de las dudas que crean los dos primeros parámetros (¿qué es un manipulador? ¿qué es una instancia?) por lo que en los dos siguientes capítulos abriré espacios para explicar detenidamente esos conceptos y básicamente incrementaremos el nivel del curso para que no se quejen los listos.
Etiquetas: Mi primer programa en Win32
