Inicio > Desarrollo, Tutorial > Temporizadores nativos de Windows con C++ 11

Temporizadores nativos de Windows con C++ 11

Existen multitud de razones para programar en C++. Pero el API de Windows puede parecer un poco intimidante al principio y esa es la razón por la que tanta y tanta gente prefiere usar o bien librerías que se hagan cargo del bajo nivel o de entornos gestionados donde todo es mucho más fácil.

Pero la verdad es que con poquísimo esfuerzo es posible ir creando pequeñas herramientas que nos faciliten la vida y que llevan a la creación de código extremadamente legible y fácil de mantener.

El problema con los temporizadores

Hacer un temporizador en el API nativo de Windows puede ser, bien extremadamente simple o bien sorprendentemente complejo, dependiendo de los conjuntos de APIs que queramos emplear. Si usamos ventanas clásicas (ay! esos tiempos), con la función SetTimer() conseguimos que nos llegue un mensaje WM_TIMER periódicamente a nuestra ventana “y ya está”. La cosa se complica cuando queremos timers asíncronos, donde nos veremos obligados a emplear threads para hacerlo.

Lo que busco es poder aprovechar la versatilidad de C++ en su versión 11 y poder escribir algo como sigue:

CTimer timer;timer.Set(1000,1000,[]() 
  { 
    putc('.',stdout); 
  });

Bonito, verdad? Sin crear threads, sin declarar cosas globalmente, sin mensajes a ventanas. Todo ello se puede hacer empleando el API de Windows nativo, con una simple clase que nos haga un poco el trabajo por debajo aprovechando la función CreateTimerQueueTimer():

BOOL WINAPI CreateTimerQueueTimer(
  _Out_     PHANDLE phNewTimer,
  _In_opt_  HANDLE TimerQueue,
  _In_      WAITORTIMERCALLBACK Callback,
  _In_opt_  PVOID Parameter,
  _In_      DWORD DueTime,
  _In_      DWORD Period,
  _In_      ULONG Flags
);

Vamos a ver un poco por encima qué son todos esos argumentos que requiere el API. Para empezar, phNewTimer es un puntero a un HANDLE estándar de Windows (PHANDLE). Es una variable que deberemos suministrar al API y donde, una vez invocado, nos devolverá el handle sobre el que realizaremos el resto de las operaciones con el mismo. TimerQueue es un argumento opcional, que nos permite emplear colas de timers “custom” hechas a medida, si es que deseamos un control tan fino de la ejecución. En nuestro ejemplo vamos a emplear NULL para solicitar la cola predefinida por el sistema.

Aquí es donde viene la parte interesante, en el argumento Callback. Un tipo WAITORTIMERCALLBACK básicamente define un tipo concreto de función a la que el sistema operativo invocará al dispararse el temporizador. Con eso iremos en unos minutos, junto con Parameter, argumento que se pasará de vuelta a nuestra Callback.

DueTime y Period son, como podréis haber podido imaginar, los tiempos para el primer disparo y subsiguientes, definidos en milésimas de segundo. Finalmente, Flags es una variable donde podremos incluir opciones de funcionamiento de nuestro timer.

Dejémoslo bonito

Vamos a la implementación. Para empezar, necesitaremos guardar en nuestra clase CTimer el handle que el API de Windows nos devolverá al crear el timer, y que inicializamos a nulo en el constructor de la clase. También necesitaremos una forma de almacenar el código que deseamos ejecutar con el timer, y que nos llegará como expresión lambda de C++:

#include <functional>
class CTimer
{
public:
  CTimer()
  {
    m_hTimer = NULL;
  }
  virtual ~CTimer()
  {
    Cancel();
  }

private:
  HANDLE m_hTimer;
  std::function<void()> m_callback;
};

La plantilla std::function<> (declarada en functional.h) es un utilísimo almacén especializado precisamente en eso: en almacenar expresiones lambda, dada simplemente su firma (en nuestro caso “void()”). Aprovechamos para incluir el destructor y una llamada a una función, Cancel() que destruirá el timer junto con nuestro objeto. Ya que tenemos los miembros preparados e inicializados, vamos con la parte realmente divertida: la función Set() que nos permitirá configurar el timer:

HRESULT Set(DWORD dueTime,DWORD period,std::function<void()> callback)
{
  if (m_hTimer == NULL)
  {
    if (CreateTimerQueueTimer(&m_hTimer,NULL,&internalCallback,this,
                              dueTime,period,
                              period ? WT_EXECUTEDEFAULT : WT_EXECUTEONLYONCE) == FALSE)
      return(GetLastError());
  }
  else
  {
    if (ChangeTimerQueueTimer(NULL,m_hTimer,dueTime,period) == FALSE)
      return(GetLastError());
  }
  m_callback = callback;
  return(S_OK);
}

La función Set() crea o altera el timer nativo dependiendo de si la variable m_hTimer ya tiene un valor. De esta forma podremos alterar el timer al vuelo, cambiando por ejemplo los tiempos de disparo. Cada llamada a Set() tambien almacena en la variable m_callback la expresión lambda que queremos emplear – esto nos permitiría, por ejemplo, emplear el mismo timer con distintas funciones lambda dependiendo de nuestras necesidades.

Si os fijáis, al llamar al API siempre usamos la misma función callback: internalCallback. Esta función, junto con el parámetro “this” que le sigue, nos permitirán a la hora de recibir la notificación del sistema operativo, saber qué lambda debemos invocar.

La implementación de la función internalCallback() sería como sigue:

private:
static VOID CALLBACK internalCallback(PVOID lpParameter,BOOLEAN TimerOrWaitFired)
{
  CTimer *pTimer = (CTimer*)lpParameter;
  if (pTimer->m_callback)
    pTimer->m_callback();
}

La función es estática (el API de Windows no sabe de clases de C++), pero hemos incluido la instancia del objeto como argumento al crear el timer, y nos llegará en el argumento lpParameter. Simplemente castear el parámetro a CTimer* nos devuelve un puntero utilizable para determinar el contenido de la función lambda e invocarla.

Finalmente, la función Cancel() destruye el timer. Podremos invocar esta función manualmente o simplemente, dejar que se llame desde el destructor de la clase:

HRESULT Cancel()
{
  if (m_hTimer != NULL)
  {
    DeleteTimerQueueTimer(NULL,m_hTimer,NULL);
    m_hTimer = NULL;
  }
  return(S_OK);
}

La función DeleteTimerQueueTimer() del API destruye un timer creado previamente con CreateTimerQueueTimer(), pasando el handle que nos devolvió al crearlo.

Y ya está. Hemos creado desde cero un componente que encapsula el API de Windows dentro de una clase con todas las funcionalidades del lenguaje C++. Vamos a darle un poco de contexto a nuestro ejemplo al principio del artículo, para poder probar el componente:

int _tmain(int argc, _TCHAR* argv[])
{
  printf("AsyncTimer test\n");

  CTimer timer;
  timer.Set(1000,1000,[]()
  {
    putc('.',stdout);
  });

  printf("Press any key ");
  _getch();

  return 0;
}

Chorra, pero nos sirve: el programa simplemente imprime un mensaje (“Press any key”) mientras nuestro timer imprimirá un punto cada segundo hasta que pulsemos una tecla. El programa está totalmente detenido en la función __getch(), que detiene la ejecución hasta que se pulse una tecla. El timer se ejecutará de forma totalmente asíncrona, en un thread gestionado por el sistema operativo. Al terminarse el programa y salir de la función, el objeto timer se destruirá, cancelando el temporizador y limpiando los recursos antes de la salir.

A continuación, la clase completa, y a continuación, enlaces al proyecto completo de ejemplo:

#pragma once

#include <functional>

class CTimer
{
public:
  CTimer()
  {
    m_hTimer = NULL;
  }
  virtual ~CTimer()
  {
    Cancel();
  }

public:
  HRESULT Set(DWORD dueTime,DWORD period,std::function<void()> callback)
  {
    if (m_hTimer == NULL)
    {
      if (CreateTimerQueueTimer(&m_hTimer,NULL,&internalCallback,this,
      dueTime,period,
      period ? WT_EXECUTEDEFAULT : WT_EXECUTEONLYONCE) == FALSE)
      return(GetLastError());
    }
    else
    {
      if (ChangeTimerQueueTimer(NULL,m_hTimer,dueTime,period) == FALSE)
        return(GetLastError());
    }
    m_callback = callback;
    return(S_OK);
  }

  HRESULT Cancel()
  {
    if (m_hTimer != NULL)
    {
      DeleteTimerQueueTimer(NULL,m_hTimer,NULL);
      m_hTimer = NULL;
    }
    return(S_OK);
  }

private:
  static VOID CALLBACK internalCallback(PVOID lpParameter,BOOLEAN TimerOrWaitFired)
  {
    CTimer *pTimer = (CTimer*)lpParameter;
    if (pTimer->m_callback)
      pTimer->m_callback();
  }

private:
  HANDLE m_hTimer;
  std::function<void()> m_callback;
};

El proyecto completo está aquí.

Anuncios
Categorías:Desarrollo, Tutorial Etiquetas:
  1. Aún no hay comentarios.
  1. No trackbacks yet.

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s

A %d blogueros les gusta esto: