[WD17] Créer un service Windows

Sur Windows comme sur d’autres systèmes d’exploitation, il est possible de créer ce que l’on appelle des services. Bien sûr, cela ne porte pas ce nom lorsqu’on passe à un système basé Unix (on parlera plutôt de daemon). Il s’agit d’applications capables de fonctionner en arrière-plan sans nécessiter d’interaction de la part de l’utilisateur. Ils peuvent être lancés manuellement, ou lorsque le système démarre. Certains ont même un mode “différé” qui retarde leur exécution.

Un service peut être utile lorsqu’il est nécessaire de répondre à diverses requêtes grâce à une communication par socket par exemple (avec des threads pour gérer les différents clients connectés). Il est aussi capable de répéter une même tâche à intervalle régulier, selon les paramètres qu’on lui renseignera. En ce qui concerne le développement dans l’environnement WinDev, plusieurs points seront à prendre en compte, notamment :

  • Une fonction est appelée en boucle pendant le fonctionnement du service. Il convient de temporiser son exécution afin qu’il ne monopolise pas toutes les ressources (nous verrons comment faire plus loin).
  • Aucune action bloquante n’est autorisée, c’est-à-dire que les boites de dialogue de saisie, de dialogue ou d’information/erreur sont interdites. Une fenêtre pourra être ouverte mais devra obligatoirement être refermée afin que le service puisse se remettre en attente.
  • Le service fonctionnera sous l’identité de l’utilisateur configuré lors de l’installation, même si une autre session est ouverte.
  • Il faut bien gérer les erreurs de base de données, pour ne pas bloquer le traitement.

Créer le projet

Pour rendre les choses plus simples, nous allons créer un nouveau projet de type “Service Windows”. Il s’agira d’un service 32 bits. Nommez-le comme bon vous semble. Nous n’allons pas définir la base de données maintenant.Validez : une fois le processus terminé, vous remarquerez qu’il n’y a aucun élément dans l’explorateur du projet. Pour commencer à rédiger le code du service, il faudra se rendre dans le menu “Projet”, option “Code du projet”.

Autre remarque importante : si vous comptez exploiter des fichiers texte et que vous préférez directement travailler en Unicode (recommandé), il faudra éditer la “configuration courante” depuis la “description du projet”. Il est aussi possible, si l’on utilise des chaines ANSI à l’exécution, de correctement paramétrer les fonctions de lecture et d’écriture en précisant l’encodage adéquat.

Initialisation et exécution du service

Le code du projet laisse apparaitre deux évènements distincts :

  • Initialisation du service : permettra d’y placer les constantes et variables globales, mais aussi de charger différents paramètres depuis un fichier (.ini, par exemple) ou même depuis le registre.
  • Exécution du service (en boucle) : cet évènement est appelé en boucle, un peu comme si on relançait chaque fois une application. Afin que votre service ne monopolise pas toutes les ressources, il est important de le temporiser. C’est là que nous interviendrons.

1 – Déclarer les variables

La première étape consiste à créer vos variables et vos constantes afin de disposer de tout dès le départ. Lors de l’initialisation, nous allons aussi charger les paramètres depuis un fichier .ini qui se trouve dans le répertoire de l’application. Nous avons notamment défini quelques constantes d’erreur qui seront détaillées dans une documentation à part.

Ensuite nous avons instancié deux classes, une qui contiendra les dossiers de l’exécutable, la clé de registre de base et le chemin vers le fichier de paramètres, l’autre servira à se connecter à la base de données. Ci-dessous, on instancie déjà quelques membres de la première.

N’hésitez pas à utiliser une variable booléenne dont vous pourrez modifier la valeur pour déterminer si vous lancez le service en test ou non. En effet, un service ne pourra pas être testé à moins d’être installé sur la machine cible. A vous de trouver la méthode adéquate pour le lancer en mode test ou non (paramètre en dur avant le déploiement, clé de registre, etc).

2 – Définir la temporisation

Le code suivant est on ne peut plus simple : le code appelé en boucle va ici, faire appel à une procédure globale, qu’on va nommer “gproc_processService”. Il vous sera demandé de créer une nouvelle “collection de procédures” afin de la générer. Elle sera vide pour le moment.

On va également temporiser le service. Dans notre cas, nous récupérons un paramètre dans un fichier .ini qui se trouve dans le répertoire de l’application. Il s’agit du temps en secondes. Une fonction très utile, ServiceTemporise, permet de rendre la main aux autres applications et de ne plus consommer de ressources.

3 – Journaliser les erreurs

L’avantage d’un service est qu’il peut s’exécuter sans nécessiter d’interaction de la part des utilisateurs. Le souci, c’est qu’en cas d’erreur, on ne voit pas directement de quoi il retourne. On peut alors se servir du journal d’évènements du système. C’est pourquoi, après la déclaration des variables dans la procédure globale que nous avons créé, nous avons utilisé un code qui se sert de la fonction ServiceEcritEvenementJournal. En paramètre, elle reçoit le message d’erreur, la gravité, et le code d’erreur (on pourrait utiliser les constantes déclarées lors de la 1ère étape).

WinDev, à l’image des langages de programmation comme le C++ ou même le Java, permet d’exploiter un mécanisme d’exceptions en fournissant le même genre de bloc de code. Il est aussi possible d’utiliser la syntaxe suivante : Quand exception… Faire… Fin. On pourrait en fait comparer cela à un bloc “try / catch”. Quant à la fonction ExceptionActive, elle réactive l’utilisation du traitement pour les erreurs suivantes, dans le cas où la précédente aurait été corrigée.

4 – Limiter l’exécution du service

Pour réaliser cette étape, vous devrez vous servir du fichier de paramètres. Il pourra contenir, par jour, les heures de début et de fin, des valeurs booléennes permettant d’indiquer s’il doit se lancer le dimanche, ou l’un des autres jour de la semaine, etc. Voici le genre de structure qui pourrait être un bon début :

A l’aide de la fonction “INILit”, il vous sera désormais possible de déterminer si le service doit être lancé ou non. Le bout de code suivant vous permettra d’avoir une idée de la méthode à employer. Evidemment, afin que cela soit un réel exercice pour vous, nous ne le vous donnons pas dans son entièreté.

5 – Gérer les autres erreurs et les journaliser

N’oubliez pas de journaliser et de gérer vos autres erreurs, notamment celles provoquées par des modifications ou des ajouts dans la base de données (dans ce cas, définissez les traitements à réaliser avec la fonction HSurErreur). Créez des procédures globales ou bien des classes qui permettront d’écrire un message dans le journal d’évènements mais aussi dans un fichier “log” (qui contiendra tout simplement du texte).

Exemples :

  • Utility.addLog (fonction “globale” dans une classe).
  • gproc_addLog (fonction dans une collection de procédures globales).

6 – Lire des fichiers, se connecter au FTP

Il est possible de réaliser des lectures de fichiers ou même de se connecter à un serveur FTP. Pour cela, une multitude de fonctions existe… Par exemple, fOuvre() permet d’ouvrir (et de créer au besoin) un fichier. fLit() permet de lire un bloc d’octets tandis que fLitLigne() lit jusqu’au caractère correspondant. Quant à fFerme(), cela libère le handle alloué pour le fichier.

Comme nous l’indiquions, les fonctions FTP sont aussi nombreuses : comme FTPConnecte(), qui permet d’ouvrir une connexion, FTPListeFichier() qui renvoie la liste de fichiers et qui appelle, par la même occasion, une fonction récursive que le développeur doit déclarer, FTPRenomme() qui permet, comme son nom l’indique, de renommer un fichier présent sur le serveur, FTPEnvoie() qui transfère un ou plusieurs fichiers, etc.

7 – Créer l’installation

Pour générer l’installation, rendez-vous dans “Atelier”, “Créer la procédure d’installation”. Nous allons nous intéresser à la partie 2, “installation” : ci-dessous, un exemple de paramètres à utiliser qui assurent le bon fonctionnement du service lorsqu’une session n’est pas ouverte (sur un serveur Windows 2008 R2, 64 bits, enregistré dans le domaine).

  • Type d’installation : autonome.
  • Répertoire d’installation : par défaut (<srProgramFiles>NOM_PROJET).
  • Langues : à votre guise.
  • Titres et décors : à votre guise.
  • Description du service : définissez le nom à utiliser lorsque vous utiliserez les fonctions ServiceXXX. Définissez également le nom et la description qui apparaitront dans la console de gestion des services.
  • Paramètres de démarrage : sélectionner “automatique”. En cas d’échec : choisir “inscrire l’évènement dans le journal système”.
  • Paramètres de gestion des erreurs : lors des deux premières défaillances – préférer “redémarrer le service”. Dans le troisième cas, à vous de voir. Réinitialiser le compteur de défaillances après “1” jour, et exécuter l’action suivante après “1” minute. Vous pouvez jouer sur ces paramètres.
  • Paramètres du compte utilisateur : nous avons choisi “compte système local” et nous avons coché “service autorisé à interagir avec le bureau”, bien qu’il semblerait que cette fonctionnalité n’ait plus d’impact à partir de Windows Server 2008 et Windows Vista.
  • Modules complémentaires : uniquement le désinstalleur.
  • Génération : sous forme d’exécutable “auto-extractible”.

8 – Déploiement

Déployez simplement le service à l’aide de l’installeur créé.Ca y est, vous êtes fin prêt ! Il ne vous reste plus qu’à compléter votre service et à effectuer plusieurs tests, d’abord sur votre machine, puis de déployer le tout en production.

Notez qu’il existe d’autres moyens de générer des services et ce, dans d’autres langages de programmation (API Java “Wrapper” – Running Java Application as a Windows Service, EDN), ainsi que depuis d’autres IDE (Integrated Development Environment, c’est-à-dire en français, Environnement de Développement Intégré), comme Embarcadero Delphi… Le choix est vaste.

2 commentaires sur « [WD17] Créer un service Windows »

  1. Bonjour
    Je ne sais pas si tu lis encore ces message mais je tente
    J’ai créé un service Windev a partir de l’exemple inclus
    Le but c’est d’avoir un systeme d’alerte lorsqu’un message est publié (webservice, DB, fichier texte, .. à définir)
    Je veux donc que mon service scrute le ws, la db, le fichier et qu’il ouvre un autre exe avec une fenetre qui affichera le message. Ca fonctionne « presque », mon exe se lance en arriere plan ! Aurais tu une solution ?
    Merci à toi

    1. Je crois qu’il est préférable d’avoir un autre exécutable actif (par exemple en arrière-plan, avec une icône de notification dans la barre de tâches) qui va voir tous les X temps s’il n’y a pas un message pour lui. Pas sûr que les services puissent lancer des exécutables 🙂

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *