domingo, 19 de enero de 2014

La programación Windows II

En la entrada anterior vimos un programa Windows sencillo elaborado en el lenguaje C, el lenguaje que es el estándard de facto para muchos programadores alrededor del mundo, la lingua franca universal con la cual un programador en China puede leer un programa escrito por un programador en Finlandia.

Es posible que algunos lectores aún no tengan muy clara la diferencia entre Windows usado como una mera interfaz visual y Windows usado como sistema operativo. En las primeras versiones de Windows se trataba de una interfaz visual en una máquina que al iniciar operaciones arrancaba con la típica ventana de líneas de comandos DOS. Al encender la máquina no se veía a Windows con su interfaz gráfica por ningún lado, era necesario cargar en la memoria RAM de la máquina al programa ejecutable principal de Windows, generalmente un archivo ejecutable con un título como WINDOWS.EXE, haciendo la invocación desde una línea de comandos como la siguiente:

 C:> WINDOWS\WIN.EXE                       

Esta orden emitida desde la ventana de líneas de comandos se encargaba de inicializar los adaptadores gráficos de video necesarios para la interfaz gráfica de Windows, sacar fuera de la memoria RAM lo que estuviera siendo ocupado por el sistema operativo DOS, y cargar en la memoria RAM la interfaz gráfica de Windows. Con la interfaz gráfica Windows ejecutándose y las bibliotecas de enlazado dinámico del Windows API disponibles a través de la interfaz gráfica Windows, era posible entonces poder correr programas de aplicación Windows al tener Windows el control de la máquina. La ejecución de un programa Windows en una máquina cuyo sistema operativo nativo es el DOS involucra entonces tres pasos: (1) cargar la interfaz gráfica Windows en la memoria RAM pasando el control de la máquina del DOS a Windows, (2) cargar el programa de aplicación Windows en la memoria RAM pasando el control de la máquina de la interfaz gráfica Windows al programa de aplicación Windows, (3) ejecutar el programa de aplicación Windows hasta que ya no sea necesario usarlo, para devolverle el control de la máquina a la interfaz gráfica Windows.

El proceso de transferencias al usar Windows como mera interfaz gráfica puede ser ilustrado del modo siguiente:




Bajo este esquema, cuando ya no se van a correr programas Windows, se puede cerrar la interfaz gráfica Windows regresando al sistema operativo DOS, desde el cual se puede apagar la máquina cuando ya no se requiera seguir trabajando con la máquina. Aunque es posible apagar la máquina desde la interfaz gráfica Windows, lo que en realidad ocurre es que en este proceso de apagado la interfaz gráfica Windows se cierra por cuenta propia pasándole el control de la máquina al sistema operativo DOS con la orden de llevar a cabo automáticamente el apagado de la máquina.

Una máquina vendida originalmente con un sistema operativo DOS (como DOS 6.0) y con la interfaz gráfica Windows instalada posteriormente en la máquina podía empezar en Windows al ser encendida, pero en realidad se trataba de una ilusión, ya que al ser encendida la máquina entraba en acción el sistema operativo DOS, ejecutando de manera automática las órdenes contenidas en el archivo DOS de autoejecución batch (paquete de órdenes diversas) AUTOEXEC.BAT (un archivo de texto legible con el Bloc de Notas o cualquier editor de texto o procesador de palabras), estando en dicho archivo la orden:

 C:> WINDOWS\WINDOWS.EXE                 

con la cual se ponía en marcha la inicialización de la interfaz gráfica Windows. Pero se repite que aunque esto creaba la ilusión de que el sistema operativo que tomaba el control de la máquina al ser encendida era el sistema operativo Windows, en realidad todo empezaba y terminaba con DOS.

En una máquina que tiene instalado Windows como sistema operativo nativo no existe un DOS que se encargue del arranque de la máquina; Windows toma el control directo de la máquina desde un principio y para apagar la máquina se tiene que llevar a cabo el apagado desde el sistema operativo Windows. Fuera de esta diferencia en el modo de actuar al encender y apagar la máquina, la interfaz gráfica Windows 3.1 tenía ya todas las capacidades que se podían encontrar en el primer sistema operativo Windows 95 (el Windows API, la capacidad de poder correr los mismos programas elaborados en Visual Basic tanto en Windows 3.1 como en Windows 95, etcétera), y esta es la razón por la cual algunos académicos no encuentran diferencia significativa alguna entre una interfaz gráfica Windows y un sistema operativo Windows. Se podría argumentar que una diferencia importante entre Windows 3.1 (interfaz gráfica) y Windows 95 (sistema operativo) es que aprovechando los avances espectaculares del hardware Windows 95 podía correr tanto programas Windows de 32 bits como programas Windows de 16 bits, mientras que Windows 3.1 solo podía correr programas de 16 bits. Pero esta diferencia se debe única y exclusivamente al hecho de que cuando Windows 95 hizo su aparición ya se contaba con recursos de hardware que no estaban disponibles cuando se usaba Windows 3.1. De no ser por esto, Windows 3.1 como interfaz gráfica habría sido equiparable y comparable en casi todo a Windows 95 como sistema operativo. Después de Windows 1.x y Windows 2.x, al ser introducido Windows 3.x éste ya había madurado a grado tal que se podía confiar en él para otorgarle el control completo de la máquina desde el arranque. ¿Pero entonces los programas tipo DOS ya no se podían ejecutar bajo un sistema operativo Windows? Sí, pero para ello fue necesario crear una caja de compatibilidad (compatibility box) que pudiera ser invocada desde el sistema operativo Windows para crear una emulación de la ventana típica de líneas de comandos DOS. La siguiente captura de imagen muestra  DOS ejecutándose bajo en control de Windows (se recomienda ampliar la imagen para poder apreciar mejor los detalles a una resolución mayor):



Pero al hacerse esto, al permitir la ejecución de programas DOS con Windows reteniendo el control de la máquina, se precipitaron encima problemas inevitables con muchos programas de aplicación DOS (como simuladores de vuelos, procesadores de palabras y juegos) que requerían que DOS les cediera el control de los adaptadores gráficos de video de la máquina, algo que Windows como sistema operativo capaz de efectuar multitareas (varios programas distintos corriendo al mismo tiempo) no puede hacer bajo ninguna circunstancia si es que quiere retener el control de lo que ocurre coordinando como director de orquesta la sincronía entre distintos instrumentos musicales. Aclarado esto, no haremos distinción entre los programas de aplicación Windows elaborados para Windows como interfaz gráfica y aquellos programas elaborados para Windows como sistema operativo, los supondremos iguales (la única diferencia significativa se encuentra en que el programa Windows sea compilado para correr en una máquina de 16 bits o que sea compilado para correr en una máquina con mayor capacidad en el procesador CPU, por ejemplo una máquina de 32 bits, o de 64 bits, o lo que sea, pero muchos entornos de programación le ofrecen al programador la opción de seleccionar el tipo de programa ejecutable que será producido, ya sea de 16 bits, de 32 bits, o lo que sea; el código fuente sigue siendo el mismo, lo único que cambia es el archivo ejecutable a ser producido de acuerdo a la opción seleccionada por el programador).

Una diferencia importante entre una máquina que tiene el sistema operativo DOS instalado en ella con Windows agregado como mera interfaz gráfica opcional, y otra máquina que tiene instalado a Windows como sistema operativo nativo y desde el cual se puede abrir una ventana de emulación DOS, es que mientras que en el primer caso el sistema operativo DOS le cede por completo el control de la máquina a Windows para serle devuelto a DOS cuando sea cerrada la interfaz gráfica Windows, en el segundo caso el sistema operativo Windows siempre retiene el control de la máquina sin cedérselo por completo a la ventana de emulación DOS; es por ello que en el segundo caso la ventana DOS aparece superpuesta sobre un fondo que corresponde a Windows que a su vez muestra todos los elementos de Windows (íconos puestos en el escritorio, Barra de Tareas, etcétera). La siguiente ilustración intenta dar una idea de esto:




En la entrada anterior ya obtuvimos nuestro primer contacto con el aspecto que toma un programa Windows elaborado en el lenguaje C, familiarizándonos con conceptos fundamentales tales como los tipos derivados, la notación húngara, la función principal WinMain() y el Windows API. La ocasión es propicia para que continuemos familiarizándonos con tópicos más avanzados para poder llevar a cabo la programación Windows en lenguaje C.

Veamos ahora otro ejemplo de un programa Windows, mediante el cual obtendremos la versión del sistema operativo Windows instalado en una máquina así como la versión del DOS que es emulado bajo Windows en la misma máquina. Supondremos que el programa fuente se encuentra archivado bajo el nombre VERSION.C:

// Programa VERSION.C
// El objetivo es proporcionar el numero de version de Windows
// Windows usandose una caja de mensaje para generar la
// respuesta, o sea la obtención de la version del Windows
// instalado en la maquina

#define STRICT          // chequeo estricto de tipos
#include "windows.h"    // archivo include para los
                        // programas Windows

/////////////////////////////////////////////////
// WinMain() -- punto de entrada del programa  //
/////////////////////////////////////////////////

#pragma argsused        // ignorar argumentos no usados

int PASCAL WinMain(HINSTANCE hInstance,
                   HINSTANCE hPrevInst,
     LPSTR lpszCmdLine,
                   int nCmdShow)
   {
   DWORD dwVersion;    // informacion compactada de versiones
   WORD wMsdos;        // MS-DOS;  alt=mayor, bajo=menor
   WORD wWindows;      // Windows; bajo=mayor, alto=menor
   char szBuffer[80];  // buffer para hilera

   dwVersion = GetVersion();     // obtener numero de version
   wMsdos = HIWORD(dwVersion);   // MS-DOS en mitad alta
   wWindows = LOWORD(dwVersion); // Windows en mitad baja


   if( MessageBox(NULL,
                  "Quieres también la versión del DOS?",
                  "VERSION", MB_YESNO | MB_ICONQUESTION) == IDNO )

      // obtener version Windows pero no DOS
      wsprintf( szBuffer, "Windows %u.%u",
      LOBYTE(wWindows), HIBYTE(wWindows) );

   else
      // obtener version Windows y tambien version DOS
      wsprintf( szBuffer, "Windows %u.%u\nMS-DOS %u.%u",
                LOBYTE(wWindows), HIBYTE(wWindows),
                HIBYTE(wMsdos), LOBYTE(wMsdos) );


   // poner una caja de mensaje
   MessageBox(NULL, szBuffer,
              "VERSION", MB_OK | MB_ICONINFORMATION);

   return NULL;                  // regresar a Windows
   }  // finalizar WinMain

Nuevamente, volvemos a encontrar cosas con las que ya nos habíamos familiarizado en la entrada anterior. El programa nos permitirá conocer la versión del Windows que está instalado en la máquina. Al ser compilado y ejecutado este programa, se pone en la pantalla una caja de mensaje que le pregunta al usuario si quiere conocer también la versión del DOS además de la versión de Windows que está instalado en la máquina. Para que el usuario pueda escoger su selección, la caja de mensaje contiene en este caso dos botones, un botón de aceptación Yes y un botón de rechazo No, una caja como la siguiente:




Cuando el usuario selecciona Yes, aparece otra caja de mensaje con dos líneas de texto que contienen los números de versión de Windows y DOS, una caja como la siguiente:




En caso de que el usuario seleccione No, aparece una caja de mensaje como la anterior que únicamente contendrá la versión del Windows instalado en la máquina.

Para que el programa funcione, tenemos que recurrir a una función del Windows API, la función GetVersion() usada para obtener la versión del Windows que está instalado en la máquina. De hecho, esta función nos permite conocer también la versión del DOS que está instalado en la máquina. Toda la información es regresada por esta función en una variable de tipo DWORD (unsigned long) de cuatro bytes de extensión. La función API GetVersion() puede parecer trivial y superflua, considerando que desde la ventana de información de las características del sistema (o bien desde la línea del menú en la interfaz visual Explorer de Windows) el usuario puede buscar y encontrar este tipo de información. Sin embargo, esta función API tiene otra aplicación extremadamente importante en programas profesionales serios. Muchos programas de aplicación requieren conocer la versión del Windows o del DOS instalado en la máquina para tomar conocimiento de las capacidades de la máquina con la finalidad si se cuentan con los suficientes recursos de hardware para que el programa se pueda ejecutar. No es posible, por ejemplo, tratar de ejecutar un programa Windows de 64 bits en una máquina de 32 bits, ni es posible tratar de ejecutar un programa Windows de 32 bits en una máquina de 16 bits. El programador puede desde luego proporcionar tres tipos de programas diferentes, uno para ser ejecutado en máquinas de 16 bits, otro para ser ejecutado en máquinas de 32 bits, y otro para ser ejecutado en máquinas de 64 bits. Pero para que se seleccione automáticamente entre los tres programas disponibles el programa que será utilizado, se tiene que determinar la versión del Windows instalado en la máquina, y solo así se podrá invocar el tipo correcto del programa a ser usado. Y es precisamente para este tipo de cosas que se recurre a la función GetVersion(). En algunas ocasiones, el programa tendrá que negarse por completo a ejecutar, proporcionándole al usuario información útil como “Este programa no puede correr bajo la versión de Windows instalada en la máquina”. Dejarle a los usuarios la tarea de determinar este tipo de cosas por cuenta propia sin ayuda de la máquina no es una opción cuando algunos de los usuarios puedan ser recepcionistas, amas de casa, oficinistas, o estudiantes de primaria que carezcan de conocimientos técnicos. Las computadoras gozan hoy de popularidad porque se les ha hecho accesibles al usuario común y corriente, y no al revés.

¿Y cómo se obtiene de la variable de tipo DWORD regresada por la función GetVersion() los números de versiones Windows y DOS que estamos buscando? La extracción de esta información nos lleva a algo que ya habíamos visto previamente en nuestro primer contacto con el lenguaje C, el tema de los macros (véase la entrada “El lenguaje C VII”). Como posiblemente lo recuerde el lector, un macro es una directiva dada con un #define al preprocesador C que puede tomar argumentos. Los macros actúan en forma muy parecida a las funciones, excepto que los macros se pueden ejecutar con mayor rapidez. El archivo de cabecera WINDOWS.H contiene varios macros que resultan útiles para la manipulación de datos, y puesto que los macros que usaremos están definidos en el archivo de cabecera WINDOWS.H, están disponibles para todos los programas Windows sin necesidad de tener que hacer inclusiones adicionales con #include, al igual que como ocurre con los programas DOS. En el programa VERSION.C dado arriba usamos cuatro macros para extraer los números de versión, y las partes mayor y menor de los números de versión a partir del valor regresado por GetVersion(). A continuación se reproduce una tabla con algunos de los macros más utilizados en los programas Windows:


Macro Propósito
 LOWORD(dw)  Extrae el WORD bajo de una palabra DWORD 
 HIWORD(dw)  Extrae el WORD alto de una palabra DWORD
 LOBYTE(w)  Extrae el BYTE bajo de una palabra WORD
 HIBYTE(w)  Extrae el BYTE alto de una palabra WORD
 MAKELONG(w,w)   Hace una DWORD con dos WORD
 max(a,b)  Extrae el máximo de a o b
 min(a,b)  Extrae el mínimo de a o b
 MAKEPOINT(dw)  Hace una estructura del tipo POINT de una
 DWORD


No entraremos a fondo en los macros y los usaremos tal cual como “cajas negras”. Si el usuario quiere saber qué es lo que se define con los macros usados en Windows, todo lo que tiene que hacer es buscar el archivo de cabecera WINDOWS.H, abrirlo con un editor de texto, y buscar el macro cuya definición se quiere encontrar.

Los macros HIWORD (palabra alta) y LOWORD (palabra baja) toman una palabra doble (de 4 bytes de extensión) como argumento, regresando en 2 bytes lo que corresponde a la palabra de orden alto y regresando en otros 2 bytes lo que corresponde a la palabra de orden bajo. Aunque podemos lograr lo mismo usando operadores de manipulación de bits, los macros son más fáciles de usar. Usamos estos macros para separar los números de versión de Windows y DOS de la variable dwVersion en las dos líneas:

   wMsdos = HIWORD(dwVersion);        // MS-DOS en mitad alta
   wWindows = LOWORD(dwVersion);  // Windows en mitad baja

Las variables wMsdos y wWindows ambas son del tipo derivado WORD que está definido en el archivo de cabecera WINDOWS.H como unsigned short. Las variables declaradas como variables de este tipo usan una “w” minúscula como la notación Húngara para indicar el tipo.

La siguiente ilustración nos explica la forma en la que está codificada la información que nos es regresada por la función GetVersion() (la palabra doble DWORD está formada por las palabras WORD de color amarillo y WORD de color ciano):




Muchos propietarios de las computadoras caseras que utilizan el software de Microsoft instalado en sus máquinas ya están familiarizados con lo que es la versión mayor y lo que es la versión menor de su sistema operativo, usándose la misma convención que la que se utiliza para las versiones de actualización de muchos programas de aplicación. Un ejemplo es el sistema operativo Windows 8. La primera ocasión que se comercializa un sistema operativo nuevo, el número de versión menor es cero, de modo tal que el primer Windows 8 fue el Windows 8.0. El número de versión mayor es, desde luego, 8. Usualmente, la primera gran versión de un sistema operativo nuevo adolece de fallas que aparecen en ciertas combinaciones de sistemas en donde no se había probado el sistema operativo nuevo, y se empiezan a liberar actualizaciones y “parches” (patches) para corregir tales defectos. Eventualmente, se han acumulado tantas actualizaciones y “parches” que en vez de suministrarse por separado se acostumbra incluir dicho material en un número de versión menor superior, de modo tal que el Windows 8.0 pasa a ser Windows 8.1. El sistema operativo 8.0 sigue siendo esencialmente el mismo que el sistema operativo 8.1, pero el cambio de versión menor indica una mejor opción de adquisición porque incluye muchas actualizaciones y parches que de otra manera se tendrían que procurar por otros medios. Pero cuando el sistema operativo cambia de una versión mayor a la siguiente en secuencia numérica, se trata de un sistema operativo nuevo y diferente y no de una mera actualización de un sistema operativo ya existente, que frecuentemente requiere de algún cambio substancial en el hardware a tal grado que es posible que una máquina con Windows 8.1 no podrá ser actualizada a Windows 10.0.

En el ejemplo de la figura dada arriba, la versión menor para el MS-DOS es 00, y la versión mayor para el MS-DOS es 6, de modo tal que el MS-DOS instalado en la máquina es el MS-DOS 6.00. Del mismo modo, la versión mayor para el Windows es 3 y la versión menor es 10, de modo tal que el Windows instalado en la máquina es el 3.10.

Una vez que hemos separado la palabra doble en dos palabras que contienen el número de la versión MS-DOS de la máquina y el número de la versión de Windows instalada, usamos los macros HIBYTE y LOBYTE para separar los números de versión mayor y menor de cada palabra. Estos macros regresan el byte de orden alto y el byte de orden bajo de la palabra que se les dá como argumento. La siguiente figura muestra la manera en la cual trabajan los macros:




En vez de crear cuatro variables adicionales, usamos los macros HIBYTE y LOBYTE directamente en las invocaciones a la función wsprintf(). Para mostrar únicamente la versión del Windows, la invocación es la siguiente:

      wsprintf( szBuffer, "Windows %u.%u,
         LOBYTE(wWindows), HIBYTE(wWindows);

Como puede verse, los valores regresados por LOBYTE y HIBYTE son injertados directamente en la hilera de formateo, en donde aparecen los códigos de formato <b>%u</b>. La hilera resultante es almacenada en el buffer szBuffer.

Obsérvese que hay una inconsistencia entre los números de versión del DOS y de Windows: en el número de versión del DOS el byte de orden alto contiene el número de la versión mayor, mientras que en Windows es el byte de orden bajo. Esta es una de las idiosincrasias de Windows que es mejor no tratar de explicar.

En el programa, tanto la versión Windows como la versión DOS son mostradas en la Caja de Mensaje, y una invocación similar en wsprintf() mete los cuatro números en la hilera de formateo. Obsérvese cómo la notación húngara hace más fácil verificar si usamos los argumentos correctos. Supóngase por ejemplo que en una parte del programa se hace que el macro LOBYTE tome una palabra como argumento, entonces sabríamos de inmediato que la siguiente expresión es incorrecta:

LOBYTE(dwConteo)

El programa que acabamos de ver arriba introduce un elemento verdaderamente propio del lenguaje C: el enunciado de control del tipo if-else. Esto implica que cualquier conocimiento previo en las estructuras de control del lenguaje C se puede aplicar de inmediato en la programación Windows. El programa introduce dos variantes nuevas en la Caja de Mensaje que no habíamos visto previamente. En primer lugar, se agrega un ícono decorativo a un lado del texto de la caja. Y en segundo lugar, usamos el valor de retorno de la función API MessageBox() para dirigir el control del flujo del programa. Para esto último, incrustamos la función en el enunciado if para poder checar de un modo conveniente el valor de retorno de la función:

   if( MessageBox(NULL,
                  "Quieres también la versión del DOS?",
                  "VERSION", MB_YESNO | MB_ICONQUESTION) == IDNO )

Siempre podemos poner uno de varios íconos en una Caja de Mensaje. La siguiente tabla en la que el símbolo MB significa Message Box nos muestra las posibilidades:


Constantes-icono para Cajas de Mensaje
(Message Box)
Constante Icono
 MB_ICONQUESTION  Un signo de interrogación verde
 (pregunta formulada al usuario)
 MB_ICONINFORMATION  Una letra "i" azul
 (información para el usuario)
 MB_ICONEXCLAMATION   Un símbolo de exclamación
 (para mensajes urgentes)
 MB_ICONSTOP  Un signo rojo de alto
 (para mensajes muy urgentes)


Puesto que el propósito caso de la Caja de Mensaje que estamos viendo es formular una pregunta, usamos la constante MB_ICONQUESTION para generar el ícono de interrogación. El operador lógico OR (|) se encarga de combinar esto con la constante MB_YESNO.

Si una Caja de Mensaje tiene dos o más botones, tenemos que determinar cuál de los botones ha sido oprimido por el usuario para cerrar la Caja de Mensaje. Logramos esto checando el valor de retorno proporcionado por la función MessageBox(). La siguiente tabla nos muestra las posibilidades:


Valores de retorno para MessageBox()
Constante Significado
 IDOK  Se ha oprimido el boton OK
 IDYES  Se ha oprimido el boton Yes
 IDNO  Se ha oprimido el boton No
 IDCANCEL   Se ha oprimido el boton Cancel 
 IDIGNORE  Se ha oprimido el boton Ignore
 IDABORT  Se ha oprimido el boton Abort
 IDRETRY  Se ha oprimido el boton Retry


En el programa VERSION.C, si el usuario oprime el botón Yes montamos una hilera de caracteres que contiene tanto el número de versión MS-DOS como el número de versión Windows. Si el usuario oprime No, formamos una hilera que contiene únicamente el número de versión de Windows. En ambos casos, se hace una segunda invocación a la función MessageBox() para imprimir la hilera seleccionada, usándose en esta ocasión no el ícono de interrogación sino el ícono de información.

Si se desea obtener una familiarización con programas sencillos que solo usan cajas de mensaje, podemos usar otras funciones del Windows API que regresan información útil e interesante acerca del sistema. Una de ellas es GetNumTask(), la cual no toma argumentos y regresa el número de tareas o instancias que se están ejecutando en la máquina, regresando esta información como un número entero del tipo int.

Un crítico de los que nunca faltan podría objetar de que en lo que hemos visto previamente en realidad no se refleja casi nada de lo que es realmente Windows, habido el hecho de que las cajas de mensaje en realidad no son ventanas que puedan ser maximizadas o minimizadas, y en esto el crítico tendría razón. Es por ello que la ocasión es propicia para introducir un programa mediante el cual se creará una verdadera ventana Windows (la palabra inglesa “windows” significa ventana) que será puesta en la pantalla.

// Programa VENTANA.C
// El objetivo es poner una ventana Windows en la pantalla

#define STRICT           // chequeo estricto de tipos
#include "windows.h"     // archivo include para los
                         // programas Windows

// prototipo para el procedimiento WndProc()
LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM);

/////////////////////////////////////////////////
// WinMain() -- punto de entrada del programa  //
/////////////////////////////////////////////////

#pragma argsused    // ignorar argumentos no usados

int PASCAL WinMain(HINSTANCE hInstance,  // en que programa estamos?
     HINSTANCE hPrevInst,         // hay otro programa?
            LPSTR lpCmdLine,      // argumentos linea comandos
            int nCmdShow)         // tamaño ventana (icon, etc)
   {
   HWND hWnd;              // agarradera (handle) a una ventana
   MSG msg;                // mensaje de GetMessage
   WNDCLASS wndClass;      // estructura de clase window

   if(!hPrevInst)     // en caso de que sea la primera ventana
      {
      wndClass.style         = NULL;     // estilo predeterminado

      // domicilio en RAM de la funcion WndProc
      wndClass.lpfnWndProc   = (WNDPROC)WndProc;

      wndClass.cbClsExtra    = 0;            // no datos de clase
                                             // extra

      wndClass.cbWndExtra    = 0;            // no datos de ventana
                                             // extra

      wndClass.hInstance     = hInstance;    // que programa?

      // cursor de flecha convencional
      wndClass.hCursor       = LoadCursor(NULL, IDC_ARROW);

      // icono convencional blanco
      wndClass.hIcon         = LoadIcon(NULL, IDI_APPLICATION);

      // no habra una barrea de menu en la ventana
      wndClass.lpszMenuName  = NULL;

      // un fondo blanco para el interor de la ventana
      wndClass.hbrBackground = GetStockObject(WHITE_BRUSH);

      // nombre de la clase de ventana
      wndClass.lpszClassName = "fundwinClass";

      // registrar la clase
      RegisterClass(&wndClass);
      }  // finalizar if

   hWnd = CreateWindow("fundwinClass",       // nombre clase ventana
                       "Ventana",            // titulo en la ventana
                       WS_OVERLAPPEDWINDOW,  // estilo de traslape
                       CW_USEDEFAULT,        // posicion x
                                             // predeterminada
                       CW_USEDEFAULT,        // posicion y
                                             // predeterminada
                       CW_USEDEFAULT,        // anchura
                                             // predeterminada
                       CW_USEDEFAULT,        // altura
                                             // predeterminada
                       NULL,                 // agarradera (handle)
                                             // del progenitor
                       NULL,                 // agarradera (handle)
                                             // de menu
                hInstance,            // cual programa?
                       NULL);                // no hay datos
                                             // iniciales

   ShowWindow(hWnd, nCmdShow);     // mostrar la ventana

   // bucle de mensaje
   while(GetMessage(&msg,0,0,0) )  // obtener mensaje de Windows
      {
      TranslateMessage(&msg);      // convertir tecleados
      DispatchMessage(&msg);       // invocar procedimiento window
      }
   return msg.wParam;              // regresar valor de
                                   // PostQuitMessage() 
   }  // finalizar WinMain()

//////////////////////////////////////////////////////////////
// procedimiento de la ventana principal -- recibe mensajes //
//////////////////////////////////////////////////////////////

LRESULT CALLBACK _export WndProc(HWND hWnd,  // agarradera de ventana
                                 UINT msg,       // numero mensaje
                                 WPARAM wParam,  // parametro word
                                 LPARAM lParam)  // parametro long
   {
   switch(msg)                   // cual mensaje?
      {
      case WM_DESTROY:    // nosotros generaamos este mensaje
  PostQuitMessage(0);     // enviar WM_QUIT
         break;

      // todos los demas mensajes son manejados por el
      // procedimiento DefWindowProc()

      default:
         return( DefWindowProc(hWnd, msg, wParam, lParam) );
      }   // finalizar switch(msg)

   return 0L;              // definicion de retorno si se manejo msg
   }  // finalizar WndProc



Dependiendo del entorno de programación que se utilice, si se tienen problemas para ejecutar el programa anterior es posible que se tenga que eliminar la línea que impone un chequeo estricto de tipos:

#define STRICT

Todo el código del programa Windows que se ha proporcionado, cuando es ejecutado, lo único que hace es poner en la pantalla del monitor una ventana como la siguiente:




Si comparamos la manera en la cual se genera una ventana sencilla (llamada Forma) con su fondo interior en blanco con la ayuda del entorno de programación Visual Basic, podemos apreciar el hecho de que el código requerido en lenguaje C para generar una ventana ordinaria en Windows es extenso. Aquí no tenemos (en lo que hemos visto hasta este punto) el beneficio de contar con una Caja de Herramientas de la cual podamos crear el objeto Forma, redimensionándolo y ajustando las propiedades de la Forma (como su título y su color de fondo) a través de la caja de Propiedades para la Forma proporcionada por Visual Basic. Como también es cierto que no se ha usado en la elaboración del código de VENTANA.C concepto alguno que tenga que ver con la programación orientada a objetos. Al usar la función API CreateWindow() para crear una ventana básica Windows, hemos simplificado un poco la creación de la ventana especificando el uso de los valores predeterminados (default) proporcionados por Windows para la creación de una ventana. En el entorno de programación Visual Basic, si no se hace modificación alguna de las características de una Forma (ventana) mediante la caja de Propiedades de la Forma dejándola tal cual, también se obtendrá una Forma con valores predeterminados prefijados por Visual Basic. El recurrir al lenguaje C para llevar a cabo la programación Windows trae consigo un costo inmediato, el aumento en la complejidad de la programación. Sin embargo, y en el fondo, debemos tener en cuenta que el entorno de programación Visual Basic lo único que hace es proporcionar un “colchón” para esconder los numerosos detalles desagradables que surjen cuando se programa usando el lenguaje C, porque a fin de cuentas la maquinaria de cómputo de Visual Basic tiene que “convertir” todo a su estructura en C, habido el hecho de que todas las funciones del Windows API fueron especificadas desde un principio en C y no en Visual Basic y mucho menos en el lenguaje BASIC (que carece de herramientas adecuadas para poder elaborar programas a ser ejecutados en un entorno puramente gráfico).

Precisamente a causa del incremento considerable en complejidad al tener que efectuar la programación Windows usando el lenguaje C, se desarrollaron entornos de programación en C que permitieran hacer algo parecido a lo que se hace en Visual Basic; o sea incorporando la programación visual a las interfaces usadas en los paquetes comerciales de software para llevar a cabo la programación Windows usando el lenguaje C. De este modo, se puede crear algo como una ventana sin entrar en tanto detalle, y el entorno de programación C se encarga de generar el código C correspondiente para lo que el programador ha especificado visualmente. Pero además se tuvo que incorporar también la programación orientada a objetos, algo para lo cual el lenguaje C por sí solo resultó insuficiente, requiriéndose el uso de un C capaz de permitir llevar a cabo la programación orientada a objetos.

Los entornos Visual C++Borland C++ reflejaron desde un principio la incorporación de las herramientas necesarias no solo para poder llevar a cabo la programación Windows en C sino para poder especificar visualmente objetos cuyo código equivalente en una versión refinada de C podía ser generado en forma rápida y automática por el entorno de programación. La mayoría de los entornos de programación usados en la actualidad reflejan esta filosofía de programación.

Resta decir que además del lenguaje C, se han desarrollado entornos de programación visual para Windows basados no en el lenguaje C sino en una versión refinada de otro lenguaje estructurado conocido como Pascal. Tal es el caso de Delphi. Pero estando especificado el Windows API en lenguaje C, tales opciones en el fondo requieren llevar a cabo la conversión de los programas fuente Windows escritos en el lenguaje Pascal a su equivalente en programas fuente Windows escritos en C.

A continuación analizaremos la manera en la cual está estructurado el programa Windows VENTANA.C que se ha dado arriba para generar una simple ventana.

Como puede verse, el programa consta de dos funciones, la función principal WinMain(), y la función de procedimiento WndProc(). La función principal WinMain() contiene tres secciones principales de código: en primer lugar se le asignan varios valores a una estructura en C llamada wndClass con lo que estaremos especificando algunas de las características de la manera en la que se comportará y se verá la ventana cuando aparezca en la pantalla. Estos valores son esencialmente lo mismo que al usar Visual Basic se fijaba y se ajustaba a través de las propiedades de la ventana (Forma). Una vez que se han asignado los valores a la estructura, se ejecuta una función del Windows API llamada CreateWindow() (CrearVentana) que contiene muchos argumentos, los cuales complementan las primeras especificaciones dadas para la ventana en la estructura wndClass. Y finalmente se entra en un bucle perpetuo usado para la captura de mensajes de acuerdo a la manera en la que trabaja la programación Windows que bajo cualquier lenguaje que se utilice sigue (y seguirá siendo) una programación impulsada por eventos (la opresión de una tecla en el teclado, un clic en uno de los botones del Mouse, una orden dada a través de un micrófono, etcétera).

La segunda función usada en el programa, WndProc(), es considerablemente más breve que la función principal WinMain(), y consiste principalmente de un enunciado switch (recuérdese que switch es un enunciado de control que forma parte de las palabras clave reservadas del lenguaje C). Como su nombre lo indica, es un procedimiento de Windows.

La gran mayoría de los programas Windows constan por lo menos de las dos funciones WinMain() y WndProc(). Algo a tener en cuenta es que, mientras que a WinMain() no se le puede cambiar el nombre y siempre se debe llamar WinMain(), al procedimiento Windows que hemos llamado aquí WndProc() se le puede dar cualquier otro nombre; igual lo podríamos haber llamado Procedimiento() o WinMainProc() o FuncionAccesoria(), aunque es conveniente usar nombres que reflejen el propósito de la función.

La siguiente figura muestra el esquema utilizado para la construcción del programa Windows VENTANA.C:




A diferencia de la Caja de Mensaje generada por el primer programa Windows que vimos en la entrada anterior, la ventana puesta por el programa anterior es una ventana cuya anchura y cuya altura pueden ser variadas posicionando la flecha del cursor sobre uno de los bordes y arrastrando la línea del borde con el Mouse hacia donde se quiera para cambiar una de las dimensiones laterales de la ventana; y se trata de una ventana que puede ser minimizada o maximizada con los botones puestos en la esquina superior derecha de la ventana, además de poder ser cerrada con el botón de la “X” puesta para tales efectos en todas las ventanas Windows.

La ventana puesta por el programa en la pantalla del monitor es lo que se conoce como una ventana principal, y es similar a las ventanas puestas en la pantalla por la mayoría de los programas Windows cuando se hace clic en un ícono o se seleccionan ciertas líneas en un menú. La función principal WinMain() hace algo más que simplemente crear una ventana nueva. Se sigue una serie ordenada de pasos consistentes en crear primero una clase de ventana o clase Window, crear una ventana principal a partir de dicha clase, y proporcionar un bucle perpetuo para permitir que el programa pueda responder a acciones tomadas por el usuario.

En general, para crear una ventana Windows así sea la más sencilla, se requieren dos pasos: (1) en primer lugar es necesario especificar una clase, y una vez hecho esto, (2) se especifica una ventana individual de dicha clase. Para aquellos que tienen alguna familiaridad con la terminología usada en la programación orientada a objetos o los conceptos en los que se basa la programación visual llevada a cabo bajo el entorno Visual Basic, la idea de usar cierto “armazón” o “esqueleto” llamado clase que sirve como una “plantilla general” a partir de la cual se pueden ir creando uno o más objetos (instancias) va dejando de ser algo abstracto (general) para ir tomando forma en las diversas maneras en que los conceptos pueden ser aplicados, usándo la terminología de la programación orientada a objetos para describir lo que sucede. Una clase de ventana es una especie de formato para crear un número de ventanas que comparten características similares, como si fuera el plano de una licuadora enviado a una fábrica con el cual en la fábrica (la computadora) se pueden fabricar varias licuadoras (instancias, objetos) de acuerdo a las características especificadas en el plano (la clase). Para especificar una clase, la clase wndClass en el programa Windows de arriba, esto se hace con la instrucción:

WNDCLASS wndClass;

En este caso, wndClass es una variable de estructura del tipo WNDCLASS como se acostumbra hacerlo con las estructuras en el lenguaje C. La estructura WNDCLASS, tal y como está definida en el archivo de cabecera WINDOWS.H, es la siguiente:
typedef struct tagWNDCLASS
{
    UINT        style;
    WNDPROC     lpfnWndProc;
    int         cbClsExtra;
    int         cbWndExtra;
    HINSTANCE   hInstance;
    HICON       hIcon;
    HCURSOR     hCursor;
    HBRUSH      hbrBackground;
    LPCSTR      lpszMenuName;
    LPCSTR      lpszClassName;
} WNDCLASS;

Una vez que se han especificado los atributos de una clase, el siguiente paso consiste en registrar la clase con la función API RegisterClass():

RegisterClass(&wndClass);

Cuando creamos una clase de ventana, lo hacemos especificando los atributos que tendrá la ventana, tales como la forma del cursor del Mouse que aparecerá cuando sea posicionado dentro de la ventana, la forma del ícono que será asociado con dicha ventana, así como la estructura de la barra de menú que será desplegada en la parte superior de la ventana si es que queremos que la ventana posea una barra de menú. Una vez que los atributos han sido especificados, usamos la clase como una especie de plano para crear las ventanas actuales que tendrán dichos atributos.

Aunque el concepto de las clases de ventanas es derivado de los conceptos usados en la programación orientada a objetos, al madurar Windows con la versión Windows 3.x no se usaban otras características propias de la programación orientada a objetos, y por lo tanto no se podía afirmar que Windows fuera algo realmente orientado a objetos. Sin embargo, el código fuente de los programas de aplicación a ser ejecutados en Windows sí podía ser codificado para estar orientado a objetos, como lo demostró Visual Basic.

La especificación de los atributos llevada a cabo mediante la asignación de los valores a la variable del tipo estructura WNDCLASS se hace con la ayuda del operador punto:

     wndClass.style         = NULL;
     wndClass.lpfnWndProc   = (WNDPROC)WndProc;
     wndClass.cbClsExtra    = 0;
     wndClass.cbWndExtra    = 0;
     wndClass.hInstance     = hInstance;
     wndClass.hCursor       = LoadCursor(NULL, IDC_ARROW);
     wndClass.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
     wndClass.lpszMenuName  = NULL;
     wndClass.hbrBackground = GetStockObject(WHITE_BRUSH);
     wndClass.lpszClassName = "fundwinClass";

Sin la intención de atiborrar al lector con demasiados detalles, a continuación daremos una descripción breve de lo que se logra con la anterior asignación de valores a los atributos de la variable wndClass.

La clase especificada por la variable wndClass usa un cursor de Mouse convencional del tipo flecha, lo cual se especifica con la instrucción:

wndClass.hCursor = LoadCursor(NULL, IDC_ARROW);

En este enunciado en lenguaje C, fijamos el miembro hCursor de la variable de estructura wndClass al valor de retorno proporcionado por la función API LoadCursor(). Esta función del Windows API carga un cursor que puede ser uno de los cursores convencionales que el mismo sistema Windows proporciona ya de por sí, o un cursor que nosotros mismos podemos crear con un programa gráfico para construcción de íconos. La constante IDC_ARROW proporcionada como segundo argumento a la función especifica un cursor en forma de flecha que apunta hacia arriba y hacia la izquierda, es el mismo cursor que se utiliza en muchos programas Windows. Cambiando esta constante podemos especificar otros cursores, incluyendo aquellos cursores de nuestra propia creación.

Del mismo modo, el enunciado:

wndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);

recurre a la función API para especificar el ícono que será utilizado para representar a la ventana cuando sea minimizada a un ícono. La constante IDI_APPLICATION produce un ícono convencional de aplicación que parece una pequeña ventana multicolores (sugestiva del logo de las ventanas Windows). Cambiando este enunciado, podemos especificar un ícono creado por nosotros mismos.

La clase wndClass en este programa no incluye una barra de menú, pero si queremos una podemos agregarla cambiando la línea:

wndClass.lpszMenuName = NULL;

Tal vez el lector se pregunte: ¿por qué razón debemos preocuparnos con clases? ¿Por qué no especificar simplemente atributos, tales como cursores e íconos, para cada ventana individual? La respuesta radica en el hecho de que Windows como programa gráfico fue diseñado para correr inicialmente en computadoras con procesadores Intel 80286, en una época en la que la memoria RAM era escasa y cara, y la conservación de la memoria estaba en primer lugar en la lista de prioridades. Una manera de minimizar el uso de la memoria era juntar y agrupar aquellos atributos de ventana que consumen mucha memoria. Esta colección de atributos fue llamada “clase” por la empresa creadora de Windows. De este modo, si se tienen abiertas varias ventanas al mismo tiempo, y todas ellas usan los mismos atributos, no es necesario almacenar por separado los mismos atributos usados repetidamente para cada ventana, simplemente se define la ventana como perteneciente a tal o cual clase, y la ventana adquiere automáticamente los atributos. Cuando esto ocurría, el paradigma de la programación orientada a objetos empezaba a tomar forma y pronto encontraría su nicho en la simplificación de la elaboración de programas Windows complejos en donde uno de los objetivos es precisamente aprovechar el código y los datos que son comunes a dos o varios objetos. Interesantemente, la mayoría de los programas Windows sencillos no aprovechan las ventajas de las clases, porque solo usan una ventana principal.

Una especificación de atributos que debe llamarnos la atención es lo siguiente:

wndClass.lpfnWndProc = (WNDPROC)WndProc;

Esta instrucción le informa a la clase en dónde puede encontrar el procedimiento que será usado por la ventana, es el domicilio en la memoria RAM de la función WndProc() que aparece posteriormente en el programa. Obsérvese que el miembro <b>lpfnWndProc</b> de la estructura WNDCLASS es definido como una variable del tipo WNDPROC, y es por ello que se requiere hacer una operación de cast sobre lo que nos proporciona el domicilio en el RAM de la función WndProc().

Otros miembros de la estructura WNDCLASS rara vez son alterados en programas Windows sencillos. Podemos ignorarlos hasta que sea necesario hacer algo poco usual. De cualquier modo, todos tienen que ser fijados a algún valor, y no se debe tratar de ahorrar espacio en la memoria borrando tales enunciados.

Una vez que se ha especificado una clase de ventana asignando valores a los diversos atributos de la variable wndClass, tenemos que pedirle a Windows que almacene esta información en la memoria (lo cual no ocurre automáticamente), lo cual se logra con la función API RegisterClass(), la cual toma como su único argumento el domicilio de la estructura que define a la clase, en este caso el domicilio de wndClass que viene dado por &wndClass.

Una vez que Windows conoce la especificación de la variable wndClass, podemos crear una ventana individual de dicha clase. Esto se logra con la función API CreateWindow() que crea una ventana individual dándole estas características. En el programa Windows que estamos analizando, esto se lleva a cabo proporcionándole a la función API once argumentos en la manera que se muestra a continuación:

   hWnd = CreateWindow("fundwinClass",
                       "Ventana",
                       WS_OVERLAPPEDWINDOW,
                       CW_USEDEFAULT,
                       CW_USEDEFAULT,
                       CW_USEDEFAULT,
                       CW_USEDEFAULT,
                       NULL,
                       NULL,
                       hInstance,
                       NULL);

El primer argumento dado a la función API CreateWindow() es el nombre de la clase de la cual será creada la ventana. Aquí se le ha dado el nombre “fundwinClass” (ventana de clase fundamental), aunque podríamos haberle dado cualquier otro nombre (incluso en Castellano). Este nombre tiene que ser el mismo que la hilera de caracteres asignada al miembro lpszClassName de la estructura wndClass. Si los dos nombres no coinciden, en muchos compiladores y entornos el programa será compilado a un archivo ejecutable sin que se proporcione advertencia alguna de error de omisión, pero no aparecerá ventana alguna cuando se ejecute el programa. Este tipo de bicho puede ser muy difícil de detectar sobre todo en programas Windows grandes, así que hay que asegurarse desde un principio que los nombres son exactamente iguales (incluyendo mayúsculas y minúsculas).

El segundo argumento dado a la función API CreateWindow() es el título que aparecerá en la ventana en la barra del título en la parte superior. En una ventana principal, este título generalmente será también el nombre del programa.

Posiblemente el conjunto más complejo de características de la función <b>CreateWindow()</b> es el que tiene que ver con el estilo de la ventana. El estilo especifica un cierto conjunto de propiedades de la ventana. La selección de estas propiedades es parecido a lo que queremos tener cuando compramos un carro, por ejemplo un carro de color gris con transmisión automática y con tocador de CDs equipado con Bluetooth, pero sin bocinas de alta potencia y sin geolocalizador GPS. Para la ventana que queremos crear con el programa dado arriba, queremos que haya una barra de título en donde aparezca el título de la ventana, un menú de sistema, botones de minimización y maximización, y un borde grueso que pueda ser usado para variar las dimensiones de la ventana, todo lo cual crea una ventana “convencional”. Podríamos especificar estas opciones por separado, usando constantes como WS_CAPTION, WS_SYSMENU, WS_THICKFRAME y así sucesivamente (las primeras dos letras WS significan “Window Style”). Sin embargo, existe otra constante que nos permite especificar todas estas opciones a la vez: WS_OVERLAPPEDWINDOW, proporcionada como tercer argumento a la función. La ventana principal en un programa generalmente usa este tamaño estándard.

En lo que toca a los argumentos del cuarto al séptimo, estos especifican el tamaño y la posición. La constante usada para todos estos argumentos en nuestro programa C es CW_USEDEFAULT. Esta constante le indica a Windows que utilice su propio criterio en la ubicación y el dimensionamiento de la ventana cuando sea puesta en la pantalla (la ventana tiene que caber dentro de la pantalla y debe ser completamente visible sin rebasar los bordes del Escritorio). Si queremos que una ventana aparezca en una cierta posición en la pantalla, o que tenga cierto tamaño, podemos usar números específicos proporcionados como argumentos. Las coordenadas son medidas en pixeles a partir de la esquina superior izquierda de la pantalla.

No entraremos a fondo en los últimos cuatro argumentos de la función API CreateWindow(). Nuestra ventana no tiene una ventana progenitora dentro de la cual aparecerá puesta, de modo tal que el octavo argumento es puesto a NULL. El noveno argumento es una manera alternativa de especificar un menú, que no usamos aquí, y por lo tanto también es puesto a NULL. El décimo argumento es el valor hInstance obtenido como un parámetro de WinMain(), mientras que el onceavo argumento es usado únicamente en situaciones muy especiales, y por lo tanto también es puesto a NULL.

La función API CreateWindow() nos regresa una agarradera a la ventana. Esta agarradera puede ser usada por otras funciones para hacer cosas tales como actuar de alguna manera sobre una ventana o bien obtener información de la ventana.

Otra de las cosas que Windows no hace automáticamente es mostrar en la pantalla una vez que se ha creado una ventana con CreateWindow(). Cuando se crea una ventana por vez primera, no es visible. Para hacer visible la ventana, es necesario usar la función API ShowWindow():

ShowWindow(hWnd, nCmdShow);

Esta función toma como argumentos la agarradera de la ventana regresada por CreateWindow() y el valor de nCmdShow tomado de un parámetro a WinMain(). Windows usa este parámetro para decirle al programa cuando arranca si la ventana debe ser creada minimizada (como un ícono) o maximizada (como una ventana de tamaño completo).

El programa Windows que hemos visto para la creación de una ventana Windows incorpora enunciados de control que son propios al lenguaje C y los cuales son independientes del tipo de interfaz que se esté utilizando (lo cual le confiere a C cierta universalidad). Si se repasa el programa Windows dado arriba, encontramos que los enunciados que definen y registran la clase son parte de un enunciado C del tipo if. A grosso modo, este es el aspecto del condicional:

  if(!hPrevInst)     // en caso de que sea la primera ventana
      {
      // enunciados que definen la estructura de wndClass
      // funcion RegisterClass() para registrar la clase
      }

Para poder entender el propósito de este condicional, imagínese que se están ejecutando dos instancias del mismo programa. Cada instancia requiere la creación de una ventana basada en la clase wndClass. La primera instancia (la primera ventana) ciertamente requiere crear la clase que servirá como el esquelo o armazón, pero todas las instancias subsecuentes no deberían de hacer esto, simplemente deben crear ventanas a partir de una clase que ya ha sido definida. ¿Cómo sabe una instancia si debe crear primero una clase? El parámetro hPrevInst a WinMain() es nulo (NULL) si no hay una instancia previa del programa. En el enunciado if checamos este valor. Si somos la primera instancia, continuamos adelante y se ejecuta todo el código del condicional. Y si no somos la primera instancia, pasamos por encima del condicional y nos vamos directamente a la creación de una ventana específica de la clase usando CreateWindow().

El programa consta de dos funciones definidas en el lenguaje C. Cuando usamos un esquema de programación rígido como lo demanda Windows, es necesario declarar de antemano el prototipo de cualquier función antes de que la función sea invocada en el programa, de acuerdo a las convenciones definidas en el ANSI C. En este caso, el prototipo de la función que sirve como procedimiento está dado al principio del programa por la línea:

LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM);

Una diferencia entre este programa y los programas Windows vistos con anterioridad es que el valor de retorno de la función principal WinMain() ya no es NULL, como podemos verlo en la última línea dentro de WinMain():

return msg.wParam;

Este valor de retorno está relacionado con los mensajes, y es aquí cuando volvemos a anticipar el papel que desempeñan los mensajes en un entorno en el que la programación es manejada o impulsada por eventos en lo que se conoce como programación manejada por eventos o alternativamente programación impulsada por eventos. Este material requiere desde luego una discusión posterior.

No discutiremos aquí en detalle la función WndProc(), pero si observaremos la aparición de otro enunciado de control C, el enunciado switch.

LRESULT CALLBACK _export WndProc(HWND hWnd,
                                 UINT msg,
                                 WPARAM wParam,
                                 LPARAM lParam)
   {
   switch(msg)
      {
      case WM_DESTROY:
  PostQuitMessage(0);
         break;

      default:
         return( DefWindowProc(hWnd, msg, wParam, lParam) );
      }

   return 0L;
   }

Hay un solo enunciado de control case aparejado con el enunciado de control switch, que es el siguiente:

case WM_DESTROY:

Este enunciado case es ejecutado cuando el segundo parámetro a la función WndProc(), que es el parámetro msg, tiene el valor WM_DESTROY. Al estudiar esto más a fondo, se descubre que msg es un mensaje, lo cual es algo que se debe anticipar en una programación manejada por eventos en donde se están intercambiando mensajes. Otros programas Windows más complejos utilizan más enunciados case porque manipulan más mensajes.

Resumiendo a grandes rasgos, tenemos la siguiente forma general para un programa Windows básico que es la que usaremos como punto de partida para elaborar programas Windows más complejos:




Una cosa aparentemente extraña que tal vez le haya llamado la atención a algunos lectores ya familiarizados con el lenguaje C es que la función WndProc() jamás es invocada desde la función principal WinMain(). ¿Cómo puede ser esto posible? se preguntarán. Si el programa principal no invoca a la función, entonces ¿quién lo hace y cómo lo hace? De nueva cuenta, esto tiene que ver con la forma en la cual se ejecuta un programa en un entorno como Windows en donde todo va tomando un camino de acuerdo a los eventos que van ocurriendo.