Première partie : Gestion des entrées/Sorties de périphériques
L’une des grandes force Windows NT et de Windows 95 réside dans le nombre de périphériques que ces environnements savent gérer. D’un façon générale, nous pouvons définir un périphérique comme quelque chose permettant une communication. Voici une liste décrivant leur usage habituel :
Périphériques
Usage
Fichier
Stockage de données
Répertoire
Attributs et compression de fichiers
Disque logique
Formatage
Disque physique
Table de partitionnement
Port série
Transmission de données en série
Port parallèle
Envoi de données en parallèle
Console
Une fenêtre texte
Win32 tente de masquer les différences entre périphériques afin de faciliter la tâche du développeur d’application. Ainsi, lorsque vous avez ouvert un périphérique, les fonctions Win32 qui permettent de lire ou d’écrire des données, sont les même quel que soit le mode de communication employé.
Cette partie présente les différents mécanismes disponibles pour lire et écrire des données sur un périphérique. Nous nous intéresserons plus particulièrement à la manipulation de ces fonctions pour des périphérique de type port série.
Il faut noter que bien que les fonctions utilisées soient communes à tout type de périphérique leur utilisation et paramétrage diffère d’un périphérique à l’autre. Par exemple nous pouvons régler la vitesse de transmission sur une ligne série, mais ce paramètre n’a aucun sens pour un fichier.
Ouvrir et fermer un périphérique
Avant de pouvoir effectuer une quelconque opération d’entrée/sortie, vous devez ouvrir le périphérique et obtenir un handle. D’une façon générale la plupart des périphériques sont ouvert avec la fonction CreateFile, dont la description est donnée ci après :
Le paramètre lpFileName identifie le type de périphérique ainsi que l’instance spécifique de ce périphérique. LpFileName aura pour valeur :
« COMx » avec n° du port série dans le cas de l’ouverture du port série
« LPTx » avec x n° du port parallèle dans le cas de l’ouverture du port parallèle
Un nom de fichier dans le cas de l’ouverture dans fichier
Le paramètre dwDesiredAccess précise comment vous souhaitez transférer les données avec le périphérique. Quatre valeurs sont possibles :
Valeur
Signification
0
Vous ne souhaitez ni Lire ni écrire sur le périphérique. Vous souhaitez juste changer sa configuration et ses réglages
GENERIC_READ
Permet des accès en lecture seule sur le périphérique
GENERIC_WRITE
Permet des accès en écriture seule sur le périphérique.
GENERIC_READ| GENERIC_WRITE
Permet des accès en lecture et en écriture sur le périphérique
Le paramètre dwShareMode détermine les privilèges de partage. Sous Windows 95 et Windows NT, un périphérique peut être employé par plusieurs ordinateurs à la fois ou par plusieurs processus en même temps. Il devient alors important de pouvoir réduire les droits d’accès des autres systèmes et processus sur le périphérique que vous utilisez. Les différentes valeurs possibles pour dwShareMode sont :
Valeur
Signification
0
Vous souhaitez un accès exclusif à ce périphérique. S’il est déjà ouvert, CreateFile échoue. Si vous l’ouvrez et qu’un autre appel CreateFile est effectué ensuite, cet appel échouera.
FILE_SHARE_READ
Vous souhaitez utiliser le périphérique en lecture seul. S’il est déjà ouvert en écriture, CreateFile échouera. Si vous l’ouvrez et qu’un autre appel CreateFile est effectué en écriture, cet appel échouera.
FILE_SHARE_WRITE
Vous souhaitez uniquement écrire dans le périphérique. S’il est déjà ouvert en lecture, CreateFile échouera. Si vous l’ouvrez et qu’un autre appel CreateFile est effectué en lecture, cet appel échouera.
FILE_SHARE_READ| FILE_SHARE_WRITE
Vous souhaitez utiliser le périphérique en lecture et en écriture. Si le périphérique est déjà ouvert pour un accès exclusif, CreateFile échouera. Si vous l’ouvrez et qu’un autre appel CreateFile est effectué en lecture ou en écriture, cet appel échouera.
Le paramètre lpSecurityAttributes, pointe sur une structure SECURITY_ATTRIBUTES qui vous permet de préciser les informations de sécurité de l’objet du noyau associé au périphérique. Vous définissez également si le handle obtenu en retour sera héritable ou non ( permettant ainsi à des processus enfant d’accéder au périphérique). Le paramètre est NULL si vous souhaiter mettre en œuvre la sécurité par défaut et un handle non héritable.
Le paramètre dwCreationDistribution est utile lorsque CreateFile ouvre un fichier. Les différentes valeurs dwCreationDistribution sont :
Valeur
Signification
CREATE_NEW
Demande à CreateFile de créer un nouveau fichier. Echec si un fichier de même nom existe.
CREATE_ALWAYS
Demande à CreateFile de créer un fichier sans vérification de l’existance ou non de l’existance d’un fichier de même nom.
OPEN_EXISTING
Demande à CreateFile d’ouvrir un fichier existant. Erreur si le fichier n’existe pas.
OPEN_ALWAYS
Demande à CreateFile d’ouvrir un fichier s’il existe ou de le créer s’il n’existe pas encore.
TRUNCATE_EXISTING
Demande à CreateFile d’ouvrir un fichier existant et de ramener sa taille à 0 octet.
Echec si le fichier n’existe pas déjà. Le drapeau GENERIC_WRITE doit être également utilisé.
Lorsque vous appelez CreateFile pour ouvrir un périphérique qui n’est pas un fichier, vous devez utiliser OPEN_EXISTING pour le paramètre dwCreationDistribution.
Le paramètre dwFlagsAndAttributes vous permet de définir des drapeaux affinant la communication avec le périphérique et si le périphérique est un fichier vous obtenez ses attributs. Dans le cas de la communication qui nous intéresse nous n’utiliserons pas ce paramètre : valeur = 0. Il faut noter que si vous souhaitez utiliser le périphérique de manière asynchrone, il faut utiliser le drapeau FILE_FLAG_OVERLAPPED. Ce drapeau indique au système que vous voulez accéder au fichier de manière asynchrone ( par défaut l’ouverture est synchrone).
Si vous communiquer en mode synchrone, le programme est suspendu dans l’attente des informations devant être lues. Une fois la lecture terminée, le programme reprend le contrôle et continue sont exécution.
Théoriquement les entrées/sorties sur les périphériques étant très lentes par rapport à la plupart des opérations du processeur, il est préférable de communiquer avec un périphérique de façon asynchrone. Dans le principe, vous appelez une fonction demandant au système d’exploitation de lire ou d’écrire des données mais cette fonction rend la main immédiatement, sans attendre la fin de l’opération. Le système d’exploitation traite votre demande de façon autonome et vous renvoie un message lorsque le traitement est terminé. Pendant ce temps votre application a continué à s’exécuter. Nous ne traiterons pas ce cas dans notre exemple.
Le paramètre hTemplateFile permet de spécifier les attributs du fichier. Il aura pour valeur NULL dans notre cas
Nous allons maintenant aborder la lecture et l’écriture de donnée synchrone sur un périphérique
Lecture et écriture synchrone sur un périphérique :
Cette section aborde les fonctions de Win32 qui permettent des lectures et des écritures synchrones sur des périphériques ( nous ne traiterons pas les lecture asynchrones ). Rappel un périphérique peut être un fichier, un canal, un port …
La méthode la plus simple et la plus facile à mettre en œuvre pour lire et écrire des fichiers passe par les deux fonctions :
BOOL ReadFile(
HANDLE hFile, // handle of file to read
LPVOID lpBuffer, // address of buffer that receives data
DWORD nNumberOfBytesToRead, // number of bytes to read
LPDWORD lpNumberOfBytesRead, // address of number of bytes read
LPOVERLAPPED lpOverlapped // address of structure for data
);
BOOL WriteFile(
HANDLE hFile, // handle to file to write to
LPCVOID lpBuffer, // pointer to data to write to file
DWORD nNumberOfBytesToWrite, // number of bytes to write
LPDWORD lpNumberOfBytesWritten, // pointer to number of bytes written
LPOVERLAPPED lpOverlapped // pointer to structure needed for overlapped I/O
);
Bien que chacune de ces fonctions possèdent le mot File dans son nom, elles peuvent être utilisées avec n’importe quel type de périphérique.
Le paramètre hFile identifie le handle du périphérique auquel vous souhaitez accéder.
Le paramètre lpBuffer pointe sur une mémoire tampon qui servira lors des échanges d’écriture/lecture entre le périphérique et l’application.
Le paramètre nNumberOfBytesToWrite et nNumberOfBytesToRead indique à ReadFile et WriteFile combien d’octets doivent être lus ou écrits sur le périphérique.
Le paramètre lpNumberOfBytesRead et lpNumberOfBytesWritten indique l’adresse d’un DWORD contenant le nombre d’octets réellement transmis.
Le paramètre lpOverlapped doit être NULL lors d’opération synchrone.
Les fonctions ReadFile et WriteFile renvoient TRUE en cas de succès. ReadFile ne peut être utilisée qu’avec des périphériques ouvert avec le drapeau GENERIC_READ. De même WriteFile ne peut être utilisée que si le périphérique est ouvert avec le drapeau GENERIC_WRITE.
Configuration du périphérique de communication
Le fichier étant ouvert il ne nous reste plus qu’à configurer le port de communication. Pour cela nous utilisons la fonction SetCommSate :
BOOL SetCommState(
HANDLE hFile, // handle of communications device
LPDCB lpDCB // address of device-control block structure
);
Le paramètre hFile identifie le handle de communication retourné par la fonction CreateFile
Le paramètre lpDCB indique l’adresse de la structure DCB contenant les informations de configuration spécifique au port de communication.
Description de la structure DCB
La structure DCB permet de configurer le port serie. Cette structure est définie comme suit :
DWORD fOutxCtsFlow:1; // CTS output flow control
DWORD fOutxDsrFlow:1; // DSR output flow control
DWORD fDtrControl:2; // DTR flow control type
DWORD fDsrSensitivity:1; // DSR sensitivity
DWORD fTXContinueOnXoff:1; // XOFF continues Tx
DWORD fOutX: 1; // XON/XOFF out flow control
DWORD fInX: 1; // XON/XOFF in flow control
DWORD fErrorChar: 1; // enable error replacement
DWORD fNull: 1; // enable null stripping
DWORD fRtsControl:2; // RTS flow control
DWORD fAbortOnError:1; // abort reads/writes on error
DWORD fDummy2:17; // reserved
WORD wReserved; // not currently used
WORD XonLim; // transmit XON threshold
WORD XoffLim; // transmit XOFF threshold
BYTE ByteSize; // number of bits/byte, 4-8
BYTE Parity; // 0-4=no,odd,even,mark,space
BYTE StopBits; // 0,1,2 = 1, 1.5, 2
char XonChar; // Tx and Rx XON character
char XoffChar; // Tx and Rx XOFF character
char ErrorChar; // error replacement character
char EofChar; // end of input character
char EvtChar; // received event character
WORD wReserved1; // reserved; do not use
} DCB;
Le paramètre DCBlength donne la taille en octets de la structure.
Le paramètre BaudRate spécifie la vitesse de transmission sur le port série. La valeur peut être n’importe quelle valeur ou une des valeurs standards définies ci après :
FBinary doit être à TRUE pour des application Windows.
Le paramètre fParity spécifie la vérification ou non de la parité. TRUE la parité est vérifiée et une erreur est générée si nécessaire.
Les paramètres suivants ne sont pas utilisés en communication 3 fils. Il sont uniquement à configurer dans le cas de protocole XON/XOFF et permettent de contrôler les signaux DTR ( terminal prêt), CTS(Voie Libre), DSR ( jeu de donnée prêt), RTS (demande pour émettre) . Nous ne rentrerons donc pas dans le détail de ces paramètres. De façon générale, et dans le cadre de cette présentation, nous leur affecterons la valeur 0.
FOutxCtsFlow Specifies whether the CTS (clear-to-send) signal is monitored for output flow control. If this member is TRUE and CTS is turned off, output is suspended until CTS is sent again.
fOutxDsrFlow Specifies whether the DSR (data-set-ready) signal is monitored for output flow control. If this member is TRUE and DSR is turned off, output is suspended until DSR is sent again.
fDtrControl Specifies the DTR (data-terminal-ready) flow control. This member can be one of the following values:
Value
Meaning
DTR_CONTROL_DISABLE
Disables the DTR line when the device is opened and leaves it disabled.
DTR_CONTROL_ENABLE
Enables the DTR line when the device is opened and leaves it on..
DTR_CONTROL_HANDSHAKE
Enables DTR handshaking. If handshaking is enabled, it is an error for the application to adjust the line by using the EscapeCommFunction function.
fDsrSensitivity Specifies whether the communications driver is sensitive to the state of the DSR signal. If this member is TRUE, the driver ignores any bytes received, unless the DSR modem input line is high.
fTXContinueOnXoff Specifies whether transmission stops when the input buffer is full and the driver has transmitted the XoffChar character. If this member is TRUE, transmission continues after the input buffer has come within XoffLim bytes of being full and the driver has transmitted the XoffChar character to stop receiving bytes. If this member is FALSE, transmission does not continue until the input buffer is within XonLim bytes of being empty and the driver has transmitted the XonChar character to resume reception.
fOutX Specifies whether XON/XOFF flow control is used during transmission. If this member is TRUE, transmission stops when the XoffChar character is received and starts again when the XonChar character is received.
fInX Specifies whether XON/XOFF flow control is used during reception. If this member is TRUE, the XoffChar character is sent when the input buffer comes within XoffLim bytes of being full, and the XonChar character is sent when the input buffer comes within XonLim bytes of being empty.
fErrorChar Specifies whether bytes received with parity errors are replaced with the character specified by the ErrorChar member. If this member is TRUE and the fParity member is TRUE, replacement occurs.
fNull Specifies whether null bytes are discarded. If this member is TRUE, null bytes are discarded when received.
fRtsControl Specifies the RTS (request-to-send) flow control. If this value is zero, the default is RTS_CONTROL_HANDSHAKE. This member can be one of the following values:
Value
Meaning
RTS_CONTROL_DISABLE
Disables the RTS line when the device is opened and leaves it disabled.
RTS_CONTROL_ENABLE
Enables the RTS line when the device is opened and leaves it on.
RTS_CONTROL_HANDSHAKE
Enables RTS handshaking. The driver raises the RTS line when the "type-ahead" (input)
buffer is less than one-half full and lowers the RTS line when the buffer is more than
three-quarters full. If handshaking is enabled, it is an error for the application to adjust
the line by using the EscapeCommFunction function.
RTS_CONTROL_TOGGLE
Specifies that the RTS line will be high if bytes are available for transmission.
After all buffered bytes have been sent, the RTS line will be low.
fAbortOnError Specifies whether read and write operations are terminated if an error occurs. If this member is TRUE, the driver terminates all read and write operations with an error status if an error occurs. The driver will not accept any further communications operations until the application has acknowledged the error by calling the ClearCommError function.
fDummy2 Reserved; do not use.
wReserved Not used; must be set to zero.
XonLim Specifies the minimum number of bytes allowed in the input buffer before the XON character is sent.
XoffLim Specifies the maximum number of bytes allowed in the input buffer before the XOFF character is sent. The maximum number of bytes allowed is calculated by subtracting this value from the size, in bytes, of the input buffer.
XonChar Specifies the value of the XON character for both transmission and reception.
XoffChar Specifies the value of the XOFF character for both transmission and reception.
ErrorChar Specifies the value of the character used to replace bytes received with a parity error.
EofChar Specifies the value of the character used to signal the end of data.
EvtChar Specifies the value of the character used to signal an event.
wReserved1 Reserved; do not use.
Le paramètre ByteSize définit le nombre de bits de l’octet
Le paramètre Parity définit le type de parité utilisé dans le protocole. Les valeurs possibles sont :
Valeur
Description
EVENPARITY
Parité paire
NOPARITY
Pas de parité
ODDPARITY
Parité impaire
Le paramètre StopBits définit le nombre de stop bit. Les valeurs possibles sont :
Valeur
Description
ONESTOPBIT
1 stop bit
ONE5STOPBITS
1.5 stop bits
TWOSTOPBITS
2 stop bits
Définition des TimeOut de communication
La définition des timeouts de communication est utilisée pour arrêter le process de communication au bout de x (ms). Dans notre cas un timeout sera utilisé pour sortir de la boucle de réception étant donnée que l’on ne veut pas gérer la taille des trames reçue. Nous fixerons donc une taille de buffer de réception fixe que nous traiterons en fin de time out.
La gestion des timeouts se fait à l’aide des commandes SetCommTimeouts pour la configuration, GetCommTimeouts pour la lecture de la configuration et de la structure COMMTIMEOUTS contenant la description des timeouts :
Fonction SetCommTimeouts : utilisée pour configurer les timeOuts
BOOL SetCommTimeouts(
HANDLE hFile, // handle of communications device
LPCOMMTIMEOUTS lpCommTimeouts // address of communications time-out structure
);
Le paramètre hFile identifie le port de communication retourné par la fonction CreateFile
Le paramètre lpCommTimeouts pointe sur une structure COMMTIMEOUTS qui contient les nouvelles valeurs de time out
La valeur de retour est différente de 0 en cas de succès.
Fonction GetCommTimeouts : utilisée pour lire les timeouts
BOOL GetCommTimeouts(
HANDLE hFile, // handle of communications device
LPCOMMTIMEOUTS lpCommTimeouts // address of comm. time-outs structure
);
Le paramètre hFile identifie le port de communication retourné par la fonction CreateFile
Le paramètre lpCommTimeouts pointe une structure contenant la configuration actuelle des timeouts.
La valeur de retour est différente de 0 en cas de succès.
La structure COMMTIMEOUTS : utilisée pour définir les timeouts
ReadIntervalTimeout Specifies the maximum time, in milliseconds, allowed to elapse between the arrival of two characters on the communications line. During a ReadFile operation, the time period begins when the first character is received. If the interval between the arrival of any two characters exceeds this amount, the ReadFile operation is completed and any buffered data is returned. A value of zero indicates that interval time-outs are not used.
A value of MAXDWORD, combined with zero values for both the ReadTotalTimeoutConstant and ReadTotalTimeoutMultiplier members, specifies that the read operation is to return immediately with the characters that have already been received, even if no characters have been received.
ReadTotalTimeoutMultiplier Specifies the multiplier, in milliseconds, used to calculate the total time-out period for read operations. For each read operation, this value is multiplied by the requested number of bytes to be read.
ReadTotalTimeoutConstant Specifies the constant, in milliseconds, used to calculate the total time-out period for read operations. For each read operation, this value is added to the product of the ReadTotalTimeoutMultiplier member and the requested number of bytes.
A value of zero for both the ReadTotalTimeoutMultiplier and ReadTotalTimeoutConstant members indicates that total time-outs are not used for read operations.
WriteTotalTimeoutMultiplier Specifies the multiplier, in milliseconds, used to calculate the total time-out period for write operations. For each write operation, this value is multiplied by the number of bytes to be written.
WriteTotalTimeoutConstant Specifies the constant, in milliseconds, used to calculate the total time-out period for write operations. For each write operation, this value is added to the product of the WriteTotalTimeoutMultiplier member and the number of bytes to be written.
A value of zero for both the WriteTotalTimeoutMultiplier and WriteTotalTimeoutConstant members indicates that total time-outs are not used for write operations.
Remarks
If an application sets ReadIntervalTimeout and ReadTotalTimeoutMultiplier to MAXDWORD and sets ReadTotalTimeoutConstant to a value greater than zero and less than MAXDWORD, one of the following occurs when the ReadFile function is called:
- If there are any characters in the input buffer, ReadFile returns immediately with the characters in the buffer.
- If there are no characters in the input buffer, ReadFile waits until a character arrives and then returns immediately.
- If no character arrives within the time specified by ReadTotalTimeoutConstant, ReadFile times out.
Deuxième partie : Application des différente fonctions de gestion des Entrées / Sorties
Nous avons présenté dans le paragraphe précédent une liste de fonctions et de structures permettant la gestion des périphériques. Dans cette partie nous utiliserons ces fonctions pour configurer un port série et faire une lecture/écriture sur le port série.
Configuration du port série : exemple de fonction
/*------------------------------------------------------------------------*/
/* Fonction d’initialisation d’un port RS232 */
/* */
/* Paramétres */
/* char *Port : "COM1" pour Port1, "COM2" pour port2 */
/* char *Parite : "Paire", "Impaire", "Aucune" */
/* char *Vitesse : la vitesse en bauds */
/* char *Data : le nombre de bit de donnée */
/* char *StopBit : Le nombre de stop Bit */
/* */
/* Retour : un entier egal à 0 si erreur. */
/*------------------------------------------------------------------------*/
int InitCOM(char *Port,char *Parite,char *Vitesse,char *Data,char *StopBit)
{
DCB dcb; // déclarer une variable contenant la configuration du port
HANDLE hCom; // déclarer un handle
DWORD dwError; // n° de l’erreur
BOOL fSuccess; // tout c’est bien passé
/*--------------------------------------------------------*/
/* Ouverture du port de Com */
/*--------------------------------------------------------*/
hCom = CreateFile(
Port, // Choix du port « COMx »
GENERIC_READ | GENERIC_WRITE, // accès pour lire et écrire sur le port
0, // accès exclusif au port de COM
NULL, // sécurité par défaut
OPEN_EXISTING, //Doit être à cette valeur car se n’est pas un fichier
0,
NULL // mode synchrone
/*-----------------------------------------------------------*/
/* Vérifier si handle ouvert correctement */
/*-----------------------------------------------------------*/
if (hCom == INVALID_HANDLE_VALUE)
{
dwError = GetLastError();
/* Fichier non créer Gérer l'erreur */
}
/*-------------------------------------------------------------------*/
/* Configuration du port */
/*-------------------------------------------------------------------*/
/* Gestion de la vitesse */
dcb.BaudRate = StrToInt(Vitesse);
/* Gestion du nombre de bits */
dcb.ByteSize = StrToInt(Data);
/* Gestion de la parité */
if (strcmp(Parite,"Aucune")==0)
dcb.Parity = NOPARITY;
if (strcmp(Parite,"Paire")==0)
dcb.Parity = EVENPARITY;
if (strcmp(Parite,"Impaire")==0)
dcb.Parity = ODDPARITY;
/* Gestion du Stop Bit */
if (strcmp(StopBit,"1")==0)
dcb.StopBits = ONESTOPBIT;
if (strcmp(StopBit,"1.5")==0)
dcb.StopBits = ONE5STOPBITS;
if (strcmp(StopBit,"2")==0)
dcb.StopBits = TWOSTOPBITS;
dcb.DCBlength;
dcb.BaudRate;
dcb.fBinary=1;
dcb.fParity=0;
dcb.fOutxCtsFlow=0;
dcb.fOutxDsrFlow=0;
dcb.fDtrControl=0;
dcb.fDsrSensitivity=0;
dcb.fTXContinueOnXoff=0;
dcb.fRtsControl=0;
/*-----------------------------------------------*/
/* Configurer le port */
/*-----------------------------------------------*/
fSuccess = SetCommState(hCom, &dcb);
if (!fSuccess)
{
/* Gérer l'erreur*/
}
/*------------------------------------------*/
/* fermer le port de com */
/*------------------------------------------*/
CloseHandle(hCom);
return(fSuccess);
}
Ecriture sur un port série : Exemple de fonction d’envoie de donnée
/*-----------------------------------------------------------------------------*/
/* Envoyer une chaine de caractére sur la RS232 */
/* */
/* Paramètres : */
/* char *Chaine La chaine à Envoyer */
/* char *Port, le port de COM : "COM1" ou "COM2" */
/* char *EolChar, le caractère fin de ligne */
/* */
/* Retour : 0 si erreur */
/*-----------------------------------------------------------------------------*/
int EnvoiChaineRS232(char *Chaine,char *Port,char *EolChar)
{
HANDLE hCom;
DWORD dwError;
BOOL fSuccess;
DWORD dwEvtMask;
int i;
int NbOctet;
char *Message;
unsigned long nBytesWrite;
Message = new char[200];
/*-----------------------------------------------*/
/* Ouverture du port de communiucation */
/*-----------------------------------------------*/
// compter le nombre d’octet à envoyer
NbOctet = StrLen(Message);
// écrire dans le fichier
WriteFile(hCom,Message,NbOctet,&nBytesWrite,NULL);
// Fermer le handle de com
CloseHandle(hCom);
// Libération mémoire
delete[] Message;
return(fSuccess);
}
Lecture sur un port série : Exemple de fonction de réception de données
/*------------------------------------------------------------------------------*/
/* Recevoir une chaine de caractére sur la RS232 */
/* */
/* Paramètres : */
/* char *ChaineRecue , buffer de réception */
/* char *Port, le port de COM : "COM1" ou "COM2" */
/*------------------------------------------------------------------------------*/
int RecevoirRS232(char *ChaineRecue,char *Port)
{
DCB dcb;
HANDLE hCom;
DWORD dwError;
BOOL fSuccess;
BOOL bResult;
DWORD dwEvtMask;
COMMTIMEOUTS tTimeout;
unsigned long nBytesRead;
char *inBuffer;
int TimeoutRead;
int i;
int NbOctet;
char c;
/*-------------------------------------*/
/* Initialisation des variables */
/*-------------------------------------*/
inBuffer = new char[200]; // réservation mémoire pour le buffer de récéption
sprintf(inBuffer,"%s","");
nBytesRead=0;
/*-----------------------------------------------*/
/* Ouverture du port de communication */
/*-----------------------------------------------*/
TimeoutRead = 500; // timeout de 500ms
tTimeout.ReadIntervalTimeout = MAXWORD;
tTimeout.ReadTotalTimeoutMultiplier = 0;
tTimeout.ReadTotalTimeoutConstant = TimeoutRead; // pas de time out = 0
tTimeout.WriteTotalTimeoutMultiplier = 0;
tTimeout.WriteTotalTimeoutConstant = 0;
// configurer le timeout
SetCommTimeouts(hCom,&tTimeout);
/*-------------------------------------------------------------*/
/* boucle d'attente de lecture des octets */
/* Sortie de la boucle par timeout par exemple, si */
/* l’on suppose le format de la trame reçu n’est */
/* variable. */
/*-------------------------------------------------------------*/
if (nBytesRead!=0) // Il existe des octets lus
{
// Mettre en forme la trame : recherche de CR
}
else
sprintf(inBuffer,, »%s », “Pas de donnée reçue”) ;
sprintf(ChaineRecue,"%s",inBuffer); // Retourner la chaine recue
// fermer le port
CloseHandle(hCom);
// Libérer la mémoire
delete[] inBuffer;
return(fSuccess);
}
Exemple d’appel de ses fonctions
Soit à configurer le port série 1, à 9600 bauds, sans de parité, 1 stop bit, 8 bits de data et envoyer la trame « ID ? » avec retour chariot à un périphérique qui nous retournera son identification. Un exemple de mise en œuvre est proposé ci après :
void main(void)
{
int Erreur ;
char *TrameRecue ;
TrameRecue = new char[200] ;
Erreur=InitCOM(« COM1 », « Aucune », « 9600 », « 8 », « 1 ») ;
if (Erreur !=0) // périphérique initialisé correctement
{
// envoi de la commande d’identification
EnvoiChaineRS232(« ID ? », « COM1 », « CR ») ;
// recevoir la réponse
RecevoirRS232(TrameRecue, « COM1 ») ;
// Traiter la trame recue
}
else
{
// afficher une erreur
}
// Libération mémoire
delete[] TrameRecue ;
}
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 :
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
L’utilisateur saisi une chaîne de cararatère
La chaîne de caractère est recopiée et affichée dans une autre zone de l’écran automatiquement, toutes les secondes
Un indicateur du temps écoulé depuis le lancement du programme s’affiche à l’écran
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
#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 */
/*-----------------------------------------------------------------------*/
//---------------------------------------------------------------------------
__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
__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 :
__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
//---------------------------------------------------------------------------
// 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.