Tabla de Contenidos

WinAPI

WinMain y el WndProc

Estudiaremos estos dos elementos cogiendo un código general de un programa que utilice la API de Windows:

#include <windows.h>
 
LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
 
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
	HWND hWnd;
	MSG Message;
	WNDCLASS WndClass;
 
	WndClass.style = CS_HREDRAW | CS_VREDRAW;
        WndClass.lpfnWndProc = WindowProcedure;
	WndClass.cbClsExtra=0;
	WndClass.cbWndExtra = 0;
	WndClass.hbrBackground = (HBRUSH) GetStockObject (LTGRAY_BRUSH);
	WndClass.hCursor = LoadCursor (NULL, IDC_ARROW);
	WndClass.hIcon= LoadIcon (NULL, IDI_APPLICATION);
	WndClass.hInstance = hInstance;
	WndClass.lpszClassName = "NUESTRA_CLASE";
	WndClass.lpszMenuName = NULL;
 
	RegisterClass (&WndClass);
 
	hWnd = CreateWindow (
		"NUESTRA_CLASE",
		"Ventana de ejemplo",
		WS_OVERLAPPEDWINDOW,
		CW_USEDEFAULT,
		CW_USEDEFAULT,
		320,
		200,
		HWND_DESKTOP,
		NULL,
		hInstance,
		NULL
		);
 
	ShowWindow(hWnd, SW_SHOWDEFAULT);
 
	while (TRUE==GetMessage(&Message, 0,0,0))
	{
		TranslateMessage(&Message);
		DispatchMessage(&Message);
	}
 
	return Message.wParam;
}
 
LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	switch(msg)
	{
	case WM_DESTROY:
		PostQuitMessage(0);
		break;
	default:
		return DefWindowProc (hwnd, msg, wParam, lParam);
	}
	return DefWindowProc(hwnd,message,wParam,lParam);
}

En el código anterior aparecen dos funciones: WinMain y WindowProcedure. El WinMain es lo que es el main a un programa convencional en C: el punto de entrada, la primera función que se ejecuta. La WndProc es la función encargada de recibir y procesar todos los mensajes que el SO manda a tu ventana (teclas presionadas, cambio de tamaño…).
Recuerda incluir el windows.h cuando programes con la Win32.
Es posible que si desarrollas con Visual Studio el código generado dé problemas (en la WndClass o en la CreateWindow) debido a conversiones de strings, esto es debido al juego de carácteres configurado como Unicode, hay que configurarlo como Multibyte (en las propiedades generales del proyecto).

WinMain

Parámetros

Su declaración recibe los siguientes parámetros:

Bloques que contiene

  1. Declaración de variables y elementos iniciales para el programa (HWND, WNDCLASS…).
  2. Especificación de las características del programa (asignación de valores a las propiedades de la WNDCLASS y registro de esta).
  3. Creación de la ventana principal del programa (CreateWindow).
  4. Bucle del programa (o bucle de mensajes (message loop)), el while que irá recibiendo los mensajes y gestionándolos.

Tipos internos

Funciones que utiliza

WndProc

Es la función de tu aplicación a la que le llegan los mensajes enviados por el sistema operativo, generalmente contiene un switch que es donde se elige qué mensaje ha llegado, y por tanto, como gestionarlo. El mensaje viene como un int, y su valor correspondrá a uno de los defines declarados en windows.h, junto a él vienen dos parámetros wParam y lParam, estos dan más información sobre el mensaje. Algunos de los mensajes existentes son los siguientes:

Cómo consultar varios valores en el lParam

Si lParam contiene varios valores utilizaremos las macros: LOWORD y HIWORD:

xPos=LOWORD(lParam);
yPos=HIWORD(lParam);

Gestión manual de mensajes

En windows toda ventana que se crea tiene asociado un thread y una función para procesar mensajes (wndproc). Una vez se crea la ventana a esta le empiezan a llegar mensajes, estos se van enconlando en una cola de mensajes global del sistema e identifican la ventana por su hwnd.

Funciones

Todas estas funciones manipulan una estructura denominada MSG. Generalmente se les pasa un puntero a una variable de este tipo y estas funciones dan los valores concretos.

Documentación de estas funciones en la msdn

GDI

GDI es el conjunto de funciones que se utilizan para pintar sobre una ventana. Lo mejor es usarlas en el WndProc, cuando le llegue el mensaje: WM_PAINT, ya que es cuando el sistema pide un repintado. Recuerda, para utilizarlas tendrás que linkar la gdi32.lib.
Para dibujar necesitamos declarar una PAINTSTRUCT. El dibujo lo definiremos entre las llamadas a BeginPaint y EndPaint, estas reciben el HWND y el PAINTSTRUCT.

PAINTSTRUCT ps;
BeginPaint (hwnd, &ps);
...
EndPaint (hwnd, &ps);

Generalmente, el elementos que necesitaremos para hacer nuestros dibujos será la propiedad hdc del PAINTSTRUCT.

MoveToEx(ps.hdc, 40,40,NULL);
LineTo(ps.hdc, 50,50);
HPEN RedPen = CreatePen (PS_SOLID, 2, RGB(255,0,0));
HPEN OldPen = (HPEN) SelectObject(hdc, RedPen);
POINT vertices[3];
vertices[0].x =25;
vertices[0].y =25;
vertices[1].x =50;
vertices[1].y =25;
vertices[2].x =AnchoVent;
vertices[2].y =AltoVent;
Polygon(hdc, vertices, 3);
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
TextOut(hdc,10,10,"Hello World",11);
EndPaint(hWnd, &ps);

Cómo...

General

switch (uMsg) {
	case WM_ACTIVATE:
		if (!HIWORD(wParam)) active = TRUE;
		else active = FALSE;
		break;
...

Funciones avanzadas de control de teclado

A las dos se les pasa como parámetro el nombre de la tecla: VK_LSHIFT, VK_RSHIFT, VK_LCONTROL…

if (GetAsyncKeyState(VK_LSHIFT)) MessageBox(NULL, "hol", NULL, NULL);

Creación de un Timer

Normalico

Para crear un Timer convencional utilizamos la función SetTimer, esta recibe los siguientes parámetros:

SetTimer(hWnd, 101, 1, NULL);

Para recibir los mensajes del timer lo haremos desde el WndProc, llegará un mensaje WM_TIMER. En el wParam vendrá el identificador del timer:

case WM_TIMER: 
     switch (wParam) { 
        case IDT_TIMER1: ... break;
        case IDT_TIMER2: ... break;
    } 

Para destruir un timer utilizamos KillTimer, pasándole:

De alta resolución

:!: Este tipo de timers requieren linkar la librería winmm.lib.

Construiremos un timer haciendo uso de las funciones QueryPerformanceFrequency y QueryPerformanceCounter, a las dos se les pasa un puntero a referencia a LARGE_INTEGER, la primera coloca en este la frecuencia del contador de alta resolución (si no puede porque el sistema no acepta este tipo de contadores coloca un 0), la segunda devuelve el valor actual del contador.
Para construir nuestro timer utilizaremos la siguiente estructura:

struct {
  __int64		frequency;			// Frecuencia
  float			resolution;			// Resolución
  bool			performance_timer;		// Indica si es un timer de alta resolución
  unsigned long		mm_timer_start;			// Momento en el que inicia (baja resolución)
  unsigned long		mm_timer_elapsed;		// Momento en el que finaliza (baja resolución)
  __int64		performance_timer_start;	// Momento en el que inicia (alta resolución)
  __int64		performance_timer_elapsed;	// Momento en el que finaliza (alta resolución)
} timer;

Usaremos la función que inicia el timer y la que recoge el momento actual. La que inicia el timer recogerá la frecuencia del contador de alta resolución, si este es soportado configurará el timer con los parámetros adecuados para este. Si no, conficurará un timer de baja resolución. Un timer de baja resolución utilizará la función timeGetTime, esta devuelve en milisegundos la hora del sistema (su frecuencia por tanto será de 1000 en un segundo y su resolución de 0.0001). Para el de alta esto vendrá según el parámetro dado por el sistema.

void TimerInit() {
	memset(&timer, 0, sizeof(timer));						
	if (!QueryPerformanceFrequency((LARGE_INTEGER *) &timer.frequency)) {
		timer.performance_timer	= FALSE;					
		timer.mm_timer_start	= timeGetTime();			
		timer.resolution	= 1.0f/1000.0f;				
		timer.frequency		= 1000;						
		timer.mm_timer_elapsed	= timer.mm_timer_start;	
	} else {
		QueryPerformanceCounter((LARGE_INTEGER *) &timer.performance_timer_start);
		timer.performance_timer		= TRUE;	
		timer.resolution		= (float) (((double)1.0f)/((double)timer.frequency));
		timer.performance_timer_elapsed	= timer.performance_timer_start;
	}
}

Ahora, recogemos el tiempo actual y lo restamos del inicial:

float TimerGetTime() {
	__int64 time;											
	if (timer.performance_timer) {
		QueryPerformanceCounter((LARGE_INTEGER *) &time);
		return ( (float) ( time - timer.performance_timer_start) * timer.resolution)*1000.0f;
	} else {
		return( (float) ( timeGetTime() - timer.mm_timer_start) * timer.resolution)*1000.0f;
	}
}

Configurar la resolución de pantalla y crear aplicaciones fullscreen

Puedes sacar algo de cambios de resolución o de convertir la aplicación a una de pantalla completa si lees el artículo de OpenGL que habla de ello, encontrarás información sobre un DEVMODE, la función ChangeDisplaySettings y EnumDisplaySettings.

Existen otras funciones que pueden serte de utilidad si haces aplicaciones de este estilo:

Controles

Mostrar un mensaje

MessageBox(NULL, “Mensaje”, “Título”, NULL);

El primer parámetro corresponde al hwnd. Luego viene el mensaje y el título. En el último parámetro podremos elegir que icono y que botón queremos que aparezcan (icono | boton).

Luego podrá devolver distintos valores: IDNO, IDOK, IDCANCEL…

Trabajar con el menú de la aplicación

Tenemos que crear el menú como resource y luego asignarlo a la WNDCLASS.

winclass.lpszMenuName  = MAKEINTRESOURCE(IDR_MENU1);
case WM_COMMAND:
	switch (wParam)	{
		case ID_MENU1_MOSTRARCUADRO: ... break;
		case ID_MENU2_CONFIG: ... break;
	}
	break;

GDI

Crear doble buffer

  1. Crearemos un hdc a parte del que tenemos y compatible con este.
  2. Dibujaremos sobre este hdc.
  3. Volcaremos este sobre el que dibuja en la pantalla. De esa forma no mostramos y dibujamos a la vez.

Las variables que necesitamos:

HDC hdcBackBuffer;
HDC hdc;
HBITMAP BMPantes;
HBITMAP BMPahora;

Para conseguir un hdc compatible con el de la ventana utilizamos el método CreateCompatibleDC.

hdcBackBuffer = CreateCompatibleDC(NULL);
hdc = GetDC(hwnd);
BMPahora = CreateCompatibleBitmap(hdc, AnchoVent, AltoVent);
BMPantes = (HBITMAP) SelectObject(hdcBackBuffer, BMPahora);

Utilizaremos la función BitBlt para pintar sobre los hdc:

BitBlt(hdcBackBuffer, 0, 0, AnchoVent, AltoVent, NULL, NULL, NULL, WHITENESS);
RectX += velX;
RectY += velY;
SelectObject(hdcBackBuffer, Brocha);
Ellipse(hdcBackBuffer, RectX, RectY, RectX+tam,RectY+tam);
BitBlt(ps.hdc, 0, 0, AnchoVent, AltoVent, hdcBackBuffer, 0, 0, SRCCOPY);

Si la ventana cambia de tamaño tendremos que hacer los cambios adecuados en los bitmaps creados:

SelectObject(hdcBackBuffer, BMPantes);
DeleteObject(BMPahora); 
hdc = GetDC(hwnd);
BMPahora = CreateCompatibleBitmap(hdc,AnchoVent,AltoVent);
ReleaseDC(hwnd, hdc);
SelectObject(hdcBackBuffer, BMPahora);

La función BitBlt hace un volcado pixel a pixel de un hdc a otro, recibe los siguientes parámetros:

Para ver las flags y más información sobre la función: MSDN - BitBlt

Threads y procesos

Procesos

Threads

Sockets

Notas

void CALLBACK TimerProc(hwnd, uMsg, idEvent, dwTime) {...
WndClass.hIcon= LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_ICON1));
WndClass.hCursor = LoadCursor (GetModuleHandle(NULL), MAKEINTRESOURCE(IDC_CURSOR1));

Enlaces externos

Documentos