guill.net - La page des réseaux

Communication RS232 en C++ Builder
Auteur : Xavier Brun de l'ESEO, le 30/09/00


Troisième partie : Utilisation d’un THREAD et gestion des exceptions

Lancement d’une tâche de « fond » : utilisation d’un THREAD

Dans cette partie nous aborderons (très simplement) la notion de thread et comment la mettre en œuvre.

Un thread est « équivalent à une tache de fond » qui tourne en permanence au sein du thread principal qui est votre application.

A titre d’exemple, un tableur doit recalculer la feuille de calcul à chaque nouvelle saisie.  Le thread principal est la saisi, auquel on associe un thread secondaire de priorité inférieure le recalcul de la feuille qui sera donc exécuter automatiquement et uniquement quand l’utilisateur ne fait aucune saisie dans la feuille de calcul.

Pour Créer un thread en C++ Builder, sélectionner dans l’option Fichier/Nouveau l’icône objet Thread. Donner un nom à la classe, par exemple ThreadNomDuProcess. Un fichier est créé par C++ Builder du Type :

#include <vcl.h>
#pragma hdrstop

#include "UnitThreadNomDuProcess.h"

#pragma package(smart_init)

__fastcall ThreadCompteur:: ThreadNomDuProcess (bool CreateSuspended)
        : TThread(CreateSuspended)
{
}
//---------------------------------------------------------------------------
void __fastcall ThreadNomDuProcess::Execute()
{

        //---- Placez le code du thread ici ----
}
//---------------------------------------------------------------------------

Execute doit tester la valeur de la propriété Terminated afin de déterminer s'il faut sortir du thread. Typiquement le code de la méthode Execute doit être du type :

void __fastcall ThreadNomDuProcess::Execute()
{

        //---- Placez le code du thread ici ----
       while ( !Terminated)
        // actions répétitives à faire
}
//---------------------------------------------------------------------------

Un thread commence lorsque Create est appelée avec le paramètre CreateSuspended initialisé à false, ou si la méthode Resume est appelée après un appel de Create dans lequel CreateSuspended est initialisé à true.

Pour exécuter un thread il suffit de lancer la méthode Resume() : Reprend l'exécution d'un thread interrompu.

Pour Arrêter un thread il suffit de lancer la méthode Terminate() : Signale au thread de s'arrêter en affectant la valeur true à la propriété Terminated.

Terminate initialise la propriété Terminated du thread à true, en signalant que le thread doit se terminer dès que possible. A l'inverse de l'API  Windows TerminateThread, qui force le thread à se terminer immédiatement, la méthode Terminate demande simplement que le thread se termine. Ceci permet au thread d'exécuter tout nettoyage avant de se fermer.

Pour que Terminate fonctionne, la méthode Execute du thread et toute méthode appelée par Execute doit tester périodiquement Terminated et quitter lorsqu'elle vaut true.

Prenons un exemple : Nous voulons réaliser un programme dans lequel

Dans cet exemple nous pouvons considérer que l’affichage du temps écoulé est la mise à jour de la recopie de la chaîne de caractère est une tâche de fond : d’où l’utilisation d’un thread.

Créer l’interface utilisateur suivante :
 

Choisir fichier/Nouveau projet et enregistrer le avec les noms par défauts sur le répertoire voulu.

Ajouter un objet Thread au projet : Fichier/Nouveau choisir objet thread. Donner lui comme nom de classe ThreadCompteur. Enregistrer le fichier sous le répertoire voulu avec comme nom UnitThreadCompteur.cpp
Ecrivez le code suivant : dans Unit1.cpp

//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop

#include "Unit1.h"
#include "UnitThreadCompteur.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
ThreadCompteur *ThCompteur; // définition d'un pointeur sur le thread

/*----------------------------------------------------------------------*/
/* Création du process de comptage                                        */
/*----------------------------------------------------------------------*/
void ProcessCompteur_Create(void)
 {
  ThCompteur=new ThreadCompteur(true);  // resevation de la mémoire
  ThCompteur->Priority = tpLower;       // priorité inférieure
  ThCompteur->Resume();                 // Lancement du traite
  }
/*-----------------------------------------------------------------------*/
/* désactivation du process de Comptage                                */
/*-----------------------------------------------------------------------*/
void ProcessCompteur_Kill(void)
 {
  ThCompteur->Terminate(); // arrêt du thread
  delete ThCompteur;       // libération mémoire
 }
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
        : TForm(Owner)
{
 ProcessCompteur_Create(); // lancement de la tâche d'acquisition au
                           // démarage du programme
}
//---------------------------------------------------------------------------

void __fastcall TForm1::Button1Click(TObject *Sender)
{
 Label1->Caption = Edit1->Text; // recopie du test si action sur le Bouton
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormClose(TObject *Sender, TCloseAction &Action)
{
 ProcessCompteur_Kill(); // arrêter le thread en fin de programme
}
//---------------------------------------------------------------------------

Ecrivez le code suivant : dans UnitThreadCompteur.cpp

#include <vcl.h>
#pragma hdrstop

#include "UnitThreadCompteur.h"
#include "Unit1.h"
#pragma package(smart_init)

__fastcall ThreadCompteur::ThreadCompteur(bool CreateSuspended)
        : TThread(CreateSuspended)
{
}
//---------------------------------------------------------------------------
// code du thread : action à réaliser
//---------------------------------------------------------------------------
void __fastcall ThreadCompteur::Execute()
{
 int Compteur;

 Compteur = 0; // inialisation du compteur

 while (!Terminated)  // test si demande d'arrêt du thread
  {
   Compteur++;
   Compteur=Compteur%60;
   Sleep(1000);
   Form1->LabelCompteur->Caption = StrToInt(Compteur);
   Form1->Label1->Caption = Form1->Edit1->Text;
  }

}

En exécutant le programme le compteur s’incrémentera toutes les secondes et son contenu sera affiché à l’écran .De même la donnée saisie par l’utilisateur sera recopiée toutes les secondes.

Gestion des exceptions

Une exception est assimilable à une erreur. L’exemple la plus simple est la division par 0. Un programme correctement écrit doit donc gérer tous les types d’erreurs susceptibles d’engendrer des disfonctionnements et de « planter » le programme.
Dans cette partie nous décrirons une méthode permettant de gérer des exceptions.

Les exceptions sont déclenchées quand une erreur d'exécution se produit dans une application, par exemple une tentative de division par zéro. Généralement, quand une exception est déclenchée, une instance d'exception affiche une boîte de dialogue décrivant la condition d'erreur. Si une application ne traite pas la condition d'exception, le gestionnaire d'exception par défaut est appelé. Ce gestionnaire affiche également une boîte de dialogue avec un bouton OK qui permet normalement à une application de poursuivre les traitements quand l'utilisateur clique sur OK.

L'objet Exception offre une interface homogène aux conditions d'erreur et permet aux applications de gérer les conditions d'erreur d'une manière élégante. Les applications peuvent , intercepter et gérer des exceptions spécifiques dans des blocs try..catch.

La gestion d’une exceptions  à l’aide des blocs try et catch et de la forme gènerale :

try
 {
  // Ecrire le code où une exception est susceptible de se produire
 }
catch(…)
 {
  // Gérer l’exception
 }

L’exemple ci dessous illustre la gestion d’une exception, lors d’un calcul. L’utilisateur saisi 2 nombres A et B et une action sur le bouton A/B calcul et affiche le résultat de la division de A par B.
Créer un nouveau projet et donner les noms UnitDivision .cpp pour le code et ProjectDivision pour le nom de projet.

Créer l’écran utilisateur suivant :
 

La division impose que :
- A et B soit des nombres
- B soit différent de 0

Si ces 2 conditions ne sont pas respectées le programme générera une erreur. L’objectif est d’intercepter ces erreurs.

Nous devons donc contrôler que A et B sont des nombres pour cela nous décrirons 2 méthodes pour gérer ces exceptions :
- Pour A nous utiliserons les message d’erreur du système
- Pour B nous définirons notre message d’erreur

Nous devons contrôler lors de la division que B est différent de 0. Dans le cas ou B est différent de 0 nous n’afficherons pas de message d’erreur mais nous donnerons comme résultat DIV/0.

Ecrivez le code ci après dans le fichier UnitDivision.cpp :

#include <vcl.h>
#pragma hdrstop

#include "UnitDivision.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
        : TForm(Owner)
{
 // effacement des données
 EditA->Text = "";
 EditB->Text = "";
 Edit1->Text = "";
}
//---------------------------------------------------------------------------
// Gestion de la saisi sur A
//---------------------------------------------------------------------------
void __fastcall TForm1::EditAChange(TObject *Sender)
{
 float Nb;

 // Nous ne voulons saisir que des nombres
 // Une execption peut se produire => prévoir sa gestion
 try
  {
   // Nous vérifions que la chaine est différente de chaine vide
   if (StrLen(EditA->Text.c_str())!=0)
    Nb = StrToFloat(EditA->Text.c_str()); // Déclenchement de l'exception
  }                                       // si cconvertion échoue

 catch(Exception &UneException) // il y à une exception afficher un message
  {
   // Afficher le message d’erreur
   Application->ShowException(&UneException);
   // Traitement si nécessaire, dans notre cas remise à 0
   EditA->Text = "0";

  }
}
//---------------------------------------------------------------------------
// Gestion de la saisi sur B
//---------------------------------------------------------------------------
void __fastcall TForm1::EditBChange(TObject *Sender)
{
 float Nb;
 char *Chaine;

 Chaine = new char[100];

  // Nous ne voulons saisir que des nombres
 // Une exception peut se produire => prévoir sa gestion
 try
  {
   // Nous vérifions que la chaine est différente de chaine vide
   if (StrLen(EditB->Text.c_str())!=0)
    Nb = StrToFloat(EditB->Text.c_str()); // Déclenchement de l'exception
  }                                                              // si convertion échoue

 catch(Exception &UneException) // il y à une exception
  {
   // Afficher notre message et le type d'erreur systeme
   UneException.Message = " : Erreur de saisi pour nombre B";
   ShowMessage( AnsiString(UneException.ClassName())+UneException.Message);
   // Traitement si nécessaire, dans notre cas suppression
   // du dernier caractère saisi
   AnsiString(Chaine) = EditB->Text;
   Nb = StrLen(Chaine.c_str());
   Chaine[Nb]='\0'; // fin de chaine
   EditB->Text = Chaine;
  }
  // Libération mémoire
  delete[] Chaine;
}
//---------------------------------------------------------------------------
// Gestion du calcul de A / B
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
  int BestNul;
  float r;
 // Nous sommes sûr que A et B sont des nombres ou une chaîne vide
 // il nous reste à vérifier que A et B sont différents de chaîne vide
 // et que B n'est pas égale à 0

 // Si A et B différent de chaine vide on peut faire par exemple
 if ( (StrLen(EditA->Text.c_str())!=0)&&(StrLen(EditB->Text.c_str())!=0))
  {
   // Il nous faut vérifier que B n'est pas nul. Nous le gérerons pour la forme
   // par une exception. Vous pouvez le gérer autrement bien sûr

   try
    {
     1/StrToFloat(EditB->Text.c_str()); // exception ?
     BestNul = 0; // B n'est pas nul
    }
   catch(...)
    {
     // Pas d'affichage de message, Mise à jour d'un flag d'erreur
     BestNul = 1; // B est nul
    }

    // Faire le calcul si possible
    if (BestNul == 0)
     {
      r=EditA->Text.ToDouble()/EditB->Text.ToDouble();
      Edit1->Text = FloatToStr(r);
     }
    else
     Edit1->Text = "DIV/0";
  } // fin si chaîne vide
 else // A ou/et B sont vide
  {
   // Faire ce que vous voulez
  }
}
//---------------------------------------------------------------------------

Conclusion

La programmation sous Windows est extraordinairement puissante, et nécessite un foule de connaissances à acquérir, mais elle tend à devenir un standard du marché.

Les différents point abordés dans cette présentation ne sont que des bases, vous permettant de créer rapidement quelques applications. Une utilisation régulière et approfondie vous permettra certainement d’utiliser ces bases autrement et certainement plus simplement.


Retour