I. 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 monoprocesseurs) « 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 va 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 faites « 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éssaire sous 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ée.
"
;
}
Cette fois la fonction WaitForSingleObject bloquera uniquement le Thread et pas l'application. Nous y ajouterons une méthode nommée AffMessage qui nous changera le texte d'un Label se trouvant sur la fiche principale de l'application. Ceci juste pour mettre en œuvre ce qui est expliqué en commentaire dans le code généré de « Unit2.cpp ». Cette méthode sera appelé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éthodes ou des propriétés 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éclenché 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 autorisons à quitter l'application seulement si le thread est terminé, car si le clavier et la souris ne sont plus actifs pour l'application, le clic droit sur le bouton de l'application dans la barre des tâches est lui encore accessible 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 a é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 ».