subscribe
Professor de tecnologies i màrqueting
C/C++, Programación, Servicios Windows, Sockets, TCP/IP

Crear un servicio Windows, Fase 0

Introducción

Descargar este artículo en formato PDF (22 descàrregues)

Descargar código fuente en C++(1.4 MB) (20 descàrregues)

Este es un primer artículo dentro de una serie de artículos dedicados a los servicios Windows en el lenguaje de programación C/C++ con API de Windows y soporte de clases MFC.

El lenguaje C/C++ es el más rápido y efectivo, lo cual es muy importante para un servicio de este tipo que trabaja a nivel de sistema operativo a bajo nivel.

El objetivo de esta serie de artículos es crear una pequeña arquitectura para comunicar un sistema Arduino con un PC bajo Windows, de forma que el pequeño Arduino pueda solicitar información al PC sobre una base de datos o realizar procesos en de modificación de dichos datos a través de una red Wi-Fi.

Esto permitiría, por ejemplo, moverse físicamente por un almacén dotado de red Wi-Fi y desde cualquier punto solicitar la ubicación física de una mercancía, o autorizar la entrada de un pallet, o la descarga automática de un pallet al suelo o a una salida si el almacén es automático.

Para cumplir el proceso, voy a ir a través de una serie de artículos, cada uno de ellos dotado con un código fuente documentado y compilable con Visual Studio (desde la versión 2010 en adelante) que se puede descargar desde esta web y que recoge los objetivos de esa fase concreta. Este es el listado de fases y objetivos de cada una:

  1. Fase 0: Crear un servicio Windows. Este servicio es la base para el desarrollo posterior de cualquier otro servicio, porque contiene las funciones necesarias para:
    1. Ser admitido como un servicio más dentro de Windows.
      1. Usa un thread para administrar el servicio.
      2. Usa otro thread para realizar el trabajo real del servicio.
  • Responde a las órdenes del administrador de servicios de Windows y su funcionamiento es sencillo, básico y estable.
  1. Incorpora rutinas para:
    1. Auto-instalarse o auto-desinstalarse desde línea de comando.
    2. Ponerse en marcha, pausarse y detenerse desde línea de comandos.
  • Ponerse en marcha, pausarse y detenerse desde Administrador de Servicios de Windows.
  1. Poder depurarse mediante un log o escritura de traza en un archivo de texto.
  1. Fase 1: Añadir soporte XML. De este modo podremos usar XML para escribir y leer archivos de configuración y establecer el XML como lenguaje para enviar comandos remotos y recibir datos estructurados de respuesta.
  2. Fase 2: Añadir funcionalidades socket TCP/IP.
    1. Coloca un socket servidor al que puede conectarse un cliente mediante un socket con protocolo TCP/IP, siguiendo la configuración almacenada en un archivo de texto XML..
    2. Recibe comandos desde TCP/IP.
    3. Parsea (interpreta) los comandos mediante XML.
    4. Ejecuta los comandos si es posible.
    5. Retorna resultados mediante XML, sean datos o errores de sintaxis.
  3. Fase 3: Establece conexiones con una base de datos ODBC, siguiendo la configuración establecida en el archivo de texto XML.
  4. Fase 4: Soporte XML para comandos de base de datos. Añade comandos para consultar o modificar la base de datos.

Vamos ahora a responder preguntas sobre servicios Windows, para saber cómo vamos a encaminarnos hacia los objetivos de la fase 0.

¿Para qué sirve un servicio Windows?

Un servicio Windows sirve para realizar procesos automáticos en segundo plano sin intervención del usuario.

Por ejemplo: Un usuario puede estar revisando una hoja de cálculo Excel mientras que en segundo plano su máquina está, por ejemplo, realizando cálculos, imprimiendo mediante un spooler de impresora, o controlando señales que le llegan desde un dispositivo o desde la red.

El usuario dedica su atención es a la aplicación Excel; en cambio su máquina está haciendo otras diez cosas en segundo plano mediante diez servicios más que corren en segundo plano.

¿Qué es un servicio Windows?

Un servicio Windows es un proceso que se ejecuta constantemente en segundo plano (background) sin colapsar la CPU de la máquina, al cual se le añade un soporte importante de configuración y administración por parte del propio sistema operativo.

En general, los servicios Windows se usan para manejar procesos no atendidos por un usuario, pero que están monitorizados o controlados por la máquina (PC) sin que el usuario lo note.

Un servicio Windows, aunque se parezca, no es un daemon o demonio. Los daemons son procesos en segundo plano de Linux basados en bucles infinitos.

Son la manera de implementar un daemon, pero bajo Windows, con una serie de funcionalidades de control de tiempo de vida del proceso del servicio.

Windows proporciona tres cosas importantes con sus servicios:

  1. Un interfaz de administrador para ver qué servicios tenemos en marcha, cuáles queremos arrancar, cuales queremos parar, pausar o reanudar y cuáles queremos que arranquen cuando la máquina se pone en marcha (boot).
  2. Un conjunto de funciones de API del sistema operativo dedicadas específicamente para servicios que proporcionan dos funcionalidades:
    1. Permiten administrar los servicios por programa del mismo modo que lo hace el interfaz del administrador de servicios.
    2. Permiten realizar instalaciones y desinstalaciones de servicios desde línea de comando del sistema operativo, quedando listos para ser visibles por el interfaz del administrador de servicios del sistema operativo.
  3. Estos dos puntos anteriores van acompañados de controles de seguridad de usuarios y cuentas, pudiéndose adjudicar derechos que permitan a un usuario o a un grupo poner en marcha un servicio desde interfaz o desde programa propio.

Estas tres ventajas no las tiene el sistema operativo Linux, y dan unas facilidades interesantes al administrador y al programador para controlar y/o crear aplicaciones en segundo plano bajo Windows.

¿Cómo se manejan los servicios desde el interfaz de Windows?

Mediante una aplicación que viene de serie en el sistema operativo: el administrador de servicios (service manager).

Podemos acceder a él desde el panel de control hasta llegar a los servicios o bien, abreviando, desde el prompt del sistema operativo llamando a “services.msc”:

Al llamarlo, nos aparece la pantalla que vemos sobre estas líneas.

En ella aparecen todos los servicios Windows que tenemos instalados en nuestra máquina.

Para cada uno de estos servicios vemos una serie de columnas que nos dan información:

  • Columna 1: Nombre del servicio.
  • Columna 2: Descripción larga del servicio (opcional).
  • Columna 3: Estado del servicio:
    • Parado
    • Iniciado
    • Pausado (puede reanudarse desde el mismo estado que cundo se paró).
  • Columna 4: Modo de arranque del servicio:
    • Manual: Se pone en marcha cuando el usuario lo decide o bien cuando es un servicio subordinado a otro servicio que lo necesita.
    • Automático: Cuando se arranca en el mismo momento en que se inicia el sistema operativo de la máquina en la que funciona.
  • Usuario con cuyos derechos se puede operar el servicio.

Si marcamos con el ratón sobre una línea y después hacemos clic en ella con el botón derecho, nos aparece un menú contextual:

Desde este menú podemos arrancar, parar, pausar reanudar, refrescar el estado (ver si alguna aplicación lo ha cambiado) o ver sus propiedades y poder modificar algunas (dependiendo de los derechos de administrador que tengamos).

Si seleccionamos la opción Propiedades o si hacemos doble clic sobre un servicio, nos aparecerá la hoja de propiedades (dicho de otro modo, el diálogo de pestañas) de sus propiedades:

Ya, para empezar vemos que desde este diálogo podemos controlar cuatro pestañas que hacen referencia a cuatro aspectos diferentes de los servicios:

  1. Aspectos generales:
    1. Nombre del servicio y descripción si la hubiera.
    2. Ubicación del servicio (nombre de archivo y ruta de acceso a su directorio).
    3. Funciones de arranque, parada, pausa y reanudación mediante botones.
  2. Inicio de sesión: Qué usuario inicia el servicio o de parte de quién se inicia. Podemos establecer el usuario por defecto (usuario local) que está muy limitado para no infringir normas de seguridad ni provocar agujeros o podemos seleccionar derechos de administrador. Dentro del abanico tenemos opciones intermedias como seleccionar un usuario que tenga derechos a medio camino. Dicho usuario puede ser un usuario de la máquina o de un dominio de la red.
  3. Recuperación en caso de errores: un error puede deberse simplemente a que un servicio está mal programado y falla, o bien a que no tiene derechos suficientes para hacer una determinada operación, o a que no existe un archivo necesario o que un dispositivo con el que interactúa no está conectado. La opción de recuperación en caso de errores permite especificar si en caso de error no se hace nada y queda parado, o si se reinicia automáticamente o si la primera vez se reinicia y la segunda ya no (por ejemplo).
  4. Dependencias: Dos casos:
    1. Qué otros servicios deben ponerse en marcha antes de que se ponga a funcionar este. Si no están en marcha, forzará a que se pongan en marcha.
    2. Qué otros servicios pueden depender de este. Meramente informativo.

Como podemos ver, las potencialidades están bien pensadas porque permiten una configuración bastante flexible, pero todas estas opciones pagan el precio de tener un código fuente que las gestione.

¿Cómo se organiza el código fuente de un servicio Windows en C/C++?

Ahora que ya hemos visto cuáles son las opciones de administración de un servicio Windows, vamos a ver qué repercusiones tiene todo esto en el código fuente de un servicio.

En principio, vamos a tener dos threads o hilos (como mínimo):

  1. Un thread principal de administración que tiene que estar siempre disponible para que el administrador de servicios pueda hablar con nosotros (el servicio). ¿Para qué? Para ponernos en marcha, pararnos, pausarnos, reanudarnos… o sea, para administrarnos.
  2. Un thread secundario de actividad que contenga la acción real del servicio, esto es, el bucle que vamos a repetir periódicamente en segundo plan sin que el usuario lo note.

Además de estos dos, podemos tener un número genérico de N threads más y llegar a la complejidad, en cuanto a multitarea, que nos haga falta.

El thread principal de administración

El servicio se ejecuta como todas las aplicaciones EXE, es decir:

  • El thread principal se pone en marcha a partir de una función main (en este caso _tmain) bastante típica del lenguaje C, que puede recibir parámetros desde línea de comando. Estos parámetros son los que se usan en el código fuente para instalar o desinstalar el servicio, así como para arrancarlo o pararlo sin necesidad de usar el administrador de servicios.
  • Inicia recursos que necesitará más adelante, como por ejemplo, dependiendo de cada proyecto, poner a cero variables globales, abrir puertos de comunicaciones, reservar memoria global si hiciera falta o preparar sockets de protocolo TCP/IP o named pipes de protocolo NetBEUI para que alguien pueda ponerse en contacto con el servicio desde la red.

NOTA:

Dependiendo de lo que queramos hacer dentro de nuestro servicio, puede irnos mejor realizar la incialización de recursos desde el evento de arranque de servicio, y la liberación de los mismos desde el evento de parada de servicio.

De hecho, podemos hacer un mix y inicializar parte en _tmain y parte en el evento de arranque del servicio.

  • Instala el controlador del servicio que usará el sistema operativo para iniciar, parar, etc. No se trata del mismo sistema que la llamada desde línea de comando; se trata del sistema empleado, por ejemplo, desde el administrador de servicios. El thread principal contiene una función en forma de ServiceControlHandler en el que escucha los mensajes que le pueden llegar desde el sistema operativo, desde otra aplicación, etc., y reacciona frente a ellos. Se encarga de llamar a las funciones de puesta en marcha, parada, pausa y reanudación.

Para ello, registra el ServiceControlHandler con dos parámetros y se los pasa a la función de registro. Los dos parámetros son:

  1. El nombre del servicio.
  2. El puntero a la función que hará de controlador del servicio.

Al registrarlo, ya pone a disposición del sistema operativo la dirección en donde se encuentra la función que tiene que usar para parar y arrancar el servicio.

  • Inicia el segundo thread que es el que realiza la tarea que queremos controlar desde nuestro servicio. De este modo, un administrador puede estar administrando el servicio mientras el servicio sigue funcionando, porque los threads son independientes.

Código fuente de la función _tmain

Este es el código fuente básico del esqueleto de la función _tmain del thread principal.

Veremos llamadas a la función WriteLog, la cual trataremos un poco más adelante. La función WriteLog, de momento, la entenderemos como una ayuda para poder depurar externamente el servicio y verificar en qué punto funciona o falla.

Vemos que las frases que genera para depurar van precedidas de [M]. Es la M de main  thread. Esto indica, como veremos más adelante que el mensaje proviene del thread principal.

Dejémoslo así de momento y pasemos a ver el código fuente:

///////////////////////////////////////////////////////////////////////////////
// This is the main entry point for a console application, no matter when it
// is called either by the operating system (boot) or by a command line prompt.
// It receives two parameters:
//
// 1. argc is the argument counter, i.e., the number of command line parameters.
// 2. argv[] is the array of command line parameters being received.
//

int _tmain(int argc, _TCHAR* argv[])
{
       char szTemp[200];

       ::InitializeCriticalSection(&myCS);

       memset(szTemp, 0, sizeof(szTemp));
       WriteLog("[M]_tmain: main program entry point--------------------");
       if(argc >= 2)
       {
             strcpy(lpCmdLineData, argv[1]);
             sprintf(szTemp, "[M]_tmain: command line contains parameter %s", argv[1]);
             WriteLog(szTemp);
       }
       ServiceMainProc();
       return 0;
}

La función InitializeCriticalSection sirve para preparar un área de código fuente que el compilador deberá establecer como un bloque imposible de interrumpir por la concurrencia de procesos de Windows.

Este bloque se encuentra definido en la función WriteLog mediante dos funciones de definición de área crítica que son:

  1. EnterCriticalSection o inicio de área crítica.
  2. LeaveCriticalSection o final de área crítica.

El código fuente que quede entre estas dos instrucciones queda definido como un área crítica que impide que la escritura de un log, por ejemplo del thread principal, pueda solaparse  –y por tanto interrumpirse- por la coincidencia con otro WriteLog empleado por el segundo thread.

Si esto pasase, los logs quedarían machacados parcialmente uno sobre otro sin que pudieran leerse correctamente.

Cómo instalar el servicio

Tenemos dos sistemas de hacerlo: el fácil y el difícil.

  1. El sistema de instalación fácil:

Ya que hemos incorporado la funcionalidad de auto-instalación y auto-desinstalación mediante parámetros de línea de comando dentro del propio ejecutable, podemos seguir estos pasos:

  1. Cópiate en un directorio el ejecutable del servicio ATMegaService.exe.
  2. Abrir un prompt de comando de sistema operativo que cuente con derechos de administrador. Si no sabes cómo hacerlo:
    1. Botón de inicio de Windows
    2. Teclea “cmd” para ejecutar (sin pulsar la tecla de intro)
  • Cuando te aparezca el icono de la línea de comando, pulsa sobre dicho icono con el botón derecho del ratón.
  1. Aparecerá un menú contextual con la opción “Ejecutar como administrador”. Selecciónala.
  2. Verás que te aparece una línea de comando que pondrá en el título de la ventana: “Administrador: Símbolo del sistema”.
  3. Ya tienes abierto el prompt. Vete ahora al directorio donde dejaste el ejecutable utilizando el comando cd del sistema operativo.
  • Teclea ahora:

ATMegaService –i

Y pulsa la tecla Intro. Tardará un par de segundos y te generará un log en el mismo directorio en donde hayas colocado o compilado el ejecutable del servicio.

El contenido de ATMegaService.log será este:

04/02/2019, 11:09:06    [M]_tmain: main program entry point--------------------
04/02/2019, 11:09:06    [M]_tmain: command line contains parameter -i
04/02/2019, 11:09:06    [M]ServiceMainProc: SERVICE_NAME = ATMegaService
04/02/2019, 11:09:06    [M]Install: Service ATMegaService installed

 

  1. El sistema de instalación difícil:
    1. Utiliza también el prompt del sistema operativo como hemos visto en el sistema fácil y ves al directorio en el que tienes el ejecutable compilado ATMegaService.exe.
    2. Lanza el comando sc create. Ver instrucciones en este enlace:

https://support.microsoft.com/en-us/help/251192/how-to-create-a-windows-service-by-using-sc-exe

NOTA:

Este último sistema de instalación no te generará el log tan detallado, ya que no pasará por las funciones que dejan rastro.

Si has tenido algún problema y has empleado el, lo verás también en el log.

Depuración o Debug de servicios Windows

El log generado es el que se genera mediante la función WriteLog que se encuentra definida dentro del archivo de código fuente llamado auxfunctions.cpp que se entrega con el paquete de todos los archivos.

La función es la siguiente:

void WriteLog(char * lpszMsg)
{
  char szModuleFile[BUFFER_SIZE+1];
  char szLogFile[BUFFER_SIZE+1];
  char szExeFile[BUFFER_SIZE+1];
  DWORD dwSize;

  ::EnterCriticalSection(&myCS);

  dwSize = GetModuleFileName(NULL, szModuleFile, BUFFER_SIZE);
  szModuleFile[dwSize] = 0;
  if(dwSize>4 && szModuleFile[dwSize-4] == '.')
  {
    sprintf(szExeFile,"%s",szModuleFile);
    szModuleFile[dwSize-4] = 0;
    sprintf(szLogFile,"%s.log",szModuleFile);
  }

  try
  {
    SYSTEMTIME oT;
    ::GetLocalTime(&oT);
    FILE* pLog = fopen(szLogFile,"a");
    fprintf(pLog,"%02d/%02d/%04d, %02d:%02d:%02d\t    %s\r\n",oT.wMonth,oT.wDay,oT.wYear,oT.wHour,oT.wMinute,oT.wSecond,lpszMsg);
    fclose(pLog);
  } catch(...) {}
  ::LeaveCriticalSection(&myCS);
}

 

Comprobar el funcionamiento del servicio ATMegaService

  1. Tras haber instalado el servicio, iremos al Administrador de Servicios (services.msc desde el prompt de sistema) y buscaremos en la lista el ATMegaService.

 

NOTA:

Si tuviéramos abierto el administrador de servicios desde antes de haber instalado ATMegaService, no nos aparecerá en la lista hasta que hayamos refrescado con el botón  (refrescar) de la barra de herramientas o hayamos acudido al menú Acción à Actualizar.

 

  1. Haremos clic sobre el servicio y después pulsaremos el botón con el triángulo o flecha de color negro para ponerlo a funcionar. Aparecerá “Iniciado” en la columna de estado del servicio.
  2. Esperaremos unos 20 ó 30 segundos aproximadamente y después lo pararemos con el botón cuadrado. Desaparecerá “Iniciado” de la columna de estado del servicio.
  3. Ahora podemos abrir un prompt del sistema con derechos de administrador, e irnos al directorio o carpeta donde tenemos colocado el ejecutable.
  4. Desinstalaremos el servicio enviando el comando ATMegaService –u
  5. Ahora llamaremos al bloc de notas desde el mismo prompt llamando al bloc de notas seguido de espacio y del parámetro del nombre del archivo “ATMegaService.log” o sea:

notepad ATMegService.log

Nos aparecerá el log correspondiente a las acciones que acabamos de realizar con el servicio:

Comentemos el contenido del log:

  • Primero vemos un bloque de cuatro líneas que se ha creado cuando hemos instalado el servicio. Nótese que el [M] indica que estamos en el thread principal.
  • Después vemos una línea en blanco.
  • A continuación vemos un bloque de texto que procede de la puesta en marcha del servicio.
  • Una primera línea de este segundo bloque nos indica que ha vuelto a entrar por el punto de entrada principal de programa.
  • La segunda nos dice el nombre del servicio tal como ha sido definido en el código fuente y desde el thread principal [M].
  • Lo que sigue es el bucle del servicio en sí que tiene lugar en el segundo thread [S]. Ahí venos líneas que escriben “Loop” cada 3 segundos.
  • En el momento en que hemos decidido pararlo, el log nos muestra que hemos parado el servicio en la línea que pone “[S]Stopping service”.
  • Cuando hemos llamado al desinstalador del servicio , el log ha escrito las cuatro últimas líneas: hemos entrado por el punto de entrada del servicio desde el thread principal, hemos detectado que el parámetro recibido es “-u” y que por tanto hay que desinstalar.
  • Hemos verificado de nuevo el nombre del servicio.
  • Finalmente hemos desinstalado el servicio.

Ahora, para estar más seguros, borraremos el archivo de log para hacer una segunda prueba, esta vez instalando, poniendo en marcha, parando y desinstalando de nuevo desde sistema operativo.

  1. Abrimos un prompt de sistema operativo con derechos de administrador.
  2. Nos vamos al directorio en donde se encuentra el ejecutable de ATMegaSrvice. En mi caso es c:\Proyectos\CPlusPlus\ATMegaSrv\Debug (pero en su caso puede ser otro).
  3. Tecleamos ATMegaService –i
  4. Tecleamos la puesta en marcha: ATMegaService –s
  5. Esperamos 20 ó 30 segundos.
  6. Tecleamos la parada del servicio: ATMegaService –k
  7. Tecleamos la desinstalación: ATMegaService –u
  8. Abramos de nuevo el bloc de notas con el log para comprobar:
    notepad ATMegaService.log

O sea que tras esto tendremos un prompt de sistema que habrá quedado así:

Y veremos también un log como este:

NOTA:

Visto el sistema de línea de comando, resulta un punto de partida interesante para realizar la automatización de las baterías de tests de programa.

Más puntos interesantes del código fuente

ServiceMainProc, el núcleo del thread principal

En la función principal de programa _tmain  hemos visto una llamada a otra función  llamada  ServiceMainProc. Esta función contiene las acciones que debe hacer el thread principal, bien sea llamado desde línea de comando o bien sea llamado desde el administrador de servicios de Windows.

En la cabecera de comentarios he aprovechado para hacer un resumen de lo que hace la función, de modo que nos pueda servir de documentación para el programador.

Básicamente he escrito en ella que por este tronco pasa toda la acción del thread principal. Cuando se le llama desde línea de comando, tenemos parámetros que quedan en la variable lpCmdLineData. Dependiendo del valor de lpCmdLineData, captamos los casos “-i”, “-s”, “-k” y “-u” y se los pasamos a las correspondientes funciones de arranque, parada, instalación o desinstalación.

Si no, asume que se ha llamado al servicio desde el administrador de servicios de Windows y se salta a la función ExecuteNormalProcess.

El código fuente de la función ServiceMainProc es el siguiente:

///////////////////////////////////////////////////////////////////////////////
// This function is called from _tmain and chooses what does the main thread do
// depending on the way that the EXE is started.
// You can start it from command line. In this case, the lpCmdLineData contains
// the parameter provided from command line.
// This command line parameter lauches tasks that require admin rights,
// so the cmd command line prompt must be called using "as administrator".
// These command line parameters can be one of the following:
//   -i to auto-install the service ....(requires admin rights).
//   -s to start the service ...........(also requires admin rights).
//   -k to kill (stop) the service .....(also requires admin rights).
//   -u to auto-UNinstall the service ..(also requires admin rights).
// There is no need to change nothing to write different services
// having different names, since we guess the service name via GetModuleFileName
// API function from Windows, no mtter which EXE file name is.
// The only limitation is that the service name must match the EXE filename.
//

void ServiceMainProc()
{
   // initialize variables for .exe and .log file names
   char szTemp[200];
   char szExeFile[BUFFER_SIZE+1];

   memset(szTemp, 0, sizeof(szTemp));
   DWORD dwSize = GetModuleFileName(NULL, szExeFile, BUFFER_SIZE);
   szExeFile[dwSize] = 0;

   strcpy(szServiceName, SERVICE_NAME);
   sprintf(szTemp, "[M]ServiceMainProc: SERVICE_NAME = %s", szServiceName);
   WriteLog(szTemp);

   if(_stricmp("-i",lpCmdLineData) == 0 || _stricmp("-I",lpCmdLineData) == 0)
     Install(szExeFile, szServiceName);
   else if(_stricmp("-k",lpCmdLineData) == 0 || _stricmp("-K",lpCmdLineData) == 0)
     KillService(szServiceName);
   else if(_stricmp("-u",lpCmdLineData) == 0 || _stricmp("-U",lpCmdLineData) == 0)
     UnInstall(szServiceName);
   else if(_stricmp("-s",lpCmdLineData) == 0 || _stricmp("-S",lpCmdLineData) == 0)
     RunService(szServiceName);
   else
     ExecuteNormalProcess();
}

La función ExecuteNormalProcess

Esta función es la que toma el control siempre que la llamada al servicio se realice desde el Administrador de Servicios de Windows o desde otro servicio que requiera al nuestro por programa o por configuración de servicios previos al arranque.

Básicamente hace dos cosas:

  1. Pone en marcha el segundo thread, esto es, el que tenemos que dotar de sentido para que lleve a cabo la tarea propia del servicio concreto (en nuestro servicio de prueba, simplemente escribe “Loop” en un log cada tres segundos).
  2. Activa el manejador de eventos del servicio, esto es, la función que recibe las órdenes, vía mensajes, del administrador de servicios.

Este es su código fuente:

///////////////////////////////////////////////////////////////////////////////

// This function is called from ServiceMainProc when no command line is used,
// i.e., when the command line argument counter is lesser than 2,
// so it means that is is the normal process for a service.
// It basically performs two tasks:
//   1. It starts the Second Thread via _beginThread function.
//   2. It starts the Service Control dispatcher.
//

void ExecuteNormalProcess()
{
   long nError = 0;
   char szTemp[200];

   //Start the second thread that contains the real action, the purpose.
   if(_beginthread(SecondThread, 0, NULL) == -1)
   {
     nError = GetLastError();
     sprintf(szTemp, "Could not start SecondThread via _beginthread, error code = %d\n", nError);
     WriteLog(szTemp);
   }

   //Activate the service control dispatcher:
   if(!StartServiceCtrlDispatcher(lpServiceStartTable))
   {
     nError = GetLastError();
     sprintf(szTemp, "StartServiceCtrlDispatcher failed, error code = %d\n", nError);
     WriteLog(szTemp);
   }
   ::DeleteCriticalSection(&myCS);
}

La función ServiceMain o iniciador de servicio

Esta función registra el controlador de mensajes y lo activa, de forma que la función que trataremos posteriormente será el controlador de mensajes en cuestión.

De momento, la activación del bucle de mensajes, se realiza así:

///////////////////////////////////////////////////////////////////////////////
// The first execution of the service in a day is accomplished by the
// ServiceMain function.
// This is an exportable Windows function that can be seen from outside the
// service, so, the Windows Service Manager can work with it.
// Every service must have this function.
// It registers the service control handler as "start pending" and then
// it sets the service status to running.
//

void WINAPI ServiceMain(DWORD dwArgc, LPTSTR *lpszArgv)
{
   DWORD   status = 0;
    DWORD   specificError = 0xfffffff;
   long nError = 0;
   char szTemp[200];

    ServiceStatus.dwServiceType        = SERVICE_WIN32;
    ServiceStatus.dwCurrentState       = SERVICE_START_PENDING;
    ServiceStatus.dwControlsAccepted   = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_PAUSE_CONTINUE;
    ServiceStatus.dwWin32ExitCode      = 0;
    ServiceStatus.dwServiceSpecificExitCode = 0;
    ServiceStatus.dwCheckPoint         = 0;
    ServiceStatus.dwWaitHint           = 0;

    hServiceStatusHandle = RegisterServiceCtrlHandler(szServiceName, ServiceHandler);
    if (hServiceStatusHandle==0)
    {
     nError = GetLastError();
     sprintf(szTemp, "RegisterServiceCtrlHandler failed, error code = %d\n", nError);
     WriteLog(szTemp);
        return;
    }

    // Initialization complete - report running status
    ServiceStatus.dwCurrentState       = SERVICE_RUNNING;
    ServiceStatus.dwCheckPoint         = 0;
    ServiceStatus.dwWaitHint           = 0; 

    if(!SetServiceStatus(hServiceStatusHandle, &ServiceStatus))
    {
     nError = GetLastError();
     sprintf(szTemp, "SetServiceStatus failed, error code = %d\n", nError);
     WriteLog(szTemp);
    }
}

La función ServiceHandler o manejador de eventos

Cuando se recibe un evento desde el Administrador de Servicios, la función ExecuteNormalProcess expuesta anteriormente, discierne de qué mensaje se trata y realiza acciones ya concretas para atender la petición de inicio, parada, etc.

Esta es la función que escribe los logs específicos para los casos de instalación, inicio, arranque y parada que le llegan desde el Administrador de Servicios de Windows.

Se trata de una función requerida para cada servicio y debe ser exportable para Windows.

///////////////////////////////////////////////////////////////////////////////
// This is the handler for Start, Stop, Pause, Continue service commands.
// This is another required exportable function for every service.
// This handler is used by the Service Manager application from Windows
// and can be also called by other services that programatically require 
// another services to be running.
// Remarks: This function is called by the Service Manager application,
// via Windows API calls, not by the command line.
//

void WINAPI ServiceHandler(DWORD fdwControl)
{
   int i =0;
   long nError = 0;
   char szTemp[200];

   switch(fdwControl)
   {
     case SERVICE_CONTROL_STOP:
     case SERVICE_CONTROL_SHUTDOWN:
        bProcessStarted = FALSE;
        ServiceStatus.dwWin32ExitCode = 0;
        ServiceStatus.dwCurrentState  = SERVICE_STOPPED;
        ServiceStatus.dwCheckPoint    = 0;
        ServiceStatus.dwWaitHint      = 0;

        // Deallocate other resources, close other handles...
        WriteLog("[M]Stopping service.");
        break;
     case SERVICE_CONTROL_PAUSE:
        ServiceStatus.dwCurrentState = SERVICE_PAUSED;
        break;
     case SERVICE_CONTROL_CONTINUE:
        ServiceStatus.dwCurrentState = SERVICE_RUNNING;
        break;
     case SERVICE_CONTROL_INTERROGATE:
        break;
     default:
        if(!(fdwControl>=128&&fdwControl<256))
        {
           sprintf(szTemp,  "Unknown service message %d\n", fdwControl);
           WriteLog(szTemp);
        }
   };
    if (!SetServiceStatus(hServiceStatusHandle,  &ServiceStatus))
    {
      nError = GetLastError();
      sprintf(szTemp, "SetServiceStatus failed, error code = %d\n", nError);
      WriteLog(szTemp);
    }
}

El segundo thread: El trabajo real del servicio

El trabajo real del servicio se encuentra en la función SecondThread. Esta es la que tenemos que modificar para que el servicio realice lo que nosotros queremos hacer.

Dicho de otro modo: esta es la función que tenemos que modificar para añadir la funcionalidad específica de nuestro servicio:

///////////////////////////////////////////////////////////////////////////////
// SECOND THREAD
// This is the real action thread, the thread performing the backgroud tasks
// of controlling: the real purpose of creating the service.
//
// **** This is the point where you must place your functionality. ****
//

void SecondThread(VOID *)
{
   char szTemp[200];

   bProcessStarted = TRUE;
   memset(szTemp, 0, sizeof(szTemp));

   while(bProcessStarted == TRUE)
   {
     //Default functionality consists in logging a text line
     //every 3 seconds:

     sprintf(szTemp, "[S]SecondThread: Loop.", szServiceName);
     WriteLog( szTemp);

     Sleep(3000); //Sleep 3 seconds before next loop
   }
}

La cabecera del inicio del código fuente principal

Contiene las declaraciones necesarias para que no se produzca ningún error de compilación por falta de variables o por cambios en el orden de uso de las funciones:

//  ATMegaServiceMain.cpp
//

#include "stdafx.h"
#include "auxfunctions.h"

#pragma warning(disable:4996) //disable warnings due to ANSI C string functions

///////////////////////////////////////////////////////////////////////////////
// PAY ATTENTION: You will see the service declaration below these remark lines.
// The only reuirement to develop a service based upon this source code
// consists in having the same SERVICE_NAME that EXE file generated.
// In this specific sample:
//   1. "ATMegaService" is the SERVICE_NAME and...
//   2. "ATMegaService.EXE" is the executable filename.
// This rule is required because we will use GetModuleFileName to guess the
// EXE filename in order to:
//   1. Point the service itself getting the file name from the operating system.
//   2. Generate a valid filename for the log file.
// See source code in the current cpp file and the auxfunctions.cpp file.
//

#define SERVICE_NAME "ATMegaService"

//Forward function declarations:
void ExecuteNormalProcess();
void SecondThread(VOID *);

//Externally accessed functions specific for all Windows Services:
void WINAPI ServiceMain(DWORD dwArgc, LPTSTR *lpszArgv);
void WINAPI ServiceHandler(DWORD fdwControl);

//Global variables:
CRITICAL_SECTION myCS;
char lpCmdLineData[BUFFER_SIZE+1];
BOOL bProcessStarted = TRUE;

char szServiceName[BUFFER_SIZE+1];
SERVICE_TABLE_ENTRY        lpServiceStartTable[] =
{
       {szServiceName, ServiceMain},
       {NULL, NULL}
};

SERVICE_STATUS_HANDLE   hServiceStatusHandle;
SERVICE_STATUS          ServiceStatus;

Ahora ya tenemos expuesto todo el código fuente.

Cómo crear un nuevo servicio a partir de estos ficheros de ejemplo

Si queremos cambiar el nombre del servicio, deberemos hacerlo en el #define SERVICE_NAME “ATMegaService” y de paso también sería deseable cambiar el nombre del archivo.

Por ejemplo, imaginemos que vamos a crear un servicio para indexar claves que se va a llamar “IndexerService”. Deberemos cambiar la línea de define por esta:

#define SERVICE_NAME “IndexerService

Y además cambiar el nombre del archivo de código fuente ATMegaServiceMain.cpp por IndexerServiceMain.cpp.

De paso también deberíamos renombrar el proyecto:

Ahora deberíamos salir (grabando previamente) de Visual Studio y tras ello copiar (por seguridad si nos equivocásemos) y renombrar los archivos:

  • ATMegaService.sln –> IndexerService.sln
  • ATMegaService.vcproj –> IndexerService.vcproj
  • ATMegaService.vcxproj –> IndexerService.vcxproj
  • ATMegaService.vcxproj.user –> IndexerService.vcxproj.user

Una vez hecho esto, abriremos de nuevo Visual Studio y cargaremos por primera vez nuestro nuevo proyecto derivado.

Una vez cargado, deberemos sustituir los archivos antiguos por los nuevos en el árbol de proyecto.

Ya podemos compilar y verificar que nos sigue dando 0 errores y 0 warnings.

Deixa un comentari