Création d'un thread avec la classe TThread:
Ce tutoriel va vous présenter sous forme d'un exemple,
comment créer un thread avec la classe TThread de la VCL de C++Builder.
Tout d'abord qu'est qu'un thread ?
On peut voir ça comme une partie de code qui s'exécute en parallèle
au code principal de notre application, qui en fait est aussi
un thread.
Le système d'exploitation (sous les systèmes monoprocesseur)
"partage" le temps processeur entre les différents
threads en donnant régulièrement accès à chacun d'eux au processeur pendant
un très court instant, ce qui nous donne l'impression que les deux parties
de code s'exécutent simultanément.
Cela peut être utile pour exécuter des processus lents
sans bloquer l'application.
Pour aborder la classe TThread nous allons prendre un sujet qui a été traité
plusieurs fois sur le forum C++ Builder: Comment lancer une application externe
et attendre qu'elle se termine pour redonner la main à notre application ?
Nous lancerons donc notre application externe avec le code suivant :
(Pour l'exemple ce sera la calculatrice de Windows qui en principe
se trouve sur toutes les configurations.)
SHELLEXECUTEINFO shInfo;
ZeroMemory(&shInfo,sizeof(shInfo));
shInfo.cbSize=sizeof(shInfo);
shInfo.hwnd=NULL;
shInfo.fMask=SEE_MASK_NOCLOSEPROCESS;
shInfo.lpVerb=NULL;
shInfo.lpFile="Calc.exe";
shInfo.lpParameters=NULL;
shInfo.nShow=SW_SHOWNORMAL;
bool shRetour=ShellExecuteEx(&shInfo);
if (shRetour) WaitForSingleObject(shInfo.hProcess,INFINITE);
Dans ce code la méthode WaitForSingleObject bloque l'application
tant que l'application appelée n'est pas terminée. Ce qui a pour effet néfaste
que le dessin de notre application n'est plus rafraîchi. Il ne va pas sans dire
que ce n'est pas très esthétique. Donc pour remédier à cet inconvénient, nous
allons lancer l'application externe dans un thread et nous désactiverons les événements
souris et clavier de notre application tant que le thread n'est pas terminé.
Par contre le dessin de la fenêtre de notre application sera toujours opérant.
Pour l'exemple créons une nouvelle application, puis créons le thread, pour
cela faîtes "Nouveau" puis sur l'onglet "Nouveau" : "Objet Thread" ,
saisir le nom de sa classe dans la
boîte de dialogue prévue à cet effet et faire "OK", pour
l'exemple nous l'appellerons "TMonThread". Maintenant nous avons nos fichiers
Unit2.h et Unit2.cpp contenant le code minimum de notre thread.
Il ne reste plus qu'à ajouter le code pour lancer l'application externe
dans la méthode execute du thread.
Fichier Unit2.h :
#ifndef Unit2H
#define Unit2H
//---------------------------------------------------------------------------
#include <Classes.hpp>
//---------------------------------------------------------------------------
class TMonThread : public TThread
{
private:
protected:
void __fastcall Execute();
void __fastcall AffMessage();
public:
__fastcall TMonThread(bool CreateSuspended);
};
//---------------------------------------------------------------------------
#endif
Fichier Unit2.cpp :
#include <vcl.h>
//#include <shellapi.h> // nécéssairesous BCB4
#pragma hdrstop
#include "Unit2.h"
#include "Unit1.h"
#pragma package(smart_init)
//---------------------------------------------------------------------------
// Important : les méthodes et les propriétés des objets de la VCL ne peuvent être
// utilisées que dans une méthode appelée en utilisant Synchronize, comme :
//
// Synchronize(UpdateCaption);
//
// où UpdateCaption serait de la forme :
//
// void __fastcall TMonThread::UpdateCaption()
// {
// Form1->Caption = "Mise à jour dans un thread";
// }
//---------------------------------------------------------------------------
__fastcall TMonThread::TMonThread(bool CreateSuspended)
: TThread(CreateSuspended)
{
}
//---------------------------------------------------------------------------
void __fastcall TMonThread::Execute()
{
//---- Placer le code du thread ici ----
SHELLEXECUTEINFO shInfo;
ZeroMemory(&shInfo,sizeof(shInfo));
shInfo.cbSize=sizeof(shInfo);
shInfo.hwnd=NULL;
shInfo.fMask=SEE_MASK_NOCLOSEPROCESS;
shInfo.lpVerb=NULL;
shInfo.lpFile="Calc.exe";
shInfo.lpParameters=NULL;
shInfo.nShow=SW_SHOWNORMAL;
bool shRetour=ShellExecuteEx(&shInfo);
if (shRetour)
{
Synchronize(AffMessage);
WaitForSingleObject(shInfo.hProcess,INFINITE);
}
}
//---------------------------------------------------------------------------
void __fastcall TMonThread::AffMessage()
{
Form1->Label1->Caption = "Application externe lancé.";
}
Cette fois la fonction WaitForSingleObject bloquera uniquement le Thread et
pas l'application. Nous y ajouterons une méthode nommé AffMessage qui
nous changera le texte d'un Label se trouvant sur la fiche principale
de l'application. Ceci juste pour mettre en oeuvre ce qui est expliqué
en commentaire dans le code généré de "Unit2.cpp".
Cette méthode sera appellée par la méthode Synchronize
dès le démarrage de l'application externe. Comme expliqué ce mode opératoire
doit être appliqué dés que l'on utilise des méthode ou des propriété de la VCL.
Maintenant il faut lancer notre Thread depuis notre fenêtre principale.
Pour l'exemple nous poserons sur la fiche (Form1) un bouton (Button1) et
un Label (Label1). Voyons maintenant son code.
Fichier Unit1.h :
#define Unit1H
//---------------------------------------------------------------------------
#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
#include "Unit2.h"
//---------------------------------------------------------------------------
class TForm1 : public TForm
{
__published: // Composants gérés par l'EDI
TButton *Button1;
void __fastcall Button1Click(TObject *Sender);
void __fastcall FormClose(TObject *Sender, TCloseAction &Action);
private: // Déclarations de l'utilisateur
TMonThread *MonThread;
void __fastcall FinDeMonThread(TObject *Sender);
public: // Déclarations de l'utilisateur
__fastcall TForm1(TComponent* Owner);
};
//---------------------------------------------------------------------------
extern PACKAGE TForm1 *Form1;
//---------------------------------------------------------------------------
#endif
Dans le fichier Unit1.h nous incluons le fichier entête "Unit2.h" pour avoir
accès à la classe TMonThread, nous déclarons un pointeur MonThread
sur un objet TMonThread et nous déclarons une méthode FinDeMonThread qui sera
appelée quand le thread sera terminé.
Fichier Unit1.cpp :
#include <vcl.h>
#pragma hdrstop
#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
MonThread = new TMonThread(false);
MonThread->OnTerminate = FinDeMonThread;
EnableWindow(Handle, false);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FinDeMonThread(TObject *Sender)
{
EnableWindow(Handle, true);
Label1->Caption = "Thread terminé";
delete MonThread;
MonThread = NULL;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormClose(TObject *Sender, TCloseAction &Action)
{
if(MonThread) Action = caNone;
}
Sur l'événement OnClick de Button1, nous créons le Thread MonThread
avec l'opérateur new, puis nous affectons la méthode FinDeMonThread à l'événement
OnTerminate de MonThread, événement qui est déclanché comme son nom l'indique
quand le thread est terminé et puisque nous voulons que la fiche Form1 ne reçoive plus les
événements clavier et souris pendant l'exécution du thread nous les désactivons
avec la fonction de l'API Windows EnableWindow. Quand le Thread est terminé,
la méthode FinDeMonThread est exécutée, nous réactivons
le clavier et la souris pour la fiche principale et nous y détruisons le Thread.
Sur l'événement OnClose de Form1 (méthode FormClose) nous nous s'autorisons à quitter
l'application seulement si le thread est terminé, car si le clavier et la souris
n'est plus actif pour l'application, le clique droit sur le bouton de l'application
dans la barre des taches et lui encore accéssible et permet de fermer l'application.
Ce petit exemple montre comment créer un Thread avec C++ Builder, comme vous pouvez
le voir la classe TThread de la VCL nous facilite encore grandement les choses.
Ce code à été élaboré sous BCB6. Je l'ai testé sous BCB4, pour que la compilation
aboutisse il a fallu inclure le fichier entête "shellapi.h" dans "Unit2.cpp".
CGi
|