Vous êtes sur la page 1sur 289

APPRENTISSAGE DU LANGAGE VB.

NET
Serge Tah - ISTIA - Universit d'Angers Mars 2004

Introduction
VB.NET est un langage rcent. Il a t disponible en versions beta depuis lanne 2000 avant dtre officiellement disponible en fvrier 2002 en mme temps que la plate-forme .NET de Microsoft laquelle il est li. VB.NET ne peut fonctionner quavec cet environnement dexcution, environnement disponible pour le moment que sur les machines Windows NT, 2000 et XP. Avec la plate-forme .NET, trois nouveaux langages sont apparus : C#, VB.VET, JSCRIPT.NET. C# est largement une copie de Java. VB.NET et JSCRIPT.NET sont des extensions de Visual basic et Jscript pour la plate-forme .NET. Celle-ci rend disponible aux programmes qui sexcutent en son sein un ensemble trs important de classes, classes trs proches de celles que lon trouve au sein des machines virtuelles Java. En premire approximation, on peut dire que la plate-forme .NET est un environnement dexcution analogue celui d'une machine virtuelle Java. On peut noter cependant deux diffrences importantes : la plate-forme .NET ne s'excute que sur les machines Windows alors que Java s'excute sur diffrents OS (windows, unix, macintosh). la plate-forme .NET permet l'excution de programmes crits en diffrents langages. Il suffit que le compilateur de ceux-ci sache produire du code IL (Intermediate Language), code excut par la machine virtuelle .NET. Toutes les classes de .NET sont disponibles aux langages compatibles .NET ce qui tend gommer les diffrences entre langages dans la mesure o les programmes utilisent largement ces classes. Le choix d'un langage .NET devient affaire de got plus que de performances.

De la mme faon que Java ne peut tre ignor, la plate-forme .NET ne peut l'tre, la fois cause du parc trs important de machines windows installes et de l'effort fait par Microsoft pour la promouvoir et l'imposer. Il semble que C# soit un bon choix pour dmarrer avec .NET, notamment pour les programmeurs Java, tellement ces deux langages sont proches. Ensuite on pourra passer aisment de C# VB.NET ou un autre langage .NET. La syntaxe changera mais les classes .NET resteront les mmes. Contrairement aux apparences, le passage de VB VB.NET est difficile. VB n'est pas un langage orient objets alors que VB.NET l'est compltement. Le programmeur VB va donc tre confront des concepts qu'il ne matrise pas. Il parat plus simple d'affronter ceux-ci en oubliant ce qu'on sait de VB. Aussi, nous ne faisons que peu rfrence VB dans la suite. Ce document n'est pas un cours exhaustif. Il est destin des gens connaissant dj la programmation et qui veulent dcouvrir VB.NET. Il reprend la structure du document "Introduction au langage C#" du mme auteur afin de faciliter la comparaison entre les deux langages. En fait, j'ai utilis ici des traducteurs automatiques de C# vers VB.NET. Sans tre totalement parfaits, ces traducteurs font nanmoins 80 100% du travail selon les cas. On se rappellera donc, en lisant le code des programmes VB.NET qui vont suivre, que celui-ci a t d'abord gnr par une machine puis remani par moi-mme si c'tait ncessaire. On y rencontre ainsi des "tournures" de programmation qu'on n'aurait peut-tre pas utiliser soi-mme. Les livres suivants m'ont aid : Professional C# programming, Editions Wrox C# et .NET, Grard Leblanc, Editions Eyrolles

Ce sont deux excellents ouvrages dont je conseille la lecture. La traduction des programmes C# en VB.NET a t obtenue par un traducteur disponible l'url (mars 2004) http://authors.aspalliance.com/aldotnet/examples/translate.aspx. Tout le reste a t obtenu avec la documentation de Visual studio.NET.

Serge Tah, mars 2004

1. LES BASES DU LANGAGE VB.NET....................................................................................................................................7 1.1 INTRODUCTION..............................................................................................................................................................................7 1.2 LES DONNES DE VB.NET........................................................................................................................................................... 7 1.2.1 LES TYPES DE DONNES PRDFINIS................................................................................................................................................ 7 1.2.2 NOTATION DES DONNES LITTRALES..............................................................................................................................................8 1.2.3 DCLARATION DES DONNES..........................................................................................................................................................9 1.2.3.1 Rle des dclarations........................................................................................................................................................... 9 1.2.3.2 Dclaration des constantes...................................................................................................................................................9 1.2.3.3 Dclaration des variables..................................................................................................................................................... 9 1.2.4 LES CONVERSIONS ENTRE NOMBRES ET CHANES DE CARACTRES......................................................................................................... 9 1.2.5 LES TABLEAUX DE DONNES........................................................................................................................................................ 11 1.3 LES INSTRUCTIONS LMENTAIRES DE VB.NET............................................................................................................................13 1.3.1 ECRITURE SUR CRAN.................................................................................................................................................................14 1.3.2 LECTURE DE DONNES TAPES AU CLAVIER.................................................................................................................................... 14 1.3.3 EXEMPLE D'ENTRES-SORTIES...................................................................................................................................................... 14 1.3.4 REDIRECTION DES E/S............................................................................................................................................................... 15 1.3.5 AFFECTATION DE LA VALEUR D'UNE EXPRESSION UNE VARIABLE..................................................................................................... 16 1.3.5.1 Liste des oprateurs............................................................................................................................................................16 1.3.5.2 Expression arithmtique.....................................................................................................................................................16 1.3.5.3 Priorits dans l'valuation des expressions arithmtiques................................................................................................. 18 1.3.5.4 Expressions relationnelles..................................................................................................................................................18 1.3.5.5 Expressions boolennes..................................................................................................................................................... 19 1.3.5.6 Traitement de bits.............................................................................................................................................................. 19 1.3.5.7 Oprateur associ une affectation................................................................................................................................... 20 1.3.5.8 Priorit gnrale des oprateurs......................................................................................................................................... 20 1.3.5.9 Les conversions de type..................................................................................................................................................... 20 1.4 LES INSTRUCTIONS DE CONTRLE DU DROULEMENT DU PROGRAMME............................................................................................... 22 1.4.1 ARRT.....................................................................................................................................................................................22 1.4.2 STRUCTURE DE CHOIX SIMPLE...................................................................................................................................................... 22 1.4.3 STRUCTURE DE CAS....................................................................................................................................................................23 1.4.4 STRUCTURE DE RPTITION..........................................................................................................................................................23 1.4.4.1 Nombre de rptitions connu............................................................................................................................................. 23 1.4.4.2 Nombre de rptitions inconnu..........................................................................................................................................24 1.4.4.3 Instructions de gestion de boucle.......................................................................................................................................25 1.5 LA STRUCTURE D'UN PROGRAMME VB.NET.................................................................................................................................25 1.6 COMPILATION ET EXCUTION D'UN PROGRAMME VB.NET.............................................................................................................27 1.7 L'EXEMPLE IMPOTS................................................................................................................................................................ 28 1.8 ARGUMENTS DU PROGRAMME PRINCIPAL........................................................................................................................................30 1.9 LES NUMRATIONS.....................................................................................................................................................................30 1.10 LA GESTION DES EXCEPTIONS......................................................................................................................................................32 1.11 PASSAGE DE PARAMTRES UNE FONCTION..................................................................................................................................34 1.11.1 PASSAGE PAR VALEUR.............................................................................................................................................................. 34 1.11.2 PASSAGE PAR RFRENCE..........................................................................................................................................................35 2. CLASSES, STUCTURES, INTERFACES............................................................................................................................ 36 2.1 L' OBJET PAR L'EXEMPLE.............................................................................................................................................................36 2.1.1 GNRALITS............................................................................................................................................................................36 2.1.2 DFINITION DE LA CLASSE PERSONNE............................................................................................................................................ 36 2.1.3 LA MTHODE INITIALISE..............................................................................................................................................................37 2.1.4 L'OPRATEUR NEW.....................................................................................................................................................................37 2.1.5 LE MOT CL ME....................................................................................................................................................................... 38 2.1.6 UN PROGRAMME DE TEST............................................................................................................................................................38 2.1.7 UTILISER UN FICHIER DE CLASSES COMPILES (ASSEMBLY)................................................................................................................39 2.1.8 UNE AUTRE MTHODE INITIALISE..................................................................................................................................................40 2.1.9 CONSTRUCTEURS DE LA CLASSE PERSONNE.....................................................................................................................................40 2.1.10 LES RFRENCES D'OBJETS........................................................................................................................................................ 42 2.1.11 LES OBJETS TEMPORAIRES......................................................................................................................................................... 43 2.1.12 MTHODES DE LECTURE ET D'CRITURE DES ATTRIBUTS PRIVS....................................................................................................... 43 2.1.13 LES PROPRITS.......................................................................................................................................................................44 2.1.14 LES MTHODES ET ATTRIBUTS DE CLASSE.....................................................................................................................................46

2.1.15 PASSAGE D'UN OBJET UNE FONCTION........................................................................................................................................ 48 2.1.16 UN TABLEAU DE PERSONNES......................................................................................................................................................49 2.2 L'HRITAGE PAR L'EXEMPLE........................................................................................................................................................ 49 2.2.1 GNRALITS............................................................................................................................................................................49 2.2.2 CONSTRUCTION D'UN OBJET ENSEIGNANT....................................................................................................................................... 51 2.2.3 SURCHARGE D'UNE MTHODE OU D'UNE PROPRIT.......................................................................................................................... 53 2.2.4 LE POLYMORPHISME...................................................................................................................................................................54 2.2.5 REDFINITION ET POLYMORPHISME................................................................................................................................................55 2.3 DFINIR UN INDEXEUR POUR UNE CLASSE....................................................................................................................................... 57 2.4 LES STRUCTURES......................................................................................................................................................................... 63 2.5 LES INTERFACES..........................................................................................................................................................................66 2.6 LES ESPACES DE NOMS................................................................................................................................................................. 70 2.7 L'EXEMPLE IMPOTS................................................................................................................................................................ 72 3. CLASSES .NET D'USAGE COURANT................................................................................................................................75 3.1 CHERCHER DE L'AIDE AVEC SDK.NET....................................................................................................................................... 75 3.1.1 WINCV......................................................................................................................................................................................75 3.2 CHERCHER DE L'AIDE SUR LES CLASSES AVEC VS.NET................................................................................................................. 78 3.2.1 OPTION AIDE............................................................................................................................................................................78 3.2.2 AIDE/INDEX............................................................................................................................................................................. 80 3.3 LA CLASSE STRING......................................................................................................................................................................81 3.4 LA CLASSE ARRAY...................................................................................................................................................................... 83 3.5 LA CLASSE ARRAYLIST............................................................................................................................................................... 85 3.6 LA CLASSE HASHTABLE............................................................................................................................................................... 87 3.7 LA CLASSE STREAMREADER.........................................................................................................................................................91 3.8 LA CLASSE STREAMWRITER........................................................................................................................................................ 92 3.9 LA CLASSE REGEX...................................................................................................................................................................... 93 3.9.1 VRIFIER QU'UNE CHANE CORRESPOND UN MODLE DONN........................................................................................................... 95 3.9.2 TROUVER TOUS LES LMENTS D'UNE CHANE CORRESPONDANT UN MODLE..................................................................................... 96 3.9.3 RCUPRER DES PARTIES D'UN MODLE......................................................................................................................................... 97 3.9.4 UN PROGRAMME D'APPRENTISSAGE............................................................................................................................................... 98 3.9.5 LA MTHODE SPLIT....................................................................................................................................................................99 3.10 LES CLASSES BINARYREADER ET BINARYWRITER..................................................................................................................... 100 4. INTERFACES GRAPHIQUES AVEC VB.NET ET VS.NET.......................................................................................... 104 4.1 LES BASES DES INTERFACES GRAPHIQUES..................................................................................................................................... 104 4.1.1 UNE FENTRE SIMPLE............................................................................................................................................................... 104 4.1.2 UN FORMULAIRE AVEC BOUTON................................................................................................................................................. 105 4.2 CONSTRUIRE UNE INTERFACE GRAPHIQUE AVEC VISUAL STUDIO.NET........................................................................................... 108 4.2.1 CRATION INITIALE DU PROJET...................................................................................................................................................108 4.2.2 LES FENTRE DE L'INTERFACE DE VS.NET.................................................................................................................................110 4.2.3 EXCUTION D'UN PROJET...........................................................................................................................................................111 4.2.4 LE CODE GNR PAR VS.NET................................................................................................................................................112 4.2.5 COMPILATION DANS UNE FENTRE DOS........................................................................................................................................ 113 4.2.6 GESTION DES VNEMENTS........................................................................................................................................................114 4.2.7 CONCLUSION...........................................................................................................................................................................114 4.3 FENTRE AVEC CHAMP DE SAISIE, BOUTON ET LIBELL.................................................................................................................. 115 4.3.1 CONCEPTION GRAPHIQUE...........................................................................................................................................................115 4.3.2 GESTION DES VNEMENTS D'UN FORMULAIRE.............................................................................................................................. 118 4.3.3 UNE AUTRE MTHODE POUR GRER LES VNEMENTS.................................................................................................................... 120 4.3.4 CONCLUSION...........................................................................................................................................................................121 4.4 QUELQUES COMPOSANTS UTILES..................................................................................................................................................121 4.4.1 FORMULAIRE FORM.................................................................................................................................................................. 121 4.4.2 TIQUETTES LABEL ET BOTES DE SAISIE TEXTBOX....................................................................................................................... 122 4.4.3 LISTES DROULANTES COMBOBOX..............................................................................................................................................124 4.4.4 COMPOSANT LISTBOX.............................................................................................................................................................. 125 4.4.5 CASES COCHER CHECKBOX, BOUTONS RADIO BUTTONRADIO.......................................................................................................128 4.4.6 VARIATEURS SCROLLBAR..........................................................................................................................................................128 4.5 VNEMENTS SOURIS................................................................................................................................................................ 130 4.6 CRER UNE FENTRE AVEC MENU................................................................................................................................................133 4.7 COMPOSANTS NON VISUELS.........................................................................................................................................................136

4.7.1 BOTES DE DIALOGUE OPENFILEDIALOG ET SAVEFILEDIALOG.......................................................................................................137 4.7.2 BOTES DE DIALOGUE FONTCOLOR ET COLORDIALOG...................................................................................................................141 4.7.3 TIMER....................................................................................................................................................................................143 4.8 L'EXEMPLE IMPOTS.............................................................................................................................................................. 145 5. GESTION D'VNEMENTS.............................................................................................................................................. 149 5.1 OBJETS DELEGATE.....................................................................................................................................................................149 5.2 GESTION D'VNEMENTS............................................................................................................................................................ 150 5.2.1 DCLARATION D'UN VNEMENT................................................................................................................................................ 150 5.2.2 DFINIR LES GESTIONNAIRES D'UN VNEMENT............................................................................................................................. 150 5.2.3 DCLENCHER UN VNEMENT.................................................................................................................................................... 150 5.2.4 UN EXEMPLE...........................................................................................................................................................................151 6. ACCS AUX BASES DE DONNES.................................................................................................................................. 155 6.1 GNRALITS........................................................................................................................................................................... 155 6.2 LES DEUX MODES D'EXPLOITATION D'UNE SOURCE DE DONNES...................................................................................................... 156 6.3 ACCS AUX DONNES EN MODE CONNECT................................................................................................................................... 157 6.3.1 LES BASES DE DONNES DE L'EXEMPLE........................................................................................................................................ 157 6.3.2 UTILISATION D'UN PILOTE ODBC............................................................................................................................................. 161 6.3.2.1 La phase de connexion.....................................................................................................................................................163 6.3.2.2 mettre des requtes SQL................................................................................................................................................163 6.3.2.3 Exploitation du rsultat d'une requte SELECT.............................................................................................................. 164 6.3.2.4 Libration des ressources.................................................................................................................................................165 6.3.3 UTILISATION D'UN PILOTE OLE DB.......................................................................................................................................... 165 6.3.4 MISE JOUR D'UNE TABLE........................................................................................................................................................ 167 6.3.5 IMPOTS..............................................................................................................................................................................170 6.4 ACCS AUX DONNES EN MODE DCONNECT................................................................................................................................174 7. LES THREADS D'EXCUTION....................................................................................................................................... 175 7.1 INTRODUCTION..........................................................................................................................................................................175 7.2 CRATION DE THREADS D'EXCUTION..........................................................................................................................................176 7.3 INTRT DES THREADS............................................................................................................................................................... 178 7.4 ACCS DES RESSOURCES PARTAGES.........................................................................................................................................179 7.5 ACCS EXCLUSIF UNE RESSOURCE PARTAGE............................................................................................................................. 181 7.6 SYNCHRONISATION PAR VNEMENTS...........................................................................................................................................183 8. PROGRAMMATION TCP-IP............................................................................................................................................. 187 8.1 GNRALITS........................................................................................................................................................................... 187 8.1.1 LES PROTOCOLES DE L'INTERNET................................................................................................................................................187 8.1.2 LE MODLE OSI..................................................................................................................................................................... 187 8.1.3 LE MODLE TCP/IP................................................................................................................................................................188 8.1.4 FONCTIONNEMENT DES PROTOCOLES DE L'INTERNET...................................................................................................................... 190 8.1.5 L'ADRESSAGE DANS L'INTERNET................................................................................................................................................. 191 8.1.5.1 Les classes d'adresses IP.................................................................................................................................................. 192 8.1.5.2 Les protocoles de conversion Adresse Internet <--> Adresse physique.......................................................................... 193 8.1.6 LA COUCHE RSEAU DITE COUCHE IP DE L'INTERNET.....................................................................................................................193 8.1.6.1 Le routage........................................................................................................................................................................ 194 8.1.6.2 Messages d'erreur et de contrle...................................................................................................................................... 194 8.1.7 LA COUCHE TRANSPORT : LES PROTOCOLES UDP ET TCP.............................................................................................................195 8.1.7.1 Le protocole UDP : User Datagram Protocol.................................................................................................................. 195 8.1.7.2 Le protocole TCP : Transfer Control Protocol................................................................................................................ 195 8.1.8 LA COUCHE APPLICATIONS........................................................................................................................................................195 8.1.9 CONCLUSION...........................................................................................................................................................................196 8.2 GESTION DES ADRESSES RSEAU.................................................................................................................................................. 197 8.3 PROGRAMMATION TCP-IP....................................................................................................................................................... 199 8.3.1 GNRALITS..........................................................................................................................................................................199 8.3.2 LES CARACTRISTIQUES DU PROTOCOLE TCP...............................................................................................................................200

8.3.3 LA RELATION CLIENT-SERVEUR.................................................................................................................................................. 200 8.3.4 ARCHITECTURE D'UN CLIENT......................................................................................................................................................200 8.3.5 ARCHITECTURE D'UN SERVEUR................................................................................................................................................... 201 8.3.6 LA CLASSE TCPCLIENT............................................................................................................................................................ 201 8.3.7 LA CLASSE NETWORKSTREAM...................................................................................................................................................201 8.3.8 ARCHITECTURE DE BASE D'UN CLIENT INTERNET............................................................................................................................202 8.3.9 LA CLASSE TCPLISTENER......................................................................................................................................................... 202 8.3.10 ARCHITECTURE DE BASE D'UN SERVEUR INTERNET...................................................................................................................... 203 8.4 EXEMPLES................................................................................................................................................................................ 204 8.4.1 SERVEUR D'CHO..................................................................................................................................................................... 204 8.4.2 UN CLIENT POUR LE SERVEUR D'CHO......................................................................................................................................... 206 8.4.3 UN CLIENT TCP GNRIQUE..................................................................................................................................................... 207 8.4.4 UN SERVEUR TCP GNRIQUE.................................................................................................................................................... 213 8.4.5 UN CLIENT WEB..................................................................................................................................................................... 218 8.4.6 CLIENT WEB GRANT LES REDIRECTIONS.....................................................................................................................................220 8.4.7 SERVEUR DE CALCUL D'IMPTS.................................................................................................................................................. 223 9. SERVICES WEB................................................................................................................................................................... 228 9.1 INTRODUCTION..........................................................................................................................................................................228 9.2 LES NAVIGATEURS ET XML...................................................................................................................................................... 228 9.3 UN PREMIER SERVICE WEB........................................................................................................................................................ 229 9.3.1 VERSION 1............................................................................................................................................................................. 229 9.3.2 VERSION 2............................................................................................................................................................................. 235 9.3.3 VERSION 3............................................................................................................................................................................. 237 9.3.4 VERSION 4............................................................................................................................................................................. 238 9.3.5 CONCLUSION...........................................................................................................................................................................239 9.4 UN SERVICE WEB D'OPRATIONS................................................................................................................................................. 239 9.5 UN CLIENT HTTP-POST........................................................................................................................................................ 245 9.6 UN CLIENT SOAP.................................................................................................................................................................... 253 9.7 ENCAPSULATION DES CHANGES CLIENT-SERVEUR.........................................................................................................................257 9.7.1 LA CLASSE D'ENCAPSULATION....................................................................................................................................................257 9.7.2 UN CLIENT CONSOLE................................................................................................................................................................ 261 9.7.3 UN CLIENT GRAPHIQUE WINDOWS............................................................................................................................................... 263 9.8 UN CLIENT PROXY..................................................................................................................................................................... 268 9.9 CONFIGURER UN SERVICE WEB.................................................................................................................................................. 272 9.10 LE SERVICE WEB IMPOTS................................................................................................................................................... 274 9.10.1 LE SERVICE WEB....................................................................................................................................................................274 9.10.2 GNRER LE PROXY DU SERVICE IMPOTS....................................................................................................................................280 9.10.3 UTILISER LE PROXY AVEC UN CLIENT........................................................................................................................................ 281 10. A SUIVRE.........................................................................................................................................................................285

1.
1.1 Introduction

Les bases du langage VB.NET

Nous traitons VB.NET d'abord comme un langage de programmation classique. Nous aborderons les objets ultrieurement. Dans un programme on trouve deux choses - des donnes - les instructions qui les manipulent On s'efforce gnralement de sparer les donnes des instructions :
+--------------------+ DONNEES +-------------------- INSTRUCTIONS +--------------------+

1.2
1. 2. 3. 4. 5.

Les donnes de VB.NET


les nombres entiers, rels et dcimaux les caractres et chanes de caractres les boolens les dates les objets

VB.NET utilise les types de donnes suivants:

1.2.1
Boolean Byte Char Date Decimal

Les types de donnes prdfinis


Taille 2 octets 1 octet 2 octets 8 octets True ou False. 0 255 (non signs). 0 65 535 (non signs). 0:00:00 le 1er janvier 0001 23:59:59 le 31 dcembre 9999. Plage de valeurs System.Boolean System.Byte System.Char System.DateTime System.Decimal

Type VB Type .NET quivalent

16 octets 0 +/-79 228 162 514 264 337 593 543 950 335 sans dcimale ; 0 +/7,9228162514264337593543950335 avec 28 dcimales ; le plus petit nombre diffrent de zro tant +/-0,0000000000000000000000000001 (+/-1E-28). 8 octets -1,79769313486231E+308 -4,94065645841247E-324 pour les valeurs ngatives ; 4,94065645841247E-324 1,79769313486231E+308 pour les valeurs positives. -2 147 483 648 2 147 483 647. -9 223 372 036 854 775 808 9 223 372 036 854 775 807. N'importe quel type peut tre stock dans une variable de type Object. -32 768 32 767. -3,402823E+38 -1,401298E-45 pour les valeurs ngatives ; 1,401298E-45 3,402823E+38 pour les valeurs positives. 0 environ 2 milliards de caractres Unicode.

Double

System.Double

Integer Long Object Short Single String

System.Int32 System.Int64 System.Object System.Int16 System.Single System.String (classe)

4 octets 8 octets 4 octets 2 octets 4 octets

Dans le tableau ci-dessus, on dcouvre qu'il y a deux types possibles pour un entier sur 32 bits : Integer et System.Int32. Les deux types sont interchangeables. Il en est de mme pour les autres types VB et leurs quivalents dans la plate-forme .NET. Voici un exemple de programme :
Les bases de VB.NET

Module types Sub Main() ' nombres entiers Dim var1 As Integer = 100 Dim var2 As Long = 10000000000L Dim var3 As Byte = 100 Dim var4 As Short = 4 ' nombres rels Dim var5 As Decimal = 4.56789D Dim var6 As Double = 3.4 Dim var7 As Single = -0.000103F ' date Dim var8 As Date = New Date(2003, 1, 1, 12, 8, 4) ' boolen Dim var9 As Boolean = True ' caractre Dim var10 As Char = "A"c ' chane de caractres Dim var11 As String = "abcde" ' objet Dim var12 As Object = New Object ' affichages Console.Out.WriteLine("var1=" + var1.ToString) Console.Out.WriteLine("var2=" + var2.ToString) Console.Out.WriteLine("var3=" + var3.ToString) Console.Out.WriteLine("var4=" + var4.ToString) Console.Out.WriteLine("var5=" + var5.ToString) Console.Out.WriteLine("var6=" + var6.ToString) Console.Out.WriteLine("var7=" + var7.ToString) Console.Out.WriteLine("var8=" + var8.ToString) Console.Out.WriteLine("var9=" + var9.ToString) Console.Out.WriteLine("var10=" + var10) Console.Out.WriteLine("var11=" + var11) Console.Out.WriteLine("var12=" + var12.ToString) End Sub End Module

L'excution donne les rsultats suivants :


var1=100 var2=10000000000 var3=100 var4=4 var5=4,56789 var6=3,4 var7=-0,000103 var8=01/01/2003 12:08:04 var9=True var10=A var11=abcde var12=System.Object

1.2.2
Integer Long Double Single Decimal Char String Boolean date

Notation des donnes littrales


145, -7, &FF (hexadcimal) 100000L 134.789, -45E-18 (-45 10-18) 134.789F, -45E-18F (-45 10-18) 100000D "A"c "aujourd'hui" true, false New Date(2003, 1, 1) pour le 01/01/2003

On notera les points suivants : - 100000L, le L pour signifier qu'on considre le nombre comme un entier long - 134.789F, le F pour signifier qu'on considre le nombre comme un rel simple prcision - 100000D, le D pour signifier qu'on considre le nombre comme un rel dcimal
Les bases de VB.NET

"A"c, pour transformer la chane de caractres "A" en caractre 'A' la chane de caractres est entour du caractre ". Si la chane doit contenir le caractre ", on double celui-ci comme dans "abcd""e" pour reprsenter la chane [abcd"e].

1.2.3 1.2.3.1

Dclaration des donnes Rle des dclarations

Un programme manipule des donnes caractrises par un nom et un type. Ces donnes sont stockes en mmoire. Au moment de la traduction du programme, le compilateur affecte chaque donne un emplacement en mmoire caractris par une adresse et une taille. Il le fait en s'aidant des dclarations faites par le programmeur. Par ailleurs celles-ci permettent au compilateur de dtecter des erreurs de programmation. Ainsi l'opration x=x*2 sera dclare errone si x est une chane de caractres par exemple.

1.2.3.2

Dclaration des constantes

La syntaxe de dclaration d'une constante est la suivante : const identificateur as type=valeur par exemple [const PI as double=3.141592]. Pourquoi dclarer des constantes ? 1. 2. La lecture du programme sera plus aise si l'on a donn la constante un nom significatif : [const taux_tva as single=0.186F] La modification du programme sera plus aise si la "constante" vient changer. Ainsi dans le cas prcdent, si le taux de tva passe 33%, la seule modification faire sera de modifier l'instruction dfinissant sa valeur : [const taux_tva as single=0.336F]. Si l'on avait utilis 0.186 explicitement dans le programme, ce serait alors de nombreuses instructions qu'il faudrait modifier.

1.2.3.3

Dclaration des variables

Une variable est identifie par un nom et se rapporte un type de donnes. VB.NET ne fait pas la diffrence entre majuscules et minuscules. Ainsi les variables FIN et fin sont identiques. Les variables peuvent tre initialises lors de leur dclaration. La syntaxe de dclaration d'une ou plusieurs variables est : dim variable1,variable2,...,variablen as identificateur_de_type o identificateur_de_type est un type prdfini ou bien un type dfini par le programmeur.

1.2.4

Les conversions entre nombres et chanes de caractres


nombre.ToString ou "" & nombre ou CType(nombre,String) objet.ToString Integer.Parse(chaine) ou Int32.Parse Long.Parse(chaine) ou Int64.Parse Double.Parse(chane) Single.Parse(chane)

nombre -> chane objet -> chane chaine -> Integer chane -> Long chane -> Double chane -> Single

La conversion d'une chane vers un nombre peut chouer si la chane ne reprsente pas un nombre valide. Il y a alors gnration d'une erreur fatale appele exception en VB.NET. Cette erreur peut tre gre par la clause try/catch suivante :
try appel de la fonction susceptible de gnrer l'exception catch e as Exception traiter l'exception e end try instruction suivante

Si la fonction ne gnre pas d'exception, on passe alors instruction suivante, sinon on passe dans le corps de la clause catch puis instruction suivante. Nous reviendrons ultrieurement sur la gestion des exceptions. Voici un programme prsentant les principales techniques de conversion entre nombres et chanes de caractres. Dans cet exemple la fonction affiche crit l'cran la valeur de son paramtre. Ainsi affiche(S) crit la valeur de S l'cran. '
directives Option Strict On ' espaces de noms imports Imports System Les bases de VB.NET

' le module de test Module Module1 Sub Main() ' procdure principale ' donnes locales Dim S As String Const i As Integer = 10 Const l As Long = 100000 Const f As Single = 45.78F Dim d As Double = -14.98 ' nombre --> chane affiche(CType(i, String)) affiche(CType(l, String)) affiche(CType(f, String)) affiche(CType(d, String)) 'boolean --> chane Const b As Boolean = False affiche(b.ToString) ' chane --> int Dim i1 As Integer = Integer.Parse("10") affiche(i1.ToString) Try i1 = Integer.Parse("10.67") affiche(i1.ToString) Catch e As Exception affiche("Erreur [10.67] : " + e.Message) End Try ' chane --> long Dim l1 As Long = Long.Parse("100") affiche("" + l1.ToString) Try l1 = Long.Parse("10.675") affiche("" & l1) Catch e As Exception affiche("Erreur [10.675] : " + e.Message) End Try ' chane --> double Dim d1 As Double = Double.Parse("100,87") affiche(d1.ToString) Try d1 = Double.Parse("abcd") affiche("" & d1) Catch e As Exception affiche("Erreur [abcd] : " + e.Message) End Try ' chane --> single Dim f1 As Single = Single.Parse("100,87") affiche(f1.ToString) Try d1 = Single.Parse("abcd") affiche(f1.ToString) Catch e As Exception affiche("Erreur [abcd] : " + e.Message) End Try End Sub ' affiche Public Sub affiche(ByVal S As String) Console.Out.WriteLine("S=" + S) End Sub End Module

Les rsultats obtenus sont les suivants :


S=10 S=100000 S=45,78 S=-14,98 S=False S=10 S=Erreur [10.67] : Le format de la chane d'entre est incorrect. S=100 S=Erreur [10.675] : Le format de la chane d'entre est incorrect. Les bases de VB.NET

10

S=100,87 S=Erreur [abcd] : Le format de la chane d'entre est incorrect. S=100,87 S=Erreur [abcd] : Le format de la chane d'entre est incorrect.

On remarquera que les nombres rels sous forme de chane de caractres doivent utiliser la virgule et non le point dcimal. Ainsi on crira Dim d As Double = -14.98 mais Dim d1 As Double = Double.Parse("100,87")

1.2.5

Les tableaux de donnes

Un tableau VB.NET est un objet permettant de rassembler sous un mme identificateur des donnes de mme type. Sa dclaration est la suivante : Dim Tableau(n) as type ou Dim Tableau() as type=New type(n) {} o n est l'indice du dernier lment de tableau. La syntaxe Tableau(i) dsigne la donne n i o i appartient l'intervalle [0,n]. Toute rfrence la donne Tableau(i) o i n'appartient pas l'intervalle [0,n] provoquera une exception. Un tableau peut tre initialis en mme temps que dclar. Dans ce cas, on n'a pas besoin d'indiquer le n du dernier lment.
Dim entiers() As Integer = {0, 10, 20, 30}

Les tableaux ont une proprit Length qui est le nombre d'lments du tableau. Voici un programme exemple :
Module tab0 Sub Main() ' un premier tableau Dim tab0(5) As Integer For i As Integer = 0 To UBound(tab0) tab0(i) = i Next For i As Integer = 0 To UBound(tab0) Console.Out.WriteLine("tab0(" + i.ToString + ")=" + tab0(i).tostring) Next ' un second tableau Dim tab1() As Integer = New Integer(5) {} For i As Integer = 0 To tab1.Length - 1 tab1(i) = i * 10 Next For i As Integer = 0 To tab1.Length - 1 Console.Out.WriteLine("tab1(" + i.ToString + ")=" + tab1(i).tostring) Next End Sub End Module

et son excution :
tab0(0)=0 tab0(1)=1 tab0(2)=2 tab0(3)=3 tab0(4)=4 tab0(5)=5 tab1(0)=0 tab1(1)=10 tab1(2)=20 tab1(3)=30 tab1(4)=40 tab1(5)=50

Un tableau deux dimensions pourra tre dclar comme suit : Dim Tableau(n,m) as Type ou Dim Tableau(,) as Type=New Type(n,m) {} o n+1 est le nombre de lignes, m+1 le nombre de colonnes. La syntaxe Tableau(i,j) dsigne l'lment j de la ligne i de Tableau. Le tableau deux dimensions peut lui aussi tre initialis en mme temps qu'il est dclar :
Dim rels(,) As Double = {{0.5, 1.7}, {8.4, -6}}

Le nombre d'lments dans chacune des dimensions peut tre obtenue par la mthode GetLenth(i) o i=0 reprsente la dimension correspondant au 1er indice, i=1 la dimension correspondant au 2ime indice, Voici un programme d'exemple :
Module Module2 Les bases de VB.NET

11

Sub Main() ' un premier tableau Dim tab0(2, 1) As Integer For i As Integer = 0 To UBound(tab0) For j As Integer = 0 To tab0.GetLength(1) - 1 tab0(i, j) = i * 10 + j Next Next For i As Integer = 0 To UBound(tab0) For j As Integer = 0 To tab0.GetLength(1) - 1 Console.Out.WriteLine("tab0(" + i.ToString + "," + j.ToString + ")=" + tab0(i, j).tostring) Next Next ' un second tableau Dim tab1(,) As Integer = New Integer(2, 1) {} For i As Integer = 0 To tab1.GetLength(0) - 1 For j As Integer = 0 To tab1.GetLength(1) - 1 tab1(i, j) = i * 100 + j Next Next For i As Integer = 0 To tab1.GetLength(0) - 1 For j As Integer = 0 To tab1.GetLength(1) - 1 Console.Out.WriteLine("tab1(" + i.ToString + "," + j.ToString + ")=" + tab1(i, j).tostring) Next Next End Sub End Module

et les rsultats de son excution :


tab0(0)=0 tab0(1)=1 tab0(2)=2 tab0(3)=3 tab0(4)=4 tab0(5)=5 tab1(0)=0 tab1(1)=10 tab1(2)=20 tab1(3)=30 tab1(4)=40 tab1(5)=50

Un tableau de tableaux est dclar comme suit : Dim Tableau(n)() as Type ou Dim Tableau()() as Type=new Type(n)() La dclaration ci-dessus cre un tableau de n+1 lignes. Chaque lment Tableau(i) est une rfrence de tableau une dimension. Ces tableaux ne sont pas crs lors de la dclaration ci-dessus. L'exemple ci-dessous illustre la cration d'un tableau de tableaux :
' un tableau de tableaux Dim noms()() As String = New String(3)() {} ' initialisation For i = 0 To noms.Length - 1 noms(i) = New String(i) {} For j = 0 To noms(i).Length - 1 noms(i)(j) = "nom" & i & j Next Next

Ici noms(i) est un tableau de i+1 lments. Comme noms(i) est un tableau, noms(i).Length est son nombre d'lments. Voici un exemple regroupant les trois types de tableaux que nous venons de prsenter : '
directives Option Strict On Option Explicit On ' imports Imports System ' classe de test Module test Sub main() ' un tableau 1 dimension initialis Dim entiers() As Integer = {0, 10, 20, 30} Dim i As Integer Les bases de VB.NET

12

For i = 0 To entiers.Length - 1 Console.Out.WriteLine("entiers[" & i & "]=" & entiers(i)) Next ' un tableau 2 dimensions initialis Dim rels(,) As Double = {{0.5, 1.7}, {8.4, -6}} Dim j As Integer For i = 0 To rels.GetLength(0) - 1 For j = 0 To rels.GetLength(1) - 1 Console.Out.WriteLine("rels[" & i & "," & j & "]=" & rels(i, j)) Next Next ' un tableaude tableaux Dim noms()() As String = New String(3)() {} ' initialisation For i = 0 To noms.Length- 1 noms(i) =New String(i) {} For j = 0 To noms(i).Length - 1 noms(i)(j) = "nom" & i & j Next Next ' affichage For i = 0 To noms.Length- 1 For j = 0 To noms(i).Length - 1 Console.Out.WriteLine("noms[" & i & "][" & j & "]=" & noms(i)(j)) Next Next End Sub End Module

A l'excution, nous obtenons les rsultats suivants :


entiers[0]=0 entiers[1]=10 entiers[2]=20 entiers[3]=30 rels[0,0]=0,5 rels[0,1]=1,7 rels[1,0]=8,4 rels[1,1]=-6 noms[0][0]=nom00 noms[1][0]=nom10 noms[1][1]=nom11 noms[2][0]=nom20 noms[2][1]=nom21 noms[2][2]=nom22 noms[3][0]=nom30 noms[3][1]=nom31 noms[3][2]=nom32 noms[3][3]=nom33

1.3
1 2

Les instructions lmentaires de VB.NET


les instructions lmentaires excutes par l'ordinateur. les instructions de contrle du droulement du programme.

On distingue

Les instructions lmentaires apparaissent clairement lorsqu'on considre la structure d'un micro-ordinateur et de ses priphriques.
U. C MEMOIRE ECRAN +-------------------+ +-------+ 2 <-+--> 3 +-----------+ 1 ----+------+-> CLAVIER +-----------+--------+--> +-----------+ +-------------------+ +-------+ 4^ \ \ 5 +-------+ \ ----> DISQUE +-------+ Les bases de VB.NET

13

1. lecture d'informations provenant du clavier 2. traitement d'informations 3. criture d'informations l'cran 4. lecture d'informations provenant d'un fichier disque 5. criture d'informations dans un fichier disque

1.3.1

Ecriture sur cran

Il existe diffrentes instructions d'criture l'cran :


Console.Out.WriteLine(expression) Console.WriteLine(expression) Console.Error.WriteLine (expression)

o expression est tout type de donne qui puisse tre converti en chane de caractres pour tre affich l'cran. Dans les exemples vus jusqu'ici, nous n'avons utilis que l'instruction Console.Out.WriteLine(expression). La classe System.Console donne accs aux oprations d'criture cran (Write, WriteLine). La classe Console a deux proprits Out et Error qui sont des flux d'criture de type StreamWriter : Console.WriteLine() est quivalent Console.Out.WriteLine() et crit sur le flux Out associ habituellement l'cran. Console.Error.WriteLine() crit sur le flux Error, habituellement associ lui aussi l'cran. Les flux Out et Error sont associs par dfaut l'cran. Mais ils peuvent tre redirigs vers des fichiers texte au moment de l'excution du programme comme nous le verrons prochainement.

1.3.2

Lecture de donnes tapes au clavier

Le flux de donnes provenant du clavier est dsign par l'objet Console.In de type StreamReader. Ce type d'objets permet de lire une ligne de texte avec la mthode ReadLine :
Dim ligne As String = Console.In.ReadLine()

La ligne tape au clavier est range dans la variable ligne et peut ensuite tre exploite par le programme.Le flux In peut tre redirig vers un fichier comme les flux Out et Error.

1.3.3
'

Exemple d'entres-sorties

Voici un court programme d'illustration des oprations d'entres-sorties clavier/cran :


options Option Explicit On Option Strict On ' espaces de noms Imports System ' module Module io1 Sub Main() ' criture sur le flux Out Dim obj As New Object Console.Out.WriteLine(("" & obj.ToString)) ' criture sur le flux Error Dim i As Integer = 10 Console.Error.WriteLine(("i=" & i)) ' lecture d'une ligne saisie au clavier Console.Out.Write("Tapez une ligne : ") Dim ligne As String = Console.In.ReadLine() Console.Out.WriteLine(("ligne=" + ligne)) End Sub End Module

et les rsultats de l'excution :


System.Object i=10 Tapez une ligne : ceci est un essai ligne=ceci est un essai

Les instructions
Les bases de VB.NET

14

Dim obj As New Object Console.Out.WriteLine(obj.ToString)

ne sont l que pour montrer que n'importe quel objet peut faire l'objet d'un affichage. Nous ne chercherons pas ici expliquer la signification de ce qui est affich.

1.3.4
1. 2. 3.

Redirection des E/S

Il existe sous DOS/Windows trois priphriques standard appels : priphrique d'entre standard - dsigne par dfaut le clavier et porte le n 0 priphrique de sortie standard - dsigne par dfaut l'cran et porte le n 1 priphrique d'erreur standard - dsigne par dfaut l'cran et porte le n 2

En VB.NET, le flux d'criture Console.Out crit sur le priphrique 1, le flux d'criture Console.Error crit sur le priphrique 2 et le flux de lecture Console.In lit les donnes provenant du priphrique 0. Lorsqu'on lance un programme dans une fentre Dos sous Windows, on peut fixer quels seront les priphriques 0, 1 et 2 pour le programme excut. Considrons la ligne de commande suivante :
pg arg1 arg2 .. argn

Derrire les arguments argi du programme pg, on peut rediriger les priphriques d'E/S standard vers des fichiers:
0<in.txt 1>out.txt 1>>out.txt 2>error.txt 2>>error.txt 1>out.txt 2>error.txt

le flux d'entre standard n 0 est redirig vers le fichier in.txt. Dans le programme le flux Console.In prendra donc ses donnes dans le fichier in.txt. redirige la sortie n 1 vers le fichier out.txt. Cela entrane que dans le programme le flux Console.Out crira ses donnes dans le fichier out.txt idem, mais les donnes crites sont ajoutes au contenu actuel du fichier out.txt. redirige la sortie n 2 vers le fichier error.txt. Cela entrane que dans le programme le flux Console.Error crira ses donnes dans le fichier error.txt idem, mais les donnes crites sont ajoutes au contenu actuel du fichier error.txt. Les priphriques 1 et 2 sont tous les deux redirigs vers des fichiers

On notera que pour rediriger les flux d'E/S du programme pg vers des fichiers, le programme pg n'a pas besoin d'tre modifi. C'est l'OS qui fixe la nature des priphriques 0,1 et 2. Considrons le programme suivant : '
options Option Explicit On Option Strict On ' espaces de noms Imports System ' redirections Module console2 Sub Main() ' lecture flux In Dim data As String = Console.In.ReadLine() ' criture flux Out Console.Out.WriteLine(("criture dans flux Out : " + data)) ' criture flux Error Console.Error.WriteLine(("criture dans flux Error : " + data)) End Sub End Module

Compilons ce programme :
dos>vbc es2.vb Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573 Copyright (C) Microsoft Corporation 1987-2002. Tous droits rservs. dos>dir 24/02/2004 11/03/2004 15:39 08:20 416 es2.vb 3 584 es2.exe

Les bases de VB.NET

15

Faisons une premire excution:


dos>es2.exe un premier test criture dans flux Out : un premier test criture dans flux Error : un premier test

L'excution prcdente ne redirige aucun des flux d'E/S standard In, Out, Error. Nos allons maintenant rediriger les trois flux. Le flux In sera redirig vers un fichier in.txt, le flux Out vers le fichier out.txt, le flux Error vers le fichier error.txt. Cette redirection a lieu sur la ligne de commande sous la forme
dos>es2.exe 0<in.txt 1>out.txt 2>error.txt

L'excution donne les rsultats suivants :


dos>more in.txt un second test dos>es2.exe 0<in.txt 1>out.txt 2>error.txt dos>more out.txt criture dans flux Out : un second test dos>more error.txt criture dans flux Error : un second test

On voit clairement que les flux Out et Error n'crivent pas sur les mmes priphriques.

1.3.5 1.3.5.1
Arithmtique Assignation Comparaison

Affectation de la valeur d'une expression une variable Liste des oprateurs


Action lment du langage ^, , *, /, \, Mod, +, = =, ^=, *=, /=, \=, +=, -=, &= =, <>, <, >, <=, >=, Like, Is &, + Not, And, Or, Xor, AndAlso, OrElse AddressOf, GetType

On s'intresse ici l'opration variable=expression. L'expression peut tre de type : arithmtique, relationnelle, boolenne, caractres.

Concatnation Oprations logiques/de bits Oprations diverses

1.3.5.2
Arithmtique

Expression arithmtique
^, , *, /, \, Mod, +, =

Les oprateurs des expressions arithmtiques sont les suivants :

+ : addition, - : soustraction, * : multiplication, / : division relle, \ : quotient de division entire, Mod : reste de la divion entire, ^: lvation la puissance. Ainsi le programme suivant :
' oprateurs arithmtiques Module operateursarithmetiques Sub Main() Dim i, j As Integer i = 4 : j = 3 Console.Out.WriteLine(i & "/" & j & "=" & (i / j)) Console.Out.WriteLine(i & "\" & j & "=" & (i \ j)) Console.Out.WriteLine(i & " mod " & j & "=" & (i Mod j)) Dim r1, r2 As Double r1 = 4.1 : r2 = 3.6 Console.Out.WriteLine(r1 & "/" & r2 & "=" & (r1 / r2)) Console.Out.WriteLine(r1 & "^2=" & (r1 ^ 2)) Console.Out.WriteLine(Math.Cos(3)) End Sub Les bases de VB.NET

16

End Module

donne les rsultats suivants :


4/3=1,33333333333333 4\3=1 4 mod 3=1 4,1/3,6=1,13888888888889 4,1^2=16,81 -0,989992496600445

Il existe diverses fonctions mathmatiques. En voici quelques-unes :


Public Shared Function Sqrt(ByVal d As Double ) As Double Public Shared Function Cos(ByVal d As Double ) As Double Public Shared Function Sin(ByVal a As Double) As Double Public Shared Function Tan(ByVal a As Double) As Double Public Shared Function Pow(ByVal x As Double,ByVal y As Double) As Double Public Shared Function Exp(ByVal d As Double) As Double Overloads Public Shared Function Log( ByVal d As Double ) As Double Overloads Public Shared Function Abs(ByVal value As Double ) As Double ....

racine carre Cosinus Sinus Tangente x la puissance y (x>0) Exponentielle Logarithme nprien valeur absolue

Toutes ces fonctions sont dfinies dans une classe .NET appele Math. Lorsqu'on les utilise, il faut les prfixer avec le nom de la classe o elles sont dfinies. Ainsi on crira :
Dim r1, r2 As Double r2 = Math.Sqrt(9) r1 = Math.Cos(3)

La dfinition complte de la classe Math est la suivante :


E PI Abs Acos Asin Atan Atan2 BigMul Ceiling Cos Cosh DivRem Exp Floor IEEERemainder Log Log10 Max Min Les bases de VB.NET

Reprsente la base de logarithme naturelle spcifie par la constante e. Reprsente le rapport de la circonfrence d'un cercle son diamtre, spcifi par la constante . Surcharg. Retourne la valeur absolue d'un nombre spcifi. Retourne l'angle dont le cosinus est le nombre spcifi. Retourne l'angle dont le sinus est le nombre spcifi. Retourne l'angle dont la tangente est le nombre spcifi. Retourne l'angle dont la tangente est le quotient de deux nombres spcifis. Gnre le produit intgral de deux nombres 32 bits. Retourne le plus petit nombre entier suprieur ou gal au nombre spcifi. Retourne le cosinus de l'angle spcifi. Retourne le cosinus hyperbolique de l'angle spcifi. Surcharg. Retourne le quotient de deux nombres, en passant le reste en tant que paramtre de sortie. Retourne e lev la puissance spcifie. Retourne le plus grand nombre entier infrieur ou gal au nombre spcifi. Retourne le reste de la division d'un nombre spcifi par un autre. Surcharg. Retourne le logarithme d'un nombre spcifi. Retourne le logarithme de base 10 d'un nombre spcifi. Surcharg. Retourne le plus grand de deux nombres spcifis. Surcharg. Retourne le plus petit de deux nombres. 17

Pow Round Sign Sin Sinh Sqrt Tan Tanh

Retourne un nombre spcifi lev la puissance spcifie. Surcharg. Retourne le nombre le plus proche de la valeur spcifie. Surcharg. Retourne une valeur indiquant le signe d'un nombre. Retourne le sinus de l'angle spcifi. Retourne le sinus hyperbolique de l'angle spcifi. Retourne la racine carre d'un nombre spcifi. Retourne la tangente de l'angle spcifi. Retourne la tangente hyperbolique de l'angle spcifi.

Lorsqu'une fonction est dclare "surcharge", c'est qu'elle existe pour divers type de paramtres. Par exemple, la fonction Abs(x) existe pour x de type Integer, Long, Decimal, Single, Float. Pour chacun de ces types existe une dfinition spare de la fonction Abs. On dit alors qu'elle est surcharge.

1.3.5.3

Priorits dans l'valuation des expressions arithmtiques


Oprateurs Toutes les expressions sans oprateur : fonctions, parenthses ^ +, *, / \ Mod +, -

La priorit des oprateurs lors de l'valuation d'une expression arithmtique est la suivante (du plus prioritaire au moins prioritaire) : Catgorie
Primaire lvation la puissance Ngation unaire Multiplication Division par un entier Modulo Addition

1.3.5.4
Comparaison

Expressions relationnelles
=, <>, <, >, <=, >=, Like, Is

Les oprateurs sont les suivants :

= : gal , <> : diffrent de, < : plus petit que (strictement), > : plus grand que (strictement), <= : infrieur ou gal, >= : suprieur ou gal, Like : correspond un modle, Is : identit d'objets. Tous ces oprateurs ont la mme priorit. Ils sont valus de la gauche vers la droite. Le rsultat d'une expression relationnelle un boolen. Comparaison de chanes de caractres : considrons le programme suivant :
' espaces de noms Imports System Module string1 Sub main() Dim ch1 As Char = "A"c Dim ch2 As Char = "B"c Dim ch3 As Char = "a"c Console.Out.WriteLine("A<B=" & (ch1 < ch2)) Console.Out.WriteLine("A<a=" & (ch1 < ch3)) Dim chat As String = "chat" Dim chien As String = "chien" Dim chaton As String = "chaton" Dim chat2 As String = "CHAT" Console.Out.WriteLine("chat<chien=" & (chat < chien)) Console.Out.WriteLine("chat<chaton=" & (chat < chaton)) Console.Out.WriteLine("chat<CHAT=" & (chat < chat2)) Console.Out.WriteLine("chaton like chat*=" & ("chaton" Like "chat*")) End Sub End Module

et le rsultat de son excution :


A<B=True Les bases de VB.NET

18

A<a=True chat<chien=True chat<chaton=True chat<CHAT=False chaton like chat*=True

Soient deux caractres C1 et C2. Il est possible de les comparer avec les oprateurs : <, <=, =, <>, >, >=. Ce sont alors leurs valeurs Unicode des caractres, qui sont des nombres, qui sont compares. Selon l'ordre Unicode, on a les relations suivantes : espace < .. < '0' < '1' < .. < '9' < .. < 'A' < 'B' < .. < 'Z' < .. < 'a' < 'b' < .. <'z' Les chanes de caractres sont compares caractre par caractre. La premire ingalit rencontre entre deux caractres induit une ingalit de mme sens sur les chanes. Avec ces explications, le lecteur est invit tudier les rsultats du programme prcdent.

1.3.5.5

Expressions boolennes
Not, And, Or, Xor, AndAlso, OrElse

Les oprateurs sont les suivants :


Oprations logiques/de bits

Not : et logique, Or : ou logique, Not : ngation, Xor : ou exclusif. op1 AndAlso op2 : si op1 est faux, op2 n'est pas valu et le rsultat est faux. op1 OrElse op2 : si op1 est vrai, op2 n'est pas valu et le rsultat est vrai. La priorit de ces oprateurs entre-eux est la suivante :
NOT logique AND logique OR logique XOR logique

Not And, AndAlso Or, OrElse Xor

Le rsultat d'une expression boolenne est un boolen.

1.3.5.6

Traitement de bits

On retrouve d'une part les mmes oprateurs que les oprateurs boolens avec la mme priorit. On trouve galement deux oprateurs de dplacement : << et >>. Soient i et j deux entiers.
i<<n i>>n i & j i | j ~i i^j

dcale i de n bits sur la gauche. Les bits entrants sont des zros. dcale i de n bits sur la droite. Si i est un entier sign (signed char, int, long) le bit de signe est prserv. fait le ET logique de i et j bit bit. fait le OU logique de i et j bit bit. complmente i 1 fait le OU EXCLUSIF de i et j

Soit le programme suivant :


Module operationsbit Sub main() ' manipulation de bits Dim i As Short = &H123F Dim k As Short = &H7123 Console.Out.WriteLine("i<<4=" & (i << Console.Out.WriteLine("i>>4=" & (i >> Console.Out.WriteLine("k>>4=" & (k >> Console.Out.WriteLine("i and 4=" & (i Console.Out.WriteLine("i or 4 =" & (i Console.Out.WriteLine("not i=" & (Not End Sub End Module

4).ToString("X")) 4).ToString("X")) 4).ToString("X")) And 4).ToString("X")) Or 4).ToString("X")) i).ToString("X"))

Son excution donne les rsultats suivants :


i<<4=23F0 i>>4=123 k>>4=712 i and 4=4 Les bases de VB.NET

19

i or k =123F not i=EDC0

1.3.5.7

Oprateur associ une affectation


^=, *=, /=, \=, +=, -=, &=

Il est possible d'crire a+=b qui signifie a=a+b. La liste des oprateurs pouvant se comniner avec l'opration d'affectation est la suivante :
combinaison d'oprateurs

1.3.5.8
Primaire

Priorit gnrale des oprateurs


Oprateurs Toutes les expressions sans oprateur ^ +, *, / \ Mod +, & <<, >> =, <>, <, >, <=, >=, Like, Is, TypeOf...Is Not And, AndAlso Or, OrElse Xor

Catgorie
lvation la puissance Ngation unaire Multiplication Division par un entier Modulo Addition Concatnation Dplacement Relationnel NOT logique AND logique OR logique XOR logique

Lorsqu'un oprande est plac entre deux oprateurs de mme priorit, l'associativit des oprateurs rgit l'ordre dans lequel les oprations sont effectues. Tous les oprateurs sont associatifs gauche, ce qui signifie que les oprations sont excutes de gauche droite. La priorit et l'associativit peuvent tre contrles l'aide d'expressions entre parenthses.

1.3.5.9

Les conversions de type

Il existe un certain nombre de fonction prdfinies permettant de passer d'un type de donnes un autre. Leur liste est la suivante :
CBool,CByte,CChar,CDate,CDbl,CDec,CInt,CLng,CObj,CShort,CSng,CStr

Ces fonctions acceptent comme argument une expression numrique ou une chane de caractres. Le type du rsultat est indiqu dans le tableau suivant : fonction rsultat
CBool CByte CChar CDate CDbl

Domaine de valeurs du paramtre de la fonction 0 255 ; les fractions sont arrondies. Toute expression String valide ; la valeur peut tre comprise entre 0 et 65 535. Toute reprsentation valide de la date et de l'heure.

Boolean Toute chane ou expression numrique valide. Byte Char Date

Double -1,79769313486231E+308 -4,94065645841247E-324 pour les valeurs ngatives ; 4,94065645841247E-324 1,79769313486231E+308 pour les valeurs positives. Decimal +/-79 228 162 514 264 337 593 543 950 335 pour les nombres sans dcimales. La plage de valeurs des nombres 28 dcimales est +/-7,9228162514264337593543950335. Le plus petit nombre diffrent de zro est 0,0000000000000000000000000001. Integer -2 147 483 648 2 147 483 647 ; les fractions sont arrondies. 20

CDec

CInt

Les bases de VB.NET

CLng CObj

fonction rsultat Domaine de valeurs du paramtre de la fonction Long -9 223 372 036 854 775 808 9 223 372 036 854 775 807 ; les fractions sont arrondies. Object Short Single String Toute expression valide. -32 768 32 767 ; les fractions sont arrondies. -3,402823E+38 -1,401298E-45 pour les valeurs ngatives ; 1,401298E-45 3,402823E+38 pour les valeurs positives. Les valeurs retournes par la fonction Cstr dpendent de l'argument expression.

CShort CSng CStr

Voici un programme exemple :


Module conversion Sub main() Dim var1 As Boolean = CBool("true") Dim var2 As Byte = CByte("100") Dim var3 As Char = CChar("A") Dim var4 As Date = CDate("30 janvier 2004") Dim var5 As Double = CDbl("100,45") Dim var6 As Decimal = CDec("1000,67") Dim var7 As Integer = CInt("-30") Dim var8 As Long = CLng("456") Dim var9 As Short = CShort("-14") Dim var10 As Single = CSng("56,78") Console.Out.WriteLine("var1=" & var1) Console.Out.WriteLine("var2=" & var2) Console.Out.WriteLine("var3=" & var3) Console.Out.WriteLine("var4=" & var4) Console.Out.WriteLine("var5=" & var5) Console.Out.WriteLine("var6=" & var6) Console.Out.WriteLine("var7=" & var7) Console.Out.WriteLine("var8=" & var8) Console.Out.WriteLine("var9=" & var9) Console.Out.WriteLine("var10=" & var10) End Sub End Module

et les rsultats de son excution :


var1=True var2=100 var3=A var4=30/01/2004 var5=100,45 var6=1000,67 var7=-30 var8=456 var9=-14 var10=56,78

On peut galement utiliser la fonction CType(expression, type) comme le montre le programme suivant :
Module ctype1 Sub main() Dim var1 As Boolean = CType("true", Boolean) Dim var2 As Byte = CType("100", Byte) Dim var3 As Char = CType("A", Char) Dim var4 As Date = CType("30 janvier 2004", Date) Dim var5 As Double = CType("100,45", Double) Dim var6 As Decimal = CType("1000,67", Decimal) Dim var7 As Integer = CType("-30", Integer) Dim var8 As Long = CType("456", Long) Dim var9 As Short = CType("-14", Short) Dim var10 As Single = CType("56,78", Single) Dim var11 As String = CType("47,89", String) Dim var12 As String = 47.89.ToString Dim var13 As String = "" & 47.89 Console.Out.WriteLine("var1=" & var1) Console.Out.WriteLine("var2=" & var2) Console.Out.WriteLine("var3=" & var3) Console.Out.WriteLine("var4=" & var4) Console.Out.WriteLine("var5=" & var5) Console.Out.WriteLine("var6=" & var6) Console.Out.WriteLine("var7=" & var7) Console.Out.WriteLine("var8=" & var8) Console.Out.WriteLine("var9=" & var9) Les bases de VB.NET

21

Console.Out.WriteLine("var10=" Console.Out.WriteLine("var11=" Console.Out.WriteLine("var12=" Console.Out.WriteLine("var13=" End Sub End Module

& & & &

var10) var11) var12) var13)

qui donne les rsultats suivants :


var1=True var2=100 var3=A var4=30/01/2004 var5=100,45 var6=1000,67 var7=-30 var8=456 var9=-14 var10=56,78 var11=47,89 var12=47,89 var13=47,89

1.4 1.4.1

Les instructions de contrle du droulement du programme Arrt

La mthode Exit dfinie dans la classe Environment permet d'arrter l'excution d'un programme :
Public Shared Sub Exit(ByVal exitCode As Integer )

arrte le processus en cours et rend la valeur exitCode au processus pre. La valeur de exitCode peut tre utilise par celui-ci. Sous DOS, cette variable status est rendue DOS dans la variable systme ERRORLEVEL dont la valeur peut tre teste dans un fichier batch. Sous Unix, c'est la variable $? qui rcupre la valeur de exitCode.
Environment.Exit(0)

arrtera l'excution du programme avec une valeur d'tat 0.

1.4.2

Structure de choix simple

if condition then actions_alors else actions_sinon end if

chaque action est sur une ligne la clause else peut tre absente.

On peut imbriquer les structures de choix comme le montre l'exemple suivant :


' options Option Explicit On Option Strict On ' espaces de noms Imports System Module if1 Sub main() Dim i As Integer = 10 If i > 4 Then Console.Out.WriteLine(i & " est > " & 4) Else If i = 4 Then Console.Out.WriteLine(i & " est = " & 4) Else Console.Out.WriteLine(i & " est < " & 4) End If End If Les bases de VB.NET

22

End Sub End Module

Le rsultat obtenu :
10 est > 4

1.4.3

Structure de cas

La syntaxe est la suivante :


select case expression case liste_valeurs1 actions1 case liste_valeurs2 actions2 ... case else actions_sinon end select

le type de [expression] doit tre l'un des types suivants :

Boolean, Byte, Char, Date, Decimal, Double, Integer, Long, Object, Short, Single et String

la clause [case else] peut tre absente. [liste_valeursi] sont des valeurs possibles de l'expression. [listes_valeursi] reprsente une liste de conditions condition1, condition2, ..., conditionx. Si [expression] vrofie l'une des conditions, les actions derrire la clause [liste_valeursi] sont excutes. Les conditions peuvent prendre la forme suivante : - val1 to val2 : vrai si [expression] appartient au domaine [val1,val2] - val1 : vrai si [expression] est gal val1 - is > val1 : vrai si [expression] > val1. Le mot cl [is] peut tre absent - idem avec les oprateurs =, <, <=, >, >=, <> seules les actions lies la premire condition vrifie sont excutes. Considrons le programme suivant :
' options Option Explicit On Option Strict On ' espaces de noms Imports System Module selectcase1 Sub main() Dim i As Integer = 10 Select Case i Case 1 To 4, 7 To 8 Console.Out.WriteLine("i Case Is > 12 Console.Out.WriteLine("i Case Is < 15 Console.Out.WriteLine("i Case Is < 20 Console.Out.WriteLine("i End Select End Sub End Module

est dans l'intervalle [1,4] ou [7,8]") est > 12") est < 15") est < 20")

Il donne les rsultats suivants :


i est < 15

1.4.4 1.4.4.1

Structure de rptition Nombre de rptitions connu


23

For counter [ As datatype ] = start To end [ Step step ] actions Les bases de VB.NET

Next [ counter ]

Les actions sont effectues pour chacune des valeurs prises par la variable [counter]. Considrons le programme suivant :
' options Option Explicit On Option Strict On ' espaces de noms Imports System Module for1 Sub main() Dim somme As Integer = 0 Dim rsultat As String = "somme(" For i As Integer = 0 To 10 Step 2 somme += i rsultat += " " + i.ToString Next rsultat += ")=" + somme.ToString Console.Out.WriteLine(rsultat) End Sub End Module

Les rsultats :
somme( 0 2 4 6 8 10)=30

Une autre structure d'itration nombre d'itrations connu est la suivante :


For Each element [ As datatype ] In groupe [ actions ] Next [ element ]

groupe est une collection d'objets. La collection d'objets que nous connaissons dj est le tableau datatype est le type des objets de la collection. Pour un tableau, ce serait le type des lments du tableau element est une variable locale la boucle qui va prendre successivement pour valeur, toutes les valeurs de la collection.

Ainsi le code suivant :


' options Option Explicit On Option Strict On ' espaces de noms Imports System Module foreach1 Sub main() Dim amis() As String = {"paul", "hlne", "jacques", "sylvie"} For Each nom As String In amis Console.Out.WriteLine(nom) Next End Sub End Module

afficherait :
paul hlne jacques sylvie

1.4.4.2

Nombre de rptitions inconnu

Il existe de nombreuses structures en VB.NET pour ce cas.


Do { While | Until } condition [ statements ] Loop

On boucle tant que la condition est vrifie (while) ou jusqu' ce que la condition soit vrifie (until). La boucle peut ne jamais tre excute.
Les bases de VB.NET

24

Do

[ statements ] Loop { While | Until } condition

On boucle tant que la condition est vrifie (while) ou jusqu' ce que la condition soit vrifie (until). La boucle est toujours excute au moins une fois.
While condition [ statements ] End While

On boucle tant que la condition est vrifie. La boucle peut ne jamais tre excute. Les boucles suivantes calculent toutes la somme des 10 premiers nombres entiers.
' options Option Explicit On Option Strict On ' espaces de noms Imports System Module boucles1 Sub main() Dim i, somme As Integer i = 0 : somme = 0 Do While i < 11 somme += i i += 1 Loop Console.Out.WriteLine("somme=" i = 0 : somme = 0 Do Until i = 11 somme += i i += 1 Loop Console.Out.WriteLine("somme=" i = 0 : somme = 0 Do somme += i i += 1 Loop Until i = 11 Console.Out.WriteLine("somme=" i = 0 : somme = 0 Do somme += i i += 1 Loop While i < 11 Console.Out.WriteLine("somme=" End Sub End Module somme=55 somme=55 somme=55 somme=55

+ somme.ToString)

+ somme.ToString)

+ somme.ToString)

+ somme.ToString)

1.4.4.3
exit do exit for

Instructions de gestion de boucle


fait sortir d'une boucle do ... loop fait sortir d'une boucle for

1.5

La structure d'un programme VB.NET

Un programme VB.NET n'utilisant pas de classe dfinie par l'utilisateur ni de fonctions autres que la fonction principale Main pourra avoir la structure suivante :
' options Option Explicit On Option Strict On ' espaces de noms Imports espace1 Les bases de VB.NET

25

Imports .... Module nomDuModule Sub main() .... End Sub End Module

La directive [Option Explicit on] force la dclaration des variables. En VB.NET, celle-ci n'est pas obligatoire. Une variable non dclare est alors de type Object. La directive [Option Strict on] interdit toute conversion de types de donnes pouvant entraner une perte de donnes et toute conversion entre les types numriques et les chanes. Il faut alors explicitement utiliser des fonctions de conversion. Le programme importe tous les espaces de noms dont il a besoin. Nous n'avons pas introduit encore cette notion. Nous avons, dans les programmes prcdents, souvent rencontr des instructions du genre :
Console.Out.WriteLine(unechaine)

Nous aurions du crire en fait :


System.Console.Out.WriteLine(unechaine)

o System est l'espace de noms contenant la classe [Console]. En important l'espace de noms [System] avec une instruction Imports, VB.NET l'explorera systmatiquement lorsqu'il rencontrera une classe qu'il ne connat pas. Il rptera la recherche avec tous les espaces de noms dclars jusqu' trouver la classe recherche. On crit alors :
' espaces de noms Imports System .... Console.Out.WriteLine(unechaine)

Un exemple de programme pourrait tre le suivant :


' options Option Explicit On Option Strict On 'espaces de noms Imports System ' module principal Module main1 Sub main() Console.Out.WriteLine("main1") End Sub End Module

Le mme programme peut tre crit de la faon suivante :


' options Option Explicit On Option Strict On 'espaces de noms Imports System ' classe de test Public Class main2 Public Shared Sub main() Console.Out.WriteLine("main2") End Sub End Class

Ici, nous utilisons le concept de classe qui sera introduit au chapitre suivant. Lorsqu'une telle classe contient une procdure statique (shared) appele main, celle-ci est excute. Si nous introduisons cette criture ici, c'est parce que le langage jumeau de VB.NET qu'est C# ne connat que le concept de classe, c.a.d. que tout code excut appartient ncessairement une classe. La notion de classe appartient la programmation objet. L'imposer dans la conception de tout programme est un peu maladroit. On le voit ici dans la version 2 du programme prcdent o on est amen introduire un concept de classe et de mthode statique l o il n'y en a pas besoin. Aussi, par la suite, n'introduirons-nous le concept de classe que lorsqu'il est ncessaire. Dans les autres cas, nous utiliserons la notion de module comme dans la version 1 ci-dessus.

Les bases de VB.NET

26

1.6

Compilation et excution d'un programme VB.NET

La compilation d'un programme VB.NET ne ncessite que le SDK.NET. Prenons le programme suivant :
' options Option Explicit On Option Strict On ' espaces de noms Imports System Module boucles1 Sub main() Dim i, somme As Integer i = 0 : somme = 0 Do While i < 11 somme += i i += 1 Loop Console.Out.WriteLine("somme=" i = 0 : somme = 0 Do Until i = 11 somme += i i += 1 Loop Console.Out.WriteLine("somme=" i = 0 : somme = 0 Do somme += i i += 1 Loop Until i = 11 Console.Out.WriteLine("somme=" i = 0 : somme = 0 Do somme += i i += 1 Loop While i < 11 Console.Out.WriteLine("somme=" End Sub End Module

+ somme.ToString)

+ somme.ToString)

+ somme.ToString)

+ somme.ToString)

Supposons qu'il soit dans un fichier appel [boucles1.vb]. Pour le compiler, nous procdons ainsi :
dos>dir boucles1.vb 11/03/2004 15:55 583 boucles1.vb

dos>vbc boucles1.vb Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573 Copyright (C) Microsoft Corporation 1987-2002. Tous droits rservs. dos>dir boucles1.* 11/03/2004 16:04 11/03/2004 16:04 601 boucles1.vb 3 584 boucles1.exe

Le programme vbc.exe est le compilateur VB.NET. Ici, il tait dans le PATH du DOS :
dos>path PATH=E:\Program Files\Microsoft Visual Studio .NET 2003\Common7\IDE;E:\Program Files\Microsoft Visual Studio .NET 2003\VC7\BIN;E:\Program Files\Microsoft Visual Studio .NET 2003\Common7\Tools;E:\Program Files\Microsoft Visual Studio .NET 2003\Common7\Tools\bin\prerelease;E:\Program Files\Microsoft Visual Studio .NET 2003\Common7\Tools\bin;E:\Program Files\Microsoft Visual Studio .NET 2003\SDK\v1.1\bin;E:\WINNT\Microsoft.NET\Framework\v1.1.4322;e:\winnt\system32;e:\winnt; dos>dir E:\WINNT\Microsoft.NET\Framework\v1.1.4322\vbc.exe 21/02/2003 10:20 737 280 vbc.exe

Le compilateur [vbc] produit un fichier .exe exutable par la machine virtuelle .NET :
dos>boucles1 somme=55 somme=55 somme=55 somme=55

Les bases de VB.NET

27

1.7

L'exemple IMPOTS

On se propose d'crire un programme permettant de calculer l'impt d'un contribuable. On se place dans le cas simplifi d'un contribuable n'ayant que son seul salaire dclarer : on calcule le nombre de parts du salari nbParts=nbEnfants/2 +1 s'il n'est pas mari, nbEnfants/2+2 s'il est mari, o nbEnfants est son nombre d'enfants. s'il a au moins trois enfants, il a une demi-part de plus on calcule son revenu imposable R=0.72*S o S est son salaire annuel on calcule son coefficient familial QF=R/nbParts on calcule son impt I. Considrons le tableau suivant :
12620.0 13190 15640 24740 31810 39970 48360 55790 92970 127860 151250 172040 195000 0 0 0.05 0.1 0.15 0.2 0.25 0.3 0.35 0.4 0.45 0.50 0.55 0.60 0.65 0 631 1290.5 2072.5 3309.5 4900 6898.5 9316.5 12106 16754.5 23147.5 30710 39312 49062

Chaque ligne a 3 champs. Pour calculer l'impt I, on recherche la premire ligne o QF<=champ1. Par exemple, si QF=23000 on trouvera la ligne 24740 0.15 2072.5 L'impt I est alors gal 0.15*R - 2072.5*nbParts. Si QF est tel que la relation QF<=champ1 n'est jamais vrifie, alors ce sont les coefficients de la dernire ligne qui sont utiliss. Ici : 0 0.65 49062 ce qui donne l'impt I=0.65*R - 49062*nbParts. Le programme VB.NET correspondant est le suivant :
' options Option Explicit On Option Strict On ' espaces de noms Imports System Module impots ' ------------ main Sub Main() ' tableaux de donnes ncessaires au calcul de l'impt Dim Limites() As Decimal = {12620D, 13190D, 15640D, 24740D, 31810D, 39970D, 48360D, 55790D, 92970D, 127860D, 151250D, 172040D, 195000D, 0D} Dim CoeffN() As Decimal = {0D, 631D, 1290.5D, 2072.5D, 3309.5D, 4900D, 6898.5D, 9316.5D, 12106D, 16754.5D, 23147.5D, 30710D, 39312D, 49062D} ' on rcupre le statut marital Dim OK As Boolean = False Dim reponse As String = Nothing While Not OK Console.Out.Write("Etes-vous mari(e) (O/N) ? ") reponse = Console.In.ReadLine().Trim().ToLower() If reponse <> "o" And reponse <> "n" Then Console.Error.WriteLine("Rponse incorrecte. Recommencez") Else OK = True End If End While Dim Marie As Boolean = reponse = "o" ' nombre d'enfants OK = False Dim NbEnfants As Integer = 0 While Not OK Console.Out.Write("Nombre d'enfants : ") reponse = Console.In.ReadLine() Try NbEnfants = Integer.Parse(reponse) Les bases de VB.NET

28

If NbEnfants >= 0 Then OK = True Else Console.Error.WriteLine("Rponse incorrecte. Recommencez") End If Catch Console.Error.WriteLine("Rponse incorrecte. Recommencez") End Try End While ' salaire OK = False Dim Salaire As Integer = 0 While Not OK Console.Out.Write("Salaire annuel : ") reponse = Console.In.ReadLine() Try Salaire = Integer.Parse(reponse) If Salaire >= 0 Then OK = True Else Console.Error.WriteLine("Rponse incorrecte. Recommencez") End If Catch Console.Error.WriteLine("Rponse incorrecte. Recommencez") End Try End While ' calcul du nombre de parts Dim NbParts As Decimal If Marie Then NbParts = CDec(NbEnfants) / 2 + 2 Else NbParts = CDec(NbEnfants) / 2 + 1 End If If NbEnfants >= 3 Then NbParts += 0.5D End If ' revenu imposable Dim Revenu As Decimal Revenu = 0.72D * Salaire ' quotient familial Dim QF As Decimal QF = Revenu / NbParts ' recherche de la tranche d'impots correspondant QF Dim i As Integer Dim NbTranches As Integer = Limites.Length Limites((NbTranches - 1)) = QF i = 0 While QF > Limites(i) i += 1 End While ' l'impt Dim impots As Integer = CInt(i * 0.05D * Revenu - CoeffN(i) * NbParts) ' on affiche le rsultat Console.Out.WriteLine(("Impt payer : " & impots)) End Sub End Module

Le programme est compil dans une fentre Dos par :


dos>vbc impots1.vb Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573 dos>dir impots1.exe 24/02/2004 15:42 5 632 impots1.exe

La compilation produit un excutable impots.exe. Il faut noter que impots.exe n'est pas directement excutable par le processeur. Il contient en ralit du code intermdiaire qui n'est excutable que sur une plate-forme .NET. Les rsultats obtenus sont les suivants :
dos>impots1 Etes-vous mari(e) (O/N) ? o Nombre d'enfants : 3 Salaire annuel : 200000 Impt payer : 16400 Les bases de VB.NET

29

dos>impots1 Etes-vous mari(e) (O/N) ? n Nombre d'enfants : 2 Salaire annuel : 200000 Impt payer : 33388 dos>impots1 Etes-vous mari(e) (O/N) ? w Rponse incorrecte. Recommencez Etes-vous mari(e) (O/N) ? q Rponse incorrecte. Recommencez Etes-vous mari(e) (O/N) ? o Nombre d'enfants : q Rponse incorrecte. Recommencez Nombre d'enfants : 2 Salaire annuel : q Rponse incorrecte. Recommencez Salaire annuel : 1 Impt payer : 0

1.8

Arguments du programme principal

La procdure principale Main peut admettre comme paramtre un tableau de chanes :


Sub main(ByVal args() As String)

Le paramtre args est un tableau de chanes de caractres qui reoit les arguments passs sur la ligne de commande lors de l'appel du programme. args.Length est le nombre d'lements du tableau args args(i) est l'lment i du tableau Si on lance le programme P avec la commande : P arg0 arg1 argn et si la procdure Main du programme P est dclare comme suit :
Sub main(ByVal args() As String)

on aura arg(0)="arg0", arg(1)="arg1" Voici un exemple :


' directives Option Strict On Option Explicit On ' espaces de noms Imports System Module arg Sub main(ByVal args() As String) ' nombre d'arguments console.out.writeline("Il y a " & args.length & " arguments") Dim i As Integer For i = 0 To args.Length - 1 Console.Out.WriteLine("argument n " & i & "=" & args(i)) Next End Sub End Module

L'excution donne les rsultats suivants :


dos>arg1 Il y a 3 argument argument argument a b c arguments n 0=a n 1=b n 2=c

1.9

Les numrations

Une numration est un type de donnes dont le domaine de valeurs est un ensemble de cosntantes entires. Considrons un programme qui a grer des mentions un examen. Il y en aurait cinq : Passable,AssezBien,Bien,TrsBien, Excellent. On pourrait alors dfinir une numration pour ces cinq constantes :
Enum mention Les bases de VB.NET

30

Passable AssezBien Bien TrsBien Excellent End Enum

De faon interne, ces cinq constantes sont codes par des entiers conscutifs commenant par 0 pour la premire constante, 1 pour la suivante, etc... Une variable peut tre dclare comme prenant ces valeurs dans l'numration :
' une variable qui prend ses valeurs dans l'numration mention Dim maMention As mention = mention.Passable

On peut comparer une variable aux diffrentes valeurs possibles de l'numration :


' test avec valeur de l'numration If (maMention = mention.Passable) Then Console.Out.WriteLine("Peut mieux faire") End If

On peut obtenir toutes les valeurs de l'numration :


For Each m In mention.GetValues(maMention.GetType) Console.Out.WriteLine(m) Next

De la mme faon que le type simple Integer est quivalent la structure Int32, le type simple Enum est quivalent la structure Enum. Cette classe a une mthode statique GetValues qui permet d'obtenir toutes les valeurs d'un type numr que l'on passe en paramtre. Celui-ci doit tre un objet de type Type qui est une classe d'information sur le type d'une donne. Le type d'une variable v est obtenu par v.GetType(). Donc ici maMention.GetType() donne l'objet Type de l'numration mentions et Enum.GetValues(maMention.GetType()) la liste des valeurs de l'numration mentions. C'est ce que montre le programme suivant :
' directives Option Strict On Option Explicit On ' espaces de noms Imports System Public Module enum2 ' une numration Enum mention Passable AssezBien Bien TrsBien Excellent End Enum ' pg de test Sub Main() ' une variable qui prend ses valeurs dans l'numration mention Dim maMention As mention = mention.Passable ' affichage valeur variable Console.Out.WriteLine("mention=" & maMention) ' test avec valeur de l'numration If (maMention = mention.Passable) Then Console.Out.WriteLine("Peut mieux faire") End If ' liste des mentions littrales For Each m As mention In [Enum].GetValues(maMention.GetType) Console.Out.WriteLine(m.ToString) Next ' liste des mentions entires For Each m As Integer In [Enum].GetValues(maMention.GetType) Console.Out.WriteLine(m) Next End Sub End Module Les bases de VB.NET

31

Les rsultats d'excution sont les suivants :


dos>enum2 mention=0 Peut mieux faire Passable AssezBien Bien TrsBien Excellent 0 1 2 3 4

1.10

La gestion des exceptions

De nombreuses fonctions VB.NET sont susceptibles de gnrer des exceptions, c'est dire des erreurs. Lorsqu'une fonction est susceptible de gnrer une exception, le programmeur devrait la grer dans le but d'obtenir des programmes plus rsistants aux erreurs : il faut toujours viter le "plantage" sauvage d'une application. La gestion d'une exception se fait selon le schma suivant :
try appel de la fonction susceptible de gnrer l'exception catch e as Exception e) traiter l'exception e end try instruction suivante

Si la fonction ne gnre pas d'exception, on passe alors instruction suivante, sinon on passe dans le corps de la clause catch puis instruction suivante. Notons les points suivants : e est un objet driv du type Exception. On peut tre plus prcis en utilisant des types tels que IOException, SystemException, etc : il existe plusieurs types d'exceptions. En crivant catch e as Exception, on indique qu'on veut grer toutes les types d'exceptions. Si le code de la clause try est susceptible de gnrer plusieurs types d'exceptions, on peut vouloir tre plus prcis en grant l'exception avec plusieurs clauses catch :

try appel de la fonction susceptible de gnrer l'exception catch e as IOException traiter l'exception e catch e as SystemException traiter l'exception e end try instruction suivante

On peut ajouter aux clauses try/catch, une clause finally :

try appel de la fonction susceptible de gnrer l'exception catch e as Exception traiter l'exception e finally code excut aprs try ou catch end try instruction suivante

Qu'il y ait exception ou pas, le code de la clause finally sera toujours excut. Dans la clause catch, on peut ne pas vouloir utiliser l'objet Exception disponible. Au lieu d'crire catch e as Exception, on crit alors catch. La classe Exception a une proprit Message qui est un message dtaillant l'erreur qui s'est produite. Ainsi si on veut afficher celui-ci, on crira :

catch e as Exception Console.Error.WriteLine("L'erreur suivante s'est produite : "+e.Message); ... Les bases de VB.NET

32

end try

La classe Exception a une mthode ToString qui rend une chane de caractres indiquant le type de l'exception ainsi que la valeur de la proprit Message. On pourra ainsi crire : catch ex as Exception Console.Error.WriteLine("L'erreur suivante s'est produite : "+ex.ToString) ... end try

L'exemple suivant montre une exception gnre par l'utilisation d'un lment de tableau inexistant :
' options Option Explicit On Option Strict On ' espaces de noms Imports System Module tab1 Sub Main() ' dclaration & initialisation d'un tableau Dim tab() As Integer = {0, 1, 2, 3} Dim i As Integer ' affichage tableau avec un for For i = 0 To tab.Length - 1 Console.Out.WriteLine(("tab[" & i & "]=" & tab(i))) Next i ' affichage tableau avec un for each Dim lmt As Integer For Each lmt In tab Console.Out.WriteLine(lmt) Next lmt ' gnration d'une exception Try tab(100) = 6 Catch e As Exception Console.Error.WriteLine(("L'erreur suivante s'est produite : " & e.Message)) End Try End Sub End Module

L'excution du programme donne les rsultats suivants :


dos>exception1 tab[0]=0 tab[1]=1 tab[2]=2 tab[3]=3 0 1 2 3 L'erreur suivante s'est produite : L'index se trouve en dehors des limites du tableau.

Voici un autre exemple o on gre l'exception provoque par l'affectation d'une chane de caractres un nombre lorsque la chane ne reprsente pas un nombre :
' options Option Strict On Option Explicit On 'imports Imports System Public Module console1 Public Sub Main() ' On demande le nom System.Console.Write("Nom : ") ' lecture rponse Dim nom As String = System.Console.ReadLine() ' on demande l'ge Dim age As Integer Les bases de VB.NET

33

Dim ageOK As Boolean = False Do While Not ageOK ' question Console.Out.Write("ge : ") ' lecture-vrification rponse Try age = Int32.Parse(System.Console.ReadLine()) If age < 0 Then Throw New Exception ageOK = True Catch Console.Error.WriteLine("Age incorrect, recommencez...") End Try Loop ' affichage final Console.Out.WriteLine("Vous vous appelez [" & nom & "] et vous avez [" & age & "] ans") End Sub End Module

Quelques rsultats d'excution :


dos>console1 Nom : dupont ge : 23 Vous vous appelez dupont et vous avez 23 ans dos>console1 Nom : dupont ge : xx Age incorrect, recommencez... ge : 12 Vous vous appelez dupont et vous avez 12 ans

1.11

Passage de paramtres une fonction

Nous nous intressons ici au mode de passage des paramtres d'une fonction. Considrons la fonction :
Sub changeInt(ByVal a As Integer) a = 30 Console.Out.WriteLine(("Paramtre formel a=" & a)) End Sub

Dans la dfinition de la fonction, a est appel un paramtre formel. Il n'est l que pour les besoins de la dfinition de la fonction changeInt. Il aurait tout aussi bien pu s'appeler b. Considrons maintenant une utlisation de cette fonction :
Sub Main() Dim age As Integer = 20 changeInt(age) Console.Out.WriteLine(("Paramtre effectif age=" & age)) End Sub

Ici dans l'instruction changeInt(age), age est le paramtre effectif qui va transmettre sa valeur au paramtre formel a. Nous nous intressons la faon dont un paramtre formel rcupre la valeur du paramtre effectif qui lui correspond.

1.11.1

Passage par valeur

L'exemple suivant nous montre que les paramtres d'une fonction/procdure sont par dfaut passs par valeur : c'est dire que la valeur du paramtre effectif est recopie dans le paramtre formel correspondant. On a deux entits distinctes. Si la fonction modifie le paramtre formel, le paramtre effectif n'est lui en rien modifi.
' options Option Explicit On Option Strict On ' passage de paramtres par valeur une fonction Imports System Module param1 Sub Main() Dim age As Integer = 20 changeInt(age) Console.Out.WriteLine(("Paramtre effectif age=" & age)) Les bases de VB.NET

34

End Sub Sub changeInt(ByVal a As Integer) a = 30 Console.Out.WriteLine(("Paramtre formel a=" & a)) End Sub End Module

Les rsultats obtenus sont les suivants :


Paramtre formel a=30 Paramtre effectif age=20

La valeur 20 du paramtre effectif a t recopie dans le paramtre formel a. Celui-ci a t ensuite modifi. Le paramtre effectif est lui rest inchang. Ce mode de passage convient aux paramtres d'entre d'une fonction.

1.11.2

Passage par rfrence

Dans un passage par rfrence, le paramtre effectif et le paramtre formel sont une seule et mme entit. Si la fonction modifie le paramtre formel, le paramtre effectif est lui aussi modifi. En VB.NET, le paramtre formel doit tre prcd du mot cl ByRef . Voici un exemple :
' options Option Explicit On Option Strict On ' passage de paramtres par valeur une fonction Imports System Module param2 Sub Main() Dim age As Integer = 20 changeInt(age) Console.Out.WriteLine(("Paramtre effectif age=" & age)) End Sub Sub changeInt(ByRef a As Integer) a = 30 Console.Out.WriteLine(("Paramtre formel a=" & a)) End Sub End Module

et les rsultats d'excution :


Paramtre formel a=30 Paramtre effectif age=30

Le paramtre effectif a suivi la modification du paramtre formel. Ce mode de passage convient aux paramtres de sortie d'une fonction.

Les bases de VB.NET

35

2.
2.1 2.1.1 Gnralits

Classes, stuctures, interfaces

L' objet par l'exemple

Nous abordons maintenant, par l'exemple, la programmation objet. Un objet est une entit qui contient des donnes qui dfinissent son tat (on les appelle des proprits) et des fonctions (on les appelle des mthodes). Un objet est cr selon un modle qu'on appelle une classe :
Public Class c1 ' attributs Private p1 As type1 Private p2 As type2 .... ' mthode Public Sub m1(....) ... End Sub ' mthode Public Function m2(...) .... End Function End Class

A partir de la classe C1 prcdente, on peut crer de nombreux objets O1, O2, Tous auront les proprits p1, p2, et les mthodes m3, m4, Mais ils auront des valeurs diffrentes pour leurs proprits pi ayant ainsi chacun un tat qui leur est propre. Par analogie la dclaration
dim i, j as integer

cre deux objets (le terme est incorrect ici) de type (classe) Integer. Leur seule proprit est leur valeur. Si O1 est un objet de type C1, O1.p1 dsigne la proprit p1 de O1 et O1.m1 la mthode m1 de O1. Considrons un premier modle d'objet : la classe personne.

2.1.2

Dfinition de la classe personne

La dfinition de la classe personne sera la suivante :


Public Class personne ' attributs Private prenom As String Private nom As String Private age As Integer ' mthode Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer) Me.prenom = P Me.nom = N Me.age = age End Sub ' mthode Public Sub identifie() Console.Out.WriteLine((prenom & "," & nom & "," & age)) End Sub End Class

Nous avons ici la dfinition d'une classe, donc d'un type de donnes. Lorsqu'on va crer des variables de ce type, on les appellera des objets ou des instances de classes. Une classe est donc un moule partir duquel sont construits des objets. Les membres ou champs d'une classe peuvent tre des donnes (attributs), des mthodes (fonctions), des proprits. Les proprits sont des mthodes particulires servant connatre ou fixer la valeur d'attributs de l'objet. Ces champs peuvent tre accompagns de l'un des trois mots cls suivants :
priv public

Un champ priv (private) n'est accessible que par les seules mthodes internes de la classe Un champ public (public) est accessible par toute fonction dfinie ou non au sein de la classe 36

Classes, Structures, Interfaces

protg

Un champ protg (protected) n'est accessible que par les seules mthodes internes de la classe ou d'un objet driv (voir ultrieurement le concept d'hritage).

En gnral, les donnes d'une classe sont dclares prives alors que ses mthodes et proprits sont dclares publiques. Cela signifie que l'utilisateur d'un objet (le programmeur) : n'aura pas accs directement aux donnes prives de l'objet pourra faire appel aux mthodes publiques de l'objet et notamment celles qui donneront accs ses donnes prives.

La syntaxe de dclaration d'une clase est la suivante :


public class classe private donne ou mthode ou proprit prive public donne ou mthode ou proprit publique protected donne ou mthode ou proprit protge end class

L'ordre de dclaration des attributs private, protected et public est quelconque.

2.1.3

La mthode initialise

Revenons notre classe [personne] dclare comme :


Public Class personne ' attributs Private prenom As String Private nom As String Private age As Integer ' mthode Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer) Me.prenom = P Me.nom = N Me.age = age End Sub ' mthode Public Sub identifie() Console.Out.WriteLine((prenom & "," & nom & "," & age)) End Sub End Class

Quel est le rle de la mthode initialise ? Parce que nom, prenom et age sont des donnes prives de la classe personne, les instructions :
dim p1 as personne p1 p1.prenom="Jean" p1.nom="Dupont" p1.age=30

sont illgales. Il nous faut initialiser un objet de type personne via une mthode publique. C'est le rle de la mthode initialise. On crira :
dim p1 as personne p1.initialise("Jean","Dupont",30)

L'criture p1.initialise est lgale car initialise est d'accs public.

2.1.4

L'oprateur new

La squence d'instructions
dim p1 as personne p1.initialise("Jean","Dupont",30)

est incorrecte. L'instruction


dim p1 as personne

dclare p1 comme une rfrence un objet de type personne. Cet objet n'existe pas encore et donc p1 n'est pas initialis. C'est comme si on crivait :
Classes, Structures, Interfaces

37

dim p1 as personne=nothing

o on indique explicitement avec le mot cl nothing que la variable p1 ne rfrence encore aucun objet. Lorsqu'on crit ensuite
p1.initialise("Jean","Dupont",30)

on fait appel la mthode initialise de l'objet rfrenc par p1. Or cet objet n'existe pas encore et le compilateur signalera l'erreur. Pour que p1 rfrence un objet, il faut crire :
dim p1 as personne=new personne()

Cela a pour effet de crer un objet de type personne non encore initialis : les attributs nom et prenom qui sont des rfrences d'objets de type String auront la valeur nothing, et age la valeur 0. Il y a donc une initialisation par dfaut. Maintenant que p1 rfrence un objet, l'instruction d'initialisation de cet objet
p1.initialise("Jean","Dupont",30)

est valide.

2.1.5

Le mot cl Me

Regardons le code de la mthode initialise :


Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer) Me.prenom = P Me.nom = N Me.age = age End Sub

L'instruction Me.prenom=P signifie que l'attribut prenom de l'objet courant (Me) reoit la valeur P. Le mot cl Me dsigne l'objet courant : celui dans lequel se trouve la mthode excute. Comment le connat-on ? Regardons comment se fait l'initialisation de l'objet rfrenc par p1 dans le programme appelant :
p1.initialise("Jean","Dupont",30)

C'est la mthode initialise de l'objet p1 qui est appele. Lorsque dans cette mthode, on rfrence l'objet Me, on rfrence en fait l'objet p1. La mthode initialise aurait aussi pu tre crite comme suit :
Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer) prenom = P nom = N Me.age = age End Sub

Lorsqu'une mthode d'un objet rfrence un attribut A de cet objet, l'criture Me.A est implicite. On doit l'utiliser explicitement lorsqu'il y a conflit d'identificateurs. C'est le cas de l'instruction :
Me.age=age;

o age dsigne un attribut de l'objet courant ainsi que le paramtre age reu par la mthode. Il faut alors lever l'ambigut en dsignant l'attribut age par Me.age.

2.1.6

Un programme de test

Voici un court programme de test :


Public Class personne ' attributs Private prenom As String Private nom As String Private age As Integer ' mthode Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer) Me.prenom = P Me.nom = N Me.age = age End Sub ' mthode Public Sub identifie() Console.Out.WriteLine((prenom & "," & nom & "," & age)) Classes, Structures, Interfaces

38

End Sub End Class

et les rsultats obtenus :


dos>vbc personne1.vb Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573 dos>personne1 Jean,Dupont,30

2.1.7

Utiliser un fichier de classes compiles (assembly)

On notera que dans l'exemple prcdent il y a deux classes dans notre programme de test : les classes personne et test1. Il y a une autre faon de procder : - on compile la classe personne dans un fichier particulier appel un assemblage (assembly). Ce fichier a une extension .dll - on compile la classe test1 en rfrenant l'assemblage qui contient la classe personne. Les deux fichiers source deviennent les suivants : test.vb
Module test1 Sub Main() Dim p1 As New personne p1.initialise("Jean", "Dupont", 30) p1.identifie() End Sub End module ' options Option Explicit On Option Strict On ' espaces de noms Imports System Public Class personne ' attributs Private prenom As String Private nom As String Private age As Integer ' mthode Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer) Me.prenom = P Me.nom = N Me.age = age End Sub'initialise ' mthode Public Sub identifie() Console.Out.WriteLine((prenom & "," & nom & "," & age)) End Sub'identifie End Class 'personne

personne2.vb

La classe personne est compile par l'instruction suivante :


dos>vbc /t:library personne2.vb Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573 dos>dir 24/02/2004 24/02/2004 24/02/2004 16:50 16:49 16:50 509 personne2.vb 143 test.vb 3 584 personne2.dll

La compilation a produit un fichier personne2.dll. C'est l'option de compilation /t:library qui indique de produire un fichier "assembly". Maintenant compilons le fichier test.vb :
dos>vbc /r:personne2.dll test.vb Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573 Classes, Structures, Interfaces

39

dos>dir 24/02/2004 16:50 24/02/2004 16:49 24/02/2004 16:50 24/02/2004 16:51

509 personne2.vb 143 test.vb 3 584 personne2.dll 3 072 test.exe

L'option de compilation /r:personne2.dll indique au compilateur qu'il trouvera certaines classes dans le fichier personne2.dll. Lorsque dans le fichier source test.vb, il trouvera une rfrence la classe personne classe non dclare dans le source test.vb, il cherchera la classe personne dans les fichiers .dll rfrencs par l'option /r. Il trouvera ici la classe personne dans l'assemblage personne2.dll. On aurait pu mettre dans cet assemblage d'autres classes. Pour utiliser lors de la compilation plusieurs fichiers de classes compiles, on crira :
vbc /r:fic1.dll /r:fic2.dll ... fichierSource.vb

L'excution du programme test1.exe donne les rsultats suivants :


dos>test Jean,Dupont,30

2.1.8

Une autre mthode initialise

Considrons toujours la classe personne et rajoutons-lui la mthode suivante :


' mthode Public Sub initialise(ByVal P As personne) prenom = P.prenom nom = P.nom Me.age = P.age End Sub

On a maintenant deux mthodes portant le nom initialise : c'est lgal tant qu'elles admettent des paramtres diffrents. C'est le cas ici. Le paramtre est maintenant une rfrence P une personne. Les attributs de la personne P sont alors affects l'objet courant (Me). On remarquera que la mthode initialise a un accs direct aux attributs de l'objet P bien que ceux-ci soient de type private. C'est toujours vrai : un objet O1 d'une classe C a toujours accs aux attributs des objets de la mme classe C. Voici un test de la nouvelle classe personne, celle-ci ayant t compile dans personne.dll comme il a t expliqu prcdemment :
' options Option Explicit On Option Strict On ' espaces de noms Imports System Module test1 Sub Main() Dim p1 As New personne p1.initialise("Jean", "Dupont", 30) Console.Out.Write("p1=") p1.identifie() Dim p2 As New personne p2.initialise(p1) Console.Out.Write("p2=") p2.identifie() End Sub End Module

et ses rsultats :
p1=Jean,Dupont,30 p2=Jean,Dupont,30

2.1.9

Constructeurs de la classe personne

Un constructeur est une procdure qui porte le nom New et qui est appele lors de la cration de l'objet. On s'en sert gnralement pour l'initialiser. Si une classe a un constructeur acceptant n arguments argi, la dclaration et l'initialisation d'un objet de cette classe pourra se faire sous la forme : ou
dim objet as classe =new classe(arg1,arg2, ... argn) dim objet as classe objet=new classe(arg1,arg2, ... argn)

Classes, Structures, Interfaces

40

Lorsqu'une classe a un ou plusieurs constructeurs, l'un de ces constructeurs doit tre obligatoirement utilis pour crer un objet de cette classe. Si une classe C n'a aucun constructeur, elle en a un par dfaut qui est le constructeur sans paramtres : public New(). Les attributs de l'objet sont alors initialiss avec des valeurs par dfaut. C'est ce qui s'est pass dans les programmes prcdents, o on avait crit :
dim p1 as personne p1=new personne

Crons deux constructeurs notre classe personne :


' options Option Explicit On Option Strict On ' espaces de noms Imports System ' la classe personne Public Class personne ' attributs Private prenom As String Private nom As String Private age As Integer ' constructeurs Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer) initialise(P, N, age) End Sub Public Sub New(ByVal P As personne) initialise(P) End Sub ' mthodes d'initialisation de l'objet Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer) Me.prenom = P Me.nom = N Me.age = age End Sub Public Sub initialise(ByVal P As personne) prenom = P.prenom nom = P.nom Me.age = P.age End Sub ' mthode Public Sub identifie() Console.Out.WriteLine((prenom & "," & nom & "," & age)) End Sub End Class

Nos deux constructeurs se contentent de faire appel aux mthodes initialise correspondantes. On rappelle que lorsque dans un constructeur, on trouve la notation initialise(P) par exemple, le compilateur traduit par Me.initialise(P). Dans le constructeur, la mthode initialise est donc appele pour travailler sur l'objet rfrenc par Me, c'est dire l'objet courant, celui qui est en cours de construction. Voici un court programme de test :
' options Option Explicit On Option Strict On ' espaces de noms Imports System ' pg de test Module test Sub Main() Dim p1 As New personne("Jean", "Dupont", 30) Console.Out.Write("p1=") p1.identifie() Dim p2 As New personne(p1) Console.Out.Write("p2=") p2.identifie() End Sub End Module

et les rsultats obtenus :


Classes, Structures, Interfaces

41

p1=Jean,Dupont,30 p2=Jean,Dupont,30

2.1.10

Les rfrences d'objets

Nous utilisons toujours la mme classe personne. Le programme de test devient le suivant :
' options Option Explicit On Option Strict On ' espaces de noms Imports System ' pg de test Module test Sub Main() ' p1 Dim p1 As New personne("Jean", "Dupont", 30) Console.Out.Write("p1=") p1.identifie() ' p2 rfrence le mme objet que p1 Dim p2 As personne = p1 Console.Out.Write("p2=") p2.identifie() ' p3 rfrence un objet qui sera une copie de l'objet rfrenc par p1 Dim p3 As New personne(p1) Console.Out.Write("p3=") p3.identifie() ' on change l'tat de l'objet rfrenc par p1 p1.initialise("Micheline", "Benot", 67) Console.Out.Write("p1=") p1.identifie() ' comme p2=p1, l'objet rfrenc par p2 a du changer d'tat Console.Out.Write("p2=") p2.identifie() ' comme p3 ne rfrence pas le mme objet que p1, l'objet rfrenc par p3 n'a pas du changer Console.Out.Write("p3=") p3.identifie() End Sub End Module

Les rsultats obtenus sont les suivants :


p1=Jean,Dupont,30 p2=Jean,Dupont,30 p3=Jean,Dupont,30 p1=Micheline,Benot,67 p2=Micheline,Benot,67 p3=Jean,Dupont,30

Lorsqu'on dclare la variable p1 par


dim p1 as personne=new personne("Jean","Dupont",30)

p1 rfrence l'objet personne("Jean","Dupont",30) mais n'est pas l'objet lui-mme. En C, on dirait que c'est un pointeur, c.a.d. l'adresse de l'objet cr. Si on crit ensuite :
p1=nothing

Ce n'est pas l'objet personne("Jean","Dupont",30) qui est modifi, c'est la rfrence p1 qui change de valeur. L'objet personne("Jean","Dupont",30) sera "perdu" s'il n'est rfrenc par aucune autre variable. Lorsqu'on crit :
dim p2 as personne=p1

on initialise le pointeur p2 : il "pointe" sur le mme objet (il dsigne le mme objet) que le pointeur p1. Ainsi si on modifie l'objet "point" (ou rfrenc) par p1, on modifie celui rfrenc par p2.
Classes, Structures, Interfaces

42

Lorsqu'on crit :
dim p3 as personne =new personne(p1);

il y a cration d'un nouvel objet, copie de l'objet rfrenc par p1. Ce nouvel objet sera rfrenc par p3. Si on modifie l'objet "point" (ou rfrenc) par p1, on ne modifie en rien celui rfrenc par p3. C'est ce que montrent les rsultats obtenus.

2.1.11

Les objets temporaires

Dans une expression, on peut faire appel explicitement au constructeur d'un objet : celui-ci est construit, mais nous n'y avons pas accs (pour le modifier par exemple). Cet objet temporaire est construit pour les besoins d'valuation de l'expression puis abandonn. L'espace mmoire qu'il occupait sera automatiquement rcupr ultrieurement par un programme appel "ramassemiettes" dont le rle est de rcuprer l'espace mmoire occup par des objets qui ne sont plus rfrencs par des donnes du programme. Considrons le nouveau programme de test suivant :
' options Option Strict On Option Explicit On ' espaces de noms Imports System ' pg de test Module test Sub Main() Dim p As New personne(New personne("Jean", "Dupont", 30)) p.identifie() End Sub End Module

et modifions les constructeurs de la classe personne afin qu'ils affichent un message :


' constructeurs Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer) Console.Out.WriteLine("Constructeur personne(String, String, integer)") initialise(P, N, age) End Sub Public Sub New(ByVal P As personne) Console.Out.WriteLine("Constructeur personne(personne)") initialise(P) End Sub

Nous obtenons les rsultats suivants :


dos>test Constructeur personne(String, String, integer) Constructeur personne(personne) Jean,Dupont,30

montrant la construction successive des deux objets temporaires.

2.1.12

Mthodes de lecture et d'criture des attributs privs

Nous rajoutons la classe personne les mthodes ncessaires pour lire ou modifier l'tat des attributs des objets :
Imports System Public Class personne ' attributs Private prenom As [String] Private nom As [String] Private age As Integer ' constructeurs Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer) Me.prenom = P Me.nom = N Me.age = age End Sub Public Sub New(ByVal P As personne) Me.prenom = P.prenom Classes, Structures, Interfaces

43

Me.nom = P.nom Me.age = P.age End Sub ' identifie Public Sub identifie() Console.Out.WriteLine((prenom + "," + nom + "," + age)) End Sub ' accesseurs Public Function getPrenom() As [String] Return prenom End Function Public Function getNom() As [String] Return nom End Function Public Function getAge() As Integer Return age End Function 'modifieurs Public Sub setPrenom(ByVal P As [String]) Me.prenom = P End Sub Public Sub setNom(ByVal N As [String]) Me.nom = N End Sub Public Sub setAge(ByVal age As Integer) Me.age = age End Sub End Class

Nous testons la nouvelle classe avec le programme suivant :


' options Option Strict On Option Explicit On ' espaces de noms Imports System ' pg de test Public Module test Sub Main() Dim P As New personne("Jean", "Michelin", 34) Console.Out.WriteLine(("P=(" & P.getPrenom() & "," & P.getNom() & "," & P.getAge() & ")")) P.setAge(56) Console.Out.WriteLine(("P=(" & P.getPrenom() & "," & P.getNom() & "," & P.getAge() & ")")) End Sub End Module

et nous obtenons les rsultats suivants :


P=(Jean,Michelin,34) P=(Jean,Michelin,56)

2.1.13

Les proprits

Il existe une autre faon d'avoir accs aux attributs d'une classe c'est de crer des proprits. Celles-ci nous permettent de manipuler des attributs privs comme s'ils taient publics. Considrons la classe personne suivante o les accesseurs et modifieurs prcdents ont t remplacs par des proprits en lecture et criture :
' options Option Explicit On Option Strict On ' espaces de noms Imports System ' classe personne Public Class personne ' attributs Private _prenom As [String] Classes, Structures, Interfaces

44

Private _nom As [String] Private _age As Integer ' constructeurs Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer) Me._prenom = P Me._nom = N Me._age = age End Sub Public Sub New(ByVal P As personne) Me._prenom = P._prenom Me._nom = P._nom Me._age = P._age End Sub ' identifie Public Sub identifie() Console.Out.WriteLine((_prenom & "," & _nom & "," & _age)) End Sub ' proprits Public Property prenom() As String Get Return _prenom End Get Set(ByVal Value As String) _prenom = Value End Set End Property Public Property nom() As String Get Return _nom End Get Set(ByVal Value As String) _nom = Value End Set End Property Public Property age() As Integer Get Return _age End Get Set(ByVal Value As Integer) ' age valide ? If Value >= 0 Then _age = Value Else Throw New Exception("ge (" & Value & ") invalide") End If End Set End Property End Class

Une proprit Property permet de lire (get) ou de fixer (set) la valeur d'un attribut. Dans notre exemple, nous avons prfix les noms des attributs du signe _ afin que les proprits portent le nom des attributs primitifs. En effet, une proprit ne peut porter le mme nom que l'attribut qu'elle gre car alors il y a un conflit de noms dans la classe. Nous avons donc appel nos attributs _prenom, _nom, _age et modifi les constructeurs et mthodes en consquence. Nous avons ensuite cr trois proprits nom, prenom et age. Une proprit est dclare comme suit :
Public Property nom() As Type Get ... End Get Set(ByVal Value As Type) ... End Set End Property

o Type doit tre le type de l'attribut gr par la proprit. Elle peut avoir deux mthodes appeles get et set. La mthode get est habituellement charge de rendre la valeur de l'attribut qu'elle gre (elle pourrait rendre autre chose, rien ne l'empche). La mthode set reoit un paramtre appel value qu'elle affecte normalement l'attribut qu'elle gre. Elle peut en profiter pour faire des vrifications sur la validit de la valeur reue et ventuellement lancer un exception si la valeur se rvle invalide. C'est ce qui est fait ici pour l'ge.
Classes, Structures, Interfaces

45

Comment ces mthodes get et set sont-elles appeles ? Considrons le programme de test suivant :
' options ' options Option Explicit On Option Strict On ' espaces de noms Imports System ' pg de test Module test Sub Main() Dim P As New personne("Jean", "Michelin", 34) Console.Out.WriteLine(("P=(" & P.prenom & "," & P.nom & "," & P.age & ")")) P.age = 56 Console.Out.WriteLine(("P=(" & P.prenom & "," & P.nom & "," & P.age & ")")) Try P.age = -4 Catch ex As Exception Console.Error.WriteLine(ex.Message) End Try End Sub End Module

Dans l'instruction on cherche avoir les valeurs des proprits prenom, nom et age de la personne P. C'est la mthode get de ces proprits qui est alors appele et qui rend la valeur de l'attribut qu'elles grent. Dans l'instruction on veut fixer la valeur de la proprit age. C'est alors la mthode set de cette proprit qui est alors appele. Elle recevra 56 dans son paramtre value. Une proprit P d'une classe C qui ne dfinirait que la mthode get est dite en lecture seule. Si c est un objet de classe C, l'opration c.P=valeur sera alors refuse par le compilateur. L'excution du programme de test prcdent donne les rsultats suivants :
P=(Jean,Michelin,34) P=(Jean,Michelin,56) ge (-4) invalide P.age = 56 Console.Out.WriteLine(("P=(" & P.prenom & "," & P.nom & "," & P.age & ")"))

Les proprits nous permettent donc de manipuler des attributs privs comme s'ils taient publics.

2.1.14

Les mthodes et attributs de classe

Supposons qu'on veuille compter le nombre d'objets [personne] cres dans une application. On peut soi-mme grer un compteur mais on risque d'oublier les objets temporaires qui sont crs ici ou l. Il semblerait plus sr d'inclure dans les constructeurs de la classe [personne], une instruction incrmentant un compteur. Le problme est de passer une rfrence de ce compteur afin que le constructeur puisse l'incrmenter : il faut leur passer un nouveau paramtre. On peut aussi inclure le compteur dans la dfinition de la classe. Comme c'est un attribut de la classe elle-mme et non d'un objet particulier de cette classe, on le dclare diffremment avec le mot cl Shared :
Private Shared _nbPersonnes As Long = 0

Pour le rfrencer, on crit personne._nbPersonnes pour montrer que c'est un attribut de la classe personne elle-mme. Ici, nous avons cr un attribut priv auquel on n'aura pas accs directement en-dehors de la classe. On cre donc une proprit publique pour donner accs l'attribut de classe nbPersonnes. Pour rendre la valeur de nbPersonnes la mthode get de cette proprit n'a pas besoin d'un objet personne particulier : en effet _nbPersonnes n'est pas l'attribut d'un objet particulier, il est l'attribut de toute une classe. Aussi a-t-on besoin d'une proprit dclare elle-aussi Shared :
Public Shared ReadOnly Property nbPersonnes() As Long Get Return _nbPersonnes End Get End Property

qui de l'extrieur sera appele avec la syntaxe personne.nbPersonnes. La proprit est dclare en lecture seule (ReadOnly) car elle ne propose pas de mthode set. Voici un exemple. La classe personne devient la suivante :
Classes, Structures, Interfaces

46

Option Explicit On Option Strict On ' espaces de noms Imports System ' classe Public Class personne ' attributs de classe Private Shared _nbPersonnes As Long = 0 ' attributs d'instance Private _prenom As [String] Private _nom As [String] Private _age As Integer ' constructeurs Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer) ' une personne de plus _nbPersonnes += 1 Me._prenom = P Me._nom = N Me._age = age End Sub Public Sub New(ByVal P As personne) ' une personne de plus _nbPersonnes += 1 Me._prenom = P._prenom Me._nom = P._nom Me._age = P._age End Sub ' identifie Public Sub identifie() Console.Out.WriteLine((_prenom & "," & _nom & "," & _age)) End Sub ' proprit de classe Public Shared ReadOnly Property nbPersonnes() As Long Get Return _nbPersonnes End Get End Property ' proprits d'instance Public Property prenom() As String Get Return _prenom End Get Set(ByVal Value As String) _prenom = Value End Set End Property Public Property nom() As String Get Return _nom End Get Set(ByVal Value As String) _nom = Value End Set End Property Public Property age() As Integer Get Return _age End Get Set(ByVal Value As Integer) ' age valide ? If Value >= 0 Then _age = Value Else Throw New Exception("ge (" & Value & ") invalide") End If End Set End Property End Class

Avec le programme suivant :


Classes, Structures, Interfaces

47

' options Option Explicit On Option Strict On ' espaces de noms Imports System ' pg de test Module test Sub Main() Dim p1 As New personne("Jean", "Dupont", 30) Dim p2 As New personne(p1) Console.Out.WriteLine(("Nombre de personnes cres : " & personne.nbPersonnes)) End Sub End Module

on obtient les rsultats suivants :


Nombre de personnes cres : 2

2.1.15

Passage d'un objet une fonction

Nous avons dj dit que par dfaut VB.NET passait les paramtres effectifs d'une fonction par valeur : les valeurs des paramtres effectifs sont recopies dans les paramtres formels. Dans le cas d'un objet, il ne faut pas se laisser tromper par l'abus de langage qui est fait systmatiquement en parlant d'objet au lieu de rfrence d'objet. Un objet n'est manipul que via une rfrence (un pointeur) sur lui. Ce qui est donc transmis une fonction, n'est pas l'objet lui-mme mais une rfrence sur cet objet. C'est donc la valeur de la rfrence et non la valeur de l'objet lui-mme qui est duplique dans le paramtre formel : il n'y a pas construction d'un nouvel objet. Si une rfrence d'objet R1 est transmise une fonction, elle sera recopie dans le paramtre formel correspondant R2. Aussi les rfrences R2 et R1 dsignent-elles le mme objet. Si la fonction modifie l'objet point par R2, elle modifie videmment celui rfrenc par R1 puisque c'est le mme. R1 Recopie R2 objet

C'est ce que montre l'exemple suivant :


' options Option Explicit On Option Strict On ' espaces de noms Imports System ' pg de test Module test Sub Main() ' une personne p1 Dim p1 As New personne("Jean", "Dupont", 30) ' affichage p1 Console.Out.Write("Paramtre effectif avant modification : ") p1.identifie() ' modification p1 modifie(p1) ' affichage p1 Console.Out.Write("Paramtre effectif aprs modification : ") p1.identifie() End Sub Sub modifie(ByVal P As personne) ' affichage personne P Console.Out.Write("Paramtre formel avant modification : ") P.identifie() ' modification P P.prenom = "Sylvie" P.nom = "Vartan" P.age = 52 Classes, Structures, Interfaces

48

' affichage P Console.Out.Write("Paramtre formel aprs modification : ") P.identifie() End Sub End Module

Les rsultats obtenus sont les suivants :


Paramtre Paramtre Paramtre Paramtre effectif avant modification formel avant modification : formel aprs modification : effectif aprs modification : Jean,Dupont,30 Jean,Dupont,30 Sylvie,Vartan,52 : Sylvie,Vartan,52

On voit qu'il n'y a construction que d'un objet : celui de la personne p1 de la procdure Main et que l'objet a bien t modifi par la fonction modifie.

2.1.16

Un tableau de personnes

Un objet est une donne comme une autre et ce titre plusieurs objets peuvent tre rassembls dans un tableau :
' options Option Explicit On Option Strict On ' espaces de noms Imports System ' pg de test Module test Sub Main() ' un tableau de personnes Dim amis(2) As personne amis(0) = New personne("Jean", "Dupont", 30) amis(1) = New personne("Sylvie", "Vartan", 52) amis(2) = New personne("Neil", "Armstrong", 66) ' affichage Console.Out.WriteLine("----------------") Dim i As Integer For i = 0 To amis.Length - 1 amis(i).identifie() Next i End Sub End Module

L'instruction Dim amis(2) As personne cre un tableau de 3 lments de type personne. Ces 3 lments sont initialiss ici avec la valeur nothing, c.a.d. qu'ils ne rfrencent aucun objet. De nouveau, par abus de langage, on parle de tableau d'objets alors que ce n'est qu'un tableau de rfrences d'objets. La cration du tableau d'objets, qui est un objet lui-mme ne cre aucun objet du type de ses lments : il faut le faire ensuite. On obtient les rsultats suivants :
Jean,Dupont,30 Sylvie,Vartan,52 Neil,Armstrong,66

2.2 2.2.1

L'hritage par l'exemple Gnralits

Nous abordons ici la notion d'hritage. Le but de l'hritage est de "personnaliser" une classe existante pour qu'elle satisfasse nos besoins. Supposons qu'on veuille crer une classe enseignant : un enseignant est une personne particulire. Il a des attributs qu'une autre personne n'aura pas : la matire qu'il enseigne par exemple. Mais il a aussi les attributs de toute personne : prnom, nom et ge. Un enseignant fait donc pleinement partie de la classe personne mais a des attributs supplmentaires. Plutt que d'crire une classe enseignant partir de rien, on prfrerait reprendre l'acquis de la classe personne qu'on adapterait au caractre particulier des enseignants. C'est le concept d'hritage qui nous permet cela. Pour exprimer que la classe enseignant hrite des proprits de la classe personne, on crira :
Public Class enseignant Inherits personne

On notera la syntaxe particulire sur deux lignes. La classe personne est appele la classe parent (ou mre) et la classe enseignant la classe drive (ou fille). Un objet enseignant a toutes les qualits d'un objet personne : il a les mmes attributs et les mmes mthodes.
Classes, Structures, Interfaces

49

Ces attributs et mthodes de la classe parent ne sont pas rptes dans la dfinition de la classe fille : on se contente d'indiquer les attributs et mthodes rajouts par la classe fille. Nous supposons que la classe personne est dfinie comme suit :
' options ' options Option Strict On Option Explicit On ' espaces de noms Imports System ' classe personne Public Class personne ' attributs de classe Private Shared _nbPersonnes As Long = 0 ' attributs d'instance Private _prenom As [String] Private _nom As [String] Private _age As Integer ' constructeurs Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer) ' une personne de plus _nbPersonnes += 1 ' construction Me._prenom = P Me._nom = N Me._age = age ' suivi Console.Out.WriteLine("Construction personne(string, string, int)") End Sub Public Sub New(ByVal P As personne) ' une personne de plus _nbPersonnes += 1 ' construction Me._prenom = P._prenom Me._nom = P._nom Me._age = P._age ' suivi Console.Out.WriteLine("Construction personne(string, string, int)") End Sub ' proprit de classe Public Shared ReadOnly Property nbPersonnes() As Long Get Return _nbPersonnes End Get End Property ' proprits d'instance Public Property prenom() As String Get Return _prenom End Get Set(ByVal Value As String) _prenom = Value End Set End Property Public Property nom() As String Get Return _nom End Get Set(ByVal Value As String) _nom = Value End Set End Property Public Property age() As Integer Get Return _age End Get Set(ByVal Value As Integer) ' age valide ? If Value >= 0 Then _age = Value Classes, Structures, Interfaces

50

Else Throw New Exception("ge (" & Value & ") invalide") End If End Set End Property Public ReadOnly Property identite() As String Get Return "personne(" & _prenom & "," & _nom & "," & age & ")" End Get End Property End Class

La mthode identifie a t remplace par la proprit identit en lecture seule et qui identifie la personne. Nous crons une classe enseignant hritant de la classe personne :
' options Option Strict On Option Explicit On ' espaces de noms Imports System Public Class enseignant Inherits personne ' attributs Private _section As Integer ' constructeur Public Sub New(ByVal P As String, ByVal N As String, ByVal age As Integer, ByVal section As Integer) MyBase.New(P, N, age) Me._section = section ' suivi Console.Out.WriteLine("Construction enseignant(string,string,int,int)") End Sub ' proprit section Public Property section() As Integer Get Return _section End Get Set(ByVal Value As Integer) _section = Value End Set End Property End Class

La classe enseignant rajoute aux mthodes et attributs de la classe personne : un attribut section qui est le n de section auquel appartient l'enseignant dans le corps des enseignants (une section par discipline en gros) un nouveau constructeur permettant d'initialiser tous les attributs d'un enseignant

La dclaration
Public Class enseignant Inherits personne

indique que la classe enseignant drive de la classe personne.

2.2.2

Construction d'un objet enseignant

Le constructeur de la classe enseignant est le suivant :


' constructeur Public Sub New(ByVal P As String, ByVal N As String, ByVal age As Integer, ByVal section As Integer) MyBase.New(P, N, age) Me._section = section ' suivi Console.Out.WriteLine("Construction enseignant(string,string,int,int)") End Sub

La dclaration
Public Sub New(ByVal P As String, ByVal N As String, ByVal age As Integer, ByVal section As Integer) Classes, Structures, Interfaces

51

dclare que le constructeur reoit quatre paramtres P, N, age, section. Elle doit en passer trois (P,N,age) sa classe de base, ici la classe personne. On sait que cette classe a un constructeur personne(string, string, int) qui va permettre de construire une personne avec les paramtres passss (P,N,age). La classe [enseignant] passe les paramtres (P,N,age) sa classe de base de la faon suivante :
MyBase.New(P, N, age)

Une fois la construction de la classe de base termine, la construction de l'objet enseignant se poursuit par l'excution du corps du constructeur :
Me._section = section

En rsum, le constructeur d'une classe drive : passe sa classe de base les paramtres dont elle a besoin pour se construire utilise les autres paramtres pour initialiser les attributs qui lui sont propres On aurait pu prfrer crire :
Public Sub New(ByVal P As String, ByVal N As String, ByVal age As Integer, ByVal section As Integer) Me._prenom = P Me._nom = N Me._age = age Me._section = section ' suivi Console.Out.WriteLine("Construction enseignant(string,string,int,int)") End Sub

C'est impossible. La classe personne a dclar privs (private) ses trois champs _prenom, _nom et _age. Seuls des objets de la mme classe ont un accs direct ces champs. Tous les autres objets, y compris des objets fils comme ici, doivent passer par des mthodes publiques pour y avoir accs. Cela aurait t diffrent si la classe personne avait dclar protgs (protected) les trois champs : elle autorisait alors des classes drives avoir un accs direct aux trois champs. Dans notre exemple, utiliser le constructeur de la classe parent tait donc la bonne solution et c'est la mthode habituelle : lors de la construction d'un objet fils, on appelle d'abord le constructeur de l'objet parent puis on complte les initialisations propres cette fois l'objet fils (section dans notre exemple). Compilons les classes personne et enseignant dans des assemblages :
dos>vbc /t:library personne.vb Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573 dos>vbc /r:personne.dll /t:library enseignant.vb Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573 dos>dir 25/02/2004 25/02/2004 25/02/2004 25/02/2004 25/02/2004 10:08 10:11 10:12 10:16 10:16 1 828 675 223 4 096 3 584 personne.vb enseignant.vb test.vb personne.dll enseignant.dll

On remarquera que pour compiler la classe fille enseignant, il a fallu rfrencer le fichier personne.dll qui contient la classe personne. Tentons un premier programme de test :
' options Option Strict On Option Explicit On ' espaces de noms Imports System Module test Sub Main() Console.Out.WriteLine(New enseignant("Jean", "Dupont", 30, 27).identite) End Sub End Module

Ce programme ce contente de crer un objet enseignant (new) et de l'identifier. La classe enseignant n'a pas de mthode identit mais sa classe parent en a une qui de plus est publique : elle devient par hritage une mthode publique de la classe enseignant. Les rsultats obtenus sont les suivants :
dos>vbc /r:personne.dll /r:enseignant.dll test.vb Classes, Structures, Interfaces

52

Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573 dos>test Construction personne(string, string, int) Construction enseignant(string,string,int,int) personne(Jean,Dupont,30)

On voit que : un objet personne a t construit avant l'objet enseignant l'identit obtenue est celle de l'objet personne

2.2.3

Surcharge d'une mthode ou d'une proprit

Dans l'exemple prcdent, nous avons eu l'identit de la partie personne de l'enseignant mais il manque certaines informations propres la classe enseignant (la section). On est donc amen crire une proprit permettant d'identifier l'enseignant :
' options Option Strict On Option Explicit On ' espaces de noms Imports System Public Class enseignant Inherits personne ' attributs Private _section As Integer ' constructeur Public Sub New(ByVal P As String, ByVal N As String, ByVal age As Integer, ByVal section As Integer) MyBase.New(P, N, age) Me._section = section ' suivi Console.Out.WriteLine("Construction enseignant(string,string,int,int)") End Sub ' proprit section Public Property section() As Integer Get Return _section End Get Set(ByVal Value As Integer) _section = Value End Set End Property ' surcharge proprit identit Public Shadows ReadOnly Property identite() As String Get Return "enseignant(" & MyBase.identite & "," & _section & ")" End Get End Property End Class

La mthode identite de la classe enseignant s'appuie sur la mthode identite de sa classe mre (MyBase.identite) pour afficher sa partie "personne" puis complte avec le champ _section qui est propre la classe enseignant. Notons la dclaration de la proprit identite :
Public Shadows ReadOnly Property identite() As String

qui indique que la proprit identite "cache" la mthode de mme nom qui pourrait exister dans la classe parent. Soit un objet enseignant E. Cet objet contient en son sein un objet personne : enseignant E personne identite identite

Classes, Structures, Interfaces

53

La proprit identit est dfinie la fois dans la classe enseignant et sa classe mre personne. Dans la classe fille enseignant, la proprit identite doit tre prcde du mot cl shadows pour indiquer qu'on redfinit une nouvelle proprit identite pour la classe enseignant.
Public Shadows ReadOnly Property identite() As String

La classe enseignant dispose maintenant de deux proprits identite : celle hrite de la classe parent personne la sienne propre Si E est un ojet enseignant, E.identite dsigne la mthode identite de la classe enseignant. On dit que la proprit identite de la classe mre est "redfinie" par la proprit identite de la classe fille. De faon gnrale, si O est un objet et M une mthode, pour excuter la mthode O.M, le systme cherche une mthode M dans l'ordre suivant : dans la classe de l'objet O dans sa classe mre s'il en a une dans la classe mre de sa classe mre si elle existe etc L'hritage permet donc de redfinir dans la classe fille des mthodes/proprits de mme nom dans la classe mre. C'est ce qui permet d'adapter la classe fille ses propres besoins. Associe au polymorphisme que nous allons voir un peu plus loin, la surcharge de mthodes/proprits est le principal intrt de l'hritage. Considrons le mme exemple que prcdemment :
' options Option Strict On Option Explicit On ' espaces de noms Imports System Module test Sub Main() Console.Out.WriteLine(New enseignant("Jean", "Dupont", 30, 27).identite) End Sub End Module

Les rsultats obtenus sont cette fois les suivants :


Construction personne(string, string, int) Construction enseignant(string,string,int,int) enseignant(personne(Jean,Dupont,30),27)

2.2.4

Le polymorphisme

Considrons une ligne de classes : C0 C1 C2 Cn o Ci Cj indique que la classe Cj est drive de la classe Ci. Cela entrane que la classe Cj a toutes les caractristiques de la classe Ci plus d'autres. Soient des objets Oi de type Ci. Il est lgal d'crire : Oi=Oj avec j>i En effet, par hritage, la classe Cj a toutes les caractristiques de la classe C i plus d'autres. Donc un objet Oj de type Cj contient en lui un objet de type Ci. L'opration Oi=Oj fait que Oi est une rfrence l'objet de type Ci contenu dans l'objet Oj. Le fait qu'une variable Oi de classe Ci puisse en fait rfrencer non seulement un objet de la classe Ci mais en fait tout objet driv de la classe Ci est appel polymorphisme : la facult pour une variable de rfrencer diffrents types d'objets. Prenons un exemple et considrons la fonction suivante indpendante de toute classe :
Sub affiche(ByVal p As personne)

On pourra aussi bien crire


dim p as personne ... affiche(p);

que
dim e as enseignant ... affiche(e); Classes, Structures, Interfaces

54

Dans ce dernier cas, le paramtre formel de type personne de la fonction affiche va recevoir une valeur de type enseignant. Comme le type enseignant drive du type personne, c'est lgal.

2.2.5

Redfinition et polymorphisme

Compltons notre procdure affiche :


Sub affiche(ByVal p As personne) ' affiche identit de p Console.Out.WriteLine(p.identite) End Sub

La mthode p.identite rend une chane de caractres identifiant l'objet personne. Que se passe-t-il dans le cas de notre exemple prcdent dans le cas d'un objet enseignant :
Dim e As New enseignant("Lucile", "Dumas", 56, 61) affiche(e)

Regardons l'exemple suivant :


' options Option Strict On Option Explicit On ' espaces de noms Imports System Module test Sub Main() ' un enseignant Dim e As New enseignant("Lucile", "Dumas", 56, 61) affiche(e) ' une personne Dim p As New personne("Jean", "Dupont", 30) affiche(p) End Sub ' affiche Sub affiche(ByVal p As personne) ' affiche identit de p Console.Out.WriteLine(p.identite) End Sub End Module

Les rsultats obtenus sont les suivants :


Construction personne(string, string, int) Construction enseignant(string,string,int,int) personne(Lucile,Dumas,56) Construction personne(string, string, int) personne(Jean,Dupont,30)

L'excution montre que l'instruction p.identite a excut chaque fois la proprit identite d'une personne, la personne contenue dans l'enseignant e, puis la personne p elle-mme. Elle ne s'est pas adapte l'objet rellement pass en paramtre affiche. On aurait prfr avoir l'identit complte de l'enseignant e. Il aurait fallu pour cela que la notation p.identite rfrence la proprit identite de l'objet rellement point par p plutt que la proprit identite de partie "personne" de l'objet rellement point par p. Il est possible d'obtenir ce rsultat en dclarant identite comme une proprit redfinissable (overridable) dans la classe de base personne :
Public Overridable ReadOnly Property identite() As String Get Return "personne(" & _prenom & "," & _nom & "," & age & ")" End Get End Property

Le mot cl overridable fait de identite une proprit rfinissable ou virtuelle. Ce mot cl peut s'appliquer galement aux mthodes. Les classes filles qui redfinissent une proprit ou mthode virtuelle doivent utiliser alors le mot cl overrides au lieu de shadows pour qualifier leur proprit/mthode redfinie. Ainsi dans la classe enseignant, la proprit identite est dfinie comme suit :
' surcharge proprit identit Public Overrides ReadOnly Property identite() As String Get Return "enseignant(" & MyBase.identite & "," & _section & ")" End Get End Property Classes, Structures, Interfaces

55

Le programme de test :
' options Option Strict On Option Explicit On ' espaces de noms Imports System Module test Sub Main() ' un enseignant Dim e As New enseignant("Lucile", "Dumas", 56, 61) affiche(e) ' une personne Dim p As New personne("Jean", "Dupont", 30) affiche(p) End Sub ' affiche Sub affiche(ByVal p As personne) ' affiche identit de p Console.Out.WriteLine(p.identite) End Sub End Module

produit alors les rsultats suivants :


Construction personne(string, string, int) Construction enseignant(string,string,int,int) enseignant(personne(Lucile,Dumas,56),61) Construction personne(string, string, int) personne(Jean,Dupont,30)

Cette fois-ci, on a bien eu l'identit complte de l'enseignant. Redfinissons maintenant une mthode plutt qu'une proprit. La classe object est la classe "mre" de toutes les classes VB.NET. Ainsi lorsqu'on crit :
public class personne

on crit implicitement :
public class personne inherits object

La classe object dfinit une mthode virtuelle ToString :

La mthode ToString rend le nom de la classe laquelle appartient l'objet comme le montre l'exemple suivant :
' options Option Strict On Option Explicit On ' espaces de noms Imports System Module test2 Sub Main() ' un enseignant Console.Out.WriteLine(New enseignant("Lucile", "Dumas", 56, 61).ToString()) ' une personne Console.Out.WriteLine(New personne("Jean", "Dupont", 30).ToString()) Classes, Structures, Interfaces

56

End Sub End Module

Les rsultats produits sont les suivants :


Construction personne(string, string, int) Construction enseignant(string,string,int,int) enseignant Construction personne(string, string, int) personne

On remarquera que bien que nous n'ayons pas redfini la mthode ToString dans les classes personne et enseignant, on peut cependant constater que la mthode ToString de la classe object est quand mme capable d'afficher le nom rel de la classe de l'objet. Redfinissons la mthode ToString dans les classes personne et enseignant :
' ToString Public Overrides Function ToString() As String ' on rend la proprit identite Return identite End Function

La dfinition est la mme dans les deux classes. Considrons le programme de test suivant :
' options Option Strict On Option Explicit On ' espaces de noms Imports System Module test Sub Main() ' un enseignant Dim e As New enseignant("Lucile", "Dumas", 56, 61) affiche(e) ' une personne Dim p As New personne("Jean", "Dupont", 30) affiche(p) End Sub ' affiche Sub affiche(ByVal p As personne) ' affiche identit de p Console.Out.WriteLine(p.identite) End Sub End Module

Les rsultats d'excution sont les suivants :


Construction personne(string, string, int) Construction enseignant(string,string,int,int) enseignant(personne(Lucile,Dumas,56),61) Construction personne(string, string, int) personne(Jean,Dupont,30)

2.3

Dfinir un indexeur pour une classe

Considrons la classe [ArrayList] prdfinie dans la plate-forme .NET. Cette classe permet de mmoriser des objets dans une liste. Elle appartient l'espace de noms [System.Collections]. Dans l'exemple suivant, nous utilisons cette classe pour mmoriser une liste de personnes (au sens large) :
' options Option Strict On Option Explicit On ' espaces de noms Imports System Imports System.Collections ' classe listeDePersonnes Public Class listeDePersonnes Inherits ArrayList ' pour ajouter une personne la liste Classes, Structures, Interfaces

57

Public Overloads Sub Add(ByVal p As personne) MyBase.Add(p) End Sub ' un indexeur Default Public Shadows Property Item(ByVal i As Integer) As personne Get Return CType(MyBase.Item(i), personne) End Get Set(ByVal Value As personne) MyBase.Item(i) = Value End Set End Property ' un autre indexeur Default Public Shadows ReadOnly Property Item(ByVal N As String) As Integer Get ' on recherche la personne de nom N Dim i As Integer For i = 0 To Count - 1 If CType(Me(i), personne).nom = N Then Return i End If Next i Return -1 End Get End Property ' toString Public Overrides Function ToString() As String ' rend (l1, l2, ..., ln) Dim liste As String = "(" Dim i As Integer ' on parcourt le tableau dynamique For i = 0 To (Count - 2) liste += "[" + Me(i).ToString + "]" + "," Next i 'for ' dernier lment If Count <> 0 Then liste += "[" + Me(i).ToString + "]" End If liste += ")" Return liste End Function End Class

Donnons quelques informations sur certaines proprits et mthodes de la classe [ArrayList] :


Count Add(Object) Item(Integer i)

attribut donnant le nombre d'lments de la liste mthode permettant d'ajouter un objet la liste mthode donnant l'lment i de la liste

Nous remarquons que pour obtenir l'lment n i de liste, nous n'avons pas crit [liste.Item(i)] mais directement [liste(i)], ce qui priori semble erron. Ceci est nanmoins possible parce que la classe [ArrayList] dfinit une proprit par dfaut [Item] selon la syntaxe analogue la suivante :
Default Public Property Item(ByVal i As Integer) As Object Get ... End Get Set(ByVal Value As personne) ... End Set End Property

Lorsque le compilateur rencontre la notation [liste(i)], il cherche si la classe [ArrayList] a dfini une proprit avec la signature suivante :
Default Public Property Proc(ByVal var As Integer) As Type

Ici, il trouvera la procdure [Item]. Il traduira alors l'criture [liste(i)] en [liste.Item(i)]. On appelle la proprit [Item], la proprit indexe par dfaut de la classe [ArrayList]. L'excution du programme prcdent donne les rsultats suivants :
dos>vbc /r:personne.dll /r:enseignant.dll lstpersonnes1.vb Classes, Structures, Interfaces

58

Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573 dos>dir 11/03/2004 12/03/2004 12/03/2004 11/03/2004

18:26 15:34 13:39 18:26

3 584 enseignant.dll 3 584 lstpersonnes1.exe 661 lstpersonnes1.vb 4 096 personne.dll

dos>lstpersonnes1 Construction personne(string, string, int) Construction personne(string, string, int) Construction personne(string, string, int) Construction enseignant(string,string,int,int) personne(paul,chenou,31) personne(nicole,chenou,11) enseignant(personne(jacques,sileau,33),61)

Crons une classe appel [listeDePersonnes] qui serait une liste de personnes, donc une liste particulire qu'il semble naturel de driver de la classe [ArrayList] :
' options Option Strict On Option Explicit On ' espaces de noms Imports System Imports System.Collections ' classe listeDePersonnes Public Class listeDePersonnes Inherits ArrayList ' pour ajouter une personne la liste Public Shadows Sub Add(ByVal p As Object) ' il faut que p soit une personne If Not TypeOf (p) Is personne Then Throw New Exception("L'objet ajout (" + p.ToString + ") n'est pas une personne") Else MyBase.Add(p) End If End Sub ' un indexeur Default Public Shadows Property Index(ByVal i As Integer) As personne Get Return CType(MyBase.Item(i), personne) End Get Set(ByVal Value As personne) MyBase.Item(i) = Value End Set End Property ' toString Public Overrides Function ToString() As String ' rend (l1, l2, ..., ln) Dim liste As String = "(" Dim i As Integer ' on parcourt le tableau dynamique For i = 0 To (Count - 2) liste += "[" + Me(i).ToString + "]" + "," Next i 'for ' dernier lment If Count <> 0 Then liste += "[" + Me(i).ToString + "]" End If liste += ")" Return liste End Function End Class

La classe prsente les mthodes et proprits suivantes :


ToString Add(personne) Item(Integer i)

rend une chane de caractres "reprsentant" le contenu de la liste mthode permettant d'ajouter une personne la liste proprit indexe par dfaut donnant la personne i de la liste 59

Classes, Structures, Interfaces

Considrons les points nouveaux : Une nouvelle mthode [Add] est cre.
' pour ajouter une personne la liste Public Shadows Sub Add(ByVal p As Object) ' il faut que p soit une personne If Not TypeOf (p) Is personne Then Throw New Exception("L'objet ajout (" + p.ToString + ") n'est pas une personne") Else MyBase.Add(p) End If End Sub

Il en existe dj une dans la classe parent [ArrayList] avec la mme signature, d'o le mot cl [Shadows] pour indiquer que la nouvelle procdure remplace celle de la classe parent. La mthode [Add] de la classe fille vrifie que l'objet ajout est bien de type [personne] ou driv avec la fonction [TypeOf]. Si ce n'est pas le cas, une exception est lance avec l'instruction [Throw]. Si l'objet ajout est bien de type [personne], la mthode [Add] de la classe de base est utilise pour faire l'ajout. Une proprit indexe est cre :
' un indexeur Default Public Shadows Property Index(ByVal i As Integer) As personne Get Return CType(MyBase.Item(i), personne) End Get Set(ByVal Value As personne) MyBase.Item(i) = Value End Set End Property

Il existe dj une proprit par dfaut appele [Item] dans la classe de base avec la mme signature. Aussi est-on oblig d'utiliser le mot cl [Shadows] pour indiquer que la nouvelle proprit indexe [Index] va "cacher" celle de la classe de base [Item]. On notera que ceci est vrai mme si les deux proprits ne portent pas le mme nom. La proprit [Index] permet de rfrencer la personne n i de la liste. Elle s'appuie sur la proprit [Item] de la classe de base pour avoir accs l'lment n i de l'objet [ArrayList] sousjacent. Des changements de type sont oprs pour tenir compte que la proprit [Item] travaille avec des lments de type [Object] alors que la proprit [Index] travaille avec des lments de type [personne]. Enfin, nous redfinissons (Overrides) la mthode [ToString] de la classe [ArrayList] :
' toString Public Overrides Function ToString() As String ' rend (l1, l2, ..., ln) Dim liste As String = "(" Dim i As Integer ' on parcourt le tableau dynamique For i = 0 To (Count - 2) liste += "[" + Me(i).ToString + "]" + "," Next i 'for ' dernier lment If Count <> 0 Then liste += "[" + Me(i).ToString + "]" End If liste += ")" Return liste End Function

Cette mthode rend une chane de caractres de la forme "(e1,e2,...,en)" o ei sont les lments de la liste. On remarquera la notation [Me(i)] qui dsigne l'lment n i de l'objet courant [Me]. C'est la proprit indexe par dfaut qui est utilise. Ainsi [Me(i)] est quivalent [Me.Index(i)]. Le code de la classe est plac dans le fichier [lstpersonnes2.vb] et compil :
dos>dir 11/03/2004 12/03/2004 11/03/2004 18:26 15:45 18:26 3 584 enseignant.dll 970 lstpersonnes2.vb 4 096 personne.dll

dos>vbc /r:personne.dll /r:enseignant.dll /t:library lstpersonnes2.vb Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573 Classes, Structures, Interfaces

60

dos>dir 11/03/2004 12/03/2004 12/03/2004 11/03/2004

18:26 15:50 15:45 18:26

3 584 enseignant.dll 3 584 lstpersonnes2.dll 970 lstpersonnes2.vb 4 096 personne.dll

Un programme de test est construit :


' options Option Explicit On Option Strict On ' espaces de noms Imports System Imports System.Collections Module test Sub Main() ' cration d'une liste vide de personnes Dim liste As listeDePersonnes = New listeDePersonnes ' cration de personnes Dim p1 As personne = New personne("paul", "chenou", 31) Dim p2 As personne = New personne("nicole", "chenou", 11) Dim e1 As enseignant = New enseignant("jacques", "sileau", 33, 61) ' remplissage de la liste liste.Add(p1) liste.Add(p2) liste.Add(e1) ' affichage de la liste Console.Out.WriteLine(liste.ToString) ' ajout d'un objet diffrent de personne Try liste.Add(4) Catch e As Exception Console.Error.WriteLine(e.Message) End Try End Sub End Module

Il est compil :
dos>vbc /r:personne.dll /r:enseignant.dll /r:lstpersonnes2.dll test.vb Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573 dos>dir 11/03/2004 12/03/2004 12/03/2004 11/03/2004 12/03/2004 12/03/2004

18:26 15:50 15:45 18:26 15:50 15:49

3 584 enseignant.dll 3 584 lstpersonnes2.dll 970 lstpersonnes2.vb 4 096 personne.dll 3 584 test.exe 623 test.vb

puis excut :
dos>test Construction personne(string, string, int) Construction personne(string, string, int) Construction personne(string, string, int) Construction enseignant(string,string,int,int) ([personne(paul,chenou,31)],[personne(nicole,chenou,11)],[enseignant(personne(jacques,sileau,33),61)]) L'objet ajout (4) n'est pas une personne

On pourrait vouloir crire


dim p as personne=l("nom")

o l serait de type [listeDePersonnes]. Ici, on veut indexer la liste l non plus par un n d'lment mais par un nom de personne. Pour cela on dfinit une nouvelle proprit indexe par dfaut :
' un autre indexeur Default Public Shadows ReadOnly Property Item(ByVal N As String) As Integer Get ' on recherche la personne de nom N Dim i As Integer For i = 0 To Count - 1 If CType(Me(i), personne).nom = N Then Classes, Structures, Interfaces

61

Return i End If Next i Return -1 End Get End Property

La premire ligne
Default Public Shadows ReadOnly Property Index(ByVal N As String) As Integer

indique qu'on cre de nouveau une proprit indexe par dfaut. Toutes les proprits par dfaut doivent porter le meme nom, ici [Index]. La nouvelle proprit [Index] indexe la classe listeDePersonnes par une chane de caractres N. Le rsultat de listeDePersonnes(N) est un entier. Cet entier sera la position dans la liste, de la personne portant le nom N ou -1 si cette personne n'est pas dans la liste. On ne dfinit que la proprit get interdisant ainsi l'criture listeDePersonnes ("nom")=valeur qui aurait ncessit la dfinition de la proprit set. D'o le mot cl [ReadOnly]. Le mot cl [Shadows] est ncessaire pour cacher la proprit par dfaut de la classe de base (bien qu'elle n'ait pas la mme signature). Dans le corps du get, on parcourt la liste des personnes la recherche du nom N pass en paramtre. Si on le trouve en position i, on renvoie i sinon on renvoie -1. Un nouveau programme de test pourrait tre le suivant :
' options Option Strict On Option Explicit On ' espaces de noms Imports System ' pg de test Module test Sub Main() ' une liste de personnes Dim l As New listeDePersonnes ' ajout de personnes l.Add(New personne("jean", "dumornet", 10)) l.Add(New personne("pauline", "duchemin", 12)) ' affichage Console.Out.WriteLine(("l=" + l.ToString)) l.Add(New personne("jacques", "tartifume", 27)) Console.Out.WriteLine(("l=" + l.ToString)) ' changement lment 1 l(1) = New personne("sylvie", "cachan", 5) ' affichage lment 1 Console.Out.WriteLine(("l[1]=" + l(1).ToString)) ' affichage liste l Console.Out.WriteLine(("l=" + l.ToString)) ' recherche de personnes Dim noms() As String = New [String]() {"cachan", "inconnu"} Dim i As Integer For i = 0 To noms.Length - 1 Dim inom As Integer = l(noms(i)) If inom <> -1 Then Console.Out.WriteLine(("personne(" & noms(i) & ")=" & l(inom).ToString)) Else Console.Out.WriteLine(("personne(" + noms(i) + ") n'existe pas")) End If Next i End Sub End Module

L'excution donne les rsultats suivants :


personne(string, string, int) Construction personne(string, string, int) l=([personne(jean,dumornet,10)],[personne(pauline,duchemin,12)]) Construction personne(string, string, int) l=([personne(jean,dumornet,10)],[personne(pauline,duchemin,12)],[personne(jacques,tartifume,27)]) Construction personne(string, string, int) l[1]=personne(sylvie,cachan,5) l=([personne(jean,dumornet,10)],[personne(sylvie,cachan,5)],[personne(jacques,tartifume,27)]) personne(cachan)=personne(sylvie,cachan,5) personne(inconnu) n'existe pas

Classes, Structures, Interfaces

62

2.4

Les structures

La structure VB.NET est directement issue de la structure du langage C et est trs proche de la classe. Une structure est dfinie comme suit :
Structure spersonne ' attributs ... ' proprits ... ' constructeurs ... ' mthodes End Structure

Il y a, malgr une similitude de dclaration, des diffrences importantes entre classe et structure. La notion d'hritage n'existe par exemple pas avec les structures. Si on crit une classe qui ne doit pas tre drive, quelles sont les diffrences entre structure et classe qui vont nous aider choisir entre les deux ? Aidons-nous de l'exemple suivant pour le dcouvrir :
' options Option Strict On Option Explicit On ' espaces de noms Imports System ' structure spersonne Structure spersonne Public nom As String Public age As Integer End Structure ' classe cpersonne Class cpersonne Public nom As String Public age As Integer End Class ' une module de test Public Module test Sub Main() ' une spersonne p1 Dim sp1 As spersonne sp1.nom = "paul" sp1.age = 10 Console.Out.WriteLine(("sp1=spersonne(" ' une spersonne p2 Dim sp2 As spersonne = sp1 Console.Out.WriteLine(("sp2=spersonne(" ' sp2 est modifi sp2.nom = "nicole" sp2.age = 30 ' vrification sp1 et sp2 Console.Out.WriteLine(("sp1=cpersonne(" Console.Out.WriteLine(("sp2=cpersonne(" ' une cpersonne cp1 Dim cp1 As New cpersonne cp1.nom = "paul" cp1.age = 10 Console.Out.WriteLine(("cP1=cpersonne(" ' une cpersonne P2 Dim cp2 As cpersonne = cp1 Console.Out.WriteLine(("cP2=cpersonne(" ' P2 est modifi cp2.nom = "nicole" cp2.age = 30 ' vrification P1 et P2 Console.Out.WriteLine(("cP1=cpersonne(" Console.Out.WriteLine(("cP2=cpersonne(" End Sub End Module

& sp1.nom & "," & sp1.age & ")")) & sp2.nom & "," & sp2.age & ")"))

& sp1.nom & "," & sp1.age & ")")) & sp2.nom & "," & sp2.age & ")"))

& cp1.nom & "," & cp1.age & ")")) & cp2.nom & "," & cp2.age & ")"))

& cp1.nom & "," & cp1.age & ")")) & cp2.nom & "," & cp2.age & ")"))

Si on excute ce programme, on obtient les rsultats suivants :


sp1=spersonne(paul,10) sp2=spersonne(paul,10) sp1=cpersonne(paul,10) Classes, Structures, Interfaces

63

sp2=cpersonne(nicole,30) cP1=cpersonne(paul,10) cP2=cpersonne(paul,10) cP1=cpersonne(nicole,30) cP2=cpersonne(nicole,30)

L o dans les pages prcdentes de ce chapitre on utilisait une classe personne, nous utilisons maintenant une structure spersonne :
' structure spersonne Structure spersonne Public nom As String Public age As Integer End Structure

La dclaration cre une structure (nom,age) et la valeur de sp1 est cette structure elle-mme. La dclaration cre un objet [cpersonne] (grosso modo l'quivalent de notre structure) et cp1 est alors l'adresse (la rfrence) de cet objet. Rsumons dans le cas de la structure, la valeur de sp1 est la structure elle-mme dans le cas de la classe, la valeur de p1 est l'adresse de l'objet cr Structure p1 sp1
Dim cp1 As New cpersonne Dim sp1 As spersonne

contient

nom age

Objet p1 nom age

p1

pointe sur

Lorsque dans le programme on crit une nouvelle structure (nom,age) est cre et initialise avec la valeur de p1 donc la structure elle-mme. sp1 paul 10
Dim sp2 As spersonne = sp1

sp2

paul 10

La structure de sp1 est donc duplique dans sp2. C'est une recopie de valeur. L'instruction agit diffremment. La valeur de cp1 est recopie dans cp2, mais comme cette valeur est en fait l'adresse de l'objet, celui-ci n'est pas dupliqu. Il a simplement deux rfrences sur lui :
Dim cp2 As cpersonne = cp1

Classes, Structures, Interfaces

64

cp1 cp2

---------> --------->

paul 10

Dans le cas de la structure, si on modifie la valeur de sp2 on ne modifie pas la valeur de sp1, ce que montre le programme. Dans le cas de l'objet, si on modifie l'objet point par cp2, celui point par cp1 est modifi puisque c'est le mme. C'est ce que montrent galement les rsultats du programme. On retiendra donc de ces explications que : la valeur d'une variable de type structure est la structure elle-mme la valeur d'une variable de type objet est l'adresse de l'objet point Une fois cette diffrence fondamentale comprise, la structure se montre trs proche de la classe comme le montre le nouvel exemple suivant :
' options Option Strict On Option Explicit On ' espaces de noms Imports System ' structure personne Structure personne ' attributs Private _nom As String Private _age As Integer ' proprits Public Property nom() As String Get Return _nom End Get Set(ByVal Value As String) _nom = value End Set End Property Public Property age() As Integer Get Return _age End Get Set(ByVal Value As Integer) _age = value End Set End Property ' Constructeur Public Sub New(ByVal NOM As String, ByVal AGE As Integer) _nom = NOM _age = AGE End Sub'New ' TOSTRING Public Overrides Function ToString() As String Return "personne(" & nom & "," & age & ")" End Function End Structure ' un module de test Module test Sub Main() ' une personne p1 Dim p1 As New personne("paul", Console.Out.WriteLine(("p1=" & ' une personne p2 Dim p2 As personne = p1 Console.Out.WriteLine(("p2=" & ' p2 est modifi p2.nom = "nicole" p2.age = 30 ' vrification p1 et p2 Console.Out.WriteLine(("p1=" & Console.Out.WriteLine(("p2=" & End Sub Classes, Structures, Interfaces

10) p1.ToString)) p2.ToString))

p1.ToString)) p2.ToString))

65

End Module

On obtient les rsultats d'excution suivants :


p1=personne(paul,10) p2=personne(paul,10) p1=personne(paul,10) p2=personne(nicole,30)

La seule notable diffrence ici entre structure et classe, c'est qu'avec une classe, les objets p1 et p2 auraient eu la mme valeur la fin du programme, celle de p2.

2.5

Les interfaces

Une interface est un ensemble de prototypes de mthodes ou de proprits qui forme un contrat. Une classe qui dcide d'implmenter une interface s'engage fournir une implmentation de toutes les mthodes dfinies dans l'interface. C'est le compilateur qui vrifie cette implmentation. Voici par exemple la dfinition de l'interface Istats :
Public Interface Istats Function moyenne() As Double Function cartType() As Double End Interface

Toute classe implmentant cette interface sera dclare comme


public class C Implements Istats ... function moyenne() as Double Implements Istats.moyenne ... end function function cartType () as Double Implements Istats. cartType ... end function end class

Les mthodes [moyenne] et [cartType] devront tre dfinies dans la classe C. Considrons le code suivant :
' options Option Strict On Option Explicit On ' espaces de noms Imports System ' structure Public Structure lve Public _nom As String Public _note As Double ' constructeur Public Sub New(ByVal NOM As String, ByVal NOTE As Double) Me._nom = NOM Me._note = NOTE End Sub End Structure ' classe notes Public Class notes ' attribut Protected _matire As String Protected _lves() As lve ' constructeur Public Sub New(ByVal MATIERE As String, ByVal ELEVES() As lve) ' mmorisation lves & matire Me._matire = MATIERE Me._lves = ELEVES End Sub ' ToString Public Overrides Function ToString() As String Dim valeur As String = "matire=" + _matire + ", notes=(" Dim i As Integer ' on concatne toutes les notes For i = 0 To (_lves.Length - 1) - 1 Classes, Structures, Interfaces

66

valeur &= "[" & _lves(i)._nom & "," & _lves(i)._note & "]," Next i 'dernire note If _lves.Length <> 0 Then valeur &= "[" & _lves(i)._nom & "," & _lves(i)._note & "]" End If valeur += ")" ' fin Return valeur End Function End Class

La classe notes rassemble les notes d'une classe dans une matire :
Public Class notes ' attribut Protected _matire As String Protected _lves() As lve

Les attributs sont dclars protected pour tre accessibles d'une classe drive. Le type lve est une structure mmorisant le nom de l'lve et sa note dans la matire :
Public Structure lve Public _nom As String Public _note As Double ' constructeur Public Sub New(ByVal NOM As String, ByVal NOTE As Double) Me._nom = NOM Me._note = NOTE End Sub End Structure

Nous dcidons de driver cette classe notes dans une classe notesStats qui aurait deux attributs supplmentaires, la moyenne et l'carttype des notes :
Public Class notesStats Inherits notes Implements Istats ' attributs Private _moyenne As Double Private _cartType As Double

La classe notesStats implmente l'interface Istats suivante :


Public Interface Istats Function moyenne() As Double Function cartType() As Double End Interface

Cela signifie que la classe notesStats doit avoir deux mthodes appeles moyenne et cartType avec la signature indique dans l'interface Istats. La classe notesStats est la suivante :
' options Option Strict On Option Explicit On ' espaces de noms Imports System Public Class notesStats Inherits notes Implements Istats ' attributs Private _moyenne As Double Private _cartType As Double ' constructeur Public Sub New(ByVal MATIERE As String, ByVal ELEVES() As lve) MyBase.New(MATIERE, ELEVES) ' calcul moyenne des notes Dim somme As Double = 0 Dim i As Integer For i = 0 To ELEVES.Length - 1 somme += ELEVES(i)._note Next i Classes, Structures, Interfaces

67

If ELEVES.Length <> 0 Then _moyenne = somme / ELEVES.Length Else _moyenne = -1 End If ' cart-type Dim carrs As Double = 0 For i = 0 To ELEVES.Length - 1 carrs += Math.Pow(ELEVES(i)._note - _moyenne, 2) Next i If ELEVES.Length <> 0 Then _cartType = Math.Sqrt((carrs / ELEVES.Length)) Else _cartType = -1 End If End Sub ' ToString Public Overrides Function ToString() As String Return MyBase.ToString() & ",moyenne=" & _moyenne & ",cart-type=" & _cartType End Function 'ToString ' mthodes de l'interface Istats Public Function moyenne() As Double Implements Istats.moyenne ' rend la moyenne des notes Return _moyenne End Function Public Function cartType() As Double Implements Istats.cartType ' rend l'cart-type Return _cartType End Function End Class

La moyenne _moyenne et l'cart-type _ecartType sont calculs ds la construction de l'objet. Aussi les mthodes moyenne et cartType n'ont-elles qu' rendre la valeur des attributs _moyenne et _ecartType. Les deux mthodes rendent -1 si le tableau des lves est vide. La classe de test suivante :
' options Option Strict On Option Explicit On ' espaces de noms Imports System Module test Sub Main() ' qqs lves & notes Dim ELEVES() As lve = {New lve("paul", 14), New lve("nicole", 16), New lve("jacques", 18)} ' qu'on enregistre dans un objet notes Dim anglais As New notes("anglais", ELEVES) ' et qu'on affiche Console.Out.WriteLine((anglais.ToString)) ' idem avec moyenne et cart-type anglais = New notesStats("anglais", ELEVES) Console.Out.WriteLine((anglais.ToString)) End Sub End Module

donne les rsultats :


matire=anglais, notes=([paul,14],[nicole,16],[jacques,18]) matire=anglais, notes=([paul,14],[nicole,16],[jacques,18]),moyenne=16,cart-type=1,63299316185545

La classe notesStats aurait trs bien pu implmenter les mthodes moyenne et cartType pour elle-mme sans indiquer qu'elle implmentait l'interface Istats. Quel est l'intrt des interfaces ? C'est le suivant : une fonction peut admettre pour paramtre formel une donne ayant le type d'une interface I. Tout objet d'une classe C implmentant l'interface I pourra alors tre paramtre effectif de cette fonction. Considrons l'exemple suivant :
' options Option Strict On Option Explicit On ' espaces de noms Imports System ' une interface Iexemple Classes, Structures, Interfaces

68

Public Interface Iexemple Function ajouter(ByVal i As Integer, ByVal j As Integer) As Integer Function soustraire(ByVal i As Integer, ByVal j As Integer) As Integer End Interface ' une 1re classe Public Class classe1 Implements Iexemple Public Function ajouter(ByVal a As Integer, ByVal b As Integer) As Integer Implements Iexemple.ajouter Return a + b + 10 End Function Public Function soustraire(ByVal a As Integer, ByVal b As Integer) As Integer Implements Iexemple.soustraire Return a - b + 20 End Function End Class 'une 2ime classe Public Class classe2 Implements Iexemple Public Function ajouter(ByVal a As Integer, ByVal b As Integer) As Integer Implements Iexemple.ajouter Return a + b + 100 End Function Public Function soustraire(ByVal a As Integer, ByVal b As Integer) As Integer Implements Iexemple.soustraire Return a - b + 200 End Function End Class

L'interface Iexemple dfinit deux mthodes ajouter et soustraire. Les classes classe1 et classe2 implmentent cette interface. On remarquera que ces classes ne font rien d'autre, ceci par souci de simplification de l'exemple. Maintenant considrons l'exemple suivant :
' options Option Strict On Option Explicit On ' espaces de noms Imports System ' classe de test Module test 'calculer Sub calculer(ByVal i As Integer, ByVal j As Integer, ByVal inter As Iexemple) Console.Out.WriteLine(inter.ajouter(i, j)) Console.Out.WriteLine(inter.soustraire(i, j)) End Sub ' la fonction Main Sub Main() ' cration de deux objets classe1 et classe2 Dim c1 As New classe1 Dim c2 As New classe2 ' appels de la fonction statique calculer calculer(4, 3, c1) calculer(14, 13, c2) End Sub End Module

La fonction calculer admet pour paramtre un lment de type Iexemple. Elle pourra donc recevoir pour ce paramtre aussi bien un objet de type classe1 que de type classe2. C'est ce qui est fait dans la procdure Main avec les rsultats suivants :
17 21 127 201

On voit donc qu'on a l une proprit proche du polymorphisme vu pour les classes. Si un ensemble de classes Ci non lies entreelles par hritage (donc on ne peut utiliser le polymorphisme de l'hritage) prsente un ensemble de mthodes de mme signature, il peut tre intressant de regrouper ces mthodes dans une interface I dont hriteraient toutes les classes concernes. Des instances de ces classes Ci peuvent alors tre utilises comme paramtres de fonctions admettant un paramtre de type I, c.a.d. des fonctions n'utilisant que les mthodes des objets Ci dfinies dans l'interface I et non les attributs et mthodes particuliers des diffrentes classes Ci. Notons enfin que l'hritage d'interfaces peut tre multiple, c.a.d. qu'on peut crire Classes, Structures, Interfaces 69

Public Class classe Implements I1,I2,...

o les Ij sont des interfaces.

2.6

Les espaces de noms

Pour crire une ligne l'cran, nous utilisons l'instruction


Console.Out.WriteLine(...)

Si nous regardons la dfinition de la classe Console


Namespace: System Assembly: Mscorlib (in Mscorlib.dll)

on dcouvre qu'elle fait partie de l'espace de noms System. Cela signifie que la classe Console devrait tre dsigne par System.Console et on devrait en fait crire :
System.Console.Out.WriteLine(...)

On vite cela en utilisant une clause imports :


imports System ... Console.Out.WriteLine(...)

On dit qu'on importe l'espace de noms System avec la clause imports. Lorsque le compilateur va rencontrer le nom d'une classe (ici Console) il va chercher la trouver dans les diffrents espaces de noms imports par les clauses imports. Ici il trouvera la classe Console dans l'espace de noms System. Notons maintenant la seconde information attache la classe Console :
Assembly: Mscorlib (in Mscorlib.dll)

Cette ligne indique dans quelle "assemblage" se trouve la dfinition de la classe Console. Lorsqu'on compile en-dehors de Visual Studio.NET et qu'on doit donner les rfrences des diffrentes dll contenant les classes que l'on doit utiliser cette information peut s'avrer utile. On rappelle que pour rfrencer les dll ncessaires la compilation d'une classe, on crit :
vbc /r:fic1.dll /r:fic2.dll ... prog.vb

Lorsqu'on cre une classe, on peut la crer l'intrieur d'un espace de noms. Le but de ces espaces de noms est d'viter les conflits de noms entre classes lorsque celles-ci sont vendues par exemple. Considrons deux entreprises E1 et E2 distribuant des classes empaquetes respectivement dans les dll, E1.dll et E2.dll. Soit un client C qui achte ces deux ensembles de classes dans lesquelles les deux entreprises ont dfini toutes deux une classe personne. Le client C compile un programme de la faon suivante :
vbc /r:E1.dll /r:E2.dll prog.vb

Si le source prog.vb utilise la classe personne, le compilateur ne saura pas s'il doit prendre la classe personne de E1.dll ou celle de E2.dll. Il signalera une erreur. Si l'entreprise E1 prend soin de crer ses classes dans un espace de noms appel E1 et l'entreprise E2 dans un espace de noms appel E2, les deux classes personne s'appelleront alors E1.personne et E2.personne. Le client devra employer dans ses classes soit E1.personne, soit E2.personne mais pas personne. L'espace de noms permet de lever l'ambigut. Pour crer une classe dans un espace de noms, on crit :
Namespace istia.st Public Class personne ' dfinition de la classe ... end class end namespace

Pour l'exemple, crons dans un espace de noms notre classe personne tudie prcdemment. Nous choisirons istia.st comme espace de noms. La classe personne devient :
' options Option Strict On Option Explicit On ' espaces de noms Imports System Classes, Structures, Interfaces

70

' cration de l'espace de nom istia.st Namespace istia.st Public Class personne ' attributs Private prenom As String Private nom As String Private age As Integer ' mthode Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer) Me.prenom = P Me.nom = N Me.age = age End Sub ' mthode Public Sub identifie() Console.Out.WriteLine((prenom & "," & nom & "," & age)) End Sub End Class End Namespace

Cette classe est compile dans personne.dll :


dos>dir 11/03/2004 18:27 610 personne.vb

dos>vbc /t:library personne.vb Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573 dos>dir 12/03/2004 11/03/2004

18:06 18:27

3 584 personne.dll 610 personne.vb

Maintenant utilisons la classe personne dans une classe de test :


' options Option Strict On Option Explicit On ' espaces de noms Imports System Imports istia.st ' pg test Public Module test Sub Main() Dim p1 As New personne p1.initialise("Jean", "Dupont", 30) p1.identifie() End Sub End Module

Pour viter d'crire


Dim p1 As New istia.st.personne

nous avons import l'espace de noms istia.st avec une clause imports :
Imports istia.st

Maintenant compilons le programme de test :


dos>dir 12/03/2004 11/03/2004 12/03/2004 18:06 18:27 18:05 3 584 personne.dll 610 personne.vb 254 test.vb

dos>vbc /r:personne.dll test.vb Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573 dos>dir 12/03/2004 11/03/2004 18:06 18:27 3 584 personne.dll 610 personne.vb

Classes, Structures, Interfaces

71

12/03/2004 12/03/2004

18:08 18:05

3 072 test.exe 254 test.vb

Cela produit un fichier test.exe qui excut donne les rsultats suivants :
Jean,Dupont,30

2.7

L'exemple IMPOTS

On reprend le calcul de l'impt dj tudi dans le chapitre prcdent et on le traite en utilisant une classe. Rappelons le problme : On se place dans le cas simplifi d'un contribuable n'ayant que son seul salaire dclarer : on calcule le nombre de parts du salari nbParts=nbEnfants/2 +1 s'il n'est pas mari, nbEnfants/2+2 s'il est mari, o nbEnfants est son nombre d'enfants. s'il a au moins trois enfants, il a une demie part de plus on calcule son revenu imposable R=0.72*S o S est son salaire annuel on calcule son coefficient familial QF=R/nbParts on calcule son impt I. Considrons le tableau suivant :
12620.0 13190 15640 24740 31810 39970 48360 55790 92970 127860 151250 172040 195000 0 0 0.05 0.1 0.15 0.2 0.25 0.3 0.35 0.4 0.45 0.50 0.55 0.60 0.65 0 631 1290.5 2072.5 3309.5 4900 6898.5 9316.5 12106 16754.5 23147.5 30710 39312 49062

Chaque ligne a 3 champs. Pour calculer l'impt I, on recherche la premire ligne o QF<=champ1. Par exemple, si QF=23000 on trouvera la ligne 24740 0.15 2072.5 L'impt I est alors gal 0.15*R - 2072.5*nbParts. Si QF est tel que la relation QF<=champ1 n'est jamais vrifie, alors ce sont les coefficients de la dernire ligne qui sont utiliss. Ici : 0 0.65 49062 ce qui donne l'impt I=0.65*R - 49062*nbParts. La classe impot sera dfinie comme suit :
' options Option Strict On Option Explicit On ' espaces de noms Imports System Public Class impot ' les donnes ncessaires au calcul de l'impt ' proviennent d'une source extrieure Private limites(), coeffR(), coeffN() As Decimal ' constructeur Public Sub New(ByVal LIMITES() As Decimal, ByVal COEFFR() As Decimal, ByVal COEFFN() As Decimal) ' on vrifie que les 3 tableaux ont la mme taille Dim OK As Boolean = LIMITES.Length = COEFFR.Length And LIMITES.Length = COEFFN.Length If Not OK Then Throw New Exception("Les 3 tableaux fournis n'ont pas la mme taille(" & LIMITES.Length & "," & COEFFR.Length & "," & COEFFN.Length & ")") End If ' c'est bon Me.limites = LIMITES Me.coeffR = COEFFR Me.coeffN = COEFFN End Sub Classes, Structures, Interfaces

72

' calcul de l'impt Public Function calculer(ByVal mari As Boolean, ByVal nbEnfants As Integer, ByVal salaire As Long) As Long ' calcul du nombre de parts Dim nbParts As Decimal If mari Then nbParts = CDec(nbEnfants) / 2 + 2 Else nbParts = CDec(nbEnfants) / 2 + 1 End If If nbEnfants >= 3 Then nbParts += 0.5D End If ' calcul revenu imposable & Quotient familial Dim revenu As Decimal = 0.72D * salaire Dim QF As Decimal = revenu / nbParts ' calcul de l'impt limites((limites.Length - 1)) = QF + 1 Dim i As Integer = 0 While QF > limites(i) i += 1 End While Return CLng(revenu * coeffR(i) - nbParts * coeffN(i)) End Function End Class

Un objet impt est cr avec les donnes permettant le calcul de l'impt d'un contribuable. C'est la partie stable de l'objet. Une fois cet objet cr, on peut appeler de faon rpte sa mthode calculer qui calcule l'impt du contribuable partir de son statut marital (mari ou non), son nombre d'enfants et son salaire annuel. Un programme de test pourait tre le suivant :
' options Option Strict On Option Explicit On ' espaces de noms Imports System Imports Microsoft.VisualBasic Module test Sub Main() ' programme interactif de calcul d'impt ' l'utilisateur tape trois donnes au clavier : mari nbEnfants salaire ' le programme affiche alors l'impt payer Const syntaxe As String = "syntaxe : mari nbEnfants salaire" + ControlChars.Lf + "mari : o pour mari, n pour non mari" + ControlChars.Lf + "nbEnfants : nombre d'enfants" + ControlChars.Lf + "salaire : salaire annuel en F" ' tableaux de donnes ncessaires au calcul de l'impt Dim limites() As Decimal = {12620D, 13190D, 15640D, 24740D, 31810D, 39970D, 48360D, 55790D, 92970D, 127860D, 151250D, 172040D, 195000D, 0D} Dim coeffR() As Decimal = {0D, 0.05D, 0.1D, 0.15D, 0.2D, 0.25D, 0.3D, 0.35D, 0.4D, 0.45D, 0.5D, 0.55D, 0.6D, 0.65D} Dim coeffN() As Decimal = {0D, 631D, 1290.5D, 2072.5D, 3309.5D, 4900D, 6898.5D, 9316.5D, 12106D, 16754.5D, 23147.5D, 30710D, 39312D, 49062D} ' cration d'un objet impt Dim objImpt As impot = Nothing Try objImpt = New impot(limites, coeffR, coeffN) Catch ex As Exception Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message)) Environment.Exit(1) End Try ' boucle infinie Dim mari As String Dim nbEnfants As Integer Dim salaire As Long While True ' on demande les paramtres du calcul de l'impt Console.Out.Write("Paramtres du calcul de l'impt au format mari nbEnfants salaire ou rien pour arrter :") Dim paramtres As String = Console.In.ReadLine().Trim() ' qq chose faire ? If paramtres Is Nothing OrElse paramtres = "" Then Exit While End If ' vrification du nombre d'arguments dans la ligne saisie Dim erreur As Boolean = False Dim args As String() = paramtres.Split(Nothing) Dim nbParamtres As Integer = args.Length Classes, Structures, Interfaces

73

If nbParamtres <> 3 Then Console.Error.WriteLine(syntaxe) erreur = True End If ' vrification de la validit des paramtres If Not erreur Then ' mari mari = args(0).ToLower() If mari <> "o" And mari <> "n" Then erreur = True End If ' nbEnfants Try nbEnfants = Integer.Parse(args(1)) If nbEnfants < 0 Then Throw New Exception End If Catch erreur = True End Try ' salaire Try salaire = Integer.Parse(args(2)) If salaire < 0 Then Throw New Exception End If Catch erreur = True End Try End If ' si les paramtres sont corrects - on calcule l'impt If Not erreur Then Console.Out.WriteLine(("impt=" & objImpt.calculer(mari = "o", nbEnfants, salaire) & " F")) Else Console.Error.WriteLine(syntaxe) End If End While End Sub End Module

Voici un exemple d'excution du programme prcdent :


dir>dir 12/03/2004 12/03/2004 18:20 18:21 1 483 impots.vb 2 805 test.vb

dos>vbc /t:library impots.vb Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573 dir>dir 12/03/2004 12/03/2004 12/03/2004

18:24 18:20 18:21

4 096 impots.dll 1 483 impots.vb 2 805 test.vb

dos>vbc /r:impots.dll test.vb Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573 dos>dir 12/03/2004 12/03/2004 12/03/2004 12/03/2004

18:24 18:20 18:26 18:21

4 1 6 2

096 483 144 805

impots.dll impots.vb test.exe test.vb

dos>test Paramtres du calcul de l'impt au format mari nbEnfants salaire ou rien pour arrter :x x x syntaxe : mari nbEnfants salaire mari : o pour mari, n pour non mari nbEnfants : nombre d'enfants salaire : salaire annuel en F Paramtres du calcul de l'impt au format mari nbEnfants salaire ou rien pour arrter :o 2 200000 impt=22504 F Paramtres du calcul de l'impt au format mari nbEnfants salaire ou rien pour arrter :

Classes, Structures, Interfaces

74

3.
3.1 3.1.1

Classes .NET d'usage courant


Chercher de l'aide avec SDK.NET wincv

Nous prsentons ici quelques classes de la plate-forme .NET prsentant un intrt, mme pour un dbutant. Nous montrons tout d'abord comment obtenir des renseignements sur les centaines de classes disponibles.

Si on a install seulement le SDK et pas Visual Studio.NET on pourra utiliser le programme wincv.exe situ normalement dans l'arborescence du sdk, par exemple C:\Program Files\Microsoft Visual Studio .NET 2003\SDK\v1.1. Lorsqu'on lance cet utilitaire, on a l'interface suivante :

On tape en (1) le nom de la classe dsire. Cela ramne en (2) divers thmes possibles. On choisit celui qui convient et on a le rsultat en (3), ici la classe HashTable. Cette mthode convient si on connat le nom de la classe que l'on cherche. Si on veut explorer la liste des possibilts offertes par la plate-forme .NET ou pourra utiliser le fichier HTML StartHere.htm situ lui aussi directement dans le dossier d'installation de SSK.Net, par exemple C:\Program Files\Microsoft Visual Studio .NET 2003\SDK\v1.

Exemples de classes .NET

75

Le lien .NET Framework SDK Documentation est celui qu'il faut suivre pour avoir une vue d'ensemble des classes .NET :

L, on suivra le lien [Bibliothque de classes]. On y trouve la liste de toutes les classes de .NET :

Suivons par exemple le lien System.Collections. Cet espace de noms regroupe diverses classes implmentant des collections dont la classe HashTable :

Exemples de classes .NET

76

Suivons le lien HashTable ci-dessous :

Nous obtenons la page suivante :

On remarquera ci-dessous, la position de la main. Elle pointe sur un lien permettant de prciser le langage dsir, ici Visual Basic. On y trouve le prototype de la classe ainsi que des exemples d'utilisation. Suivons le lien [Hashtable, membres] ci-dessous :

On obtient la description complte de la classe :

Cette mthode est la meilleure pour dcouvrir le SDK et ses classes. L'outil WinCV s'avre utile lorsqu'on connat dj un peu la classe et qu'on a oubli certains de ses membres. WinCV permet alors de retrouver rapidement la classe et ses membres.

Exemples de classes .NET

77

3.2 3.2.1

Chercher de l'aide sur les classes avec VS.NET Option Aide

Nous donnons ici quelques indications pour trouver de l'aide avec Visual Studio.NET

Prenons l'option [?] du menu.

On obtient la fentre suivante :

Dans la liste droulante, on peut choisir un filtre d'aide. Ici, on prendra le filtre [Visual Basic].

Deux aides sont utiles : l'aide sur le langage VB.NET lui-mme (syntaxe) l'aide sur les classes.NET utilisables par le langage VB.NET L'aide au langage VB.NET est accessible via [Visual Studio.NET/Visual Basic et Visual C#/Reference/Visual Basic] :

On obtient la page d'aide suivante :

Exemples de classes .NET

78

A partir de l, les diffrentes sous-rubriques nous permettent d'avoir de l'aide sur diffrents thmes de VB.NET. On prtera attention aux tutoriels de VB.NET :

Pour avoir accs aux diffrentes classes de la plate-forme .NET, on choisira l'aide [Visual Studio.NET/.NET Framework].

On s'intressera notamment la rubrique [Rfrence/Bibliothque de classes] :

Exemples de classes .NET

79

Supposons qu'on s'intresse la classe [ArrayList]. Elle se trouve dans l'espace de noms [System.Collections]. Il faut le savoir sinon on prfrera la mthode de recherche expose ci-aprs. On obtient l'aide suivante :

Le lien [ArrayList, classe] donne une vue d'ensemble de la classe :

Ce type de page existe pour toutes les classes. Elle donne un rsum de la classe avec des exemples. Pour une description des membres de la classe, on suivra le lien [ArrayList, membres] :

3.2.2

Aide/Index

L'option [Aide/index] permet de chercher une aide plus cible que l'aide prcdente. Il suffit de taper le mot cl cherch :

Exemples de classes .NET

80

L'avantage de cette mthode par rapport la prcdente est qu'on n'a pas besoin de savoir o se trouve ce qu'on cherche dans le systme d'aide. C'est probablement la mthode prfrer lorsqu'on fait une recherche cibl, l'autre mthode tant plus approprie une dcouverte de tout ce que propose l'aide.

3.3

La classe String

La classe String prsente de nombreuses proprits et mthodes. En voici quelques-unes :


Public ReadOnly Property Length As Integer Public Default ReadOnly Property Chars(ByVal index As Integer) As Char Public Function EndsWith(ByVal value As String) As Boolean Public Function StartsWith(ByVal value As String) As Boolean Overloads Public Function Equals(ByVal value As String) As Boolean Overloads Public Function IndexOf(ByVal value As String) As Integer Overloads Public Function IndexOf(ByVal value As String,ByVal startIndex As Integer) As Integer Overloads Public Shared Function Join(ByVal separator As String,ByVal value() As String) As String Overloads Public Function Replace(ByVal oldChar As Char,ByVal newChar As Char) As String Overloads Public Function Split(ByVal ParamArray separator() As Char) As String()

nombre de caractres de la chane proprit indexe par dfaut. [String].Chars(i) est le caractre n i de la chane rend vrai si la chane se termine par value rend vrai si la chane commence par value rend vrai si la chane est gale value rend la premire position dans la chane de la chane value - la recherche commence partir du caractre n 0 rend la premire position dans la chane de la chane value - la recherche commence partir du caractre n startIndex mthode de classe - rend une chane de caractres, rsultat de la concatnation des valeurs du tableau value avec le sparateur separator rend une chane copie de la chane courante o le caractre oldChar a t remplac par le caractre newChar la chane est vue comme une suite de champs spars par les caractres prsents dans le tableau separator. Le rsultat est le tableau de ces champs sous-chane de la chane courante commenant la position startIndex et ayant length caractres rend la chane courante en minuscules rend la chane courante en majuscules rend la chane courante dbarrasse de ses espaces de dbut et fin

Overloads Public Function Substring(ByVal startIndex As Integer,ByVal length As Integer) As String Overloads Public Function ToLower() As String Overloads Public Function ToUpper() As String Overloads Public Function Trim() As String

Une chane C peut tre considre comme un tableau de caractres. Ainsi C.Chars(i) est le caractre i de C C.Length est le nombre de caractres de C Considrons l'exemple suivant : Exemples de classes .NET 81

' options Option Strict On Option Explicit On ' espaces de noms

Imports System

Module test Sub Main() Dim uneChaine As String = "l'oiseau vole au-dessus des nuages" affiche("uneChaine=" + uneChaine) affiche("uneChaine.Length=" & uneChaine.Length) affiche("chaine[10]=" + uneChaine.Chars(10)) affiche("uneChaine.IndexOf(""vole"")=" & uneChaine.IndexOf("vole")) affiche("uneChaine.IndexOf(""x"")=" & uneChaine.IndexOf("x")) affiche("uneChaine.LastIndexOf('a')=" & uneChaine.LastIndexOf("a"c)) affiche("uneChaine.LastIndexOf('x')=" & uneChaine.LastIndexOf("x"c)) affiche("uneChaine.Substring(4,7)=" + uneChaine.Substring(4, 7)) affiche("uneChaine.ToUpper()=" + uneChaine.ToUpper()) affiche("uneChaine.ToLower()=" + uneChaine.ToLower()) affiche("uneChaine.Replace('a','A')=" + uneChaine.Replace("a"c, "A"c)) Dim champs As String() = uneChaine.Split(Nothing) Dim i As Integer For i = 0 To champs.Length - 1 affiche("champs[" & i & "]=[" & champs(i) & "]") Next i affiche("Join("":"",champs)=" + System.String.Join(":", champs)) affiche("("" abc "").Trim()=[" + " abc ".Trim() + "]") End Sub ' affiche Sub affiche(ByVal msg As [String]) ' affiche msg Console.Out.WriteLine(msg) End Sub

End Module

L'excution donne les rsultats suivants :


dos>vbc string1.vb dos>string1 uneChaine=l'oiseau vole au-dessus des nuages uneChaine.Length=34 chaine[10]=o uneChaine.IndexOf("vole")=9 uneChaine.IndexOf("x")=-1 uneChaine.LastIndexOf('a')=30 uneChaine.LastIndexOf('x')=-1 uneChaine.Substring(4,7)=seau vo uneChaine.ToUpper()=L'OISEAU VOLE AU-DESSUS DES NUAGES uneChaine.ToLower()=l'oiseau vole au-dessus des nuages uneChaine.Replace('a','A')=l'oiseAu vole Au-dessus des nuAges champs[0]=[l'oiseau] champs[1]=[vole] champs[2]=[au-dessus] champs[3]=[des] champs[4]=[nuages] Join(":",champs)=l'oiseau:vole:au-dessus:des:nuages (" abc ").Trim()=[abc]

Considrons un nouvel exemple :


' options Option Strict On Option Explicit On ' espaces de noms Imports System Module string2 Sub Main() ' la ligne analyser Dim ligne As String = "un:deux::trois:" ' les sparateurs de champs Dim sparateurs() As Char = {":"c} ' split Dim champs As String() = ligne.Split(sparateurs)

Exemples de classes .NET

82

Dim i As Integer For i = 0 To champs.Length - 1 Console.Out.WriteLine(("Champs[" & i & "]=" & champs(i))) Next i ' join Console.Out.WriteLine(("join=[" + System.String.Join(":", champs) + "]")) End Sub End Module

et les rsultats d'excution :


Champs[0]=un Champs[1]=deux Champs[2]= Champs[3]=trois Champs[4]= join=[un:deux::trois:]

La mthode Split de la classe String permet de mettre dans un tableau les champs d'une chane de caractres. La dfinition de la mthode utilise ici est la suivante :
Overloads Public Function Split(ByVal ParamArray separator() As Char) As String() separator

tableau de caractres. Ces caractres reprsentent les caractres utiliss pour sparer les champs de la chane de caractres. Ainsi si la chane est [champ1, champ2, champ3] on pourra utiliser separator=new char() {","c}. Si le sparateur est une suite d'espaces on utilisera separator=nothing. tableau de chanes de caractres o chaque lment est un champ de la chane.

rsultat

La mthode Join est une mthode statique de la classe String :


Overloads Public Shared Function Join(ByVal separator As String,ByVal value() As String) As String value separator rsultat

tableau de chanes de caractres une chane de caractres qui servira de sparateur de champs une chane de caractres forme de la concatnation des lments du tableau value spars par la chane separator.

3.4

La classe Array

La classe Array implmente un tableau. Nous utiliserons dans notre exemple les proprits et mthodes suivantes :
Public ReadOnly Property Length As Integer Overloads Public Shared Function BinarySearch(ByVal array As Array,ByVal index As Integer,ByVal length As Integer,ByVal value As Object) As Integer Overloads Public Shared Sub Copy(ByVal sourceArray As Array,ByVal destinationArray As Array,ByVal length As Integer) Overloads Public Shared Sub Sort(ByVal array As Array)

proprit - nombre d'lments du tableau mthode de classe - rend la position de value dans le tableau tri array - cherche partir de la position index et avec length lments mthode de classe - copie length lments de sourceArray dans destinationArray - destinationArray est cr pour l'occasion mthode de classe - trie le tableau array - celui doit contenir un type de donnes ayant une fonction d'ordre par dfaut (chanes, nombres).

Le programme suivant illustre l'utilisation de la classe Array :


' options Option Strict On Option Explicit On ' espaces de noms Imports System Module test Sub Main() ' lecture des lments d'un tableau taps au clavier Dim termin As [Boolean] = False Dim i As Integer = 0

Exemples de classes .NET

83

Dim Dim Dim Dim Dim

lments1 As Double() = Nothing lments2 As Double() = Nothing lment As Double = 0 rponse As String = Nothing erreur As [Boolean] = False

While Not termin ' question Console.Out.Write(("Elment (rel) " & i & " du tableau (rien pour terminer) : ")) ' lecture de la rponse rponse = Console.ReadLine().Trim() ' fin de saisie si chane vide If rponse.Equals("") Then Exit While End If ' vrification saisie Try lment = [Double].Parse(rponse) erreur = False Catch Console.Error.WriteLine("Saisie incorrecte, recommencez") erreur = True End Try ' si pas d'erreur If Not erreur Then ' nouveau tableau pour accueillir le nouvel lment lments2 = New Double(i) {} ' copie ancien tableau vers nouveau tableau If i <> 0 Then Array.Copy(lments1, lments2, i) End If ' nouveau tableau devient ancien tableau lments1 = lments2 ' plus besoin du nouveau tableau lments2 = Nothing ' insertion nouvel lment lments1(i) = lment ' un lmt de plus dans le tableau i += 1 End If End While ' affichage tableau non tri System.Console.Out.WriteLine("Tableau non tri") For i = 0 To lments1.Length - 1 Console.Out.WriteLine(("lments[" & i & "]=" & lments1(i))) Next i ' tri du tableau System.Array.Sort(lments1) ' affichage tableau tri System.Console.Out.WriteLine("Tableau tri") For i = 0 To lments1.Length - 1 Console.Out.WriteLine(("lments[" & i & "]=" & lments1(i))) Next i ' Recherche While Not termin ' question Console.Out.Write("Elment cherch (rien pour arrter) : ") ' lecture-vrification rponse rponse = Console.ReadLine().Trim() ' fini ? If rponse.Equals("") Then Exit While End If ' vrification Try lment = [Double].Parse(rponse) erreur = False Catch Console.Error.WriteLine("Saisie incorrecte, recommencez") erreur = True End Try ' si pas d'erreur If Not erreur Then ' on cherche l'lment dans le tableau tri i = System.Array.BinarySearch(lments1, 0, lments1.Length, lment) ' Affichage rponse If i >= 0 Then Console.Out.WriteLine(("Trouv en position " & i)) Else Console.Out.WriteLine("Pas dans le tableau") End If

Exemples de classes .NET

84

End If End While End Sub End Module

Les rsultats cran sont les suivants :


Elment (rel) 0 du tableau (rien pour terminer) Elment (rel) 1 du tableau (rien pour terminer) Elment (rel) 2 du tableau (rien pour terminer) Elment (rel) 3 du tableau (rien pour terminer) Elment (rel) 4 du tableau (rien pour terminer) Tableau non tri lments[0]=10,4 lments[1]=5,2 lments[2]=8,7 lments[3]=3,6 Tableau tri lments[0]=3,6 lments[1]=5,2 lments[2]=8,7 lments[3]=10,4 Elment cherch (rien pour arrter) : 8,7 Trouv en position 2 Elment cherch (rien pour arrter) : 11 Pas dans le tableau Elment cherch (rien pour arrter) : a Saisie incorrecte, recommencez Elment cherch (rien pour arrter) : : : : : : 10,4 5,2 8,7 3,6

La premire partie du programme construit un tableau partir de donnes numriques tapes au clavier. Le tableau ne peut tre dimensionn priori puisqu'on ne connat pas le nombre d'lments que va taper l'utilisateur. On travaille alors avec deux tableaux lments1 et lments2.
' nouveau tableau pour accueillir le nouvel lment lments2 = New Double(i) {} ' copie ancien tableau vers nouveau tableau If i <> 0 Then Array.Copy(lments1, lments2, i) End If ' nouveau tableau devient ancien tableau lments1 = lments2 ' plus besoin du nouveau tableau lments2 = Nothing ' insertion nouvel lment lments1(i) = lment ' un lmt de plus dans le tableau i += 1

Le tableau lments1 contient les valeurs actuellement saisies. Lorsque l'utilisateur ajoute une nouvelle valeur, on construit un tableau lments2 avec un lment de plus que lments1, on copie le contenu de lments1 dans lments2 (Array.Copy), on fait "pointer" lments1 sur lments2 et enfin on ajoute le nouvel lment lments1. C'est un processus complexe qui peut tre simplifi si au lieu d'utiliser un tableau de taille fixe (Array) on utilise un tableau de taille variable (ArrayList). Le tableau est tri avec la mthode Array.Sort. Cette mthode peut tre appele avec diffrents paramtres prcisant les rgles de tri. Sans paramtres, c'est ici un tri en ordre croissant qui est fait par dfault. Enfin, la mthode Array.BinarySearch permet de chercher un lment dans un tableau tri.

3.5

La classe ArrayList

La classe ArrayList permet d'implmenter des tableaux de taille variable au cours de l'excution du programme, ce que ne permet pas la classe Array prcdente. Voici quelques-unes des proprits et mthodes courantes :
Public Sub New() Public Overridable ReadOnly Property Count As Integer Implements ICollection.Count Public Overridable Function Add(ByVal value As Object) As Integer Implements IList.Add Public Overridable Sub Clear() Implements IList.Clear

construit une liste vide nombre d'lments de la collection ajoute l'objet value la fin de la collection efface la liste 85

Exemples de classes .NET

Overloads Public Overridable Function IndexOf(ByVal value As Object) As Integer Implements IList.IndexOf Overloads Public Overridable Function IndexOf(ByVal value As Object, ByVal startIndex As Integer) As Integer Overloads Public Overridable Function LastIndexOf(ByVal value As Object) As Integer Overloads Public Overridable Function LastIndexOf(ByVal value As Object, ByVal startIndex As Integer) As Integer Public Overridable Sub Remove( ByVal obj As Object) Implements IList.Remove Public Overridable Sub RemoveAt(ByVal index As Integer) Implements IList.RemoveAt Overloads Public Overridable Function BinarySearch(ByVal value As Object) As Integer

indice de l'objet value dans la liste ou -1 s'il n'existe pas idem mais en cherchant partir de l'lment n startIndex

idem mais rend l'indice de la dernire occurrence de value dans la liste idem mais en cherchant partir de l'lment n startIndex

enlve l'objet obj s'il existe dans la liste enlve l'lment index de la liste rend la position de l'objet value dans la liste ou -1 s'il ne s'y trouve pas. La liste doit tre trie trie la liste. Celle-ci doit contenir des objets ayant une relation d'ordre prdfinie (chanes, nombres) trie la liste selon la relation d'ordre tablie par la fonction comparer

Overloads Public Overridable Sub Sort()

Overloads Public Overridable Sub Sort(ByVal comparer As IComparer)

Reprenons l'exemple trait avec des objets de type Array et traitons-le avec des objets de type ArrayList :
' options Option Strict On Option Explicit On ' espaces de noms Imports System Imports System.Collections Module test Sub Main() ' lecture des lments d'un tableau taps au clavier Dim termin As [Boolean] = False Dim i As Integer = 0 Dim lments As New ArrayList Dim lment As Double = 0 Dim rponse As String = Nothing Dim erreur As [Boolean] = False While Not termin ' question Console.Out.Write(("Elment (rel) " & i & " du tableau (rien pour terminer) : ")) ' lecture de la rponse rponse = Console.ReadLine().Trim() ' fin de saisie si chane vide If rponse.Equals("") Then Exit While End If ' vrification saisie Try lment = Double.Parse(rponse) erreur = False Catch Console.Error.WriteLine("Saisie incorrecte, recommencez") erreur = True End Try ' si pas d'erreur If Not erreur Then ' un lmt de plus dans le tableau lments.Add(lment) End If End While ' affichage tableau non tri

Exemples de classes .NET

86

System.Console.Out.WriteLine("Tableau non tri") For i = 0 To lments.Count - 1 Console.Out.WriteLine(("lments[" & i & "]=" & lments(i).ToString)) Next i ' tri du tableau lments.Sort() ' affichage tableau tri System.Console.Out.WriteLine("Tableau tri") For i = 0 To lments.Count - 1 Console.Out.WriteLine(("lments[" & i & "]=" & lments(i).ToString)) Next i ' Recherche While Not termin ' question Console.Out.Write("Elment cherch (rien pour arrter) : ") ' lecture-vrification rponse rponse = Console.ReadLine().Trim() ' fini ? If rponse.Equals("") Then Exit While End If ' vrification Try lment = [Double].Parse(rponse) erreur = False Catch Console.Error.WriteLine("Saisie incorrecte, recommencez") erreur = True End Try ' si pas d'erreur If Not erreur Then ' on cherche l'lment dans le tableau tri i = lments.BinarySearch(lment) ' Affichage rponse If i >= 0 Then Console.Out.WriteLine(("Trouv en position " & i)) Else Console.Out.WriteLine("Pas dans le tableau") End If End If End While End Sub End Module

Les rsultats d'excution sont les suivants :


Elment (rel) 0 du tableau (rien pour terminer) Elment (rel) 0 du tableau (rien pour terminer) Elment (rel) 0 du tableau (rien pour terminer) Saisie incorrecte, recommencez Elment (rel) 0 du tableau (rien pour terminer) Elment (rel) 0 du tableau (rien pour terminer) Elment (rel) 0 du tableau (rien pour terminer) Tableau non tri lments[0]=10,4 lments[1]=5,2 lments[2]=3,7 lments[3]=15 Tableau tri lments[0]=3,7 lments[1]=5,2 lments[2]=10,4 lments[3]=15 Elment cherch (rien pour arrter) : a Saisie incorrecte, recommencez Elment cherch (rien pour arrter) : 15 Trouv en position 3 Elment cherch (rien pour arrter) : 1 Pas dans le tableau Elment cherch (rien pour arrter) : : 10,4 : 5,2 : a : 3,7 : 15 :

3.6

La classe Hashtable

La classe Hashtable permet d'implmenter un dictionnaire. On peut voir un dictionnaire comme un tableau deux colonnes :

Exemples de classes .NET

87

cl cl1 cl2 ..

valeur valeur1 valeur2 ...

Les cls sont uniques, c.a.d. qu'il ne peut y avoir deux cls indentiques. Les mthodes et proprits principales de la classe Hashtable sont les suivantes :
Public Sub New() Public Overridable Sub Add(ByVal key As Object,ByVal value As Object) Implements IDictionary.Add Public Overridable Sub Remove(ByVal key As Object) Implements IDictionary.Remove Public Overridable Sub Clear() Implements IDictionary.Clear Public Overridable Function ContainsKey(ByVal key As Object) As Boolean Public Overridable Function ContainsValue(ByVal value As Object) As Boolean Public Overridable ReadOnly Property Count As Integer Implements ICollection.Count Public Overridable ReadOnly Property Keys As ICollection Implements IDictionary.Keys Public Overridable ReadOnly Property Values As ICollection Implements IDictionary.Values Public Overridable Default Property Item(ByVal key As Object) As Object Implements IDictionary.Item

cre un dictionnaire vide ajoute une ligne (key,value) dans le dictionnaire o key et value sont des rfrences d'objets. limine du dictionnaire la ligne de cl=key vide le dictionnaire rend vrai (true) si la cl key appartient au dictionnaire. rend vrai (true) si la valeur value appartient au dictionnaire.

proprit : nombre d'lments du dictionnaire (cl,valeur) proprit : collection des cls du dictionnaire proprit : collection des valeurs du dictionnaire proprit indexe : permet de connatre ou de fixer la valeur associe une cl key

Considrons le programme exemple suivant :


' options Option Strict On Option Explicit On ' espaces de noms Imports System Imports System.Collections Module test Sub Main() Dim liste() As [String] = {"jean:20", "paul:18", "mlanie:10", "violette:15"} Dim i As Integer Dim champs As [String]() = Nothing Dim sparateurs() As Char = {":"c} ' remplissage du dictionnaire Dim dico As New Hashtable For i = 0 To liste.Length - 1 champs = liste(i).Split(sparateurs) dico.Add(champs(0), champs(1)) Next i ' nbre d'lments dans le dictionnaire Console.Out.WriteLine(("Le dictionnaire a " & dico.Count & " lments")) ' liste des cls Console.Out.WriteLine("[Liste des cls]") Dim cls As IEnumerator = dico.Keys.GetEnumerator() While cls.MoveNext() Console.Out.WriteLine(cls.Current) End While ' liste des valeurs Console.Out.WriteLine("[Liste des valeurs]")

Exemples de classes .NET

88

Dim valeurs As IEnumerator = dico.Values.GetEnumerator() While valeurs.MoveNext() Console.Out.WriteLine(valeurs.Current) End While ' liste des cls & valeurs Console.Out.WriteLine("[Liste des cls & valeurs]") cls.Reset() While cls.MoveNext() Console.Out.WriteLine(("cl=" & cls.Current.ToString & " valeur=" & dico(cls.Current).ToString)) End While ' on supprime la cl "paul" Console.Out.WriteLine("[Suppression d'une cl]") dico.Remove("paul") ' liste des cls & valeurs Console.Out.WriteLine("[Liste des cls & valeurs]") cls = dico.Keys.GetEnumerator() While cls.MoveNext() Console.Out.WriteLine(("cl=" & cls.Current.ToString & " valeur=" & dico(cls.Current).ToString)) End While ' recherche dans le dictionnaire Dim nomCherch As [String] = Nothing Console.Out.Write("Nom recherch (rien pour arrter) : ") nomCherch = Console.ReadLine().Trim() Dim value As [Object] = Nothing While Not nomCherch.Equals("") If dico.ContainsKey(nomCherch) Then value = dico(nomCherch) Console.Out.WriteLine((nomCherch + "," + CType(value, [String]))) Else Console.Out.WriteLine(("Nom " + nomCherch + " inconnu")) End If ' recherche suivante Console.Out.Write("Nom recherch (rien pour arrter) : ") nomCherch = Console.ReadLine().Trim() End While End Sub End Module

Les rsultats d'excution sont les suivants :


Le dictionnaire a 4 lments [Liste des cls] mlanie paul violette jean [Liste des valeurs] 10 18 15 20 [Liste des cls & valeurs] cl=mlanie valeur=10 cl=paul valeur=18 cl=violette valeur=15 cl=jean valeur=20 [Suppression d'une cl] [Liste des cls & valeurs] cl=mlanie valeur=10 cl=violette valeur=15 cl=jean valeur=20 Nom recherch (rien pour arrter) : paul Nom paul inconnu Nom recherch (rien pour arrter) : mlanie mlanie,10 Nom recherch (rien pour arrter) :

Le programme utilise galement un objet Ienumerator pour parcourir les collections de cls et de valeurs du dictionnaire de type ICollection (cf ci-dessus les proprits Keys et Values). Une collection est un ensemble d'objets qu'on peut parcourir. L'interface ICollection est dfinie comme suit :

Exemples de classes .NET

89

La proprit Count nous permet de connatre le nombre d'lments de la collection. L'interface ICollection drive de l'interface IEnumerable :

Cette interface n'a qu'une mthode GetEnumerator qui nous permet d'obtenir un objet de type IEnumerator :

La mthode GetEnumerator() d'une collection ICollection nous permet de parcourir la collection avec les mthodes suivantes :
MoveNext

positionne sur l'lment suivant de la collection. Rend vrai (true) si cet lment existe, faux (false) sinon. Le premier MoveNext positionne sur le 1er lment. L'lment "courant" de la collection est alors disponible dans la proprit Current de l'numrateur proprit : lment courant de la collection repositionne l'numrateur en dbut de collection, c.a.d. avant le 1er lment.

Current Reset

La structure d'itration sur les lments d'une collection (ICollection) C est donc la suivante :
' dfinir la collection dim C as ICollection C=... ' obtenir un numrateur de cette collection

Exemples de classes .NET

90

dim itrateur as IEnumerator=C.GetEnumerator(); ' parcourir la collection avec cet numrateur while(itrateur.MoveNext()) ' on a un lment courant ' exploiter itrateur.Current end while

3.7

La classe StreamReader

La classe StreamReader permet de lire le contenu d'un fichier. Voici quelques-unes de ses proprits et mthodes :
Public Sub New(ByVal path As String)

ouvre un flux partir du fichier path. Une exception est lance si celui-ci n'existe pas ferme le flux lit une ligne du flux ouvert lit le reste du flux depuis la position courante

Overrides Public Sub Close() Overrides Public Function ReadLine() As String Overrides Public Function ReadToEnd() As String

Voici un exemple :
' options Option Strict On Option Explicit On ' espaces de noms Imports System Imports System.Collections Imports System.IO Module test Sub Main() Dim ligne As String = Nothing Dim fluxInfos As StreamReader = Nothing ' lecture contenu du fichier Try fluxInfos = New StreamReader("infos.txt") ligne = fluxInfos.ReadLine() While Not (ligne Is Nothing) System.Console.Out.WriteLine(ligne) ligne = fluxInfos.ReadLine() End While Catch e As Exception System.Console.Error.WriteLine("L'erreur suivante s'est produite : " & e.ToString) Finally Try fluxInfos.Close() Catch End Try End Try End Sub End Module

et ses rsultats d'excution :


dos>more infos.txt 12620:0:0 13190:0,05:631 15640:0,1:1290,5 24740:0,15:2072,5 31810:0,2:3309,5 39970:0,25:4900 48360:0,3:6898,5 55790:0,35:9316,5 92970:0,4:12106 127860:0,45:16754,5 151250:0,5:23147,5 172040:0,55:30710 195000:0,6:39312 0:0,65:49062 dos>file1 12620:0:0 13190:0,05:631

Exemples de classes .NET

91

15640:0,1:1290,5 24740:0,15:2072,5 31810:0,2:3309,5 39970:0,25:4900 48360:0,3:6898,5 55790:0,35:9316,5 92970:0,4:12106 127860:0,45:16754,5 151250:0,5:23147,5 172040:0,55:30710 195000:0,6:39312 0:0,65:49062

3.8

La classe StreamWriter

La classe StreamWriter permet d'crire dans fichier. Voici quelques-unes de ses proprits et mthodes :
Public Sub New(ByVal path As String)

ouvre un flux d'criture partir du fichier path. Une exception est lance si celui-ci ne peut tre cr si gal vrai, l'criture dans le flux ne passe pas par l'intermdiaire d'une mmoire tampon sinon l'criture dans le flux n'est pas immdiate : il y a d'abord criture dans une mmoire tampon puis dans le flux lorsque la mmoire tampon est pleine. Par dfaut c'est le mode bufferis qui est utilis. Il convient bien pour les flux fichier mais gnralement pas pour les flux rseau. pour fixer ou connatre la marque de fin de ligne utiliser par la mthode WriteLine ferme le flux crit une ligne dans le flux d'criture crit la mmoire tampon dans le flux

Public Overridable Property AutoFlush As Boolean

Public Overridable Property NewLine As String

Overrides Public Sub Close() Overloads Public Overridable Sub WriteLine(ByVal value As String) Overrides Public Sub Flush()

Considrons l'exemple suivant :


' options Option Strict On Option Explicit On ' espaces de noms Imports System Imports System.Collections Imports System.IO Module test Sub Main() Dim ligne As String = Nothing ' une ligne de texte Dim fluxInfos As StreamWriter = Nothing ' le fichier texte Try ' cration du fichier texte fluxInfos = New StreamWriter("infos.txt") ' lecture ligne tape au clavier Console.Out.Write("ligne (rien pour arrter) : ") ligne = Console.In.ReadLine().Trim() ' boucle tant que la ligne saisie est non vide While ligne <> "" ' criture ligne dans fichier texte fluxInfos.WriteLine(ligne) ' lecture nouvelle ligne au clavier Console.Out.Write("ligne (rien pour arrter) : ") ligne = Console.In.ReadLine().Trim() End While Catch e As Exception System.Console.Error.WriteLine("L'erreur suivante s'est produite : " & e.ToString) Finally ' fermeture fichier Try fluxInfos.Close() Catch

Exemples de classes .NET

92

End Try End Try End Sub End Module

et les rsultats d'excution :


dos>file2 ligne (rien ligne (rien ligne (rien ligne (rien pour pour pour pour arrter) arrter) arrter) arrter) : ligne1 : ligne2 : ligne3 :

dos>more infos.txt ligne1 ligne2 ligne3

3.9

La classe Regex

La classe Regex permet l'utilisation d'expression rgulires. Celles-ci permettent de tester le format d'une chane de caractres. Ainsi on peut vrifier qu'une chane reprsentant une date est bien au format jj/mm/aa. On utilise pour cela un modle et on compare la chane ce modle. Ainsi dans cet exemple, j m et a doivent tre des chiffres. Le modle d'un format de date valide est alors "\d\d/\d\d/\d\d" o le symbole \d dsigne un chiffre. Les symboles utilisables dans un modle sont les suivants (documentation Microsoft) :

Exemples de classes .NET

93

Caractre
\ ^ $ * + ? . (modle)

x|y {n} {n,} {n,m} [xyz] [^xyz] [a-z] [^m-z] \b \B \d \D \f \n \r \s \S \t \v \w \W \num \n

\xn

Description Marque le caractre suivant comme caractre spcial ou littral. Par exemple, "n" correspond au caractre "n". "\n" correspond un caractre de nouvelle ligne. La squence "\\" correspond "\", tandis que "\(" correspond "(". Correspond au dbut de la saisie. Correspond la fin de la saisie. Correspond au caractre prcdent zro fois ou plusieurs fois. Ainsi, "zo*" correspond "z" ou "zoo". Correspond au caractre prcdent une ou plusieurs fois. Ainsi, "zo+" correspond "zoo", mais pas "z". Correspond au caractre prcdent zro ou une fois. Par exemple, "a?ve?" correspond "ve" dans "lever". Correspond tout caractre unique, sauf le caractre de nouvelle ligne. Recherche le modle et mmorise la correspondance. La sous-chane correspondante peut tre extraite de la collection Matches obtenue, l'aide d'Item [0]...[n]. Pour trouver des correspondances avec des caractres entre parenthses ( ), utilisez "\(" ou "\)". Correspond soit x soit y. Par exemple, "z|foot" correspond "z" ou "foot". "(z|f)oo" correspond "zoo" ou "foo". n est un nombre entier non ngatif. Correspond exactement n fois le caractre. Par exemple, "o{2}" ne correspond pas "o" dans "Bob," mais aux deux premiers "o" dans "fooooot". n est un entier non ngatif. Correspond au moins n fois le caractre. Par exemple, "o{2,}" ne correspond pas "o" dans "Bob", mais tous les "o" dans "fooooot". "o{1,}" quivaut "o+" et "o{0,}" quivaut "o*". m et n sont des entiers non ngatifs. Correspond au moins n et au plus m fois le caractre. Par exemple, "o{1,3}" correspond aux trois premiers "o" dans "foooooot" et "o{0,1}" quivaut "o?". Jeu de caractres. Correspond l'un des caractres indiqus. Par exemple, "[abc]" correspond "a" dans "plat". Jeu de caractres ngatif. Correspond tout caractre non indiqu. Par exemple, "[^abc]" correspond "p" dans "plat". Plage de caractres. Correspond tout caractre dans la srie spcifie. Par exemple, "[a-z]" correspond tout caractre alphabtique minuscule compris entre "a" et "z". Plage de caractres ngative. Correspond tout caractre ne se trouvant pas dans la srie spcifie. Par exemple, "[^mz]" correspond tout caractre ne se trouvant pas entre "m" et "z". Correspond une limite reprsentant un mot, autrement dit, la position entre un mot et un espace. Par exemple, "er\b" correspond "er" dans "lever", mais pas "er" dans "verbe". Correspond une limite ne reprsentant pas un mot. "en*t\B" correspond "ent" dans "bien entendu". Correspond un caractre reprsentant un chiffre. quivaut [0-9]. Correspond un caractre ne reprsentant pas un chiffre. quivaut [^0-9]. Correspond un caractre de saut de page. Correspond un caractre de nouvelle ligne. Correspond un caractre de retour chariot. Correspond tout espace blanc, y compris l'espace, la tabulation, le saut de page, etc. quivaut "[ \f\n\r\t\v]". Correspond tout caractre d'espace non blanc. quivaut "[^ \f\n\r\t\v]". Correspond un caractre de tabulation. Correspond un caractre de tabulation verticale. Correspond tout caractre reprsentant un mot et incluant un trait de soulignement. quivaut "[A-Za-z0-9_]". Correspond tout caractre ne reprsentant pas un mot. quivaut "[^A-Za-z0-9_]". Correspond num, o num est un entier positif. Fait rfrence aux correspondances mmorises. Par exemple, "(.)\1" correspond deux caractres identiques conscutifs. Correspond n, o n est une valeur d'chappement octale. Les valeurs d'chappement octales doivent comprendre 1, 2 ou 3 chiffres. Par exemple, "\11" et "\011" correspondent tous les deux un caractre de tabulation. "\0011" quivaut "\001" & "1". Les valeurs d'chappement octales ne doivent pas excder 256. Si c'tait le cas, seuls les deux premiers chiffres seraient pris en compte dans l'expression. Permet d'utiliser les codes ASCII dans des expressions rgulires. Correspond n, o n est une valeur d'chappement hexadcimale. Les valeurs d'chappement hexadcimales doivent comprendre deux chiffres obligatoirement. Par exemple, "\x41" correspond "A". "\x041" quivaut "\x04" & "1". Permet d'utiliser les codes ASCII dans des expressions rgulires.

Un lment dans un modle peut tre prsent en 1 ou plusieurs exemplaires. Considrons quelques exemples autour du symbole \d qui reprsente 1 chiffre :
\d \d? \d* \d+

signification un chiffre 0 ou 1 chiffre 0 ou davantage de chiffres 1 ou davantage de chiffres Exemples de classes .NET

modle

94

\d{2} \d{3,} \d{5,7}

2 chiffres au moins 3 chiffres entre 5 et 7 chiffres

Imaginons maintenant le modle capable de dcrire le format attendu pour une chane de caractres : chane recherche
une date au format jj/mm/aa une heure au format hh:mm:ss un nombre entier non sign un suite d'espaces ventuellement vide un nombre entier non sign qui peut tre prcd ou suivi d'espaces un nombre entier qui peut tre sign et prcd ou suivi d'espaces un nombre rel non sign qui peut tre prcd ou suivi d'espaces un nombre rel qui peut tre sign et prcd ou suivi d'espaces une chane contenant le mot juste

modle \d{2}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \d+ \s* \s*\d+\s* \s*[+|-]?\s*\d+\s* \s*\d+(.\d*)?\s* \s*[+|]?\s*\d+(.\d*)?\s* \bjuste\b

On peut prciser o on recherche le modle dans la chane : modle


^modle modle$ ^modle$ modle

signification le modle commence la chane le modle finit la chane le modle commence et finit la chane le modle est cherch partout dans la chane en commenant par le dbut de celle-ci. chane recherche
se terminant par un point d'exclamation se terminant par un point commenant par la squence // ne comportant qu'un mot ventuellement suivi ou prcd d'espaces ne comportant deux mot ventuellement suivis ou prcds d'espaces contenant le mot secret

une une une une une une

chane chane chane chane chane chane

modle !$ \.$ ^// ^\s*\w+\s*$ ^\s*\w+\s*\w+\s*$ \bsecret\b

Les sous-ensembles d'un modle peuvent tre "rcuprs". Ainsi non seulement, on peut vrifier qu'une chane correspond un modle particulier mais on peut rcuprer dans cette chane les lments correspondant aux sous-ensembles du modle qui ont t entours de parenthses. Ainsi si on analyse une chane contenant une date jj/mm/aa et si on veut de plus rcuprer les lments jj, mm, aa de cette date on utilisera le modle (\d\d)/(\d\d)/(\d\d).

3.9.1

Vrifier qu'une chane correspond un modle donn

Un objet de type Regex se construit de la faon suivante :


Public Sub New(ByVal pattern As String)

Le constructeur cre un objet "expression rgulire" partir d'un modle pass en paramtre (pattern). Une fois l'expression rgulire modle construit, on peut la comparer des chanes de caractres avec la mthode IsMatch :
Overloads Public Function IsMatch(ByVal input As String) As Boolean

IsMatch rend vrai si la chane input correspond au modle de l'expression rgulire. Voici un exemple :
' options Option Strict On Option Explicit On ' espaces de noms Imports System Imports System.Collections Imports System.Text.RegularExpressions Module regex1 Sub Main() ' une expression rgulire modle Dim modle1 As String = "^\s*\d+\s*$" Dim regex1 As New Regex(modle1) ' comparer un exemplaire au modle

Exemples de classes .NET

95

Dim exemplaire1 As String = " If regex1.IsMatch(exemplaire1) affiche(("[" + exemplaire1 + Else affiche(("[" + exemplaire1 + End If Dim exemplaire2 As String = " If regex1.IsMatch(exemplaire2) affiche(("[" + exemplaire2 + Else affiche(("[" + exemplaire2 + End If End Sub ' affiche Sub affiche(ByVal msg As String) Console.Out.WriteLine(msg) End Sub End Module

123 " Then "] correspond au modle [" + modle1 + "]")) "] ne correspond pas au modle [" + modle1 + "]")) 123a " Then "] correspond au modle [" + modle1 + "]")) "] ne correspond pas au modle [" + modle1 + "]"))

et les rsultats d'excution :


dos>vbc /r:system.dll regex1.vb Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573 dos>regex1 [ 123 ] correspond au modle [^\s*\d+\s*$] [ 123a ] ne correspond pas au modle [^\s*\d+\s*$]

3.9.2

Trouver tous les lments d'une chane correspondant un modle

La mthode Matches
Overloads Public Function Matches(ByVal input As String) As MatchCollection

rend une collection d'lments de la chane input correspondant au modle comme le montre l'exemple suivant :
' options Option Strict On Option Explicit On ' espaces de noms Imports System Imports System.Collections Imports System.Text.RegularExpressions Module regex2 Sub Main() ' plusieurs occurrences du modle dans l'exemplaire Dim modle2 As String = "\d+" Dim regex2 As New Regex(modle2) ' Dim exemplaire3 As String = " 123 456 789 " Dim rsultats As MatchCollection = regex2.Matches(exemplaire3) affiche(("Modle=[" + modle2 + "],exemplaire=[" + exemplaire3 + "]")) affiche(("Il y a " & rsultats.Count & " occurrences du modle dans l'exemplaire ")) Dim i As Integer For i = 0 To rsultats.Count - 1 affiche((rsultats(i).Value & " en position " & rsultats(i).Index)) Next i End Sub 'affiche Sub affiche(ByVal msg As String) Console.Out.WriteLine(msg) End Sub End Module

La classe MatchCollection a une proprit Count qui est le nombre d'lments de la collection. Si rsultats est un objet MatchCollection, rsultats(i) est l'lment i de cette collection et est de type Match. Dans notre exemple, rsultats est l'ensemble des lments de la chane exemplaire3 correspondant au modle modle2 et rsultats(i) l'un de ces lments. La classe Match a deux proprits qui nous intressent ici : Value : la valeur de l'objet Match donc l'lment correspondant au modle Index : la position o l'lment a t trouv dans la chane explore Exemples de classes .NET 96

Les rsultats de l'excution du programme prcdent :


dos>vbc /r:system.dll regex2.vb Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4p our Microsoft (R) .NET Framework version 1.1.4322.573 dos>regex2 Modle=[\d+],exemplaire=[ 123 456 789 ] Il y a 3 occurrences du modle dans l'exemplaire 123 en position 2 456 en position 7 789 en position 12

3.9.3

Rcuprer des parties d'un modle

Des sous-ensembles d'un modle peuvent tre "rcuprs". Ainsi non seulement, on peut vrifier qu'une chane correspond un modle particulier mais on peut rcuprer dans cette chane les lments correspondant aux sous-ensembles du modle qui ont t entours de parenthses. Ainsi si on analyse une chane contenant une date jj/mm/aa et si on veut de plus rcuprer les lments jj, mm, aa de cette date on utilisera le modle (\d\d)/(\d\d)/(\d\d). Examinons l'exemple suivant :
' options Option Strict On Option Explicit On ' espaces de noms Imports System Imports System.Collections Imports System.Text.RegularExpressions Module regex2 Sub Main() ' capture d'lments dans le modle Dim modle3 As String = "(\d\d):(\d\d):(\d\d)" Dim regex3 As New Regex(modle3) Dim exemplaire4 As String = "Il est 18:05:49" ' vrification modle Dim rsultat As Match = regex3.Match(exemplaire4) If rsultat.Success Then ' l'exemplaire correspond au modle affiche(("L'exemplaire [" + exemplaire4 + "] correspond au modle [" + modle3 + "]")) ' on affiche les groupes Dim i As Integer For i = 0 To rsultat.Groups.Count - 1 affiche(("groupes[" & i & "]=[" & rsultat.Groups(i).Value & "] en position " & rsultat.Groups(i).Index)) Next i Else ' l'exemplaire ne correspond pas au modle affiche(("L'exemplaire[" + exemplaire4 + " ne correspond pas au modle [" + modle3 + "]")) End If End Sub 'affiche Sub affiche(ByVal msg As String) Console.Out.WriteLine(msg) End Sub End Module

L'excution de ce programme produit les rsultats suivants :


dos>vbc /r:system.dll regex3.vb Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 dos>regex3 L'exemplaire [Il est 18:05:49] correspond au modle [(\d\d):(\d\d):(\d\d)] groupes[0]=[18:05:49] en position 7 groupes[1]=[18] en position 7 groupes[2]=[05] en position 10 groupes[3]=[49] en position 13

La nouveaut se trouve dans la partie de code suivante :


' vrification modle Dim rsultat As Match = regex3.Match(exemplaire4) If rsultat.Success Then ' l'exemplaire correspond au modle affiche(("L'exemplaire [" + exemplaire4 + "] correspond au modle [" + modle3 + "]")) ' on affiche les groupes

Exemples de classes .NET

97

Dim i As Integer For i = 0 To rsultat.Groups.Count - 1 affiche(("groupes[" & i & "]=[" & rsultat.Groups(i).Value & "] en position " & rsultat.Groups(i).Index)) Next i Else

La chane exemplaire4 est compare au modle regex3 au travers de la mthode Match. Celle-ci rend un objet Match dj prsent. Nous utilisons ici deux nouvelles proprits de cette classe : Success : indique s'il y a eu correspondance Groups : collection o o Groups[0] correspond la partie de la chane correspondant au modle o Groups[i] (i>=1) correspond au groupe de parenthses n i Si rsultat est de type Match, rsultats.Groups est de type GroupCollection et rsultats.Groups[i] de type Group. La classe Group a deux proprits que nous utilisons ici : Value : la valeur de l'objet Group qui l'lment correspondant au contenu d'une paraenthse Index : la position o l'lment a t trouv dans la chane explore

3.9.4

Un programme d'apprentissage

Trouver l'expression rgulire qui nous permet de vrifier qu'une chane correspond bien un certain modle est parfois un vritable dfi. Le programme suivant permet de s'entraner. Il demande un modle et une chane et indique alors si la chane correspond ou non au modle.
' options Option Strict On Option Explicit On ' espaces de noms Imports System Imports System.Collections Imports System.Text.RegularExpressions Imports Microsoft.visualbasic Module regex4 Sub Main() ' une expression rgulire modle Dim modle, chaine As String Dim regex As Regex = Nothing Dim rsultats As MatchCollection ' on demande l'utilisateur les modles et les exemplaires comparer celui-ci Dim termin1 As Boolean = False Do While Not termin1 Dim erreur As Boolean = True Do While Not termin1 And erreur ' on demande le modle Console.Out.Write("Tapez le modle tester ou fin pour arrter :") modle = Console.In.ReadLine() ' fini ? If modle.Trim().ToLower() = "fin" Then termin1 = True Else ' on cre l'expression rgulire Try regex = New Regex(modle) erreur = False Catch ex As Exception Console.Error.WriteLine(("Erreur : " + ex.Message)) End Try End If Loop ' fini ? If termin1 Then Exit Sub ' on demande l'utilisateur les exemplaires comparer au modle Dim termin2 As Boolean = False Do While Not termin2 Console.Out.Write(("Tapez la chane comparer au modle [" + modle + "] ou fin pour arrter :")) chaine = Console.In.ReadLine() ' fini ? If chaine.Trim().ToLower() = "fin" Then termin2 = True Else ' on fait la comparaison rsultats = regex.Matches(chaine)

Exemples de classes .NET

98

' succs ? If rsultats.Count = 0 Then Console.Out.WriteLine("Je n'ai pas trouv de correspondances") Else ' on affiche les lments correspondant au modle Dim i As Integer For i = 0 To rsultats.Count - 1 Console.Out.WriteLine(("J'ai trouv la correspondance [" & rsultats(i).Value & "] en position " & rsultats(i).Index)) ' des sous-lments If rsultats(i).Groups.Count <> 1 Then Dim j As Integer For j = 1 To (rsultats(i).Groups.Count) - 1 Console.Out.WriteLine((ControlChars.Tab & "sous-lment [" & rsultats(i).Groups(j).Value & "] en position " & rsultats(i).Groups(j).Index)) Next j End If Next i End If End If Loop Loop End Sub 'affiche Sub affiche(ByVal msg As String) Console.Out.WriteLine(msg) End Sub End Module

Voici un exemple d'excution :


Tapez le modle tester ou fin pour arrter :\d+ Tapez la chane comparer au modle [\d+] ou fin pour arrter :123 456 789 J'ai trouv la correspondance [123] en position 0 J'ai trouv la correspondance [456] en position 4 J'ai trouv la correspondance [789] en position 8 Tapez la chane comparer au modle [\d+] ou fin pour arrter :fin Tapez le modle tester ou fin pour arrter :(\d\d):(\d\d) Tapez la chane comparer au modle [(\d\d):(\d\d)] ou fin pour arrter :14:15 abcd 17:18 xyzt J'ai trouv la correspondance [14:15] en position 0 sous-lment [14] en position 0 sous-lment [15] en position 3 J'ai trouv la correspondance [17:18] en position 11 sous-lment [17] en position 11 sous-lment [18] en position 14 Tapez la chane comparer au modle [(\d\d):(\d\d)] ou fin pour arrter :fin Tapez le modle tester ou fin pour arrter :^\s*\d+\s*$ Tapez la chane comparer au modle [^\s*\d+\s*$] ou fin pour arrter : 1456 J'ai trouv la correspondance [ 1456] en position 0 Tapez la chane comparer au modle [^\s*\d+\s*$] ou fin pour arrter :fin Tapez le modle tester ou fin pour arrter :^\s*(\d+)\s*$ Tapez la chane comparer au modle [^\s*(\d+)\s*$] ou fin pour arrter :1456 J'ai trouv la correspondance [1456] en position 0 sous-lment [1456] en position 0 Tapez la chane comparer au modle [^\s*(\d+)\s*$] ou fin pour arrter :abcd 1 456 Je n'ai pas trouv de correspondances Tapez la chane comparer au modle [^\s*(\d+)\s*$] ou fin pour arrter :fin Tapez le modle tester ou fin pour arrter :fin

3.9.5

La mthode Split

Nous avons dj rencontr cette mthode dans la classe String :


Overloads Public Function Split(ByVal ParamArray separator() As Char) As String()

La chane est vue comme une suite de champs spars par les caractres prsents dans le tableau [separator]. Le rsultat est le tableau de ces champs. Le sparateur de champs de lachane est ici un des caractres du tableau separator. La mthode Split de la classe Regex nous permet d'exprimer le sparateur en fonction d'un modle :
Overloads Public Function Split(ByVal input As String) As String()

Exemples de classes .NET

99

La chane [input] est dcompose en champs, ceux-ci tant spars par un sparateur correspondant au modle de l'objet Regex courant. Supposons par exemple que nous ayons dans un fichier texte des lignes de la forme champ1, champ2, .., champn. Les champs sont spars par une virgule mais celle-ci peut tre prcde ou suivie d'espaces. La mthode Split de la classe string ne convient alors pas. Celle de la mthode RegEx apporte la solution. Si ligne est la ligne lue, les champs pourront tre obtenus par
dim champs() as string=new Regex("s*,\s*").Split(ligne)

comme le montre l'exemple suivant :


' options Option Strict On Option Explicit On ' espaces de noms Imports System Imports System.Text.RegularExpressions Module regex5 Sub Main() ' une ligne Dim ligne As String = "abc , def , ghi" ' un modle Dim modle As New Regex("\s*,\s*") ' dcomposition de ligne en champs Dim champs As String() = modle.Split(ligne) ' affichage Dim i As Integer For i = 0 To champs.Length - 1 Console.Out.WriteLine(("champs[" & i & "]=[" & champs(i) & "]")) Next i End Sub End Module

Les rsultats d'excution :


dos>vbc /r:system.dll regex5.vb Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 dos>regex5 champs[0]=[abc] champs[1]=[def] champs[2]=[ghi]

3.10

Les classes BinaryReader et BinaryWriter

Les classes BinaryReader et BinaryWriter servent lire et crire des fichiers binaires. Considrons l'application suivante. On veut crire un programme qui s'appellerait de la faon suivante :
// // // // // syntaxe pg texte bin on lit un fichier texte (texte) et on range son contenu dans un fichier binaire le fichier texte a des lignes de la forme nom : age qu'on rangera dans une structure string, int

Le fichier texte a le contenu suivant :


paul : 10 helene : 15 jacques : 11 sylvain : 12

Le programme est le suivant :


' options Option Strict On Option Explicit On ' espaces de noms Imports System Imports System.Text.RegularExpressions Imports System.IO

Exemples de classes .NET

100

' BinaryWriter() Module bw1 ' syntaxe pg texte bin ' on lit un fichier texte (texte) et on range son contenu dans un ' fichier binaire ' le fichier texte a des lignes de la forme nom : age ' qu'on rangera dans une structure string, int Sub Main(ByVal arguments() As String) ' il faut 2 arguments Dim nbArgs As Integer = arguments.Length If nbArgs <> 2 Then Console.Error.WriteLine("syntaxe : pg texte binaire") Environment.Exit(1) End If ' ouverture du fichier texte en lecture Dim input As StreamReader = Nothing Try input = New StreamReader(arguments(0)) Catch ex As Exception Console.Error.WriteLine("Erreur d'ouverture du fichier Environment.Exit(2) End Try ' ouverture du fichier binaire en criture Dim output As BinaryWriter = Nothing Try output = New BinaryWriter(New FileStream(arguments(1), Catch ex As Exception Console.Error.WriteLine("Erreur d'ouverture du fichier Environment.Exit(3) End Try ' lecture fichier texte - criture fichier binaire ' ligne du fichier texte Dim ligne As String ' le sparateur des champs de la ligne Dim sparateur As New Regex("\s*:\s*") ' le modle de l'ge Dim modAge As New Regex("\s*\d+\s*") Dim numLigne As Integer = 0 Dim traitementFini As Boolean Dim champs As String() ligne = input.ReadLine() While Not (ligne Is Nothing) traitementFini = False ' ligne vide ? If ligne.Trim() = "" Then traitementFini = True End If ' une ligne de plus If Not traitementFini Then numLigne += 1 ' une ligne nom : age champs = sparateur.Split(ligne) ' il nous faut 2 champs If champs.Length <> 2 Then Console.Error.WriteLine(("La ligne n " & numLigne nombre de champs incorrect")) ' ligne suivante traitementFini = True End If End If If Not traitementFini Then ' le second champ doit tre un entier >=0 If Not modAge.IsMatch(champs(1)) Then Console.Error.WriteLine(("La ligne n " & numLigne incorrect")) ' ligne suivante traitementFini = True End If ' on crit les donnes dans le fichier binaire output.Write(champs(0)) output.Write(Integer.Parse(champs(1))) End If 'ligne suivante ligne = input.ReadLine() End While ' fermeture des fichiers input.Close() output.Close() End Sub

" & arguments(0) & "(" & ex.Message & ")")

FileMode.Create, FileAccess.Write)) " & arguments(1) & "(" & ex.Message & ")")

& " du fichier " & arguments(0) & " a un

& " du fichier " & arguments(0) & " a un ge

Exemples de classes .NET

101

End Module

Attardons-nous sur les oprations concernant la classe BinaryWriter : l'objet BinaryWriter est ouvert par l'opration
output = New BinaryWriter(New FileStream(arguments(1), FileMode.Create, FileAccess.Write))

L'argument du constructeur doit tre un flux (Stream). Ici c'est un flux construit partir d'un fichier (FileStream) dont on donne : o le nom o l'opration faire, ici FileMode.Create pour crer le fichier o le type d'accs, ici FileAccess.Write pour un accs en criture au fichier l'opration d'criture
' on crit les donnes dans le fichier binaire output.Write(champs(0)) output.Write(Integer.Parse(champs(1)))

La classe BinaryWriter dispose de diffrentes mthodes Write surcharges pour crire les diffrents types de donnes simples l'opration de fermeture du flux

output.Close()

Les rsultats de l'excution prcdente vont nous tre donns par le programme qui suit. Celui-ci accepte galement deux arguments :
' ' ' ' syntaxe pg bin texte on lit un fichier binaire bin et on range son contenu dans un fichier texte (texte) le fichier binaire a une structure string, int le fichier texte a des lignes de la forme nom : age

On fait donc l'opration inverse. On lit un fichier binaire pour crer un fichier texte. Si le fichier texte produit est identique au fichier originel on saura que la conversion texte --> binaire --> texte s'est bien passe. Le code est le suivant :
' options Option Strict On Option Explicit On ' espaces de noms Imports System Imports System.Text.RegularExpressions Imports System.IO Module br1 ' syntaxe pg bin texte ' on lit un fichier binaire bin et on range son contenu dans un fichier texte (texte) ' le fichier binaire a une structure string, int ' le fichier texte a des lignes de la forme nom : age Sub Main(ByVal arguments() As String) ' il faut 2 arguments Dim nbArgs As Integer = arguments.Length If nbArgs <> 2 Then Console.Error.WriteLine("syntaxe : pg binaire texte") Environment.Exit(1) End If ' ouverture du fichier binaire en lecture Dim dataIn As BinaryReader = Nothing Try dataIn = New BinaryReader(New FileStream(arguments(0), FileMode.Open, FileAccess.Read)) Catch ex As Exception Console.Error.WriteLine("Erreur d'ouverture du fichier " & arguments(0) & "(" & ex.Message & ")") Environment.Exit(2) End Try ' ouverture du fichier texte en criture Dim dataOut As StreamWriter = Nothing Try dataOut = New StreamWriter(arguments(1)) dataOut.AutoFlush = True Catch ex As Exception Console.Error.WriteLine("Erreur d'ouverture du fichier " & arguments(1) & "(" & ex.Message & ")")

Exemples de classes .NET

102

Environment.Exit(3) End Try ' lecture fichier binaire - criture fichier texte Dim nom As String ' nom d'une personne Dim age As Integer ' son ge ' boucle d'exploitation du fichier binaire While True ' lecture nom Try nom = dataIn.ReadString() Catch ' fin du fichier Exit Sub End Try ' lecture age Try age = dataIn.ReadInt32() Catch Console.Error.WriteLine("Le fichier " & arguments(0) + " ne semble pas avoir un format correct") Exit Sub End Try ' criture dans fichier texte dataOut.WriteLine(nom & ":" & age) System.Console.WriteLine(nom & ":" & age) End While ' on ferme tout dataIn.Close() dataOut.Close() End Sub End Module

Attardons-nous sur les oprations concernant la classe BinaryReader : l'objet BinaryReader est ouvert par l'opration
dataIn = New BinaryReader(New FileStream(arguments(0), FileMode.Open, FileAccess.Read))

L'argument du constructeur doit tre un flux (Stream). Ici c'est un flux construit partir d'un fichier (FileStream) dont on donne : le nom l'opration faire, ici FileMode.Open pour ouvrir un fichier existant le type d'accs, ici FileAccess.Read pour un accs en lecture au fichier l'opration de lecture
nom = dataIn.ReadString() age = dataIn.ReadInt32()

La classe BinaryReader dispose de diffrentes mthodes ReadXX pour lire les diffrents types de donnes l'opration de fermeture du flux

simples

dataIn.Close()

Si on excute les deux programmes la chane transformant personnes.txt en personnes.bin puis personnes.bin en personnes.txt2 on a :
dos>more personnes.txt paul : 10 helene : 15 jacques : 11 sylvain : 12 dos>more personnes.txt2 paul:10 helene:15 jacques:11 sylvain:12 dos>dir 29/04/2002 29/04/2002 29/04/2002 18:19 18:19 18:20 54 personnes.txt 44 personnes.bin 44 personnes.txt2

Exemples de classes .NET

103

4.

Interfaces graphiques avec VB.NET et VS.NET

Nous nous proposons ici de montrer comment construire des interfaces graphiques avec VB.NET. Nous voyons tout d'abord quelles sont les classes de base de la plate-forme .NET qui nous permettent de construire une interface graphique. Nous n'utilisons dans un premier temps aucun outil de gnration automatique. Puis nous utiliserons Visual Studio.NET (VS.NET), un outil de dveloppement de Microsoft facilitant le dveloppement d'applications avec les langages .NET et notamment la construction d'interfaces graphiques. La version VS.NET utilise est la version anglaise.

4.1 4.1.1

Les bases des interfaces graphiques Une fentre simple

Considrons le code suivant :


' options Option Strict On Option Explicit On ' espaces de noms Imports System Imports System.Drawing Imports System.Windows.Forms ' la classe formulaire Public Class Form1 Inherits Form ' le constructeur Public Sub New() ' titre de la fentre Me.Text = "Mon premier formulaire" ' dimensions de la fentre Me.Size = New System.Drawing.Size(300, 100) End Sub ' fonction de test Public Shared Sub Main(ByVal args() As String) ' on affiche le formulaire Application.Run(New Form1) End Sub End Class

Le code prcdent est compil puis excut


dos>vbc /r:system.dll /r:system.drawing.dll /r:system.windows.forms.dll frm1.vb dos>frm1

L'excution affiche la fentre suivante :

Une interface graphique drive en gnral de la classe de base System.Windows.Forms.Form :


Public Class Form1 Inherits Form

Interfaces graphiques

104

La classe de base Form dfinit une fentre de base avec des boutons de fermeture, agrandissement/rduction, une taille ajustable, ... et gre les vnements sur ces objets graphiques. Ici nous spcialisons la classe de base en lui fixant un titre et ses largeur (300) et hauteur (100). Ceci est fait dans son constructeur :
Public Sub New() ' titre de la fentre Me.Text = "Mon premier formulaire" ' dimensions de la fentre Me.Size = New System.Drawing.Size(300, 100) End Sub

Le titre de la fentre est fixe par la proprit Text et les dimensions par la proprit Size. Size est dfini dans l'espace de noms System.Drawing et est une structure. La procdure Main lance l'application graphique de la faon suivante :
Application.Run(New Form1)

Un formulaire de type Form1 est cr et affich, puis l'application se met l'coute des vnements qui se produisent sur le formulaire (clics, dplacements de souris, ...) et fait excuter ceux que le formulaire gre. Ici, notre formulaire ne gre pas d'autre vnement que ceux grs par la classe de base Form (clics sur boutons fermeture, agrandissement/rduction, changement de taille de la fentre, dplacement de la fentre, ...).

4.1.2

Un formulaire avec bouton

Ajoutons maintenant un bouton notre fentre :


' options Option Strict On Option Explicit On ' espaces de noms Imports System Imports System.Drawing Imports System.Windows.Forms ' la classe formulaire Public Class Form1 Inherits Form ' attributs Private cmdTest As Button ' le constructeur Public Sub New() ' le titre Me.Text = "Mon premier formulaire" ' les dimensions Me.Size = New System.Drawing.Size(300, 100) ' un bouton ' cration Me.cmdTest = New Button ' position cmdTest.Location = New System.Drawing.Point(110, 20) ' taille cmdTest.Size = New System.Drawing.Size(80, 30) ' libell cmdTest.Text = "Test" ' gestionnaire d'vt AddHandler cmdTest.Click, AddressOf cmdTest_Click ' ajout bouton au formulaire Me.Controls.Add(cmdTest) End Sub ' gestionnaire d'vnement Private Sub cmdTest_Click(ByVal sender As Object, ByVal evt As EventArgs) ' il y a eu un clic sur le bouton - on le dit MessageBox.Show("Clic sur bouton", "Clic sur bouton", MessageBoxButtons.OK, MessageBoxIcon.Information) End Sub ' fonction de test Public Shared Sub Main(ByVal args() As String) ' on affiche le formulaire Application.Run(New Form1) End Sub End Class

Nous avons rajout au formulaire un bouton :


Interfaces graphiques

105

' un bouton ' cration Me.cmdTest = New Button ' position cmdTest.Location = New System.Drawing.Point(110, 20) ' taille cmdTest.Size = New System.Drawing.Size(80, 30) ' libell cmdTest.Text = "Test" ' gestionnaire d'vt AddHandler cmdTest.Click, AddressOf cmdTest_Click ' ajout bouton au formulaire Me.Controls.Add(cmdTest)

La proprit Location fixe les coordonnes (110,20) du point suprieur gauche du bouton l'aide d'une structure Point. Les largeur et hauteur du bouton sont fixes (80,30) l'aide d'une structure Size. La proprit Text du bouton permet de fixer le libell du bouton. La classe bouton possde un vnement Click dfini comme suit :
Public Event Click As EventHandler

o EventHandler est fonction "dlgue" ayant la signature suivante :


Public Delegate Sub EventHandler(ByVal sender As Object,ByVal e As EventArgs)

Cela signifie que le gestionnaire de l'vnement [Click] sur le bouton devra avoir la signature du dlgu [EventHandler]. Ici, lors d'un clic sur le bouton cmdTest, la mthode cmdTest_Click sera appele. Celle-ci est dfinie comme suit conformment au modle EventHandler prcdent :
' gestionnaire d'vnement Private Sub cmdTest_Click(ByVal sender As Object, ByVal evt As EventArgs) ' il y a eu un clic sur le bouton - on le dit MessageBox.Show("Clic sur bouton", "Clic sur bouton", MessageBoxButtons.OK, MessageBoxIcon.Information) End Sub

On se contente d'afficher un message :

La classe est compile et excute :


dos>vbc /r:system.dll /r:system.drawing.dll /r:system.windows.forms.dll frm2.vb Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 dos>frm2

La classe MessageBox sert afficher des messages dans une fentre. Nous avons utilis ici le constructeur
Overloads Public Shared Function Show(ByVal owner As IWin32Window,ByVal text As String,ByVal caption As String,ByVal buttons As MessageBoxButtons,ByVal icon As MessageBoxIcon) As DialogResult

text caption buttons icon

le message afficher le titre de la fentre les boutons prsents dans la fentre l'icone prsente dans la fentre

Le paramtre buttons peut prendre ses valeurs parmi les constantes suivantes : constante boutons

Interfaces graphiques

106

AbortRetryIgnore

OK

OKCancel

RetryCancel

YesNo

YesNoCancel

Le paramtre icon peut prendre ses valeurs parmi les constantes suivantes :
Asterisk Error idem Stop

Exclamation

idem Warning

Hand

Interfaces graphiques

107

Information

idem Asterisk

None

Question

Stop

idem Hand

Warning

La mthode Show est une mthode statique qui rend un rsultat de type numration :
public enum System.Windows.Forms.DialogResult { Abort = 0x00000003, Cancel = 0x00000002, Ignore = 0x00000005, No = 0x00000007, None = 0x00000000, OK = 0x00000001, Retry = 0x00000004, Yes = 0x00000006, }

System.Windows.Forms.DialogResult

qui est une

Pour savoir sur quel bouton a appuy l'utilisateur pour fermer la fentre de type MessageBox on crira :
dim res as DialogResult=MessageBox.Show(..) if res=DialogResult.Yes then ' il a appuy sur le bouton oui ... end if

4.2 4.2.1

Construire une interface graphique avec Visual Studio.NET Cration initiale du projet
108

Nous reprenons certains des exemples vus prcdemment en les construisant maintenant avec Visual Studio.NET.

Interfaces graphiques

1.

Lancez VS.NET et prendre l'option Fichier/Nouveau/Projet

2.

donnez les caractristiques de votre projet

4 3 5 3. slectionnez le type de projet que vous voulez construire, ici un projet VB.NET (1) slectionnez le type d'application que vous voulez construire, ici une application windows (2) indiquez dans quel dossier vous voulez placer le sous-dossier du projet (3) indiquez le nom du projet (4). Ce sera galement le nom du dossier qui contiendra les fichiers du projet le nom de ce dossier est rappel en (5)

Un certain nombre de dossiers et de fichiers sont alors crs sous le dossier i4 :

sous-dossiers du dossier projet1

De ces fichiers, seul un est intressant, le fichier form1.cs qui est le fichier source associ au formulaire cr par VS.NET. Nous y reviendrons.

Interfaces graphiques

109

4.2.2

Les fentre de l'interface de VS.NET


Nous avons une fentre de conception de l'interface graphique :

L'interface de VS.NET laisse maintenant apparatre certains lments de notre projet i4 :

En prenant des contrles dans la barre d'outils (toolbox 2) et en les dposant sur la surface de la fentre (1), nous pouvons construire une interface graphique. Si nos amenons la souris sur la "toolbox" celle-ci s'agrandit et laisse apparatre un certain nombre de contrles :

Pour l'instant, nous n'en utilisons aucun. Toujours sur l'cran de VS.NET, nous trouvons la fentre de l'explorateur de solutions "Explorer Solution" :

Dans un premier temps, nous ne nous servirons peu de cette fentre. Elle montre l'ensemble des fichiers formant le projet. Un seul d'entre-eux nous intresse : le fichier source de notre programme, ici Form1.vb. En cliquant droit sur Form1.vb, on obtient un menu permettant d'accder soit au code source de notre interface graphique (Afficher le code) soit l'interface graphique elle-mme (Concepteur de vues) :

On peut accder ces deux entits directement partir de la fentre "Solution Explorer" :
Interfaces graphiques

110

Les fentres ouvertes "s'accumulent" dans la fentre principale de conception :

Ici Form1.vb[Design] dsigne la fentre de conception et Form1.vb la fentre de code. Il suffit de cliquer sur l'un des onglets pour passer d'une fentre l'autre. Une autre fentre importante prsente sur l'cran de VS.NET est la fentre des proprits :

Les proprits exposes dans la fentre sont celles du contrle actuellement slectionn dans la fentre de conception graphique. On a accs diffrentes fentres du projet avec le menu Affichage :

On y retrouve les fentres principales qui viennent d'tre dcrites ainsi que leurs raccourcis clavier.

4.2.3

Excution d'un projet

Alors mme que nous n'avons crit aucun code, nous avons un projet excutable. Faites F5 ou Dboguer/Dmarrer pour l'excuter. Nous obtenons la fentre suivante :

Interfaces graphiques

111

Cette fentre peut tre agrandie, mise en icne, redimensionne et ferme.

4.2.4

Le code gnr par VS.NET

Regardons le code (Affichage/Code) de notre application :


Public Class Form1 Inherits System.Windows.Forms.Form #Region " Code gnr par le Concepteur Windows Form " Public Sub New() MyBase.New() 'Cet appel est requis par le Concepteur Windows Form. InitializeComponent() 'Ajoutez une initialisation quelconque aprs l'appel InitializeComponent() End Sub 'La mthode substitue Dispose du formulaire pour nettoyer la liste des composants. Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean) If disposing Then If Not (components Is Nothing) Then components.Dispose() End If End If MyBase.Dispose(disposing) End Sub 'Requis par le Concepteur Windows Form Private components As System.ComponentModel.IContainer 'REMARQUE : la procdure suivante est requise par le Concepteur Windows Form 'Elle peut tre modifie en utilisant le Concepteur Windows Form. 'Ne la modifiez pas en utilisant l'diteur de code. <System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent() components = New System.ComponentModel.Container() Me.Text = "Form2" End Sub #End Region End Class

Une interface graphique drive de la classe de base System.Windows.Forms.Form :


Public Class Form1 Inherits System.Windows.Forms.Form

La classe de base Form dfinit une fentre de base avec des boutons de fermeture, agrandissement/rduction, une taille ajustable, ... et gre les vnements sur ces objets graphiques. Le constructeur du formulaire utilise une mthode InitializeComponent dans laquelle les contrles du formulaires sont crs et initialiss.
Public Sub New() MyBase.New() 'Cet appel est requis par le Concepteur Windows Form. InitializeComponent() 'Ajoutez une initialisation quelconque aprs l'appel InitializeComponent() End Sub

Tout autre travail faire dans le constructeur peut tre fait aprs l'appel InitializeComponent. La mthode InitializeComponent
Interfaces graphiques

112

Private Sub InitializeComponent() ' 'Form1 ' Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13) Me.ClientSize = New System.Drawing.Size(292, 53) Me.Name = "Form1" Me.Text = "Form1" End Sub

fixe le titre de la fentre "Form1", sa largeur (292) et sa hauteur (53). Le titre de la fentre est fixe par la proprit Text et les dimensions par la proprit Size. Size est dfini dans l'espace de noms System.Drawing et est une structure. Pour excuter cette application, il nous faut dfinir le module principal du projet. Pou cela, nous utilisons l'option [Projets/Proprits] :

Dans [Objet de dmarrage], nous indiquons [Form1] qui est le formulaire que nous venons de crer. Pour lancer l'excution, nous utilisons l'option [Dboguer/Dmarrer] :

4.2.5

Compilation dans une fentre dos

Maintenant, essayons de compiler et excuter cette application dans une fentre dos :
dos>dir form1.vb 14/03/2004 11:53 514 Form1.vb

dos>vbc /r:system.dll /r:system.windows.forms.dll form1.vb vbc : error BC30420: 'Sub Main' est introuvable dans 'Form1'.

Le compilateur indique qu'il ne trouve pas la procdure [Main]. En effet, VB.NET ne l'a pas gnre. Nous l'avons cependant dj rencontre dans les exemples prcdents. Elle a la forme suivante :
Shared Sub Main() ' on lance l'appli Application.Run(New Form1) ' o Form1 est le formulaire End Sub

Rajoutons dans le code de [Form1.vb] le code prcdent et recompilons :


dos>vbc /r:system.dll /r:system.windows.forms.dll form2.vb Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 Form2.vb(41) : error BC30451: Le nom 'Application' n'est pas dclar. Application.Run(New Form2) ~~~~~~~~~~~

Cette fois-ci, le nom [Application] n'est pas connu. Cela veut simplement dire que nous n'avons pas import son espace de noms [System.Windows.Forms]. Rajoutons l'instruction suivante :
Imports System.Windows.Forms Interfaces graphiques

113

puis recompilons :
dos>vbc /r:system.dll /r:system.windows.forms.dll form1.vb

Cette fois-ci, a passe. Excutons :


dos>dir 14/03/2004 14/03/2004 dos>form1 11:53 12:15 514 Form1.vb 3 584 Form1.exe

Un formulaire de type Form1 est cr et affich. On peut viter l'ajout de la procdure [Main] en utilisant l'option /m du compilateur qui permet de prciser la classe excuter dans le cas o celle-ci hrite de System.Windows.Form :
dos>vbc /r:system.dll /r:system.windows.forms.dll /r:system.drawing.dll /m:form2 form2.vb

L'option /m:form2 indique que la classe excuter est la classe de nom [form2].

4.2.6

Gestion des vnements

Une fois le formulaire affich, l'application se met l'coute des vnements qui se produisent sur le formulaire (clics, dplacements de souris, ...) et fait excuter ceux que le formulaire gre. Ici, notre formulaire ne gre pas d'autre vnement que ceux grs par la classe de base Form (clics sur boutons fermeture, agrandissement/rduction, changement de taille de la fentre, dplacement de la fentre, ...). Le formulaire gnr utilise un attribut components qui n'est utilis nulle part. La mthode dispose ne sert galement rien ici. Il en de mme de certains espaces de noms (Collections, ComponentModel, Data) utiliss et de celui dfini pour le projet projet1. Aussi, dans cet exemple le code peut tre simplifi ce qui suit :
Imports System Imports System.Drawing Imports System.Windows.Forms Public Class Form1 Inherits System.Windows.Forms.Form ' constructeur Public Sub New() ' construction du formulaire avec ses composants InitializeComponent() End Sub Private Sub InitializeComponent() ' taille de la fentre Me.Size = New System.Drawing.Size(300, 300) ' titre de la fentre Me.Text = "Form1" End Sub Shared Sub Main() ' on lance l'appli Application.Run(New Form1) End Sub End Class

4.2.7

Conclusion

Nous accepterons maintenant tel quel le code gnr par VS.NET et nous contenterons d'y ajouter le ntre notamment pour grer les vnements lis aux diffrents contrles du formulaire.

Interfaces graphiques

114

4.3 4.3.1

Fentre avec champ de saisie, bouton et libell Conception graphique

Dans l'exemple prcdent, nous n'avions pas mis de composants dans la fentre. Nous commenons un nouveau projet appel interface2. Pour cela nous suivons la procdure explicite prcdemment pour crer un projet :

Construisons maintenant une fentre avec un bouton, un libell et un champ de saisie :

1 3

Les champs sont les suivants : n 1 2 3 nom type lblSaisie Label txtSaisie TextBox btnAfficher Button rle un libell une zone de saisie pour afficher dans une bote de dialogue le contenu de la zone de saisie txtSaisie

On pourra procder comme suit pour construire cette fentre : cliquez droit dans la fentre en-dehors de tout composant et choisissez l'option Properties pour avoir accs aux proprits de la fentre :

La fentre de proprits apparat alors sur la droite :

Interfaces graphiques

115

Certaines de ces proprits sont noter :


BackColor ForeColor Menu Text FormBorderStyle Font Name

pour fixer la couleur de fond de la fentre pour fixer la couleur des dessins ou du texte sur la fentre pour associer un menu la fentre pour donner un titre la fentre pour fixer le type de fentre pour fixer la police de caractres des critures dans la fentre pour fixer le nom de la fentre

Ici, nous fixons les proprits Text et Name :


Text Name

Saisies & boutons - 1 frmSaisiesBoutons

A l'aide de la barre "Bote outils" slectionnez les composants dont vous avez besoin dposez-les sur la fentre et donnez-leur leurs bonnes dimensions

Une fois le composant choisi dans le "toolbox", utilisez la touche "Echap" pour faire disparatre la barre d'outils, puis dposez et dimensionnez le composant. faites-le pour les trois composants ncessaires : Label, TextBox, Button. Pour aligner et dimensionner correctement les composants, utilisez le menu Format :

Interfaces graphiques

116

Le principe du formatage est le suivant : 1. slectionnez les diffrents composants formater ensemble (touche Ctrl appuye) 2. slectionnez le type de formatage dsir L'option Align composants vous permet d'aligner des

L'option Make Same Size permet de faire que des composants aient la mme hauteur ou la mme largeur :

L'option Horizontal Spacing permet par exemple d'aligner horizontalement des composants avec des intervalles entre eux de mme taille. Idem pour l'option Vertical Spacing pour aligner verticalement. L'option Center in Form permet de centrer un composant horizontalement ou verticalement dans la fentre :

Une fois que les composants sont correctement placs sur la fentre, fixez leurs proprits. Pour cela, cliquez droit sur le composant et prenez l'option Properties : l'tiquette 1 (Label) Slectionnez le composant pour avoir sa fentre de proprits. Dans celle-ci, modifiez les proprits suivantes : name : lblSaisie, text : Saisie le champ de saisie 2 (TextBox) Slectionnez le composant pour avoir sa fentre de proprits. Dans celle-ci, modifiez les proprits suivantes : name : txtSaisie, text : ne rien mettre le bouton 3 (Button) : name : cmdAfficher, text : Afficher la fentre elle-mme : name : frmSaisies&Boutons, text : Saisies & boutons -1

Interfaces graphiques

117

Nous pouvons excuter (ctrl-F5) notre projet pour avoir un premier aperu de la fentre en action :

Fermez la fentre. Il nous reste crire la procdure lie un clic sur le bouton Afficher.

4.3.2
...

Gestion des vnements d'un formulaire

Regardons le code qui a t gnr par le concepteur graphique :

Public Class frmSaisiesBoutons Inherits System.Windows.Forms.Form ' composants Private components As System.ComponentModel.Container = Nothing Friend WithEvents btnAfficher As System.Windows.Forms.Button Friend WithEvents lblsaisie As System.Windows.Forms.Label Friend WithEvents txtsaisie As System.Windows.Forms.TextBox ' constructeur Public Sub New() InitializeComponent() End Sub ... ' initialisation des composants Private Sub InitializeComponent() ... End Sub End Class

On notera tout d'abord la dclaration particulire des composants : - le mot cl Friend indique une visibilit du composant qui s'tend toutes les classes du projet - le mot cl WithEvents indique que le composant gnre des vnements. Nous nous intressons maintenant la faon de grer ces vnements Faites afficher la fentre de code du formulaire (Affichage/code ou F7) :

La fentre ci-dessus prsente deux listes droulantes (1) et (2). La liste (1) est la liste des composants du formulaire :

Interfaces graphiques

118

La liste(2) la liste des vnements associs au composant slectionn dans (1) :

L'un des vnements associs au composant est affich en gras (ici Click). C'est l'vnement par dfaut du composant. L'accs au gestionnaire de cet vnement particulier peut se faire en double-cliquant sur le composant dans la fentre de conception. VB.net gnre alors automatiquement le squelette du gestionnaire d'vnement dans la fentre de code et positionne l'utilisateur dessus. L'accs aux gestionnaires des autres vnements se fait lui dans la fentre de code, en slectionnant le composant dans la liste (1) et l'vnement dans (2). VB.net gnre alors le squelette du gestionnaire d'vnement ou se positionne dessus s'il tait dj gnr :
Private Sub btnAfficher_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnAfficher.Click ... End Sub

Par dfaut, VB.net C_E nomme le gestionnaire de l'vnement E du composant C. On peut changer ce nom si cela nous convient. C'est cependant dconseill. Les dveloppeurs VB gardent en gnral le nom gnr par VB ce qui donne une cohrence du nommage dans tous les programmes VB. L'association de la procdure btnAfficher_Click l'vnement Click du composant btnAfficher ne se fait par le nom de la procdure mais par le mot cl handles :
Handles btnAfficher.Click

Ci-dessus, le mot cl handles prcise que la procdure gre l'vnement Click du composant btnAfficher. Le gestionnaire d'vnement prcdent a deux paramtres :
sender e

l'objet la source de l'vnement (ici le bouton) un objet EventArgs qui dtaille l'vnement qui s'est produit

Nous n'utiliserons aucun de ces paramtres ici. Il ne nous reste plus qu' complter le code. Ici, nous voulons prsenter une bote de dialogue avec dedans le contenu du champ txtSaisie :
Private Sub btnAfficher_Click(ByVal sender As Object, ByVal e As System.EventArgs) ' on affiche le texte qui a t saisi dans la bote de saisie TxtSaisie MessageBox.Show("texte saisi= " + txtsaisie.Text, "Vrification de la saisie", MessageBoxButtons.OK, MessageBoxIcon.Information) End Sub

Si on excute l'application on obtient la chose suivante :


Interfaces graphiques

119

4.3.3

Une autre mthode pour grer les vnements

Pour le bouton btnAfficher, VS.NET a gnr le code suivant :


Private Sub InitializeComponent() ... ' 'btnAfficher ' Me.btnAfficher.Location = New System.Drawing.Point(102, 48) Me.btnAfficher.Name = "btnAfficher" Me.btnAfficher.Size = New System.Drawing.Size(88, 24) Me.btnAfficher.TabIndex = 2 Me.btnAfficher.Text = "Afficher" ... End Sub ... ' gestionnaire d'vt clic sur bouton cmdAfficher Private Sub btnAfficher_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnAfficher.Click ... End Sub

Nous pouvons associer la procdure btnAfficher_Click l'vnement Click du bouton btnAfficher d'une autre faon :
Private Sub InitializeComponent() ... ' 'btnAfficher ' Me.btnAfficher.Location = New System.Drawing.Point(102, 48) Me.btnAfficher.Name = "btnAfficher" Me.btnAfficher.Size = New System.Drawing.Size(88, 24) Me.btnAfficher.TabIndex = 2 Me.btnAfficher.Text = "Afficher" AddHandler btnAfficher.Click, AddressOf btnAfficher_Click ... End Sub ... ' gestionnaire d'vt clic sur bouton cmdAfficher Private Sub btnAfficher_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) ... End Sub

La procdure btnAfficher_Click a perdu le mot cl Handles perdant ainsi son association avec l'vnement btnAfficher.Click. Cette association est dsormais faite l'aide du mot cl AddHandler :
AddHandler btnAfficher.Click, AddressOf btnAfficher_Click

Le code ci-dessus qui sera plac dans la procdure InitializeComponent du formulaire, associe l'vnement btnAfficher.Click la procdure portant le nom btnAfficher_Click. Par ailleurs, le composant btnAfficher n'a plus besoin du mot cl WithEvents :
Friend btnAfficher As System.Windows.Forms.Button

Quelle est la diffrence entre les deux mthodes ? le mot cl handles ne permet d'associer un vnement une procdure qu'au moment de la conception. Le concepteur sait l'avance qu'une procdure P doit grer les vnements E1, E2, ... et il crit le code Interfaces graphiques 120 -

Private Sub btnAfficher_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) handles E1, E2, ..., En

Il est en effet possible pour une procdure de grer plusieurs vnements. le mot cl addhandler permet d'associer un vnement une procdure au moment de l'excution. Ceci est utile dans un cadre producteur-consommateur d'vnements. Un objet produit un vnement particulier susceptible d'intresser d'autres objets. Ceux-ci s'abonnent auprs du producteur pour recevoir l'vnement (une temprature ayant dpass un seuil critique, par exemple). Au cours de l'excution de l'application, le producteur de l'vnement sera amen excuter diffrentes instructions :
Addhandler E, AddressOf P1 Addhandler E, AddressOf P2 ... Addhandler E, AddressOf Pn

o E est l'vnement produit par le producteur et Pi des procdures appartenant aux diffrents objets consommateurs de cet vnement. Nous aurons l'occasion de revenir sur une application producteur-consommateur d'vnements dans un prochain chapitre.

4.3.4

Conclusion

Des deux projets tudis, nous pouvons conclure qu'une fois l'interface graphique construite avec VS.NET, le travail du dveloppeur consiste crire les gestionnaires des vnements qu'il veut grer pour cette interface graphique. Nous ne prsenterons dsormais que le code de ceux-ci.

4.4

Quelques composants utiles

Nous prsentons maintenant diverses applications mettant en jeu les composants les plus courants afin de dcouvrir les principales mthodes et proprits de ceux-ci. Pour chaque application, nous prsentons l'interface graphique et le code intressant, notamment les gestionnaires d'vnements.

4.4.1

formulaire Form

Nous commenons par prsenter le composant indispensable, le formulaire sur lequel on dpose des composants. Nous avons dj prsent quelques-unes de ses proprits de base. Nous nous attardons ici sur quelques vnements importants d'un formulaire.
Load Closing Closed

le formulaire est en cours de chargement le formulaire est en cours de fermeture le formulaire est ferm

L'vnement Load se produit avant mme que le formulaire ne soit affich. L'vnement Closing se produit lorsque le formulaire est en cours de fermeture. On peut encore arrter cette fermeture par programmation. Nous construisons un formulaire de nom Form1 sans composant :

Nous traitons les trois vnements prcdents :


Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Load ' chargement initial du formulaire MessageBox.Show("Evt Load", "Load") End Sub Private Sub Form1_Closing(ByVal sender As Object, ByVal e As System.ComponentModel.CancelEventArgs) Handles MyBase.Closing ' le formulaire est en train de se fermer MessageBox.Show("Evt Closing", "Closing") ' on demande confirmation Interfaces graphiques

121

Dim rponse As DialogResult = MessageBox.Show("Voulez-vous vraiment quitter l'application", "Closing", MessageBoxButtons.YesNo, MessageBoxIcon.Question) If rponse = DialogResult.No Then e.Cancel = True End If End Sub Private Sub Form1_Closed(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Closed ' le formulaire est en train de se fermer MessageBox.Show("Evt Closed", "Closed") End Sub

Nous utilisons la fonction MessageBox pour tre averti des diffrents vnements. L'vnement Closing va se produire lorsque l'utilisateur ferme la fentre.

Nous lui demandons alors s'il veut vraiment quitter l'application :

S'il rpond Non, nous fixons la proprit Cancel de l'vnement CancelEventArgs e que la mthode a reu en paramtre. Si nous mettons cette proprit False, la fermeture de la fentre est abandonne, sinon elle se poursuit :

4.4.2

tiquettes Label et botes de saisie TextBox

Nous avons dj rencontr ces deux composants. Label est un composant texte et TextBox un composant champ de saisie. Leur proprit principale est Text qui dsigne soit le contenu du champ de saisie ou le texte du libell. Cette proprit est en lecture/criture. L'vnement habituellement utilis pour TextBox est TextChanged qui signale que l'utilisateur modifi le champ de saisie. Voici un exemple qui utilise l'vnement TextChanged pour suivre les volutions d'un champ de saisie :

1 2 3 n 1 2 3 4 type TextBox Label Button Button nom txtSaisie lblControle cmdEffacer cmdQuitter 4 rle champ de saisie affiche le texte de 1 en temps rel pour effacer les champs 1 et 2 pour quitter l'application

Le code pertinent de cette application est celui des gestionnaires d'vnements :


' clic sur btn quitter Private Sub cmdQuitter_Click(ByVal sender As Object, ByVal e As System.EventArgs) _ Handles cmdQuitter.Click ' clic sur bouton Quitter - on quitte l'application Application.Exit() End Sub Interfaces graphiques

122

' modification champ txtSaisie Private Sub txtSaisie_TextChanged(ByVal sender As Object, ByVal e As System.EventArgs) _ Handles txtSaisie.TextChanged ' le contenu du TextBox a chang - on le copie dans le Label lblControle lblControle.Text = txtSaisie.Text End Sub ' clic sur btn effacer Private Sub cmdEffacer_Click(ByVal sender As Object, ByVal e As System.EventArgs) _ Handles cmdEffacer.Click ' on efface le contenu de la bote de saisie txtSaisie.Text = "" End Sub ' une touche a t tape Private Sub txtSaisie_KeyPress(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyPressEventArgs) _ Handles txtSaisie.KeyPress ' on regarde quelle touche a t tape Dim touche As Char = e.KeyChar If touche = ControlChars.Cr Then MessageBox.Show(txtSaisie.Text, "Contrle", MessageBoxButtons.OK, MessageBoxIcon.Information) e.Handled = True End If End Sub

On notera la faon de terminer l'application dans la procdure cmdQuitter_Click : Application.Exit(). L'exemple suivant utilise un TextBox multilignes :

3 La liste des contrles est la suivante : n 1 2 3 type TextBox TextBox Button nom txtMultiLignes txtAjout btnAjouter

rle champ de saisie multilignes champ de saisie monoligne Ajoute le contenu de 2 1

Pour qu'un TextBox devienne multilignes on positionne les proprits suivantes du contrle :
Multiline=true ScrollBars=( None, Horizontal, Vertical, Both) AcceptReturn=(True, False) AcceptTab=(True, False)

pour accepter plusieurs lignes de texte pour demander ce que le contrle ait des barres de dfilement (Horizontal, Vertical, Both) ou non (None) si gal true, la touche Entre fera passer la ligne si gal true, la touche Tab gnrera une tabulation dans le texte

Le code utile est celui qui traite le clic sur le bouton [Ajouter] et celui qui traite la modification du champ de saisie [txtAjout] :
' vt btnAjouter_Click Private Sub btnAjouter_Click1(ByVal sender As Object, ByVal e As System.EventArgs) _ Handles btnAjouter.Click ' ajout du contenu de txtAjout celui de txtMultilignes txtMultilignes.Text &= txtAjout.Text txtAjout.Text = "" End Sub Interfaces graphiques

123

' evt txtAjout_TextChanged Private Sub txtAjout_TextChanged1(ByVal sender As Object, ByVal e As System.EventArgs) _ Handles txtAjout.TextChanged ' on fixe l'tat du bouton Ajouter btnAjouter.Enabled = txtAjout.Text.Trim() <> "" End Sub

4.4.3

listes droulantes ComboBox


1

Un composant ComboBox est une liste droulante double d'une zone de saisie : l'utilisateur peut soit choisir un lment dans (2) soit taper du texte dans (1). Il existe trois sortes de ComboBox fixes par la proprit Style :
Simple DropDown DropDownList

liste non droulante avec zone d'dition liste droulante avec zone d'dition liste droulante sans zone d'dition

Par dfaut, le type d'un ComboBox est DropDown. Pour dcouvrir la classe ComboBox, tapez ComboBox dans l'index de l'aide (Aide/Index). La classe ComboBox a un seul constructeur : Public Sub New() cre un objet ComboBox vide

Les lments du ComboBox sont disponibles dans la proprit Items :


Public ReadOnly Property Items As ComboBox.ObjectCollection

C'est une proprit indexe, Items(i) dsignant l'lment i du Combo. Soit C un combo et C.Items sa liste d'lments. On a les proprits suivantes :
C.Items.Count C.Items(i) C.Add(object o) C.AddRange(object() objets) C.Insert(int i, object o) C.RemoveAt(int i) C.Remove(object o) C.Clear() C.IndexOf(object o)

nombre d'lments du combo lment i du combo ajoute l'objet o en dernier lement du combo ajoute un tableau d'objets en fin de combo ajoute l'objet o en position i du combo enlve l'lment i du combo enlve l'objet o du combo supprime tous les lments du combo rend la position i de l'objet o dans le combo

On peut s'tonner qu'un combo puisse contenir des objets alors qu'habituellement il contient des chanes de caractres. Au niveau visuel, ce sera le cas. Si un ComboBox contient un objet obj, il affiche la chane obj.ToString(). On se rappelle que tout objet une mthode ToString hrite de la classe object et qui rend une chane de caractres "reprsentative" de l'objet. L'lment slectionn
Interfaces graphiques

124

dans le combo C est C.SelectedItem ou C.Items(C.SelectedIndex) o C.SelectedIndex est le n de l'lment slectionn, ce n partant de zro pour le premier lment. Lors du choix d'un lment dans la liste droulante se produit l'vnement SelectedIndexChanged qui peut tre alors utilis pour tre averti du changement de slection dans le combo. Dans l'application suivante, nous utilisons cet vnement pour afficher l'lment qui a t slectionn dans la liste.

Nous ne prsentons que le code pertinent de la fentre. Dans le constructeur du formulaire nous remplissons le combo :
Public Sub New() ' cration formulaire InitializeComponent() ' remplissage combo cmbNombres.Items.AddRange(New String() {"zro", "un", "deux", "trois", "quatre"}) ' nous slectionnons le 1er lment de la liste cmbNombres.SelectedIndex = 0 End Sub

Nous traitons l'vnement SelectedIndexChanged du combo qui signale un nouvel lment slectionn :
Private Sub cmbNombres_SelectedIndexChanged(ByVal sender As Object, ByVal e As System.EventArgs) _ Handles cmbNombres.SelectedIndexChanged ' l'lment slectionn a chang - on l'affiche MessageBox.Show("Elment slectionn : (" & cmbNombres.SelectedItem.ToString & "," & cmbNombres.SelectedIndex & ")", "Combo", MessageBoxButtons.OK, MessageBoxIcon.Information) End Sub

4.4.4

composant ListBox

On se propose de construire l'interface suivante :

0 1 5 3 4 2

Les composants de cette fentre sont les suivants : n 0 1 2 type Form TextBox Button nom Form1 txtSaisie btnAjouter rle/proprits formulaire - BorderStyle=FixedSingle champ de saisie bouton permettant d'ajouter le contenu du champ de saisie 1 dans la liste 3 125

Interfaces graphiques

3 4 5 6 7 8

ListBox ListBox Button Button Button Button

listBox1 listBox2 btn1TO2 cmd2T0 btnEffacer1 btnEffacer2

liste 1 liste 2 transfre les lments slectionns de liste 1 vers liste 2 fait l'inverse vide la liste 1 vide la liste 2

Fonctionnement L'utilisateur tape du texte dans le champ 1. Il l'ajoute la liste 1 avec le bouton Ajouter (2). Le champ de saisie (1) est alors vid et l'utilisateur peut ajouter un nouvel lment. Il peut transfrer des lments d'une liste l'autre en slectionnant l'lment transfrer dans l'une des listes et en choississant le bouton de transfert adquat 5 ou 6. L'lment transfr est ajout la fin de la liste de destination et enlev de la liste source. Il peut double-cliquer sur un lment de la liste 1. Ce lment est alors transfr dans la bote de saisie pour modification et enlev de la liste 1.

Les boutons sont allums ou teints selon les rgles suivantes : le bouton Ajouter n'est allum que s'il y a un texte non vide dans le champ de saisie le bouton 5 de transfert de la liste 1 vers la liste 2 n'est allum que s'il y a un lment slectionn dans la liste 1 le bouton 6 de transfert de la liste 2 vers la liste 1 n'est allum que s'il y a un lment slectionn dans la liste 2 les boutons 7 et 8 d'effacement des listes 1 et 2 ne sont allums que si la liste effacer contient des lments. Dans les conditions prcdentes, tous les boutons doivent tre teints lors du dmarrage de l'application. C'est la proprit Enabled des boutons qu'il faut alors positionner false. On peut le faire au moment de la conception ce qui aura pour effet de gnrer le code correspondant dans la mthode InitializeComponent ou de le faire nous-mmes dans le constructeur comme ci-dessous :
Public Sub New() ' cration initiale du formulaire InitializeComponent() ' initialisations complmentaires ' on inhibe un certain nombre de boutons btnAjouter.Enabled = False btn1TO2.Enabled = False btn2TO1.Enabled = False btnEffacer1.Enabled = False btnEffacer2.Enabled = False End Sub

L'tat du bouton Ajouter est contrl par le contenu du champ de saisie. C'est l'vnement TextChanged qui nous permet de suivre les changements de ce contenu :
' chgt dans champ txtsaisie Private Sub txtSaisie_TextChanged(ByVal sender As Object, ByVal e As System.EventArgs) _ Handles txtSaisie.TextChanged ' le contenu de txtSaisie a chang ' le bouton Ajouter n'est allum que si la saisie est non vide btnAjouter.Enabled = txtSaisie.Text.Trim() <> "" End Sub

L'tat des boutons de transfert dpend du fait qu'un lment a t slectionn ou non dans la liste qu'ils contrlent :
' chgt de l'lment slectionn sans listbox1 Private Sub listBox1_SelectedIndexChanged(ByVal sender As Object, ByVal e As System.EventArgs) _ Handles listBox1.SelectedIndexChanged ' un lment a t slectionn ' on allume le bouton de transfert 1 vers 2 btn1TO2.Enabled = True End Sub ' chgt de l'lment slectionn sans listbox2 Private Sub listBox2_SelectedIndexChanged(ByVal sender As Object, ByVal e As System.EventArgs) _ Handles listBox2.SelectedIndexChanged ' un lment a t slectionn ' on allume le bouton de transfert 2 vers 1 btn2TO1.Enabled = True End Sub

Le code associ au clic sur le bouton Ajouter est le suivant :


' clic sur btn Ajouter Interfaces graphiques

126

Private Sub btnAjouter_Click(ByVal sender As Object, ByVal e As System.EventArgs) _ Handles btnAjouter.Click ' ajout d'un nouvel lment la liste 1 listBox1.Items.Add(txtSaisie.Text.Trim()) ' raz de la saisie txtSaisie.Text = "" ' Liste 1 n'est pas vide btnEffacer1.Enabled = True ' retour du focus sur la bote de saisie txtSaisie.Focus() End Sub

On notera la mthode Focus qui permet de mettre le "focus" sur un contrle du formulaire. Le code associ au clic sur les boutons Effacer :
' clic sur btn Effacer1 Private Sub btnEffacer1_Click(ByVal sender As Object, ByVal e As System.EventArgs) _ Handles btnEffacer1.Click ' on efface la liste 1 listBox1.Items.Clear() btnEffacer1.Enabled = False End Sub ' clic sur btn effacer2 Private Sub btnEffacer2_Click(ByVal sender As Object, ByVal e As System.EventArgs) ' on efface la liste 2 listBox2.Items.Clear() btnEffacer2.Enabled = False End Sub

Le code de transfert des lments slectionns d'une liste vers l'autre :


' clic sur btn btn1to2 Private Sub btn1TO2_Click(ByVal sender As Object, ByVal e As System.EventArgs) _ Handles btn1TO2.Click ' transfert de l'lment slectionn dans Liste 1 vers Liste 2 transfert(listBox1, listBox2) ' boutons Effacer btnEffacer2.Enabled = True btnEffacer1.Enabled = listBox1.Items.Count <> 0 ' boutons de transfert btn1TO2.Enabled = False btn2TO1.Enabled = False End Sub ' clic sur btn btn2to1 Private Sub btn2TO1_Click(ByVal sender As Object, ByVal e As System.EventArgs) _ Handles btn2TO1.Click ' transfert de l'lment slectionn dans Liste 2 vers Liste 1 transfert(listBox2, listBox1) ' boutons Effacer btnEffacer1.Enabled = True btnEffacer2.Enabled = listBox2.Items.Count <> 0 ' boutons de transfert btn1TO2.Enabled = False btn2TO1.Enabled = False End Sub ' transfert Private Sub transfert(ByVal l1 As ListBox, ByVal l2 As ListBox) ' transfert de l'lment slectionn de la liste 1 vers la liste l2 ' un lment slectionn ? If l1.SelectedIndex = -1 Then Return ' ajout dans l2 l2.Items.Add(l1.SelectedItem) ' suppression dans l1 l1.Items.RemoveAt(l1.SelectedIndex) End Sub

Tout d'abord, on cre une mthode


Private Sub transfert(ByVal l1 As ListBox, ByVal l2 As ListBox)

qui transfre dans la liste l2 l'lment slectionn dans la liste l1. Cela nous permet d'avoir une seule mthode au lieu de deux pour transfrer un lment de listBox1 vers listBox2 ou de listBox2 vers listBox1. Avat de faire le transfert, on s'assure qu'il y a bien un lment slectionn dans la liste l1 :
' un lment slectionn ? Interfaces graphiques

127

If l1.SelectedIndex = -1 Then Return

La proprit SelectedIndex vaut -1 si aucun lment n'est actuellement slectionn. Dans les procdures
Private Sub btnXTOY_Click(ByVal sender As Object, ByVal e As System.EventArgs) _ Handles btnXTOY.Click

on opre le transfert de la liste X vers la liste Y et on change l'tat de certains boutons pour reflter le nouvel tat des listes.

4.4.5

cases cocher CheckBox, boutons radio ButtonRadio

Nous nous proposons d'crire l'application suivante :

1 2 3

Les composants de la fentre sont les suivants : 1 2 3 n type RadioButton CheckBox ListBox nom radioButton1 radioButton2 radioButton3 chechBox1 chechBox2 chechBox3 lstValeurs rle 3 boutons radio 3 cases cocher une liste

Si on construit les trois boutons radio l'un aprs l'autre, ils font partie par dfaut d'un mme groupe. Aussi lorsque l'un est coch, les autres ne le sont pas. L'vnement qui nous intresse pour ces six contrles est l'vnement CheckChanged indiquant que l'tat de la case cocher ou du bouton radio a chang. Cet tat est reprsent dans les deux cas par la proprit boolenne Check qui vrai signifie que le contrle est coch. Nous avons ici utilis une seule mthode pour traiter les six vnements CheckChanged, la mthode affiche :
' affiche Private Sub affiche(ByVal sender As Object, ByVal e As System.EventArgs) _ Handles checkBox1.CheckedChanged, checkBox2.CheckedChanged, checkBox3.CheckedChanged, _ radioButton1.CheckedChanged, radioButton2.CheckedChanged, radioButton3.CheckedChanged ' affiche l'tat du bouton radio ou de la case cocher ' est-ce un checkbox ? If (TypeOf (sender) Is CheckBox) Then Dim chk As CheckBox = CType(sender, CheckBox) lstValeurs.Items.Insert(0, chk.Name & "=" & chk.Checked) End If ' est-ce un radiobutton ? If (TypeOf (sender) Is RadioButton) Then Dim rdb As RadioButton = CType(sender, RadioButton) lstValeurs.Items.Insert(0, rdb.Name & "=" & rdb.Checked) End If End Sub

La syntaxe TypeOf (sender) Is CheckBox permet de vrifier si l'objet sender est de type CheckBox. Cela nous permet ensuite de faire un transtypage vers le type exact de sender. La mthode affiche crit dans la liste lstValeurs le nom du composant l'origine de l'vnement et la valeur de sa proprit Checked. A l'excution, on voit qu'un clic sur un bouton radio provoque deux vnements CheckChanged : l'un sur l'ancien bouton coch qui passe "non coch" et l'autre sur le nouveau bouton qui passe "coch".

4.4.6

variateurs ScrollBar
128

Interfaces graphiques

Il existe plusieurs types de variateur : le variateur horizontal (hScrollBar), le variateur vertical (vScrollBar), l'incrmenteur (NumericUpDown).

Ralisons l'application suivante :

1 2

3 4

n 1 2 3 4

type hScrollBar hScrollBar TextBox NumericUpDown

nom hScrollBar1 hScrollBar2 txtValeur incrmenteur

rle un variateur horizontal un variateur horizontal qui suit les variations du variateur 1 affiche la valeur du variateur horizontal ReadOnly=true pour empcher toute saisie permet de fixer la valeur du variateur 2

Un variateur ScrollBar permet l'utilisateur de choisir une valeur dans une plage de valeurs entires symbolise par la "bande" du variateur sur laquelle se dplace un curseur. La valeur du variateur est disponible dans sa proprit Value. Pour un variateur horizontal, l'extrmit gauche reprsente la valeur minimale de la plage, l'extrmit droite la valeur maximale, le curseur la valeur actuelle choisie. Pour un variateur vertical, le minimum est reprsent par l'extrmit haute, le maximum par l'extrmit basse. Ces valeurs sont reprsentes par les proprits Minimum et Maximum et valent par dfaut 0 et 100. Un clic sur les extrmits du variateur fait varier la valeur d'un incrment (positif ou ngatif) selon l'extrmit clique appele SmallChange qui est par dfaut 1. Un clic de part et d'autre du curseur fait varier la valeur d'un incrment (positif ou ngatif) selon l'extrmit clique appele LargeChange qui est par dfaut 10. Lorsqu'on clique sur l'extrmit suprieure d'un variateur vertical, sa valeur diminue. Cela peut surprendre l'utilisateur moyen qui s'attend normalement voir la valeur "monter". On rgle ce problme en donnant une valeur ngative aux proprits SmallChange et LargeChange Ces cinq proprits (Value, Minimum, Maximum, SmallChange, LargeChange) sont accessibles en lecture et criture. L'vnement principal du variateur est celui qui signale un changement de valeur : l'vnement Scroll.

Un composant NumericUpDown est proche du variateur : il a lui aussi les proprits Minimum, Maximum et Value, par dfaut 0, 100, 0. Mais ici, la proprit Value est affiche dans une bote de saisie faisant partie intgrante du contrle. L'utilisateur peut lui mme modifier cette valeur sauf si on a mis la proprit ReadOnly du contrle vrai. La valeur de l'incrment est fix par la proprit Increment, par dfaut 1. L'vnement principal du composant NumericUpDown est celui qui signale un changement de valeur : l'vnement ValueChanged. Le code utile de notre application est le suivant : Le formulaire est mis en forme lors de sa construction :
' constructeur Public Sub New() ' cration initiale du formulaire InitializeComponent() ' on donne au variateur 2 les mmes caractristiques qu'au variateur 1 hScrollBar2.Minimum = hScrollBar1.Value hScrollBar2.Minimum = hScrollBar1.Minimum hScrollBar2.Maximum = hScrollBar1.Maximum hScrollBar2.LargeChange = hScrollBar1.LargeChange hScrollBar2.SmallChange = hScrollBar1.SmallChange ' idem pour l'incrmenteur incrmenteur.Minimum = hScrollBar1.Value incrmenteur.Minimum = hScrollBar1.Minimum incrmenteur.Maximum = hScrollBar1.Maximum incrmenteur.Increment = hScrollBar1.SmallChange Interfaces graphiques

129

' on donne au TextBox la valeur du variateur 1 txtValeur.Text = "" & hScrollBar1.Value End Sub

Le gestionnaire qui suit les variations de valeur du variateur 1 :


' gestion variateur hscrollbar1 Private Sub hScrollBar1_Scroll(ByVal sender As Object, ByVal e As System.Windows.Forms.ScrollEventArgs) _ Handles hScrollBar1.Scroll ' changement de valeur du variateur 1 ' on rpercute sa valeur sur le variateur 2 et sur le textbox TxtValeur hScrollBar2.Value = hScrollBar1.Value txtValeur.Text = "" & hScrollBar1.Value End Sub

Le gestionnaire qui suit les variations de valeur du variateur 2 :


' gestion variateur hscrollbar2 Private Sub hScrollBar2_Scroll(ByVal sender As Object, ByVal e As System.Windows.Forms.ScrollEventArgs) _ Handles hScrollBar2.Scroll ' on inhibe tout changement du variateur 2 ' en le forant garder la valeur du variateur 1 e.NewValue = hScrollBar1.Value End Sub

Le gestionnaire qui suit les variations du contrle incrmenteur :


' gestion incrmenteur Private Sub incrmenteur_ValueChanged(ByVal sender As Object, ByVal e As System.EventArgs) _ Handles incrmenteur.ValueChanged ' on fixe la valeur du variateur 2 hScrollBar2.Value = CType(incrmenteur.Value, Integer) End Sub

4.5

vnements souris

Lorsqu'on dessine dans un conteneur, il est important de connatre la position de la souris pour par exemple afficher un point lors d'un clic. Les dplacements de la souris provoquent des vnements dans le conteneur dans lequel elle se dplace.

MouseEnter MouseLeave MouseMove MouseDown MouseUp DragDrop DragEnter DragLeave DragOver

la souris vient d'entrer dans le domaine du contrle la souris vient de quitter le domaine du contrle la souris bouge dans le domaine du contrle Pression sur le bouton gauche de la souris Relchement du bouton gauche de la souris l'utilisateur lche un objet sur le contrle l'utilisateur entre dans le domaine du contrle en tirant un objet l'utilisateur sort du domaine du contrle en tirant un objet l'utilisateur passe au-dessus domaine du contrle en tirant un objet

Voici un programme permettant de mieux apprhender quels moments se produisent les diffrents vnements souris :

Interfaces graphiques

130

1 2 3

type Label ListBox Button

nom lblPosition lstEvts btnEffacer

rle pour afficher la position de la souris dans le formulaire 1, la liste 2 ou le bouton 3 pour afficher les vts souris autres que MouseMove pour effacer le contenu de 2

Les gestionnaires d'vnements sont les suivants. Pour suivre les dplacements de la souris sur les trois contrles, on n'crit qu'un seul gestionnaire :
' vt form1_mousemove Private Sub Form1_MouseMove(ByVal sender As System.Object, ByVal e As System.Windows.Forms.MouseEventArgs) _ Handles MyBase.MouseMove, lstEvts.MouseMove, btnEffacer.MouseMove, lstEvts.MouseMove ' mvt souris - on affiche les coordonnes (X,Y) de celle-ci lblPosition.Text = "(" & e.X & "," & e.Y & ")" End Sub

Il faut savoir qu' chaque fois que la souris entre dans le domaine d'un contrle, son systme de coordonnes change. Son origine (0,0) est le coin suprieur gauche du contrle sur lequel elle se trouve. Ainsi l'excution, lorsqu'on passe la souris du formulaire au bouton, on voit clairement le changement de coordonnes. Afin de mieux voir ces changements de domaine de la souris, on peut utiliser la proprit Cursor des contrles :

Cette proprit permet de fixer la forme du curseur de souris lorsque celle-ci entre dans le domaine du contrle. Ainsi dans notre exemple, nous avons fix le curseur Default pour le formulaire lui-mme, Hand pour la liste 2 et No pour le bouton 3 comme le montrent les copies d'cran ci-dessous.

Interfaces graphiques

131

Dans la mthode [InitializeComponent], le code gnr par ces choix est le suivant :
Me.lstEvts.Cursor = System.Windows.Forms.Cursors.Hand Me.btnEffacer.Cursor = System.Windows.Forms.Cursors.No

Par ailleurs, pour dtecter les entres et sorties de la souris sur la liste 2, nous traitons les vnements MouseEnter et MouseLeave de cette mme liste :
' evt lstEvts_MouseEnter Private Sub lstEvts_MouseEnter(ByVal sender As System.Object, ByVal e As System.EventArgs) _ Handles lstEvts.MouseEnter affiche("MouseEnter sur liste") End Sub ' evt lstEvts_MouseLeave Private Sub lstEvts_MouseLeave(ByVal sender As System.Object, ByVal e As System.EventArgs) _ Handles lstEvts.MouseLeave affiche("MouseLeave sur liste") End Sub ' affiche Private Sub affiche(ByVal message As String) ' on affiche le message en haut de la liste des evts lstEvts.Items.Insert(0, message) End Sub

Pour traiter les clics sur le formulaire, nous traitons les vnements MouseDown et MouseUp :
' vt Form1_MouseDown Private Sub Form1_MouseDown(ByVal sender As System.Object, ByVal e As System.Windows.Forms.MouseEventArgs) _ Handles MyBase.MouseDown affiche("MouseDown sur formulaire") End Sub ' vt Form1_MouseUp Private Sub Form1_MouseUp(ByVal sender As System.Object, ByVal e As System.Windows.Forms.MouseEventArgs) _ Handles MyBase.MouseUp affiche("MouseUp sur formulaire") End Sub

Interfaces graphiques

132

Enfin, le code du gestionnaire de clic sur le bouton Effacer :


' vt btnEffacer_Click Private Sub btnEffacer_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _ Handles btnEffacer.Click ' efface la liste des evts lstEvts.Items.Clear() End Sub

4.6

Crer une fentre avec menu

Voyons maintenant comment crer une fentre avec menu. Nous allons crer la fentre suivante :

1 Le contrle 1 est un TextBox en lecture seule (ReadOnly=true) et de nom txtStatut. L'arborescence du menu est la suivante :

Les options de menu sont des contrles comme les autres composants visuels et ont des proprits et vnements. Par exemple le tableau des proprits de l'option de menu A1 :

Deux proprits sont utilises dans notre exemple :


Name Text

le nom du contrle menu le libell de l'option de menu

Les proprits des diffrentes options de menu de notre exemple sont les suivantes : Name
mnuA

Text options A 133

Interfaces graphiques

mnuA1 mnuA2 mnuA3 mnuB mnuB1 mnuSep1 mnuB2 mnuB3 mnuB31 mnuB32

A1 A2 A3 options B B1 - (sparateur) B2 B3 B31 B32

Pour crer un menu, on choisit le composant "MainMenu" dans la barre "ToolBox" :

On a alors un menu vide qui s'installe sur le formulaire avec des cases vides intitules "Type Here". Il suffit d'y indiquer les diffrentes options du menu :

Pour insrer un sparateur entre deux options comme ci-dessus entre les options B1 et B2, positionnez-vous l'emplacement du sparateur dans le menu, cliquez droit et prenez l'option Insert Separator :

Si on lance l'application par ctrl-F5, on obtient un formulaire avec un menu qui pour l'instant ne fait rien. Les options de menu sont traites comme des composants : elles ont des proprits et des vnements. Dans [fentre de code], slectionnez le composant mnuA1 puis slectionnez les vnements associs :

Interfaces graphiques

134

Si ci-dessus on gnre l'vnement Click, VS.NET gnre automatiquement la procdure suivante :


Private Sub mnuA1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles mnuA.Click .... End Sub

Nous pourrions procder ainsi pour toutes les options de menu. Ici, la mme procdure peut tre utilise pour toutes les options. Aussi renomme-t-on la procdure prcdente affiche et nous dclarons les vnements qu'elle gre :
Private Sub affiche(ByVal sender As Object, ByVal e As System.EventArgs) _ Handles mnuA1.Click, mnuA2.Click, mnuB1.Click, mnuB2.Click, mnuB31.Click, mnuB32.Click ' affiche dans le TextBox le nom du sous-menu choisi txtStatut.Text = (CType(sender, MenuItem)).Text End Sub

Dans cette mthode, nous nous contentons d'afficher la proprit Text de l'option de menu la source de l'vnement. La source de l'vnement sender est de type object. Les options de menu sont elles de type MenuItem, aussi est-on oblig ici de faire un transtypage de object vers MenuItem. Excutez l'application et slectionnez l'option A1 pour obtenir le message suivant :

Le code utile de cette application, outre celui de la mthode affiche, est celui de la construction du menu dans le constructeur du formulaire (InitializeComponent) :
Private Sub InitializeComponent() Me.mainMenu1 = New System.Windows.Forms.MainMenu Me.mnuA = New System.Windows.Forms.MenuItem Me.mnuA1 = New System.Windows.Forms.MenuItem Me.mnuA2 = New System.Windows.Forms.MenuItem Me.mnuA3 = New System.Windows.Forms.MenuItem Me.mnuB = New System.Windows.Forms.MenuItem Me.mnuB1 = New System.Windows.Forms.MenuItem Me.mnuB2 = New System.Windows.Forms.MenuItem Me.mnuB3 = New System.Windows.Forms.MenuItem Me.mnuB31 = New System.Windows.Forms.MenuItem Me.mnuB32 = New System.Windows.Forms.MenuItem Me.txtStatut = New System.Windows.Forms.TextBox Me.mnuSep1 = New System.Windows.Forms.MenuItem Me.SuspendLayout() ' ' mainMenu1 ' Me.mainMenu1.MenuItems.AddRange(New System.Windows.Forms.MenuItem() {Me.mnuA, Me.mnuB}) ' ' mnuA ' Me.mnuA.Index = 0 Me.mnuA.MenuItems.AddRange(New System.Windows.Forms.MenuItem() {Me.mnuA1, Me.mnuA2, Me.mnuA3}) Me.mnuA.Text = "Options A" ' ' mnuA1 ' Me.mnuA1.Index = 0 Me.mnuA1.Text = "A1"

'

Interfaces graphiques

135

'

' mnuA2 ' Me.mnuA2.Index = 1 Me.mnuA2.Text = "A2" ' mnuA3 ' Me.mnuA3.Index = 2 Me.mnuA3.Text = "A3"

' mnuB ' Me.mnuB.Index = 1 Me.mnuB.MenuItems.AddRange(New System.Windows.Forms.MenuItem() {Me.mnuB1, Me.mnuSep1, Me.mnuB2, Me.mnuB3}) Me.mnuB.Text = "Options B" ' ' mnuB1 ' Me.mnuB1.Index = 0 Me.mnuB1.Text = "B1" ' mnuB2 ' Me.mnuB2.Index = 2 Me.mnuB2.Text = "B2" ' ' mnuB3 ' Me.mnuB3.Index = 3 Me.mnuB3.MenuItems.AddRange(New System.Windows.Forms.MenuItem() {Me.mnuB31, Me.mnuB32}) Me.mnuB3.Text = "B3" ' mnuB31 ' Me.mnuB31.Index = 0 Me.mnuB31.Text = "B31" ' ' mnuB32 ' Me.mnuB32.Index = 1 Me.mnuB32.Text = "B32"

'

'

'

'

' txtStatut ' Me.txtStatut.Location = New System.Drawing.Point(8, 8) Me.txtStatut.Name = "txtStatut" Me.txtStatut.ReadOnly = True Me.txtStatut.Size = New System.Drawing.Size(112, 20) Me.txtStatut.TabIndex = 0 Me.txtStatut.Text = "" ' ' mnuSep1 ' Me.mnuSep1.Index = 1 Me.mnuSep1.Text = "-" ' ' Form1 ' Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13) Me.ClientSize = New System.Drawing.Size(136, 42) Me.Controls.Add(txtStatut) Me.Menu = Me.mainMenu1 Me.Name = "Form1" Me.Text = "Menus" Me.ResumeLayout(False) End Sub

On notera l'instruction qui associe le menu au formulaire :


Me.Menu = Me.mainMenu1

4.7

Composants non visuels


136

Interfaces graphiques

Nous nous intressons maintenant un certain nombre de composants non visuels : on les utilise lors de la conception mais on ne les voit pas lors de l'excution.

4.7.1

Botes de dialogue OpenFileDialog et SaveFileDialog

Nous allons construire l'application suivante :

Les contrles sont les suivants : N 1 2 3 4 type TextBox multilignes Button Button Button nom txtTexte btnSauvegarde r btnCharger btnEffacer rle texte tap par l'utilisateur ou charg partir d'un fichier permet de sauvegarder le texte de 1 dans un fichier texte permet de charger le contenu d'un fichier texte dans 1 efface le contenu de 1

Deux contrles non visuels sont utiliss :

Lorsqu'ils sont pris dans le "ToolBox " et dposs sur le formulaire, ils sont placs dans une zone part en bas du formulaire. Les composants "Dialog" sont pris dans le "ToolBox" :

Le code du bouton Effacer est simple :


Private Sub btnEffacer_Click(ByVal sender As Object, ByVal e As System.EventArgs) _ Handles btnEffacer.Click ' on efface la bote de saisie txtTexte.Text = "" End Sub

La classe SaveFileDialog est dfinie comme suit :

Interfaces graphiques

137

Elle drive de plusieurs niveaux de classe. De ces nombreuses proprits et mthodes nous retiendrons les suivantes :
string Filter int FilterIndex string InitialDirectory string FileName DialogResult.ShowDia log()

les types de fichiers proposs dans la liste droulante des types de fichiers de la bote de dialogue le n du type de fichier propos par dfaut dans la liste ci-dessus. Commence 0. le dossier prsent initialement pour la sauvegarde du fichier le nom du fichier de sauvegarde indiqu par l'utilisateur mthode qui affiche la bote de dialogue de sauvegarde. Rend un rsultat de type DialogResult.

La mthode ShowDialog affiche une bote de dialogue analogue la suivante :

3 1

1 2 3 4

liste droulante construite partir de la proprit Filter. Le type de fichier propos par dfaut est fix par FilterIndex dossier courant, fix par InitialDirectory si cette proprit a t renseigne nom du fichier choisi ou tap directement par l'utilisateur. Sera disponible dans la proprit FileName boutons Enregistrer/Annuler. Si le bouton Enregistrer est utilis, la fonction ShowDialog rend le rsultat DialogResult.OK

La procdure de sauvegarde peut s'crire ainsi :


Private Sub btnSauvegarder_Click(ByVal sender As Object, ByVal e As System.EventArgs) _ Handles btnSauvegarder.Click Interfaces graphiques

138

' on sauvegarde la bote de saisie dans un fichier texte ' on paramtre la bote de dialogue savefileDialog1 saveFileDialog1.InitialDirectory = Application.ExecutablePath saveFileDialog1.Filter = "Fichiers texte (*.txt)|*.txt|Tous les fichiers (*.*)|*.*" saveFileDialog1.FilterIndex = 0 ' on affiche la bote de dialogue et on rcupre son rsultat If saveFileDialog1.ShowDialog() = DialogResult.OK Then ' on rcupre le nom du fichier Dim nomFichier As String = saveFileDialog1.FileName Dim fichier As StreamWriter = Nothing Try ' on ouvre le fichier en criture fichier = New StreamWriter(nomFichier) ' on crit le texte dedans fichier.Write(txtTexte.Text) Catch ex As Exception ' problme MessageBox.Show("Problme l'criture du fichier (" + ex.Message + ")", "Erreur", MessageBoxButtons.OK, MessageBoxIcon.Error) Return Finally ' on ferme le fichier Try fichier.Close() Catch End Try End Try End If End Sub

On fixe le dossier initial au dossier qui contient l'excutable de l'application :

saveFileDialog1.InitialDirectory = Application.ExecutablePath

On fixe les types de fichiers prsenter

saveFileDialog1.Filter = "Fichiers texte (*.txt)|*.txt|Tous les fichiers (*.*)|*.*"

On notera la syntaxe des filtres filtre1|filtre2|..|filtren avec filtrei= Texte|modle de fichier. Ici l'utilisateur aura le choix entre les fichiers *.txt et *.*. On fixe le type de fichier prsenter au dbut

saveFileDialog1.FilterIndex = 0

Ici, ce sont les fichiers de type *.txt qui seront prsents tout d'abord l'utilisateur. La bote de dialogue est affiche et son rsultat rcupr

If saveFileDialog1.ShowDialog() = DialogResult.OK Then

Pendant que la bote de dialogue est affiche, l'utilisateur n'a plus accs au formulaire principal (bote de dialogue dite modale). L'utilisateur fixe le nom du fichier sauvegarder et quitte la bote soit par le bouton Enregistrer, soit par le bouton Annuler soit en fermant la bote. Le rsultat de la mthode ShowDialog est DialogResult.OK uniquement si l'utilisateur a utilis le bouton Enregistrer pour quitter la bote de dialogue. Ceci fait, le nom du fichier crer est maintenant dans la proprit FileName de l'objet saveFileDialog1. On est alors ramen la cration classique d'un fichier texte. On y crit le contenu du TextBox : txtTexte.Text tout en grant les exceptions qui peuvent se produire.

La classe OpenFileDialog est trs proche de la classe SaveFileDialog et drive de la mme ligne de classes. De ces proprits et mthodes nous retiendrons les suivantes :
string Filter int FilterIndex string InitialDirectory string FileName DialogResult.ShowDialog()

les types de fichiers proposs dans la liste droulante des types de fichiers de la bote de dialogue le n du type de fichier propos par dfaut dans la liste ci-dessus. Commence 0. le dossier prsent initialement pour la recherche du fichier ouvrir le nom du fichier ouvrir indiqu par l'utilisateur mthode qui affiche la bote de dialogue de sauvegarde. Rend un rsultat de type DialogResult.

La mthode ShowDialog affiche une bote de dialogue analogue la suivante :

Interfaces graphiques

139

3 1

1 2 3 4

liste droulante construite partir de la proprit Filter. Le type de fichier propos par dfaut est fix par FilterIndex dossier courant, fix par InitialDirectory si cette proprit a t renseigne nom du fichier choisi ou tap directement par l'utilisateur. Sera disponible dans la proprit FileName boutons Ouvrir/Annuler. Si le bouton Ouvrir est utilis, la fonction ShowDialog rend le rsultat DialogResult.OK

La procdure d'ouverture peut s'crire ainsi :


Private Sub btnCharger_Click(ByVal sender As Object, ByVal e As System.EventArgs) _ Handles btnCharger.Click ' on charge un fichier texte dans la bote de saisie ' on paramtre la bote de dialogue openfileDialog1 openFileDialog1.InitialDirectory = Application.ExecutablePath openFileDialog1.Filter = "Fichiers texte (*.txt)|*.txt|Tous les fichiers (*.*)|*.*" openFileDialog1.FilterIndex = 0 ' on affiche la bote de dialogue et on rcupre son rsultat If openFileDialog1.ShowDialog() = DialogResult.OK Then ' on rcupre le nom du fichier Dim nomFichier As String = openFileDialog1.FileName Dim fichier As StreamReader = Nothing Try ' on ouvre le fichier en lecture fichier = New StreamReader(nomFichier) ' on lit tout le fichier et on le met dans le TextBox txtTexte.Text = fichier.ReadToEnd() Catch ex As Exception ' problme MessageBox.Show("Problme la lecture du fichier (" + ex.Message + ")", "Erreur", MessageBoxButtons.OK, MessageBoxIcon.Error) Return Finally ' on ferme le fichier Try fichier.Close() Catch End Try End Try End If End Sub

On fixe le dossier initial au dossier qui contient l'excutable de l'application :


saveFileDialog1.InitialDirectory=Application.ExecutablePath

On fixe les types de fichiers prsenter


saveFileDialog1.Filter = "Fichiers texte (*.txt)|*.txt|Tous les fichiers (*.*)|*.*"

Interfaces graphiques

140

On fixe le type de fichier prsenter au dbut


saveFileDialog1.FilterIndex = 0

Ici, ce sont les fichiers de type *.txt qui seront prsents tout d'abord l'utilisateur. La bote de dialogue est affiche et son rsultat rcupr
If openFileDialog1.ShowDialog() = DialogResult.OK Then

Pendant que la bote de dialogue est affiche, l'utilisateur n'a plus accs au formulaire principal (bote de dialogue dite modale). L'utilisateur fixe le nom du fichier ouvrir et quitte la bote soit par le bouton Ouvrir, soit par le bouton Annuler soit en fermant la bote. Le rsultat de la mthode ShowDialog est DialogResult.OK uniquement si l'utilisateur a utilis le bouton Ouvrir pour quitter la bote de dialogue. Ceci fait, le nom du fichier crer est maintenant dans la proprit FileName de l'objet openFileDialog1. On est alors ramen la lecture classique d'un fichier texte. On notera la mthode qui permet de lire la totalit d'un fichier :
txtTexte.Text=fichier.ReadToEnd

le contenu du fichier est mis dans le TextBox txtTexte. On gre les exceptions qui peuvent se produire.

4.7.2

Botes de dialogue FontColor et ColorDialog

Nous continuons l'exemple prcdent en prsentant deux nouveaux boutons :

N 6 7 Button Button

type

nom btnCouleur btnPolice

rle pour fixer la couleur des caractres du TextBox pour fixer la police de caractres du TextBox

Nous dposons sur le formulaire un contrle ColorDialog et un contrle FontDialog :

Les classes FontDialog et ColorDialog ont une mthode ShowDialog analogue la mthode ShowDialog des classes OpenFileDialog et SaveFileDialog. La mthode ShowDialog de la classe ColorDialog permet de choisir une couleur :

Interfaces graphiques

141

Si l'utilisateur quitte la bote de dialogue avec le bouton OK, le rsultat de la mthode ShowDialog est DialogResult.OK et la couleur choisie est dans la proprit Color de l'objet ColorDialog utilis. La mthode ShowDialog de la classe FontDialog permet de choisir une police de caractres :

Si l'utilisateur quitte la bote de dialogue avec le bouton OK, le rsultat de la mthode ShowDialog est DialogResult.OK et la police choisie est dans la proprit Font de l'objet FontDialog utilis. Nous avons les lments pour traiter les clics sur les boutons Couleur et Police :
Private Sub btnCouleur_Click(ByVal sender As Object, ByVal e As System.EventArgs) _ Handles btnCouleur.Click ' choix d'une couleur de texte If colorDialog1.ShowDialog() = DialogResult.OK Then ' on change la proprit forecolor du TextBox txtTexte.ForeColor = colorDialog1.Color End If End Sub Private Sub btnPolice_Click(ByVal sender As Object, ByVal e As System.EventArgs) _ Handles btnPolice.Click ' choix d'une police de caractres If fontDialog1.ShowDialog() = DialogResult.OK Then Interfaces graphiques

142

' on change la proprit font du TextBox txtTexte.Font = fontDialog1.Font End If End Sub

4.7.3

Timer

Nous nous proposons ici d'crire l'application suivante :

n Type 1 TextBox, ReadOnly=true 2 Button 3 Timer Le chronomtre en marche :

Nom txtChrono btnArretMarche timer1

Rle affiche un chronomtre bouton Arrt/Marche du chronomtre composant mettant ici un vnement toutes les secondes

Le chronomtre arrt :

Pour changer toutes les secondes le contenu du TextBox txtChrono, il nous faut un composant qui gnre un vnement toutes les secondes, vnement qu'on pourra intercepter pour mettre jour l'affichage du chronomtre. Ce composant c'est le Timer :

Une fois ce composant install sur le formulaire (dans la partie des composants non visuels), un objet de type Timer est cr dans le constructeur du formulaire. La classe System.Windows.Forms.Timer est dfinie comme suit :

Interfaces graphiques

143

De ses proprits nous ne retiendrons que les suivantes :


Interval Tick Enabled

nombre de millisecondes au bout duquel un vnement Tick est mis. l'vnement produit la fin de Interval millisecondes rend le timer actif (true) ou inactif (false)

Dans notre exemple le timer s'appelle timer1 et timer1.Interval est mis 1000 ms (1s). L'vnement Tick se produira donc toutes les secondes. Le clic sur le bouton Arrt/Marche est trait par la procdure suivante :
Private Sub btnArretMarche_Click(ByVal sender As Object, ByVal e As System.EventArgs) _ Handles btnArretMarche.Click ' arrt ou marche ? If btnArretMarche.Text = "Marche" Then ' on note l'heure de dbut dbut = DateTime.Now ' on l'affiche txtChrono.Text = "00:00:00" ' on lance le timer timer1.Enabled = True ' on change le libell du bouton btnArretMarche.Text = "Arrt" ' fin Return End If ' If btnArretMarche.Text = "Arrt" Then ' arrt du timer timer1.Enabled = False ' on change le libell du bouton btnArretMarche.Text = "Marche" ' fin Return End If End Sub Private Sub timer1_Tick(ByVal sender As Object, ByVal e As System.EventArgs) _ Handles timer1.Tick ' une seconde s'est coule Dim maintenant As DateTime = DateTime.Now Dim dure As TimeSpan = DateTime.op_Subtraction(maintenant, dbut) txtChrono.Text = "" + dure.Hours.ToString("d2") + ":" + dure.Minutes.ToString("d2") + ":" + dure.Seconds.ToString("d2") End Sub

Le libell du bouton Arret/Marche est soit "Arrt" soit "Marche". On est donc oblig de faire un test sur ce libell pour savoir quoi faire. dans le cas de "Marche", on note l'heure de dbut dans une variable qui est une variable globale de l'objet formulaire, le timer est lanc (Enabled=true) et le libell du bouton passe "Arrt". dans le cas de "Arrt", on arrte le timer (Enabled=false) et on passe le libell du bouton "Marche".
Public Class Timer1 Inherits System.Windows.Forms.Form Private WithEvents timer1 As System.Windows.Forms.Timer Private WithEvents btnArretMarche As System.Windows.Forms.Button Private components As System.ComponentModel.IContainer Private WithEvents txtChrono As System.Windows.Forms.TextBox Private WithEvents label1 As System.Windows.Forms.Label ' variables d'instance Interfaces graphiques

144

Private dbut As DateTime

L'attribut dbut ci-dessus est connu dans toutes les mthodes de la classe. Il nous reste traiter l'vnement Tick sur l'objet timer1, vnement qui se produit toutes les secondes :
Private Sub timer1_Tick(ByVal sender As Object, ByVal e As System.EventArgs) _ Handles timer1.Tick ' une seconde s'est coule Dim maintenant As DateTime = DateTime.Now Dim dure As TimeSpan = DateTime.op_Subtraction(maintenant, dbut) txtChrono.Text = "" + dure.Hours.ToString("d2") + ":" + dure.Minutes.ToString("d2") + ":" + dure.Seconds.ToString("d2") End Sub

On calcule le temps coul depuis l'heure de lancement du chronomtre. On obtient un objet de type TimeSpan qui reprsente une dure dans le temps. Celle-ci doit tre affiche dans le chronomtre sous la forme hh:mm:ss. Pour cela nous utilisons les proprits Hours, Minutes, Seconds de l'objet TimeSPan qui reprsentent respectivement les heures, minutes, secondes de la dure que nous affichons au format ToString("d2") pour avoir un affichage sur 2 chiffres.

4.8

L'exemple IMPOTS

On reprend l'application IMPOTS dj traite deux fois. Nous y ajoutons maintenant une interface graphique :

0 1 3 4 5 6 7 8

Les contrles sont les suivants n 1 2 3 4 5 6 7 8 type RadioButton RadioButton NumericUpDown TextBox TextBox Button Button Button nom rdOui rdNon incEnfants txtSalaire txtImpots btnCalculer btnEffacer btnQuitter rle coch si mari coch si pas mari nombre d'enfants du contribuable Minimum=0, Maximum=20, Increment=1 salaire annuel du contribuable en F montant de l'impt payer ReadOnly=true lance le calcul de l'impt remet le formulaire dans son tat initial lors du chargement pour quitter l'application

Rgles de fonctionnement le bouton Calculer reste teint tant qu'il n'y a rien dans le champ du salaire si lorsque le calcul est lanc, il s'avre que le salaire est incorrect, l'erreur est signale :

Interfaces graphiques

145

Le programme est donn ci-dessous. Il utilise la classe impot cre dans le chapitre sur les classes. Une partie du code produit automatiquement pas VS.NET n'a pas t ici reproduit.
using System; using System.Drawing; using System.Collections; using System.ComponentModel; using System.Windows.Forms; using System.Data; ' options Option Explicit On Option Strict On ' espaces de noms Imports System Imports System.Drawing Imports System.Collections Imports System.ComponentModel Imports System.Windows.Forms Imports System.Data ' classe formulaire Public Class frmImpots Inherits System.Windows.Forms.Form Private WithEvents label1 As System.Windows.Forms.Label Private WithEvents rdOui As System.Windows.Forms.RadioButton Private WithEvents rdNon As System.Windows.Forms.RadioButton Private WithEvents label2 As System.Windows.Forms.Label Private WithEvents txtSalaire As System.Windows.Forms.TextBox Private WithEvents label3 As System.Windows.Forms.Label Private WithEvents label4 As System.Windows.Forms.Label Private WithEvents groupBox1 As System.Windows.Forms.GroupBox Private WithEvents btnCalculer As System.Windows.Forms.Button Private WithEvents btnEffacer As System.Windows.Forms.Button Private WithEvents btnQuitter As System.Windows.Forms.Button Private WithEvents txtImpots As System.Windows.Forms.TextBox Private components As System.ComponentModel.Container = Nothing Private WithEvents incEnfants As System.Windows.Forms.NumericUpDown ' tableaux de donnes ncessaires au calcul de l'impt Private limites() As Decimal = {12620D, 13190D, 15640D, 24740D, 31810D, 39970D, 48360D, 55790D, 92970D, 127860D, 151250D, 172040D, 195000D, 0D} Private coeffR() As Decimal = {0D, 0.05D, 0.1D, 0.15D, 0.2D, 0.25D, 0.3D, 0.35D, 0.4D, 0.45D, 0.55D, 0.5D, 0.6D, 0.65D} Private coeffN() As Decimal = {0D, 631D, 1290.5D, 2072.5D, 3309.5D, 4900D, 6898.5D, 9316.5D, 12106D, 16754.5D, 23147.5D, 30710D, 39312D, 49062D} ' objet impt Private objImpt As impot = Nothing Public Sub New() InitializeComponent() ' initialisation du formulaire btnEffacer_Click(Nothing, Nothing) btnCalculer.Enabled = False ' cration d'un objet impt Interfaces graphiques

146

Try objImpt = New impot(limites, coeffR, coeffN) Catch ex As Exception MessageBox.Show("Impossible de crer l'objet impt (" + ex.Message + ")", "Erreur", MessageBoxButtons.OK, MessageBoxIcon.Error) ' on inhibe le champ de saisie du salaire txtSalaire.Enabled = False End Try 'try-catch End Sub Protected Overloads Sub Dispose(ByVal disposing As Boolean) .... End Sub Private Sub InitializeComponent() Me.btnQuitter = New System.Windows.Forms.Button Me.groupBox1 = New System.Windows.Forms.GroupBox Me.btnEffacer = New System.Windows.Forms.Button Me.btnCalculer = New System.Windows.Forms.Button Me.txtSalaire = New System.Windows.Forms.TextBox Me.label1 = New System.Windows.Forms.Label Me.label2 = New System.Windows.Forms.Label Me.label3 = New System.Windows.Forms.Label Me.rdNon = New System.Windows.Forms.RadioButton Me.txtImpots = New System.Windows.Forms.TextBox Me.label4 = New System.Windows.Forms.Label Me.rdOui = New System.Windows.Forms.RadioButton Me.incEnfants = New System.Windows.Forms.NumericUpDown Me.groupBox1.SuspendLayout() CType(Me.incEnfants, System.ComponentModel.ISupportInitialize).BeginInit() Me.SuspendLayout() .... End Sub 'InitializeComponent Public Shared Sub Main() Application.Run(New frmImpots) End Sub 'Main Private Sub btnEffacer_Click(ByVal sender As Object, ByVal e As System.EventArgs) _ Handles btnEffacer.Click ' raz du formulaire incEnfants.Value = 0 txtSalaire.Text = "" txtImpots.Text = "" rdNon.Checked = True End Sub 'btnEffacer_Click Private Sub txtSalaire_TextChanged(ByVal sender As Object, ByVal e As System.EventArgs) _ Handles txtSalaire.TextChanged ' tat du bouton Calculer btnCalculer.Enabled = txtSalaire.Text.Trim() <> "" End Sub 'txtSalaire_TextChanged Private Sub btnQuitter_Click(ByVal sender As Object, ByVal e As System.EventArgs) _ Handles btnQuitter.Click ' fin application Application.Exit() End Sub 'btnQuitter_Click Private Sub btnCalculer_Click(ByVal sender As Object, ByVal e As System.EventArgs) _ Handles btnCalculer.Click ' le salaire est-il correct ? Dim intSalaire As Integer = 0 Try ' rcupration du salaire intSalaire = Integer.Parse(txtSalaire.Text) ' il doit tre >=0 If intSalaire < 0 Then Throw New Exception("") End If Catch ex As Exception ' msg d'erreur MessageBox.Show(Me, "Salaire incorrect", "Erreur de saisie", MessageBoxButtons.OK, MessageBoxIcon.Error) ' focus sur champ erron txtSalaire.Focus() ' slection du texte du champ de saisie Interfaces graphiques

147

txtSalaire.SelectAll() ' retour l'interface visuelle Return End Try 'try-catch ' le salaire est correct - on calcule l'impt txtImpots.Text = "" & CLng(objImpt.calculer(rdOui.Checked, CInt(incEnfants.Value), intSalaire)) End Sub 'btnCalculer_Click End Class

Nous utilisons ici l'assemblage impots.dll rsultat de la compilation de la classe impots du chapitre 2. Rappelons que cet assemblage peut tre produit en mode console par la commande
dos>vbc /t:library impots.vb

Cette commande produit le fichier impots.dll appel assemblage. Cet assemblage peut tre ensuite utilis dans diffrents projets. Ici, dans notre projet sous VS.NET, nous utilisons la fentre des proprits du projet :

Pour ajouter une rfrence (un assemblage) nous cliquons droit sur le lot cl References ci-dessus, nous prenons l'option [Ajouter une rfrence] et nous dsignons l'assemblage [impots.dll] que nous avons pris soin de mettre dans le dossier du projet :
dos>dir 01/03/2004 14:39 01/03/2004 14:37 01/03/2004 14:41 9 250 gui_impots.vb 4 096 impots.dll 12 288 gui_impots.exe

Une fois inclus l'assemblage [impots.dll] dans le projet, la classe [impots] devient connue du projet. Auparavant elle ne l'est pas. Une autre mthode est d'inclure le source impots.vb dans le projet. Pour cela, dans la fentre des proprits du projet, on clique droit sur le projet et on prend l'option [Ajouter/Ajouter un lment existant] et on dsigne le fichier impots.vb.

Interfaces graphiques

148

5.
5.1

Gestion d'vnements
Objets delegate

Nous avons dans le chapitre prcdent abord la notion d'vnements lis des composants. Nous voyons maintenant comment crer des vnements dans nos propres classes. C'est une notion difficile qui n'intressera pas les dveloppeurs dbutants auxquels il est conseill de passer directement au chapitre suivant.

L'instruction
Delegate Function opration(ByVal n1 As Integer, ByVal n2 As Integer) As Integer

dfinit un type appel opration qui est en fait un prototype de fonction acceptant deux entiers et rendant un entier. C'est le mot cl delegate qui fait de opration une dfinition de prototype de fonction. Le type [opration] peut tre affect une variable de la faon suivante :
Dim op As New opration(AddressOf [fonction])

Une variable de type [dlgu] se construit comme un objet. Le paramtre du constructeur est la fonction associe l'instance du dlgu. En VB, cette fonction est donne sous la forme AddressOf fonction, o [fonction] est le nom de la fonction. Une fois l'instance d'un dlgu cre, elle peut tre utilise comme une fonction :
' on excute le dlgu Dim n As Integer = op(4, 7)

Le dlgu [opration] a t dfini comme recevant deux paramtres de type [integer] et renvoyant un rsultat de mme type. Donc son instance [op] ci-dessus est utilise comme une telle fonction. Considrons l'exemple suivant :
' fonctions dlgues Imports System Public Class delegate1 ' dfinition d'un prototype de fonction ' accepte 2 entiers en paramtre et rend un entier Delegate Function opration(ByVal n1 As Integer, ByVal n2 As Integer) As Integer ' deux mthodes d'instance correspondant au prototype ' ajouter Public Function ajouter(ByVal n1 As Integer, ByVal n2 As Integer) As Integer Console.Out.WriteLine(("ajouter(" & n1 & "," & n2 & ")")) Return n1 + n2 End Function ' soustraire Public Function soustraire(ByVal n1 As Integer, ByVal n2 As Integer) As Integer Console.Out.WriteLine(("soustraire(" & n1 & "," & n2 & ")")) Return n1 - n2 End Function ' une mthode statique correspondant au prototype Public Shared Function augmenter(ByVal n1 As Integer, ByVal n2 As Integer) As Integer Console.Out.WriteLine(("augmenter(" & n1 & "," & n2 & ")")) Return n1 + 2 * n2 End Function ' programme de test Public Shared Sub Main() ' on dfinit un objet de type opration pour y enregistrer des fonctions ' on enregistre la fonction statique augmenter Dim op As New opration(AddressOf delegate1.augmenter) ' on excute le dlgu Dim n As Integer = op(4, 7) Console.Out.WriteLine(("n=" & n)) ' cration d'un objet c1 de type class1 Dim c1 As New delegate1 ' on enregistre dans le dlgu la mthode ajouter de c1 op = New delegate1.opration(AddressOf c1.ajouter) ' excution de l'objet dlgu n = op(2, 3) Console.Out.WriteLine(("n=" & n)) ' on enregistre dans le dlgu la mthode soustraire de c1 op = New delegate1.opration(AddressOf c1.soustraire) Interfaces graphiques

149

n = op(2, 3) Console.Out.WriteLine(("n=" & n)) End Sub End Class

Les rsultats de l'excution sont les suivants :


augmenter(4,7) n=18 ajouter(2,3) n=5 soustraire(2,3) n=-1

5.2

Gestion d'vnements

A quoi peut servir un objet de type delegate ? Comme nous le verrons dans l'exemple suivant, cela sert surtout la gestion des vnements. Une classe C1 peut gnrer des vnements evti. Lors d'un vnement evti, un objet de type C1 lancera l'excution d'un objet evtiDclench de type delegate. Toutes les fonctions enregistres dans l'objet delegate evtiDclench seront alors excutes. Si un objet C2 utilisant un objet C1 veut tre averti de l'occurrence de l'vnement evti sur l'objet C1, il enregistrera l'une de ses mthodes C2.f dans l'objet dlgu C1.evtiDclench de l'objet C1 afin que la mthode C2.f soit excute chaque fois que l'vnement evti se produit sur l'objet C1. Comme l'objet dlgu C1.evtiDclench peut enregistrer plusieurs fonctions, diffrents objets Ci pourront s'enregistrer auprs du dlgu C1.evtiDclench pour tre prvenus de l'vnement evti sur C1.

5.2.1

Dclaration d'un vnement

Un vnement se dclare de la faon suivante (1) :


Delegate Sub proc(liste d'arguments) Public Event vnement as proc

ou bien (2)
Public Event vnement(liste d'arguments)

C'est le mot cl [Event] qui dfinit une donne comme un vnement. On associe l'vnement la signature que devront avoir les gestionnaires de celui-ci. On peut dfinir cette signature au moyen d'un dlgu (mthode 1), soit directement (mthode 2).

5.2.2

Dfinir les gestionnaires d'un vnement

Lorsqu'un vnement est dclench par l'instruction [RaiseEvent], il faut le traiter. Les procdures charges de traiter les vnements sont appeles des gestionnaires d'vnements. A la cration d'un objet [Event], seule la signature des gestionnaires de l'vnement est dclare. Cette signature est celle de l'objet [delegate] associ l'vnement. Ceci fait, on va pouvoir associer l'vnement des gestionnaires. Ceux-ci sont des procdures qui doivent avoir la mme signature que le [delegate] associ l'vnement. Pour associer un gestionnaire d'vnement un vnement, on utilise l'instruction [AddHandler] de syntaxe :
AddHandler event, AddressOf eventhandler

event : variable vnement qui on associe un nouveau gestionnaire d'vnements eventhandler : nom du gestionnaire d'vnements - doit avoir la signature du [dlgu] associ l'vnement

On peut associer autant de gestionnaires que l'on veut un vnement. Ils seront tous excuts lorsque l'vnement auquel ils sont associs sera dclench.

5.2.3

Dclencher un vnement

Pour dclencher un vnement par programme, on utilise l'instruction [RaiseEvent] :


RaiseEvent eventname[( argumentlist )]

Interfaces graphiques

150

eventname : variable vnement (dclare avec le mot cl Event) argumentlist : arguments du dlgu associ l'vnement.

Tous les gestionnaires qui ont t associs l'vnement par l'instruction AddHandler vont tre appels. Ils vont recevoir comme paramtres, les paramtres dfinis dans la signature de l'vnement.

5.2.4

Un exemple

Considrons l'exemple suivant :


'gestion d'vnements Imports System Public Class myEventArgs Inherits EventArgs ' la classe d'un vt ' attribut Private _saisie As String ' constructeur Public Sub New(ByVal saisie As String) _saisie = saisie End Sub ' proprit saisie en lecture seule Public Overrides Function ToString() As String Return _saisie End Function End Class Public Class metteur ' la classe mettrice d'un vt ' attribut Private _nom As String ' nom de l'metteur ' constructeur Public Sub New(ByVal nom As String) _nom = nom End Sub ' ToString Public Overrides Function ToString() As String Return _nom End Function ' le prototype des fonctions charges de traiter l'vt Delegate Sub _evtHandler(ByVal sender As Object, ByVal evt As myEventArgs) ' le pool des gestionnaires d'vts Public Event evtHandler As _evtHandler ' mthode de demande d'mission d'un vt Public Sub envoyerEvt(ByVal evt As myEventArgs) ' on prvient tous les abonns ' on fait comme si l'vt provenait d'ici RaiseEvent evtHandler(Me, evt) End Sub End Class ' une classe de traitement de l'vt Public Class souscripteur ' attribut Private nom As String ' nom du souscripteur Private sender As metteur ' l'metteur des vts ' constructeur Public Sub New(ByVal nom As String, ByVal e As metteur) ' on note le nom du souscripteur Me.nom = nom ' et l'metteur des vts Me.sender = e ' on s'inscrit pour recevoir les evts de l'metteur e AddHandler e.evtHandler, AddressOf traitementEvt End Sub ' gestionnaire d'vt Public Sub traitementEvt(ByVal sender As Object, ByVal evt As myEventArgs) ' affichage evt Interfaces graphiques

151

Console.Out.WriteLine(("L'objet [" + sender.ToString + "] a signal la saisie errone [" + evt.ToString + "] au souscripteur [" + nom + "]")) End Sub End Class ' un programme de test Public Class test Public Shared Sub Main() ' cration d'un metteur d'evts Dim metteur1 As New metteur("metteur1") ' cration d'un tableau de souscripteurs ' pour les vts mis par metteur1 Dim souscripteurs() As souscripteur = {New souscripteur("s1", metteur1), New souscripteur("s2", metteur1)} ' on lit une suite d'entiers au clavier ' ds que l'un est erron, on met un vt Console.Out.Write("Nombre entier (rien pour arrter) : ") Dim saisie As String = Console.In.ReadLine().Trim() ' tant que la ligne saisie est non vide While saisie <> "" ' la saisie est-elle un nombre entier ? Try Dim n As Integer = Integer.Parse(saisie) Catch ' ce n'est pas un entier ' on prvient tout le monde metteur1.envoyerEvt(New myEventArgs(saisie)) End Try ' on prvient tout le monde ' nouvelle saisie Console.Out.Write("Nombre entier (rien pour arrter) : ") saisie = Console.In.ReadLine().Trim() End While ' fin Environment.Exit(0) End Sub End Class

Le code prcdent est assez complexe. Dtaillons-le. Dans une gestion d'vnements, il y a un metteur d'vnements (sender) qui envoie le dtail des vnements (EventArgs) des souscripteurs qui se sont dclars intresss par les vnements en question. La classe mettrice des vnements est ici la suivante :
Public Class metteur ' la classe mettrice d'un vt ' attribut Private _nom As String ' nom de l'metteur ' constructeur Public Sub New(ByVal nom As String) _nom = nom End Sub ' ToString Public Overrides Function ToString() As String Return _nom End Function ' le prototype des fonctions charges de traiter l'vt Delegate Sub _evtHandler(ByVal sender As Object, ByVal evt As myEventArgs) ' le pool des gestionnaires d'vts Public Event evtHandler As _evtHandler ' mthode de demande d'mission d'un vt Public Sub envoyerEvt(ByVal evt As myEventArgs) ' on prvient tous les abonns ' on fait comme si l'vt provenait d'ici RaiseEvent evtHandler(Me, evt) End Sub End Class

Chaque objet metteur a un nom fix par construction. La mthode ToString a t redfinie de telle faon que lorsqu'on transforme un objet metteur en string, c'est son nom qu'on rcupre. La classe dfinit un vnement :
' le prototype des fonctions charges de traiter l'vt Delegate Sub _evtHandler(ByVal sender As Object, ByVal evt As myEventArgs) ' l'vnement Public Event evtHandler As _evtHandler Interfaces graphiques

152

Le premier argument d'une fonction de traitement d'un vnement sera l'objet qui met l'vnement. Le second argument sera de type myEventArgs, un objet qui donnera des dtails sur l'vnement et sur lequel nous reviendrons. Afin de pouvoir dclencher un vnement de l'extrieur d'un objet metteur, nous ajoutons la classe la mthode envoyerEvt :
' mthode de demande d'mission d'un vt Public Sub envoyerEvt(ByVal evt As myEventArgs) ' on prvient tous les abonns ' on fait comme si l'vt provenait d'ici RaiseEvent evtHandler(Me, evt) End Sub

Nous devons dfinir quel sera le type d'vnements dclenchs par la classe metteur. Nous avons pour cela dfini la classe myEventArgs :
Public Class myEventArgs ' la classe d'un vt ' attribut Private _saisie As String ' constructeur Public Sub New(ByVal saisie As String) _saisie = saisie End Sub ' proprit saisie en lecture seule Public Overrides Function ToString() As String Return _saisie End Function End Class

Les vnements qui vont nous intresser sont des saisies au clavier errones. On va demander un utilisateur de taper des nombres entiers au clavier et ds qu'il tapera une chane qui ne reprsente pas un entier, on dclenchera un vnement. Comme dtail de l'vnement, nous nous conterons de donner la saisie errone. C'est le sens de l'attribut _saisie de la classe. Un objet myEventArgs est donc construit avec pour paramtre la saisie errone. On redfinit par ailleurs la mthode ToString pour quem lorsqu'on transforme un objet myEventArgs en chane, on obtienne l'attribut _saisie. Nous avons dfini l'metteur des vnements et le type d'vnements qu'il met. Nous dfinissons ensuite le type des souscripteurs intresss par ces vnements.
' une classe de traitement de l'vt Public Class souscripteur ' attribut Private nom As String ' nom du souscripteur Private sender As metteur ' l'metteur des vts ' constructeur Public Sub New(ByVal nom As String, ByVal e As metteur) ' on note le nom du souscripteur Me.nom = nom ' et l'metteur des vts Me.sender = e ' on s'inscrit pour recevoir les evts de l'metteur e AddHandler e.evtHandler, AddressOf traitementEvt End Sub ' gestionnaire d'vt Public Sub traitementEvt(ByVal sender As Object, ByVal evt As myEventArgs) ' affichage evt Console.Out.WriteLine(("L'objet [" + sender.ToString + "] a signal la saisie errone [" + evt.ToString + "] au souscripteur [" + nom + "]")) End Sub End Class

Un souscripteur sera dfini par deux paramtres : son nom (attribut nom) et l'objet metteur dont il veut traiter les vnements (attribut sender). Ces deux paramtres seront passs au constructeur de l'objet. Au cours de cette mme construction, le souscripteur s'abonne aux vnements de l'metteur :
' on s'inscrit pour recevoir les evts de l'metteur e AddHandler e.evtHandler, AddressOf traitementEvt

La fonction enregistre auprs de l'metteur est traitementEvt. Cette mthode de la classe souscripteur affiche les deux arguments qu'elle a reues (sender, evt) ainsi que le nom du rcepteur (nom). Ont t dfinis, le type des vnements produits, le type de l'metteur de ces vnements, le type des souscripteurs. Il ne nous reste plus qu' les mettre en oeuvre :
' un programme de test Public Class test Interfaces graphiques

153

Public Shared Sub Main() ' cration d'un metteur d'evts Dim metteur1 As New metteur("metteur1") ' cration d'un tableau de souscripteurs ' pour les vts mis par metteur1 Dim souscripteurs() As souscripteur = {New souscripteur("s1", metteur1), New souscripteur("s2", metteur1)} ' on lit une suite d'entiers au clavier ' ds que l'un est erron, on met un vt Console.Out.Write("Nombre entier (rien pour arrter) : ") Dim saisie As String = Console.In.ReadLine().Trim() ' tant que la ligne saisie est non vide While saisie <> "" ' la saisie est-elle un nombre entier ? Try Dim n As Integer = Integer.Parse(saisie) Catch ' ce n'est pas un entier ' on prvient tout le monde metteur1.envoyerEvt(New myEventArgs(saisie)) End Try ' on prvient tout le monde ' nouvelle saisie Console.Out.Write("Nombre entier (rien pour arrter) : ") saisie = Console.In.ReadLine().Trim() End While ' fin Environment.Exit(0) End Sub

Nous crons un objet metteur :


' cration d'un metteur d'evts Dim metteur1 As New metteur("metteur1")

Nous crons un tableau de deux souscripteurs pour les vnements mis par l'objet metteur1 :
' cration d'un tableau de souscripteurs ' pour les vts mis par metteur1 Dim souscripteurs() As souscripteur = {New souscripteur("s1", metteur1), New souscripteur("s2", metteur1)}

Nous demandons l'utilisateur de taper des nombres entiers au clavier. Ds qu'une saisie est errone, nous demandons metteur1 d'envoyer un vnement ses souscripteurs :
' on prvient tout le monde metteur1.envoyerEvt(New myEventArgs(saisie))

L'vnement envoy est de type myEventArgs et contient la saisie errone. Les deux souscripteurs devraient recevoir cet vnement et le signaler. C'est ce que montre l'excution qui suit.
dos>evt1 Nombre entier (rien Nombre entier (rien L'objet [metteur1] L'objet [metteur1] Nombre entier (rien L'objet [metteur1] L'objet [metteur1] Nombre entier (rien pour arrter) : 4 pour arrter) : a a signal la saisie a signal la saisie pour arrter) : 1.6 a signal la saisie a signal la saisie pour arrter) :

errone [a] au souscripteur [s1] errone [a] au souscripteur [s2] errone [1.6] au souscripteur [s1] errone [1.6] au souscripteur [s2]

Interfaces graphiques

154

6.
6.1 Gnralits

Accs aux bases de donnes

Il existe de nombreuses bases de donnes pour les plate-formes windows. Pour y accder, les applications passent au travers de programmes appels pilotes (drivers).

Pilote de base de donnes Application


I1 I2

Base de donnes

Dans le schma ci-dessus, le pilote prsente deux interfaces : l'interface I1 prsente l'application l'interface I2 vers la base de donnes Afin d'viter qu'une application crite pour une base de donnes B1 doive tre r-crite si on migre vers une base de donnes B2 diffrente, un effort de normalisation a t fait sur l'interface I1. Si on utilise des bases de donnes utilisant des pilotes "normaliss", la base B1 sera fournie avec un pilote P1, la base B2 avec un pilote P2, et l'interface I1 de ces deux pilotes sera identique. Aussi n'aura-t-on pas r-crire l'application. On pourra ainsi, par exemple, migrer une base de donnes ACCESS vers une base de donnes MySQL sans changer l'application. Il existe deux types de pilotes normaliss : les pilotes ODBC (Open DataBase Connectivity) les pilotes OLE DB (Object Linking and Embedding DataBase) Les pilotes ODBC permettent l'accs des bases de donnes. Les sources de donnes pour les pilotes OLE DB sont plus varies : bases de donnes, messageries, annuaires, ... Il n'y a pas de limite. Toute source de donnes peut faire l'objet d'un pilote Ole DB si un diteur le dcide. L'intrt est videmment grand : on a un accs uniforme une grande varit de donnes. La plate-forme .NET est livre avec deux types de classes d'accs aux donnes : 1. 2. les classes SQL Server.NET les classes Ole Db.NET

Les premires classes permettent un accs direct au SGBD SQL Server de Microsoft sans pilote intermdiaire. Les secondes permettent l'accs aux sources de donnes OLE DB.

La plate-forme .NET est fournie (mai 2002) avec trois pilotes OLE DB pour respectivement : SQL Server, Oracle et Microsoft Jet (Access). Si on veut travailler avec une base de donnes ayant un pilote ODBC mais pas de pilote OLE DB, on ne peut pas. Ainsi on ne peut pas travailler avec le SGBD MySQL qui (mai 2002) ne fournit pas de pilote OLE DB. Il existe cependant une srie de classes permettant l'accs aux sources de donnes ODBC, les classes odbc.net. Elles ne sont pas livres en standard avec le SDK et il faut aller les chercher sur le site de Microsoft. Dans les exemples qui vont suivre, nous utiliserons surtout ces classes ODBC car la plupart des bases de donnes sous windows sont livres avec un tel pilote. Voici par exemple, une liste des pilotes ODBC installs sur une machine Win 2000 (Menu Dmarrer/Paramtres/Panneau de configuration/Outils d'administration) : Accs aux bases de donnes 155

On choisit l'icne Source de donnes ODBC :

6.2

Les deux modes d'exploitation d'une source de donnes

La plate-forme .NET permet l'exploitation d'une source de donnes de deux manires diffrentes : 1. mode connect 2. mode dconnect En mode connect, l'application 1. ouvre une connexion avec la source de donnes 2. travaille avec la source de donnes en lecture/criture 3. ferme la connexion En mode dconnect, l'application 1. ouvre une connexion avec la source de donnes Accs aux bases de donnes 156

2. 3. 4. 5.

obtient une copie mmoire de tout ou partie des donnes de la source ferme la connexion travaille avec la copie mmoire des donnes en lecture/criture lorsque le travail est fini, ouvre une connexion, envoie les donnes modifies la source de donnes pour qu'elle les prenne en compte, ferme la connexion

Dans les deux cas, c'est l'opration d'exploitation et de mise jour des donnes qui prend du temps. Imaginons que ces mises jour soient faites par un utilisateur faisant des saisies, cette opration peut prendre des dizaines de minutes. Pendant tout ce temps, en mode connect, la connexion avec la base est maintenue et les modifications immdiatement rpercutes. En mode dconnect, il n'y a pas de connexion la base pendant la mise jour des donnes. Les modifications sont faites uniquement sur la copie mmoire. Elles sont rpercutes sur la source de donnes en une seule fois lorsque tout est termin. Quels sont les avantages et inconvnients des deux mthodes ? Une connexion est coteuse en ressources systme. S'il y a beaucoup de connexions simultanes, le mode dconnect permet de rduire leurs dures un minimum. C'est le cas des applications web ayant des milliers d'utilisateurs. L'inconvnient du mode dconnect est la gestion dlicate des mises jour simultanes. L'utilisateur U1 obtient des donnes au temps T1 et commence les modifier. Au temps T2, l'utilisateur U2 accde lui aussi la source de donnes et obtient les mmes donnes. Entre-temps l'utilisateur U1 a modifi certaines donnes mais ne les a pas encore transmises la source de donnes. U2 travaille donc avec des donnes dont certaines sont errones. Les classes .NET offrent des solutions pour grer ce problme mais il n'est pas simple rsoudre. En mode connect, la mise jour simultane de donnes par plusieurs utilisateurs ne pose normalement pas de problme. La connexion avec la base de donnes tant maintenue, c'est la base de donnes elle-mme qui gre ces mises jour simultanes. Ainsi Oracle verrouille une ligne de la base de donnes ds qu'un utilisateur la modifie. Elle restera verrouille donc inaccessible aux autres utilisateurs jusqu' ce que celui qui l'a modifie valide (commit) sa modification ou l'abandonne (rollback). Si les donnes doivent circuler sur le rseau, le mode dconnect est choisir. Il permet d'avoir une photo des donnes dans un objet appel dataset qui reprsente une base de donnes lui tout seul. Cet objet peut circuler sur le rseau entre machines.

Nous tudions d'abord le mode connect.

6.3 6.3.1

Accs aux donnes en mode connect Les bases de donnes de l'exemple

Nous considrons une base de donnes ACCESS appele articles.mdb et n'ayant qu'une table appele ARTICLES avec la structure suivante : nom code nom prix stock_actuel stock_minimum type code de larticle sur 4 caractres son nom (chane de caractres) son prix (rel) son stock actuel (entier) le stock minimum (entier) en-dea duquel il faut rapprovisionner larticle

Son contenu de dpart est le suivant :

Nous utiliserons cette base aussi bien au travers d'un pilote ODBC qu'un pilote OLE DB afin de montrer la similitude des deux approches et parce que nous disposons de ces deux types de pilotes pour ACCESS. Accs aux bases de donnes 157

Nous utiliserons galement une base MySQL DBARTICLES ayant la mme unique table ARTICLES, le mme contenu et accd au travers d'un pilote ODBC, afin de montrer que l'application crite pour exploiter la base ACCESS n'a pas tre modifie pour utiliser la base MySQL. La base DBARTICLES est accessible un utilisateur appel admarticles avec le mot de passe mdparticles. La copie d'cran suivante montre le contenu de la base MySQL :
C:\mysql\bin>mysql --database=dbarticles --user=admarticles --password=mdparticles Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 3 to server version: 3.23.49-max-debug Type 'help' for help. mysql> show tables; +----------------------+ | Tables_in_dbarticles | +----------------------+ | articles | +----------------------+ 1 row in set (0.01 sec) mysql> select * from articles; +------+--------------------------------+------+--------------+---------------+ | code | nom | prix | stock_actuel | stock_minimum | +------+--------------------------------+------+--------------+---------------+ | a300 | vlo | 2500 | 10 | 5 | | b300 | pompe | 56 | 62 | 45 | | c300 | arc | 3500 | 10 | 20 | | d300 | flches - lot de 6 | 780 | 12 | 20 | | e300 | combinaison de plonge | 2800 | 34 | 7 | | f300 | bouteilles d'oxygne | 800 | 10 | 5 | +------+--------------------------------+------+--------------+---------------+ 6 rows in set (0.02 sec) mysql> describe articles; +---------------+-------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +---------------+-------------+------+-----+---------+-------+ | code | text | YES | | NULL | | | nom | text | YES | | NULL | | | prix | double | YES | | NULL | | | stock_actuel | smallint(6) | YES | | NULL | | | stock_minimum | smallint(6) | YES | | NULL | | +---------------+-------------+------+-----+---------+-------+ 5 rows in set (0.00 sec) mysql> exit Bye

Pour dfinir la base ACCESS comme source de donnes ODBC, procdez comme suit : activez l'administrateur de sources de donnes ODBC comme il a t montr plus haut et slectionnez l'onglet User DSN (DSN=Data Source Name)

Accs aux bases de donnes

158

ajoutez une source avec le bouton Add , indiquez que cette source est accessible via un pilote Access et faites Terminer :

Donnez le nom articles-access la source de donnes, mettez une description libre et utilisez le bouton Slectionner pour dsigner le fichier .mdb de la base. Terminez par OK.

Accs aux bases de donnes

159

La nouvelle source de donnes apparat alors dans la liste des sources DSN utilisateur :

Pour dfinir la base MySQL DBARTICLES comme source de donnes ODBC, procdez comme suit : activez l'administrateur de sources de donnes ODBC comme il a t montr plus haut et slectionnez l'onglet User DSN. Ajoutez une nouvelle source de donnes avec Add et slectionnez le pilote ODBC de MySQL.

Faites Terminer. Apparat alors une page de configuration de la source MySQL :

1 3 4 5 160 2

Accs aux bases de donnes

dans (1) on donne un nom notre source de donnes ODBC dans (2) on indique la machine sur laquelle se trouve le serveur MySQL. Ici nous mettons localhost pour indiquer qu'il est sur la mme machine que notre application. Si le serveur MySQL tait sur une machine M distante, on mettrait l son nom et notre application fonctionnerait alors avec une base de donnes distante sans modification. dans (3) on met le nom de la base. Ici elle s'appelle DBARTICLES. dans (4) on met le login admarticles et dans (5) le mot de passe mdparticles.

6.3.2
1. 2. 3. 4.

Utilisation d'un pilote ODBC

Dans une application utilisant une base de donnes en mode connect, on trouvera gnralement les tapes suivantes : Connexion la base de donnes missions de requtes SQL vers la base Rception et traitement des rsultats de ces requtes Fermeture de la connexion

Les tapes 2 et 3 sont ralises de faon rpte, la fermeture de connexion nayant lieu qu la fin de lexploitation de la base. Cest un schma relativement classique dont vous avez peut-tre lhabitude si vous avez exploit une base de donnes de faon interactive. Ces tapes sont les mmes que la base soit utilise au travers d'un pilote ODBC ou d'un pilote OLE DB. Nous prsentons ci-dessous un exemple avec les classes .NET de gestion des sources de donnes ODBC. Le programme s'appelle liste et admet comme paramtre le nom DSN d'une source de donnes ODBC ayant une table ARTICLES. Il affiche alors le contenu de cette table :
dos>liste syntaxe : pg dsnArticles dos>liste articles-access ---------------------------------------code,nom,prix,stock_actuel,stock_minimum ---------------------------------------a300 b300 c300 d300 e300 f300 vlo pompe arc flches - lot de 6 combinaison de plonge bouteilles d'oxygne 2500 10 5 56 62 45 3500 10 20 780 12 20 2800 34 7 800 10 5

dos>liste mysql-artices Erreur d'exploitation de la base de donnes (ERROR [IM002] [Microsoft][ODBC Driver Manager] Data source name not found and no default driver specified) dos>liste mysql-articles ---------------------------------------code,nom,prix,stock_actuel,stock_minimum ---------------------------------------a300 b300 c300 d300 e300 f300 vlo pompe arc flches - lot de 6 combinaison de plonge bouteilles d'oxygne 2500 10 5 56 62 45 3500 10 20 780 12 20 2800 34 7 800 10 5

Sur les rsultats ci-dessus, nous voyons que le programme a list aussi bien le contenu de la base ACCESS que de la base MySQL. Etudions maintenant le code de ce programme :
' options Option Explicit On Option Strict On ' espaces de noms Imports System Imports System.Data Imports Microsoft.Data.Odbc Imports Microsoft.VisualBasic Module db1 Sub main(ByVal args As String()) ' application console ' affiche le contenu d'une table ARTICLES d'une base DSN

Accs aux bases de donnes

161

' dont le nom est pass en paramtre Const syntaxe As String = "syntaxe : pg dsnArticles" Const tabArticles As String = "articles" ' la table des articles ' vrification des paramtres ' a-t-on 1 paramtre If args.Length <> 1 Then ' msg d'erreur Console.Error.WriteLine(syntaxe) ' fin Environment.Exit(1) End If ' on rcupre le paramtre Dim dsnArticles As String = args(0) ' la base DSN ' prparation de la connexion la bd Dim articlesConn As OdbcConnection = Nothing ' la connexion Dim myReader As OdbcDataReader = Nothing ' le lecteur de donnes ' on tente d'accder la base de donnes Try ' chane de connexion la base Dim connectString As String = "DSN=" + dsnArticles + ";" articlesConn = New OdbcConnection(connectString) articlesConn.Open() ' excution d'une commande SQL Dim sqlText As String = "select * from " + tabArticles Dim myOdbcCommand As New OdbcCommand(sqlText) myOdbcCommand.Connection = articlesConn myReader = myOdbcCommand.ExecuteReader() ' Exploitation de la table rcupre ' affichage des colonnes Dim ligne As String = "" Dim i As Integer For i = 0 To (myReader.FieldCount - 1) - 1 ligne += myReader.GetName(i) + "," Next i ligne += myReader.GetName(i) Console.Out.WriteLine((ControlChars.Lf + "".PadLeft(ligne.Length, "-"c) + ControlChars.Lf + ligne + ControlChars.Lf + "".PadLeft(ligne.Length, "-"c) + ControlChars.Lf)) ' affichage des donnes While myReader.Read() ' exploitation ligne courante ligne = "" For i = 0 To myReader.FieldCount - 1 ligne += myReader(i).ToString + " " Next i Console.WriteLine(ligne) End While Catch ex As Exception Console.Error.WriteLine(("Erreur d'exploitation de la base de donnes " + ex.Message + ")")) Environment.Exit(2) Finally ' fermeture lecteur myReader.Close() ' fermeture connexion articlesConn.Close() End Try End Sub End Module

Les classes de gestion des sources ODBC se trouvent dans l'espace de noms Microsoft.Data.Odbc qu'on doit donc importer. Par ailleurs, un certain nombre de classes se trouve dans l'espace de noms System.Data.
Imports System.Data Imports Microsoft.Data.Odbc

Les espaces de noms utiliss par le programme sont dans diffrents assemblages. On compile le programme avec la commande suivante :
dos>vbc /r:microsoft.data.odbc.dll /r:microsoft.visualbasic.dll /r:system.dll /r:system.data.dll db1.vb

Accs aux bases de donnes

162

6.3.2.1

La phase de connexion

Une connexion ODBC utilise la classe OdbcConnection. Le constructeur de cette classe admet comme paramtre ce qu'on appelle une chane de connexion. Celle-ci est une chane de caractres qui dfinit tous les paramtres ncessaires pour que la connexion la base de donnes puisse se faire. Ces paramtres peuvent tre trs nombreux et donc la chane complexe. La chane a la forme "param1=valeur1;param2=valeur2;...;paramj=valeurj;".Voici quelques paramtres paramj possibles :
uid password dsn data source ...

nom d'un utilisateur qui va accder la base de donnes mot de passe de cet utilisateur nom DSN de la base si elle en a un nom de la base de donnes accde

Si on dfinit une source de donnes comme source de donnes ODBC l'aide de l'administrateur de sources de donnes ODBC, ces paramtres ont dj t donns et enregistrs. Il suffit alors de passer le paramtre DSN qui donne le nom DSN de la source de donnes. C'est ce qui est fait ici :
' prparation de la connexion la bd Dim articlesConn As OdbcConnection = Nothing ' la connexion Dim myReader As OdbcDataReader = Nothing ' le lecteur de donnes Try ' on tente d'accder la base de donnes ' chane de connexion la base Dim connectString As String = "DSN=" + dsnArticles + ";" articlesConn = New OdbcConnection(connectString) articlesConn.Open()

Une fois l'objet OdbcConnection construit, on ouvre la connexion avec la mthode Open. Cette ouverture peut chouer comme toute autre opration sur la base. C'est pourquoi l'ensemble du code d'accs la base est-il dans un try-catch. Une fois la connexion tablie, on peut mettre des requtes SQL sur la base.

6.3.2.2

mettre des requtes SQL

Pour mettre des requtes SQL, il nous faut un objet Command, ici plus exactement un objet OdbcCommand puisque nous utilisons une source de donnes ODBC. La classe OdbcCommand a plusieurs constructeurs : OdbcCommand() : cre un objet Command vide. Il faudra pour l'utiliser prciser ultrieurement diverses proprits : o CommandText : le texte de la requte SQL excuter o Connection : l'objet OdbcConnection reprsentant la connexion la base de donnes sur laquelle la requte sera faite o CommandType : le type de la requte SQL. Il y a trois valeurs possibles 1. CommandType.Text : la proprit CommandText contient le texte d'une requte SQL (valeur par dfaut) 2. CommandType.StoredProcedure : la proprit CommandText contient le nom d'une procdure stocke dans la base 3. CommandType.TableDirect : la proprit CommandText contient le nom d'une table T. Equivalent select * from T. N'existe que pour les pilotes OLE DB. OdbcCommand(string sqlText) : le paramtre sqlText sera affect la proprit CommandText. C'est le texte de la requte SQL excuter. La connexion devra tre prcise dans la proprit Connection. OdbcCommand(string sqlText, OdbcConnection connexion) : le paramtre sqlText sera affect la proprit CommandText et le paramtre connexion la proprit Connection. Pour mettre la requte SQL, on dispose de deux mthodes : OdbcdataReader ExecuteReader() : envoie la requte SELECT de CommandText la connexion Connection et construit un objet OdbcDataReader permettant l'accs toutes les lignes de la table rsultat du select int ExecuteNOnQuery() : envoie la requte de mise jour (INSERT, UPDATE, DELETE) de CommandText la connexion Connection et rend le nombre de lignes affectes par cette mise jour.

Dans notre exemple, aprs avoir ouvert la connexion la base, nous mettons une requte SQL SELECT pour avoir le contenu de la table ARTICLES :
' excution d'une commande SQL Dim sqlText As String = "select * from " + tabArticles Dim myOdbcCommand As New OdbcCommand(sqlText) myOdbcCommand.Connection = articlesConn

Accs aux bases de donnes

163

myReader = myOdbcCommand.ExecuteReader()

Une requte dinterrogation est classiquement une requte du type :


select col1, col2,... from table1, table2,... where condition order by expression ...

Seuls les mots cls de la premire ligne sont obligatoires, les autres sont facultatifs. Il existe dautres mots cls non prsents ici. 1. 2. 3. 4. Une jointure est faite avec toutes les tables qui sont derrire le mot cl from Seules les colonnes qui sont derrire le mot cl select sont conserves Seules les lignes vrifiant la condition du mot cl where sont conserves Les lignes rsultantes ordonnes selon lexpression du mot cl order by forment le rsultat de la requte.

Le rsultat dun select est une table. Si on considre la table ARTICLES prcdente et quon veuille les noms des articles dont le stock actuel est au-dessous du seuil minimal, on crira : select nom from articles where stock_actuel<stock_minimum Si on les veut par ordre alphabtique des noms, on crira : select nom from articles where stock_actuel<stock_minimum order by nom

6.3.2.3

Exploitation du rsultat d'une requte SELECT

Le rsultat d'une requte SELECT en mode non connect est un objet DataReader, ici un objet OdbcDataReader. Cet objet permet d'obtenir squentiellement toutes les lignes du rsultat et d'avoir des informations sur les colonnes de ces rsultats. Examinons quelques proprits et mthodes de cette classe :
FieldCount Item XXX GetXXX(i) string GetName(i) Close() bool Read()

le nombre de colonnes de la table Item(i) reprsente la colonne n i de la ligne courante du rsultat la valeur de la colonne n i de la ligne courante rendue comme type XXX(Int16, Int32, Int64, Double, String, Boolean, ...) nom de la colonne n i ferme l'objet OdbcdataReader et libre les ressources associes avance d'une ligne dans la table des rsultats. Rend faux si cela n'est pas possible. La nouvelle ligne devient la ligne courante du lecteur.

L'exploitation du rsultat d'un select est typiquement une exploitation squentielle analogue celle des fichiers texte : on ne peut qu'avancer dans la table, pas reculer :
While myReader.Read() ' on a une ligne - on l'exploite .... ' ligne suivante end while

Ces explications suffisent comprendre le code suivant de notre exemple :


' Exploitation de la table rcupre ' affichage des colonnes Dim ligne As String = "" Dim i As Integer For i = 0 To (myReader.FieldCount - 1) - 1 ligne += myReader.GetName(i) + "," Next i ligne += myReader.GetName(i) Console.Out.WriteLine((ControlChars.Lf + "".PadLeft(ligne.Length, "-"c) + ControlChars.Lf + ligne + ControlChars.Lf + "".PadLeft(ligne.Length, "-"c) + ControlChars.Lf)) ' affichage des donnes While myReader.Read() ' exploitation ligne courante ligne = "" For i = 0 To myReader.FieldCount - 1

Accs aux bases de donnes

164

ligne += myReader(i).ToString + " " Next i Console.WriteLine(ligne) End While

La seule difficult est dans l'instruction o les valeurs des diffrentes colonnes de la ligne courante sont concatnes :
For i = 0 To myReader.FieldCount - 1 ligne += myReader(i).ToString + " " Next i

La notation ligne+=myReader(i).ToString est traduit par ligne+=myReader.Item(i).ToString() o Item(i) est la valeur de la colonne i de la ligne courante.

6.3.2.4

Libration des ressources

Les classes OdbcReader et OdbcConnection possdent toutes deux une mthode Close() qui libre les ressources associes aux objets ainsi ferms.
' fermeture lecteur myReader.Close() ' fermeture connexion articlesConn.Close()

6.3.3

Utilisation d'un pilote OLE DB

Nous reprenons le mme exemple, cette fois avec une base accde via un pilote OLE DB. La plate-forme .NET fournit un tel pilote pour les bases ACCESS. Aussi allons-nous utiliser la mme base articles.mdb que prcdemment. Nous cherchons montrer ici que si les classes changent, les concepts restent les mmes : la connexion est reprsente par un objet OleDbConnection une requte SQL est mise grce un objet OleDbCommand si cette requte est une clause SELECT, on obtiendra en retour un objet OleDbDataReader pour accder aux lignes de la table rsultat

Ces classes sont dans l'espace de noms System.Data.OleDb. Le programme prcdent peut tre transform aisment pour grer une base OLE DB : on remplace partout OdbcXX par OleDbXX on modifie la chane de connexion. Pour une base ACCESS sans login/mot de passe, la chane de connexion est Provider=Microsoft.JET.OLEDB.4.0;Data Source=[fichier.mdb]. La partie paramtrable de cette chane est le nom du fichier ACCESS utiliser. Nous modifierons notre programme pour qu'il accepte en paramtre le nom de ce fichier. l'espace de noms importer est maintenant System.Data.OleDb. Notre programme devient le suivant :
' options Option Explicit On Option Strict On ' espaces de noms Imports System Imports System.Data Imports Microsoft.Data.Odbc Imports Microsoft.VisualBasic Imports System.Data.OleDb Module db2 Public Sub Main(ByVal args() As String) ' application console ' affiche le contenu d'une table ARRTICLES d'une base DSN ' dont le nom est pass en paramtre Const syntaxe As String = "syntaxe : pg base_access_articles" Const tabArticles As String = "articles" ' la table des articles ' vrification des paramtres ' a-t-on 1 paramtre If args.Length <> 1 Then ' msg d'erreur Console.Error.WriteLine(syntaxe) ' fin

Accs aux bases de donnes

165

Environment.Exit(1) End If ' on rcupre le paramtre Dim dbArticles As String = args(0) ' la base de donnes

' prparation de la connexion la bd Dim articlesConn As OleDbConnection = Nothing ' la connexion Dim myReader As OleDbDataReader = Nothing ' le lecteur de donnes ' on tente d'accder la base de donnes Try ' chane de connexion la base Dim connectString As String = "Provider=Microsoft.JET.OLEDB.4.0;Data Source=" + dbArticles + ";" articlesConn = New OleDbConnection(connectString) articlesConn.Open() ' excution d'une commande SQL Dim sqlText As String = "select * from " + tabArticles Dim myOleDbCommand As New OleDbCommand(sqlText) myOleDbCommand.Connection = articlesConn myReader = myOleDbCommand.ExecuteReader() ' Exploitation de la table rcupre ' affichage des colonnes Dim ligne As String = "" Dim i As Integer For i = 0 To (myReader.FieldCount - 1) - 1 ligne += myReader.GetName(i) + "," Next i ligne += myReader.GetName(i) Console.Out.WriteLine((ControlChars.Lf + "".PadLeft(ligne.Length, "-"c) + ControlChars.Lf + ligne + ControlChars.Lf + "".PadLeft(ligne.Length, "-"c) + ControlChars.Lf)) ' affichage des donnes While myReader.Read() ' exploitation ligne courante ligne = "" For i = 0 To myReader.FieldCount - 1 ligne += myReader(i).ToString + " " Next i Console.WriteLine(ligne) End While Catch ex As Exception Console.Error.WriteLine(("Erreur d'exploitation de la base de donnes (" + ex.Message + ")")) Environment.Exit(2) Finally ' fermeture lecteur myReader.Close() ' fermeture connexion articlesConn.Close() End Try ' fin Environment.Exit(0) End Sub End Module

Les rsultats obtenus :


dos>vbc liste.vb E:\data\serge\MSNET\vb.net\adonet\6>dir 07/05/2002 15:09 2 325 liste.CS 07/05/2002 15:09 4 608 liste.exe 20/08/2001 11:54 86 016 ARTICLES.MDB dos>liste articles.mdb ---------------------------------------code,nom,prix,stock_actuel,stock_minimum ---------------------------------------a300 b300 c300 d300 e300 f300 vlo pompe arc flches - lot de 6 combinaison de plonge bouteilles d'oxygne 2500 10 5 56 62 45 3500 10 20 780 12 20 2800 34 7 800 10 5

Accs aux bases de donnes

166

6.3.4

Mise jour d'une table

Les exemples prcdents se contentaient de lister le contenu d'une table. Nous modifions notre programme de gestion de la base d'articles afin qu'il puisse modifier celle-ci. Le programme s'appelle sql. On lui passe en paramtre le nom DSN de la base d'articles grer. L'utilisateur tape directement des commandes SQL au clavier que le programme excute comme le montrent les rsultats qui suivent obtenus sur la base MySQL d'articles :
dos>vbc /r:microsoft.data.odbc.dll sql.vb dos>sql mysql-articles Requte SQL (fin pour arrter) : select * from articles ---------------------------------------code,nom,prix,stock_actuel,stock_minimum ---------------------------------------a300 b300 c300 d300 e300 f300 vlo pompe arc flches - lot de 6 combinaison de plonge bouteilles d'oxygne 2500 10 5 56 62 45 3500 10 20 780 12 20 2800 34 7 800 10 5

Requte SQL (fin pour arrter) : select * from articles where stock_actuel<stock_minimum ---------------------------------------code,nom,prix,stock_actuel,stock_minimum ---------------------------------------c300 arc d300 flches - lot de 6 3500 10 20 780 12 20

Requte SQL (fin pour arrter) : insert into articles values ("1","1",1,1,1) Il y a eu 1 ligne(s) modifie(s) Requte SQL (fin pour arrter) : select * from articles ---------------------------------------code,nom,prix,stock_actuel,stock_minimum ---------------------------------------a300 vlo b300 pompe c300 arc d300 flches - lot de 6 e300 combinaison de plonge f300 bouteilles d'oxygne 1 1 1 1 1 2500 10 5 56 62 45 3500 10 20 780 12 20 2800 34 7 800 10 5

Requte SQL (fin pour arrter) : update articles set nom="2" where nom="1" Il y a eu 1 ligne(s) modifie(s) Requte SQL (fin pour arrter) : select * from articles ---------------------------------------code,nom,prix,stock_actuel,stock_minimum ---------------------------------------a300 vlo b300 pompe c300 arc d300 flches - lot de 6 e300 combinaison de plonge f300 bouteilles d'oxygne 1 2 1 1 1 2500 10 5 56 62 45 3500 10 20 780 12 20 2800 34 7 800 10 5

Requte SQL (fin pour arrter) : delete from articles where code="1" Il y a eu 1 ligne(s) modifie(s) Requte SQL (fin pour arrter) : select * from articles ---------------------------------------code,nom,prix,stock_actuel,stock_minimum ---------------------------------------a300 b300 c300 d300 vlo pompe arc flches - lot de 6 2500 10 5 56 62 45 3500 10 20 780 12 20

Accs aux bases de donnes

167

e300 combinaison de plonge f300 bouteilles d'oxygne

2800 34 7 800 10 5

Requte SQL (fin pour arrter) : select * from articles order by nom asc ---------------------------------------code,nom,prix,stock_actuel,stock_minimum ---------------------------------------c300 f300 e300 d300 b300 a300 arc bouteilles d'oxygne combinaison de plonge flches - lot de 6 pompe vlo 3500 10 20 800 10 5 2800 34 7 780 12 20 56 62 45 2500 10 5

Requte SQL (fin pour arrter) : fin

Le programme est le suivant :


' options Option Explicit On Option Strict On ' espaces de noms Imports System Imports System.Data Imports Microsoft.Data.Odbc Imports System.Data.OleDb Imports System.Text.RegularExpressions Imports System.Collections Imports Microsoft.VisualBasic Module db3 Public Sub Main(ByVal args() As String) ' application console ' excute des requtes SQL tapes au clavier sur une ' table ARTICLES d'une base DSN dont le nom est pass en paramtre Const syntaxe As String = "syntaxe : pg dsnArticles" ' vrification des paramtres ' a-t-on 2 paramtres If args.Length <> 1 Then ' msg d'erreur Console.Error.WriteLine(syntaxe) ' fin Environment.Exit(1) End If 'if ' on rcupre le paramtre Dim dsnArticles As String = args(0) ' chane de connexion la base Dim connectString As String = "DSN=" + dsnArticles + ";" ' prparation de la connexion la bd Dim articlesConn As OdbcConnection = Nothing Dim sqlCommand As OdbcCommand = Nothing Try ' on tente d'accder la base de donnes articlesConn = New OdbcConnection(connectString) articlesConn.Open() ' on cre un objet command sqlCommand = New OdbcCommand("", articlesConn) 'try Catch ex As Exception ' msg d'erreur Console.Error.WriteLine(("Erreur d'exploitation de la base de donnes (" + ex.Message + ")")) ' libration des ressources Try articlesConn.Close() Catch End Try Environment.Exit(2) End Try 'catch ' on construit un dictionnaire des commandes sql acceptes Dim commandesSQL() As String = {"select", "insert", "update", "delete"} Dim dicoCommandes As New Hashtable Dim i As Integer For i = 0 To commandesSQL.Length - 1 dicoCommandes.Add(commandesSQL(i), True) Next i

Accs aux bases de donnes

168

' lecture-excution des commandes SQL tapes au clavier Dim requte As String = Nothing ' texte de la requte SQL Dim champs() As String ' les champs de la requte Dim modle As New Regex("\s+") ' boucle de saisie-excution des commandes SQL tapes au clavier While True ' pas d'erreur au dpart Dim erreur As Boolean = False ' demande de la requte Console.Out.Write(ControlChars.Lf + "Requte SQL (fin pour arrter) : ") requte = Console.In.ReadLine().Trim().ToLower() ' fini ? If requte = "fin" Then Exit While End If ' on dcompose la requte en champs champs = modle.Split(requte) ' requte valide ? If champs.Length = 0 Or Not dicoCommandes.ContainsKey(champs(0)) Then ' msg d'erreur Console.Error.WriteLine("Requte invalide. Utilisez select, insert, update, delete") ' requte suivante erreur = True End If If Not erreur Then ' prparation de l'objet Command pour excuter la requte sqlCommand.CommandText = requte ' excution de la requte Try If champs(0) = "select" Then executeSelect(sqlCommand) Else executeUpdate(sqlCommand) End If Catch ex As Exception ' msg d'erreur Console.Error.WriteLine(("Erreur d'exploitation de la base de donnes (" + ex.Message + ")")) End Try End If End While ' libration des ressources Try articlesConn.Close() Catch End Try Environment.Exit(0) End Sub ' excution d'une requte de mise jour Sub executeUpdate(ByVal sqlCommand As OdbcCommand) ' excute sqlCommand, requte de mise jour Dim nbLignes As Integer = sqlCommand.ExecuteNonQuery() ' affichage Console.Out.WriteLine(("Il y a eu " & nbLignes & " ligne(s) modifie(s)")) End Sub ' excution d'une requte Select Sub executeSelect(ByVal sqlCommand As OdbcCommand) ' excute sqlCommand, requte select Dim myReader As OdbcDataReader = sqlCommand.ExecuteReader() ' Exploitation de la table rcupre ' affichage des colonnes Dim ligne As String = "" Dim i As Integer For i = 0 To (myReader.FieldCount - 1) - 1 ligne += myReader.GetName(i) + "," Next i ligne += myReader.GetName(i) Console.Out.WriteLine((ControlChars.Lf + "".PadLeft(ligne.Length, "-"c) + ControlChars.Lf + ligne + ControlChars.Lf + "".PadLeft(ligne.Length, "-"c) + ControlChars.Lf)) ' affichage des donnes While myReader.Read() ' exploitation ligne courante ligne = "" For i = 0 To myReader.FieldCount - 1 ligne += myReader(i).ToString + " " Next i ' affichage Console.WriteLine(ligne) End While

Accs aux bases de donnes

169

' libration des ressources myReader.Close() End Sub End Module

Nous ne commentons ici que ce qui est nouveau par rapport au programme prcdent : Nous construisons un dictionnaire des commandes sql acceptes :

' on construit un dictionnaire des commandes sql acceptes Dim commandesSQL() As String = {"select", "insert", "update", "delete"} Dim dicoCommandes As New Hashtable Dim i As Integer For i = 0 To commandesSQL.Length - 1 dicoCommandes.Add(commandesSQL(i), True) Next i

ce qui nous permet ensuite de vrifier simplement si le 1er mot (champs[0]) de la requte tape est l'une des quatre commandes acceptes :
' requte valide ? If champs.Length = 0 Or Not dicoCommandes.ContainsKey(champs(0)) Then ' msg d'erreur Console.Error.WriteLine("Requte invalide. Utilisez select, insert, update, delete") ' requte suivante erreur = True End If 'if

Auparavant la requte avait t dcompose en champs l'aide de la mthode Split de la classe RegEx :

Dim modle As New Regex("\s+") .... ' on dcompose la requte en champs champs = modle.Split(requte)

Les mots composant la requte peuvent tre spars d'un nombre quelconque d'espaces. L'excution d'une requte select n'utilise pas la mme mthode que celle d'une requte d'une mise jour (insert, update, delete). Aussi doit-on faire un test et excuter une fonction diffrente pour chacu de ces deux cas :
' prparation de l'objet Command pour excuter la requte sqlCommand.CommandText = requte ' excution de la requte Try If champs(0) = "select" Then executeSelect(sqlCommand) Else executeUpdate(sqlCommand) End If 'try Catch ex As Exception ' msg d'erreur Console.Error.WriteLine(("Erreur d'exploitation de la base de donnes (" + ex.Message + ")")) End Try

L'excution d'une requte SQL peut gnrer une exception qui est ici gre. La fonction executeSelect reprend tout ce qui a t vu dans les exemples prcdents. La fonction executeUpdate utilise la mthode ExecuteNonQuery de la class OdbcCommand qui rend le nombre de lignes affectes par la commande.

6.3.5

IMPOTS

Nous reprenons l'objet impt construit dans un chapitre prcdent :


' options Option Strict On Option Explicit On ' espaces de noms Imports System Public Class impt ' les donnes ncessaires au calcul de l'impt ' proviennent d'une source extrieure

Accs aux bases de donnes

170

Private limites(), coeffR(), coeffN() As Decimal ' constructeur Public Sub New(ByVal LIMITES() As Decimal, ByVal COEFFR() As Decimal, ByVal COEFFN() As Decimal) ' on vrifie que les 3 tablaeux ont la mme taille Dim OK As Boolean = LIMITES.Length = COEFFR.Length And LIMITES.Length = COEFFN.Length If Not OK Then Throw New Exception("Les 3 tableaux fournis n'ont pas la mme taille(" & LIMITES.Length & "," & COEFFR.Length & "," & COEFFN.Length & ")") End If ' c'est bon Me.limites = LIMITES Me.coeffR = COEFFR Me.coeffN = COEFFN End Sub ' calcul de l'impt Public Function calculer(ByVal mari As Boolean, ByVal nbEnfants As Integer, ByVal salaire As Integer) As Long ' calcul du nombre de parts Dim nbParts As Decimal If mari Then nbParts = CDec(nbEnfants) / 2 + 2 Else nbParts = CDec(nbEnfants) / 2 + 1 End If If nbEnfants >= 3 Then nbParts += 0.5D End If ' calcul revenu imposable & Quotient familial Dim revenu As Decimal = 0.72D * salaire Dim QF As Decimal = revenu / nbParts ' calcul de l'impt limites((limites.Length - 1)) = QF + 1 Dim i As Integer = 0 While QF > limites(i) i += 1 End While ' retour rsultat Return CLng(revenu * coeffR(i) - nbParts * coeffN(i)) End Function End Class

Nous lui ajoutons un nouveau constructeur permettant d'initialiser les tableaux limites, coeffR, coeffN partir d'une base de donnes ODBC :
Imports System.Data Imports Microsoft.Data.Odbc Imports System.Collections ...

' constructeur 2 Public Sub New(ByVal DSNimpots As String, ByVal Timpots As String, ByVal colLimites As String, ByVal colCoeffR As String, ByVal colCoeffN As String) ' initialise les trois tableaux limites, coeffR, coeffN partir ' du contenu de la table Timpots de la base ODBC DSNimpots ' colLimites, colCoeffR, colCoeffN sont les trois colonnes de cette table ' peut lancer une exception Dim connectString As String = "DSN=" + DSNimpots + ";" ' chane de connexion la base Dim impotsConn As OdbcConnection = Nothing ' la connexion Dim sqlCommand As OdbcCommand = Nothing ' la commande SQL ' la requte SELECT Dim selectCommand As String = "select " + colLimites + "," + colCoeffR + "," + colCoeffN + " from " + Timpots ' tableaux pour rcuprer les donnes Dim tLimites As New ArrayList Dim tCoeffR As New ArrayList Dim tCoeffN As New ArrayList ' on tente d'accder la base de donnes impotsConn = New OdbcConnection(connectString) impotsConn.Open() ' on cre un objet command sqlCommand = New OdbcCommand(selectCommand, impotsConn) ' on excute la requte Dim myReader As OdbcDataReader = sqlCommand.ExecuteReader() ' Exploitation de la table rcupre While myReader.Read() ' les donnes de la ligne courante sont mis dans les tableaux

Accs aux bases de donnes

171

tLimites.Add(myReader(colLimites)) tCoeffR.Add(myReader(colCoeffR)) tCoeffN.Add(myReader(colCoeffN)) End While ' libration des ressources myReader.Close() impotsConn.Close() ' les tableaux dynamiques sont mis dans des tableaux statiques Me.limites = New Decimal(tLimites.Count) {} Me.coeffR = New Decimal(tLimites.Count) {} Me.coeffN = New Decimal(tLimites.Count) {} Dim i As Integer For i = 0 To tLimites.Count - 1 limites(i) = Decimal.Parse(tLimites(i).ToString()) coeffR(i) = Decimal.Parse(tCoeffR(i).ToString()) coeffN(i) = Decimal.Parse(tCoeffN(i).ToString()) Next i End Sub

Le programme de test est le suivant : il reoit en arguments les paramtres transmettre au constructeur de la classe impt. Aprs avoir construit un objet impt, il fait des calculs d'impt payer :
Option Explicit On Option Strict On ' espaces de noms Imports System Imports Microsoft.VisualBasic ' pg de test Module testimpots Sub Main(ByVal arguments() As String) ' programme interactif de calcul d'impt ' l'utilisateur tape trois donnes au clavier : mari nbEnfants salaire ' le programme affiche alors l'impt payer Const syntaxe1 As String = "pg DSNimpots tabImpots colLimites colCoeffR colCoeffN" Const syntaxe2 As String = "syntaxe : mari nbEnfants salaire" + ControlChars.Lf + "mari : o pour mari, n pour non mari" + ControlChars.Lf + "nbEnfants : nombre d'enfants" + ControlChars.Lf + "salaire : salaire annuel en F" ' vrification des paramtres du programme If arguments.Length <> 5 Then ' msg d'erreur Console.Error.WriteLine(syntaxe1) ' fin Environment.Exit(1) End If 'if ' on rcupre les arguments Dim DSNimpots As String = arguments(0) Dim tabImpots As String = arguments(1) Dim colLimites As String = arguments(2) Dim colCoeffR As String = arguments(3) Dim colCoeffN As String = arguments(4) ' cration d'un objet impt Dim objImpt As impt = Nothing Try objImpt = New impt(DSNimpots, tabImpots, colLimites, colCoeffR, colCoeffN) Catch ex As Exception Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message)) Environment.Exit(2) End Try ' boucle infinie While True ' au dpart pas d'erreurs Dim erreur As Boolean = False ' on demande les paramtres du calcul de l'impt Console.Out.Write("Paramtres du calcul de l'impt au format mari nbEnfants salaire ou rien pour arrter :") Dim paramtres As String = Console.In.ReadLine().Trim() ' qq chose faire ? If paramtres Is Nothing Or paramtres = "" Then Exit While End If ' vrification du nombre d'arguments dans la ligne saisie

Accs aux bases de donnes

172

Dim args As String() = paramtres.Split(Nothing) Dim nbParamtres As Integer = args.Length If nbParamtres <> 3 Then Console.Error.WriteLine(syntaxe2) erreur = True End If Dim mari As String Dim nbEnfants As Integer Dim salaire As Integer If Not erreur Then ' vrification de la validit des paramtres ' mari mari = args(0).ToLower() If mari <> "o" And mari <> "n" Then Console.Error.WriteLine((syntaxe2 + ControlChars.Lf + "Argument mari incorrect : tapez o ou n")) erreur = True End If ' nbEnfants nbEnfants = 0 Try nbEnfants = Integer.Parse(args(1)) If nbEnfants < 0 Then Throw New Exception End If Catch Console.Error.WriteLine(syntaxe2 + "\nArgument nbEnfants incorrect : tapez un entier positif ou erreur = True End Try ' salaire salaire = 0 Try salaire = Integer.Parse(args(2)) If salaire < 0 Then Throw New Exception End If Catch Console.Error.WriteLine(syntaxe2 + "\nArgument salaire incorrect : tapez un entier positif ou

nul")

nul")

F"))

erreur = True End Try End If If Not erreur Then ' les paramtres sont corrects - on calcule l'impt Console.Out.WriteLine(("impt=" & objImpt.calculer(mari = "o", nbEnfants, salaire).ToString + "

End If End While End Sub End Module

La base utilise est une base MySQL de nom DSN mysql-impots :


C:\mysql\bin>mysql --database=impots --user=admimpots --password=mdpimpots Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 5 to server version: 3.23.49-max-debug Type 'help' for help. mysql> show tables; +------------------+ | Tables_in_impots | +------------------+ | timpots | +------------------+ mysql> select * from timpots; +---------+--------+---------+ | limites | coeffR | coeffN | +---------+--------+---------+ | 12620 | 0 | 0 | | 13190 | 0.05 | 631 | | 15640 | 0.1 | 1290.5 | | 24740 | 0.15 | 2072.5 | | 31810 | 0.2 | 3309.5 | | 39970 | 0.25 | 4900 | | 48360 | 0.3 | 6898 | | 55790 | 0.35 | 9316.5 | | 92970 | 0.4 | 12106 | | 127860 | 0.45 | 16754 |

Accs aux bases de donnes

173

| 151250 | 0.5 | 23147.5 | | 172040 | 0.55 | 30710 | | 195000 | 0.6 | 39312 | | 0 | 0.65 | 49062 | +---------+--------+---------+

L'excution du programme de test donne les rsultats suivants :


dos>D:\data\devel\vbnet\poly\chap6\impots>vbc /r:system.data.dll /r:microsoft.data.odbc.dll /r:system.dll /t:library impots.vb dos>vbc /r:impots.dll testimpots.vb dos>test mysql-impots timpots limites coeffr coeffn Paramtres du calcul de l'impt au format mari nbEnfants impt=22506 F Paramtres du calcul de l'impt au format mari nbEnfants impt=33388 F Paramtres du calcul de l'impt au format mari nbEnfants impt=16400 F Paramtres du calcul de l'impt au format mari nbEnfants impt=50082 F Paramtres du calcul de l'impt au format mari nbEnfants impt=22506 F Paramtres du calcul de l'impt au format mari nbEnfants salaire ou rien pour arrter :o 2 200000 salaire ou rien pour arrter :n 2 200000 salaire ou rien pour arrter :o 3 200000 salaire ou rien pour arrter :n 3 300000 salaire ou rien pour arrter :n 3 200000 salaire ou rien pour arrter :

6.4

Accs aux donnes en mode dconnect

Ce sujet sera trait ultrieurement.

Accs aux bases de donnes

174

7.
7.1 Introduction

Les threads d'excution

Lorsqu'on lance une application, elle s'excute dans un flux d'excution appel un thread. La classe .NET modlisant un thread est la classe System.Threading.Thread et a la dfinition suivante :

Nous n'utiliserons que certaines des proprits et mthodes de cette classe :


CurrentThread - proprit statique Name - proprit d'objet isAlive - proprit d'objet Start - mthode d'objet Abort - mthode d'objet Sleep(n) - mthode statique Suspend() - mthode d'objet Resume() - mthode d'objet Join() - mthode d'objet

donne le thread actuellement en cours d'excution nom du thread indique si le thread est actif(true) ou non (false) lance l'excution d'un thread arrte dfinitivement l'excution d'un thread arrte l'excution d'un thread pendant n millisecondes suspend temporairement l'excution d'un thread reprend l'excution d'un thread suspendu opration bloquante - attend la fin du thread pour passer l'instruction suivante

Regardons une premire application mettant en vidence l'existence d'un thread principal d'excution, celui dans lequel s'excute la fonction Main d'une classe :
' utilisation de threads Imports System Imports System.Threading Public Module thread1 Public Sub Main() ' init thread courant Dim main As Thread = Thread.CurrentThread ' affichage Console.Out.WriteLine(("Thread courant : " + main.Name)) ' on change le nom main.Name = "main" ' vrification Console.Out.WriteLine(("Thread courant : " + main.Name)) ' boucle infinie While True ' affichage Console.Out.WriteLine((main.Name + " : " + DateTime.Now.ToString("hh:mm:ss"))) ' arrt temporaire Thread.Sleep(1000) End While End Sub End Module

Les rsultats cran :


dos>thread1 Thread courant :

Les threads d'excution

175

Thread main : main : main : main : main : ^C

courant : main 06:13:55 06:13:56 06:13:57 06:13:58 06:13:59

L'exemple prcdent illustre les points suivants : la fonction Main s'excute bien dans un thread on a accs aux caractristiques de ce thread par Thread.CurrentThread le rle de la mthode Sleep. Ici le thread excutant Main se met en sommeil rgulirement pendant 1 seconde entre deux affichages.

7.2

Cration de threads d'excution

Il est possible d'avoir des applications o des morceaux de code s'excutent de faon "simultane" dans diffrents threads d'excution. Lorsqu'on dit que des threads s'excutent de faon simultane, on commet souvent un abus de langage. Si la machine n'a qu'un processeur comme c'est encore souvent le cas, les threads se partagent ce processeur : ils en disposent, chacun leur tour, pendant un court instant (quelques millisecondes). C'est ce qui donne l'illusion du paralllisme d'excution. La portion de temps accorde un thread dpend de divers facteurs dont sa priorit qui a une valeur par dfaut mais qui peut tre fixe galement par programmation. Lorsqu'un thread dispose du processeur, il l'utilise normalement pendant tout le temps qui lui a t accord. Cependant, il peut le librer avant terme : en se mettant en attente d'un vnement (wait, join, suspend) en se mettant en sommeil pendant un temps dtermin (sleep) 1. Un thread T est d'abord cr par son constructeur
Public Sub New(ByVal start As ThreadStart)

ThreadStart est de type delegate et dfinit le prototype d'une fonction sans paramtres :
Public Delegate Sub ThreadStart()

Une construction classique est la suivante :


dim T as Thread=new Thread(new ThreadStart(run));

La fonction run passe en paramtres sera excute au lancement du Thread. 2. L'excution du thread T est lanc par T.Start() : la fonction [run] passe au constructeur de T va alors tre excute par le thread T. Le programme qui excute l'instruction T.start() n'attend pas la fin de la tche T : il passe aussitt l'instruction qui suit. On a alors deux tches qui s'excutent en parallle. Elles doivent souvent pouvoir communiquer entre elles pour savoir o en est le travail commun raliser. C'est le problme de synchronisation des threads. Une fois lanc, le thread s'excute de faon autonome. Il s'arrtera lorsque la fonction start qu'il excute aura fini son travail. On peut envoyer certains signaux la tche T : a. T.Suspend() lui dit de s'arrter momentanment b. T.Resume() lui dit de reprendre son travail c. T.Abort() lui dit de s'arrter dfinitivement On peut aussi attendre la fin de son excution par T.join(). On a l une instruction bloquante : le programme qui l'excute est bloqu jusqu' ce que la tche T ait termin son travail. C'est un moyen de synchronisation.

3. 4.

5.

Examinons le programme suivant :


' options Option Strict On Option Explicit On ' espaces de noms Imports System Imports System.Threading Module thread2 Public Sub Main() ' init Thread courant

Les threads d'excution

176

Dim main As Thread = Thread.CurrentThread ' on fixe un nom au Thread main.Name = "main" ' cration de threads d'excution Dim tches(4) As Thread Dim i As Integer For i = 0 To tches.Length - 1 ' on cre le thread i tches(i) = New Thread(New ThreadStart(AddressOf affiche)) ' on fixe le nom du thread tches(i).Name = "tache_" & i ' on lance l'excution du thread i tches(i).Start() Next i ' fin de main Console.Out.WriteLine(("fin du thread " + main.Name)) End Sub Public Sub affiche() ' affichage dbut d'excution Console.Out.WriteLine(("Dbut d'excution de la mthode affiche dans le Thread " + Thread.CurrentThread.Name + " : " + DateTime.Now.ToString("hh:mm:ss"))) ' mise en sommeil pendant 1 s Thread.Sleep(1000) ' affichage fin d'excution Console.Out.WriteLine(("Fin d'excution de la mthode affiche dans le Thread " + Thread.CurrentThread.Name + " : " + DateTime.Now.ToString("hh:mm:ss"))) End Sub End Module

Le thread principal, celui qui excute la fonction Main, cre 5 autres threads chargs d'excuter la mthode statique affiche. Les rsultats sont les suivants :
dos>thread2 fin du thread main Dbut d'excution de la mthode affiche dans le Thread tache_0 Dbut d'excution de la mthode affiche dans le Thread tache_1 Dbut d'excution de la mthode affiche dans le Thread tache_2 Dbut d'excution de la mthode affiche dans le Thread tache_3 Dbut d'excution de la mthode affiche dans le Thread tache_4 Fin d'excution de la mthode affiche dans le Thread tache_0 : Fin d'excution de la mthode affiche dans le Thread tache_1 : Fin d'excution de la mthode affiche dans le Thread tache_2 : Fin d'excution de la mthode affiche dans le Thread tache_3 : Fin d'excution de la mthode affiche dans le Thread tache_4 :

: 05:27:53 : 05:27:53 : 05:27:53 : 05:27:53 : 05:27:53 05:27:54 05:27:54 05:27:54 05:27:54 05:27:54

Ces rsultats sont trs instructifs : on voit tout d'abord que le lancement de l'excution d'un thread n'est pas bloquante. La mthode Main a lanc l'excution de 5 threads en parallle et a termin son excution avant eux. L'opration
' on lance l'excution du thread i tches(i).Start()

lance l'excution du thread tches[i] mais ceci fait, l'excution se poursuit immdiatement avec l'instruction qui suit sans attendre la fin d'excution du thread. tous les threads crs doivent excuter la mthode affiche. L'ordre d'excution est imprvisible. Mme si dans l'exemple, l'ordre d'excution semble suivre l'ordre des demandes d'excution, on ne peut en conclure de gnralits. Le systme d'exploitation a ici 6 threads et un processeur. Il va distribuer le processeur ces 6 threads selon des rgles qui lui sont propres. on voit dans les rsultats une consquence de la mthode Sleep. dans l'exemple, c'est le thread 0 qui excute le premier la mthode affiche. Le message de dbut d'excution est affich puis il excute la mthode Sleep qui le suspend pendant 1 seconde. Il perd alors le processeur qui devient ainsi disponible pour un autre thread. L'exemple montre que c'est le thread 1 qui va l'obtenir. Le thread 1 va suivre le mme parcours ainsi que les autres threads. Lorsque la seconde de sommeil du thread 0 va tre termine, son excution peut reprendre. Le systme lui donne le processeur et il peut terminer l'excution de la mthode affiche.

Modifions notre programme pour le terminer la mthode Main par les instructions :
' fin de main Console.Out.WriteLine(("fin du thread " + main.Name)) Environment.Exit(0)

Les threads d'excution

177

L'excution du nouveau programme donne :


fin du thread main

Les threads crs par la fonction Main ne sont pas excuts. C'est l'instruction
Environment.Exit(0)

qui fait cela : elle supprime tous les threads de l'application et non simplement le thread Main. La solution ce problme est que la mthode Main attende la fin d'excution des threads qu'elle a crs avant de se terminer elle-mme. Cela peut se faire avec la mthode Join de la classe Thread :
' on attend la fin d'excution de tous les threads For i = 0 To tches.Length - 1 ' attente de la fin d'excution du thread i tches(i).Join() Next i 'for ' fin de main Console.Out.WriteLine(("fin du thread " + main.Name)) Environment.Exit(0)

On obtient alors les rsultats suivants :


Dbut d'excution de la mthode affiche dans le Thread tache_1 Dbut d'excution de la mthode affiche dans le Thread tache_2 Dbut d'excution de la mthode affiche dans le Thread tache_3 Dbut d'excution de la mthode affiche dans le Thread tache_4 Dbut d'excution de la mthode affiche dans le Thread tache_0 Fin d'excution de la mthode affiche dans le Thread tache_2 : Fin d'excution de la mthode affiche dans le Thread tache_1 : Fin d'excution de la mthode affiche dans le Thread tache_3 : Fin d'excution de la mthode affiche dans le Thread tache_0 : Fin d'excution de la mthode affiche dans le Thread tache_4 : fin du thread main : 05:34:48 : 05:34:48 : 05:34:48 : 05:34:48 : 05:34:48 05:34:50 05:34:50 05:34:50 05:34:50 05:34:50

7.3

Intrt des threads

Maintenant que nous avons mis en vidence l'existence d'un thread par dfaut, celui qui excute la mthode Main, et que nous savons comment en crer d'autres, arrtons-nous sur l'intrt pour nous des threads et sur la raison pour laquelle nous les prsentons ici. Il y a un type d'applications qui se prtent bien l'utilisation des threads, ce sont les applications client-serveur de l'internet. Dans une telle application, un serveur situ sur une machine S1 rpond aux demandes de clients situs sur des machines distantes C1, C2, ..., Cn. C1 Serveur S1 C2 Cn Nous utilisons tous les jours des applications de l'internet correspondant ce schma : services Web, messagerie lectronique, consultation de forums, transfert de fichiers... Dans le schma ci-dessus, le serveur S1 doit servir les clients Ci de faon simultane. Si nous prenons l'exemple d'un serveur FTP (File Transfer Protocol) qui dlivre des fichiers ses clients, nous savons qu'un transfert de fichier peut prendre parfois plusieurs heures. Il est bien sr hors de question qu'un client monopolise tout seul le serveur une telle dure. Ce qui est fait habituellement, c'est que le serveur cre autant de threads d'excution qu'il y a de clients. Chaque thread est alors charg de s'occuper d'un client particulier. Le processeur tant partag cycliquement entre tous les threads actifs de la machine, le serveur passe alors un peu de temps avec chaque client assurant ainsi la simultanit du service.

Les threads d'excution

178

Serveur
thread 1 thread 2

Clients
client 1 client 2

thread n

client n

7.4

Accs des ressources partages

Dans l'exemple client-serveur voqu ci-dessus, chaque thread sert un client de faon largement indpendante. Nanmoins, les threads peuvent tre amens cooprer pour rendre le service demand leur client notamment pour l'accs des ressources partages. Le schma ci-dessus fait penser aux guichets d'une grande administration, une poste par exemple o chaque guichet un agent sert un client. Supposons que de temps en temps ces agents soient amens faire des photocopies de documents amens par leurs clients et qu'il n'y ait qu'une photocopieuse. Deux agents ne peuvent utiliser la photocopieuse en mme temps. Si l'agent i trouve la photocopieuse utilise par l'agent j, il devra attendre. On appelle cette situation, l'accs une ressource partage et en informatique elle est assez dlicate grer. Prenons l'exemple suivant : une application va gnrer n threads, n tant pass en paramtre la ressource partage est un compteur qui devra tre incrment par chaque thread gnr la fin de l'application, la valeur du compteur est affich. On devrait donc trouver n. Le programme est le suivant :
' options Option Explicit On Option Strict On ' utilisation de threads Imports System Imports System.Threading Public Class thread3 ' variables de classe Private Shared cptrThreads As Integer = 0 Public Overloads Shared Sub Main(ByVal args() As [String]) ' mode d'emploi Const syntaxe As String = "pg nbThreads" Const nbMaxThreads As Integer = 100 ' vrification nbre d'arguments If args.Length <> 1 Then ' erreur Console.Error.WriteLine(syntaxe) ' arrt Environment.Exit(1) End If ' vrification qualit de l'argument Dim nbThreads As Integer = 0 Try nbThreads = Integer.Parse(args(0)) If nbThreads < 1 Or nbThreads > nbMaxThreads Then Throw New Exception End If Catch ' erreur Console.Error.WriteLine("Nombre de threads incorrect (entre 1 et " & nbMaxThreads & ")") ' fin Environment.Exit(2) End Try ' cration et gnration des threads Dim threads(nbThreads - 1) As Thread Dim i As Integer For i = 0 To nbThreads - 1 ' cration threads(i) = New Thread(New ThreadStart(AddressOf incrmente)) ' nommage threads(i).Name = "tache_" & i

Les threads d'excution

179

' lancement threads(i).Start() Next i ' attente de la fin des threads For i = 0 To nbThreads - 1 threads(i).Join() Next i ' affichage compteur Console.Out.WriteLine(("Nombre de threads gnrs : " & cptrThreads)) End Sub Public Shared Sub incrmente() ' augmente le compteur de threads ' lecture compteur Dim valeur As Integer = cptrThreads ' suivi Console.Out.WriteLine(("A " + DateTime.Now.ToString("hh:mm:ss") & ", le thread " & Thread.CurrentThread.Name & " a lu la valeur du compteur : " & cptrThreads)) ' attente Thread.Sleep(1000) ' incrmentation compteur cptrThreads = valeur + 1 ' suivi Console.Out.WriteLine(("A " & DateTime.Now.ToString("hh:mm:ss") & ", le thread " & Thread.CurrentThread.Name & " a crit la valeur du compteur : " & cptrThreads)) End Sub End Class

Nous ne nous attarderons pas sur la partie gnration de threads dj tudie. Intressons-nous plutt la mthode incrmente, utilise par chaque thread pour incrmenter le compteur statique cptrThreads. 1. le compteur est lu 2. le thread s'arrte 1 s. Il perd donc le processeur 3. le compteur est incrment L'tape 2 n'est l que pour forcer le thread perdre le processeur. Celui-ci va tre donn un autre thread. Dans la pratique, rien n'assure qu'un thread ne sera pas interrompu entre le moment o il va lire le compteur et le moment o il va l'incrmenter. Le risque existe de perdre le processeur entre le moment o on lit la valeur du compteur et celui on crit sa valeur incrmente de 1. En effet, l'opration d'incrmentation va faire l'objet de plusieurs instructions lmentaires au niveau du processeur qui peuvent tre interrompues. L'tape 2 de sommeil d'une seconde n'est donc l que pour systmatiser ce risque. Les rsultats obtenus sont les suivants :
dos>thread3 5 A 05:44:34, le thread tache_0 A 05:44:34, le thread tache_1 A 05:44:34, le thread tache_2 A 05:44:34, le thread tache_3 A 05:44:34, le thread tache_4 A 05:44:35, le thread tache_0 A 05:44:35, le thread tache_1 A 05:44:35, le thread tache_2 A 05:44:35, le thread tache_3 A 05:44:35, le thread tache_4 Nombre de threads gnrs : 1 a a a a a a a a a a lu la lu la lu la lu la lu la crit crit crit crit crit valeur du valeur du valeur du valeur du valeur du la valeur la valeur la valeur la valeur la valeur compteur : 0 compteur : 0 compteur : 0 compteur : 0 compteur : 0 du compteur : du compteur : du compteur : du compteur : du compteur :

1 1 1 1 1

A la lecture de ces rsultats, on voit bien ce qui se passe : un premier thread lit le compteur. Il trouve 0. il s'arrte 1 s donc perd le processeur un second thread prend alors le processeur et lit lui aussi la valeur du compteur. Elle est toujours 0 puisque le thread prcdent ne l'a pas encore incrment. Il s'arrte lui aussi 1 s. en 1 s, les 5 threads ont le temps de passer tous et de lire tous la valeur 0. lorsqu'ils vont se rveiller les uns aprs les autres, ils vont incrmenter la valeur 0 qu'ils ont lue et crire la valeur 1 dans le compteur, ce que confirme le programme principal (Main).

D'o vient le problme ? Le second thread a lu une mauvaise valeur du fait que le premier avait t interrompu avant d'avoir termin son travail qui tait de mettre jour le compteur dans la fentre. Cela nous amne la notion de ressource critique et de section critique d'un programme: une ressource critique est une ressource qui ne peut tre dtenue que par un thread la fois. Ici la ressource critique est le compteur. une section critique d'un programme est une squence d'instructions dans le flux d'excution d'un thread au cours de laquelle il accde une ressource critique. On doit assurer qu'au cours de cette section critique, il est le seul avoir accs la ressource. Les threads d'excution 180

7.5

Accs exclusif une ressource partage

Dans notre exemple, la section critique est le code situ entre la lecture du compteur et l'criture de sa nouvelle valeur :
' lecture compteur Dim valeur As Integer = cptrThreads ' attente Thread.Sleep(1000) ' incrmentation compteur cptrThreads = valeur + 1

Pour excuter ce code, un thread doit tre assur d'tre tout seul. Il peut tre interrompu mais pendant cette interruption, un autre thread ne doit pas pouvoir excuter ce mme code. La plate-forme .NET offre plusieurs outils pour assurer l'entre unitaire dans les sections critiques de code. Nous utiliserons la classe Mutex :

Nous n'utiliserons ici que les constructeurs et mthodes suivants :


public Mutex() public bool WaitOne()

cre un objet de synchronisation M Le thread T1 qui excute l'opration M.WaitOne() demande la proprit de l'objet de synchronisation M. Si le Mutex M n'est dtenu par aucun thread (le cas au dpart), il est "donn" au thread T1 qui l'a demand. Si un peu plus tard, un thread T2 fait la mme opration, il sera bloqu. En effet, un Mutex ne peut appartenir qu' un thread. Il sera dbloqu lorsque le thread T1 librera le mutex M qu'il dtient. Plusieurs threads peuvent ainsi tre bloqus en attente du Mutex M. Le thread T1 qui effectue l'opration M.ReleaseMutex() abandonne la proprit du Mutex M.Lorsque le thread T1 perdra le processeur, le systme pourra le donner l'un des threads en attente du Mutex M. Un seul l'obtiendra son tour, les autres en attente de M restant bloqus

public void ReleaseMutex()

Un Mutex M gre l'accs une ressource partage R. Un thread demande la ressource R par M.WaitOne() et la rend par M.ReleaseMutex(). Une section critique de code qui ne doit tre excute que par un seul thread la fois est une ressource partage. La synchronisation d'excution de la section critique peut se faire ainsi :
M.WaitOne() ' le thread est seul entrer ici ' section critique .... M.ReleaseMutex()

o M est un objet Mutex. Il faut bien sr ne jamais oublier de librer un Mutex devenu inutile pour qu'un autre thread puisse entrer dans la section critique, sinon les threads en attente d'un Mutex jamais libr n'auront jamais accs au processeur. Par ailleurs, il faut viter la situation d'interblocage (deadlock) dans laquelle deux threads s'attendent mutuellement. Considrons les actions suivantes qui se suivent dans le temps : un thread T1 obtient la proprit d'un Mutex M1 pour avoir accs une ressource partage R1 un thread T2 obtient la proprit d'un Mutex M2 pour avoir accs une ressource partage R2 le thread T1 demande le Mutex M2. Il est bloqu. le thread T2 demande le Mutex M1. Il est bloqu.

Ici, les threads T1 et T2 s'attendent mutuellement. Ce cas apparat lorsque des threads ont besoin de deux ressources partages, la ressource R1 contrle par le Mutex M1 et la ressource R2 contrle par le Mutex M2. Une solution possible est de demander les deux ressources en mme temps l'aide d'un Mutex unique M. Mais ce n'est pas toujours possible si par exemple cela entrane une Les threads d'excution 181

mobilisation longue d'une ressource coteuse. Une autre solution est qu'un thread ayant M1 et ne pouvant obtenir M2, relche alors M1 pour viter l'interblocage. Si nous mettons en pratique ce que nous venons de voir sur l'exemple prcdent, notre application devient la suivante :
' options Option Explicit On Option Strict On ' utilisation de threads Imports System Imports System.Threading Public Class thread4 ' variables de classe Private Shared cptrThreads As Integer = 0 ' compteur de threads Private Shared autorisation As Mutex Public Overloads Shared Sub Main(ByVal args() As [String]) ' mode d'emploi Const syntaxe As String = "pg nbThreads" Const nbMaxThreads As Integer = 100 ' vrification nbre d'arguments If args.Length <> 1 Then ' erreur Console.Error.WriteLine(syntaxe) ' arrt Environment.Exit(1) End If ' vrification qualit de l'argument Dim nbThreads As Integer = 0 Try nbThreads = Integer.Parse(args(0)) If nbThreads < 1 Or nbThreads > nbMaxThreads Then Throw New Exception End If Catch End Try ' initialisation de l'autorisation d'accs une section critique autorisation = New Mutex ' cration et gnration des threads Dim threads(nbThreads) As Thread Dim i As Integer For i = 0 To nbThreads - 1 ' cration threads(i) = New Thread(New ThreadStart(AddressOf incrmente)) ' nommage threads(i).Name = "tache_" & i ' lancement threads(i).Start() Next i ' attente de la fin des threads For i = 0 To nbThreads - 1 threads(i).Join() Next i ' affichage compteur Console.Out.WriteLine(("Nombre de threads gnrs : " & cptrThreads)) End Sub Public Shared Sub incrmente() ' augmente le compteur de threads ' on demande l'autorisation d'entrer dans la secton critique autorisation.WaitOne() ' lecture compteur Dim valeur As Integer = cptrThreads ' suivi Console.Out.WriteLine(("A " & DateTime.Now.ToString("hh:mm:ss") & ", le thread " & Thread.CurrentThread.Name & " a lu la valeur du compteur : " & cptrThreads)) ' attente Thread.Sleep(1000) ' incrmentation compteur cptrThreads = valeur + 1 ' suivi Console.Out.WriteLine(("A " & DateTime.Now.ToString("hh:mm:ss") & ", le thread " & Thread.CurrentThread.Name & " a crit la valeur du compteur : " & cptrThreads)) ' on rend l'autorisation d'accs autorisation.ReleaseMutex() End Sub

Les threads d'excution

182

End Class

Les rsultats obtenus sont conformes ce qui tait attendu :


dos>thread4 5 A 05:51:10, le thread tache_0 A 05:51:11, le thread tache_0 A 05:51:11, le thread tache_1 A 05:51:12, le thread tache_1 A 05:51:12, le thread tache_2 A 05:51:13, le thread tache_2 A 05:51:13, le thread tache_3 A 05:51:14, le thread tache_3 A 05:51:14, le thread tache_4 A 05:51:15, le thread tache_4 Nombre de threads gnrs : 5 a a a a a a a a a a lu la crit lu la crit lu la crit lu la crit lu la crit valeur du la valeur valeur du la valeur valeur du la valeur valeur du la valeur valeur du la valeur compteur : 0 du compteur : compteur : 1 du compteur : compteur : 2 du compteur : compteur : 3 du compteur : compteur : 4 du compteur :

1 2 3 4 5

7.6
1. 2. 3. 4.

Synchronisation par vnements

Considrons la situation suivante, appele parfois situation de producteurs-consommateurs. On a un tableau dans lequel des processus viennent dposer des donnes (les producteurs) et d'autres viennent les lire (les consommateurs). Les producteurs sont gaux entre-eux mais exclusifs : un seul producteur la fois peut dposer ses donnes dans le tableau. Les consommateurs sont gaux entre-eux mais exclusifs : un seul lecteur la fois peut lire les donnes dposes dans le tableau. Un consommateur ne peut lire les donnes du tableau que lorsqu'un producteur en a dpos dedans et un producteur ne peut dposer de nouvelles donnes dans le tableau que lorsque celles qui y sont ont t consommes.

On peut dans cet expos distinguer deux ressources partages : 1. le tableau en criture 2. le tableau en lecture L'accs ces deux ressources partages peut tre contrle par des Mutex comme vu prcdemment, un pour chaque ressource. Une fois qu'un consommateur a obtenu le tableau en lecture, il doit vrifier qu'il y a bien des donnes dedans. On utilisera un vnement pour l'en avertir. De mme un producteur ayant obtenu le tableau en criture devra attendre qu'un consommateur l'ait vid. On utilisera l encore un vnement. Les vnements utiliss feront partie de la classe AutoResetEvent :

Ce type d'vnement est analogue un boolen mais vite des attentes actives ou semi-actives. Ainsi si le droit d'criture est contrl par un boolen peutEcrire, un producteur avant d'crire excutera un code du genre :
while(peutEcrire==false) ' attente active

ou
while(peutEcrire==false) ' attente semi-active Thread.Sleep(100) ' attente de 100ms end while

Les threads d'excution

183

Dans la premire mthode, le thread mobilise inutilement le processeur. Dans la seconde, il vrifie l'tat du boolen peutEcrire toutes les 100 ms. La classe AutoResetEvent permet encore d'amliorer les choses : le thread va demander tre rveill lorsque l'vnement qu'il attend se sera produit :
AutoEvent peutEcrire=new AutoResetEvent(false) ' peutEcrire=false; .... peutEcrire.WaitOne() ' le thread attend que l'vt peutEcrire passe vrai

L'opration
AutoEvent peutEcrire=new AutoResetEvent(false) ' peutEcrire=false;

initialise le boolen peutEcrire false. L'opration


peutEcrire.WaitOne() ' le thread attend que l'vt peutEcrire passe vrai

excute par un thread fait que celui-ci passe si le boolen peutEcrire est vrai ou sinon est bloqu jusqu' ce qu'il devienne vrai. Un autre thread le passera vrai par l'opration peutEcrire.Set() ou faux par l'opration peutEcrire.Reset(). Le programme de producteurs-consommateurs est le suivant :
' utilisation de threads lecteurs et crivains ' illustre l'utilisation simultane de ressources partages et de synchronisation ' options Option Explicit On Option Strict On ' utilisation de threads Imports System Imports System.Threading Public Class lececr ' variables de Private Shared Private Shared Private Shared Private Shared Private Shared Private Shared classe data(5) As Integer ' ressource partage entre threads lecteur et threads crivain lecteur As Mutex ' variable de synchronisation pour lire le tableau crivain As Mutex ' variable de synchronisation pour crire dans le tableau objRandom As New Random(DateTime.Now.Second) ' un gnrateur de nombres alatoires peutLire As AutoResetEvent ' signale qu'on peut lire le contenu de data peutEcrire As AutoResetEvent

Public Shared Sub Main(ByVal args() As [String]) ' le nbre de threads gnrer Const nbThreads As Integer = 3 ' initialisation des drapeaux peutLire = New AutoResetEvent(False) peutEcrire = New AutoResetEvent(True) ' on ne peut pas encore lire ' on peut dj crire

' initialisation des variables de synchronisation lecteur = New Mutex ' synchronise les lecteurs crivain = New Mutex ' synchronise les crivains ' cration des threads lecteurs Dim lecteurs(nbThreads) As Thread Dim i As Integer For i = 0 To nbThreads - 1 ' cration lecteurs(i) = New Thread(New ThreadStart(AddressOf lire)) lecteurs(i).Name = "lecteur_" & i ' lancement lecteurs(i).Start() Next i ' cration des threads crivains Dim crivains(nbThreads) As Thread For i = 0 To nbThreads - 1 ' cration crivains(i) = New Thread(New ThreadStart(AddressOf crire)) crivains(i).Name = "crivain_" & i ' lancement crivains(i).Start() Next i 'fin de main Console.Out.WriteLine("fin de Main...")

Les threads d'excution

184

End Sub ' lire le contenu du tableau Public Shared Sub lire() ' section critique lecteur.WaitOne() ' un seul lecteur peut passer peutLire.WaitOne() ' on doit pouvoir lire ' lecture tableau Dim i As Integer For i = 0 To data.Length - 1 'attente 1 s Thread.Sleep(1000) ' affichage Console.Out.WriteLine((DateTime.Now.ToString("hh:mm:ss") & " : Le lecteur " & Thread.CurrentThread.Name & " a lu le nombre " & data(i))) Next i ' on ne peut plus lire peutLire.Reset() ' on peut crire peutEcrire.Set() ' fin de section critique lecteur.ReleaseMutex() End Sub ' crire dans le tableau Public Shared Sub crire() ' section critique ' un seul crivain peut passer crivain.WaitOne() ' on doit attendre l'autorisation d'criture peutEcrire.WaitOne() ' criture tableau Dim i As Integer For i = 0 To data.Length - 1 'attente 1 s Thread.Sleep(1000) ' affichage data(i) = objRandom.Next(0, 1000) Console.Out.WriteLine((DateTime.Now.ToString("hh:mm:ss") & " : L'crivain " & Thread.CurrentThread.Name & " a crit le nombre " & data(i))) Next i ' on ne peut plus crire peutEcrire.Reset() ' on peut lire peutLire.Set() 'fin de section critique crivain.ReleaseMutex() End Sub End Class

L'excution donne les rsultats suivants :


dos>lececr fin de Main... 05:56:56 : L'crivain 05:56:57 : L'crivain 05:56:58 : L'crivain 05:56:59 : L'crivain 05:57:00 : L'crivain 05:57:01 : L'crivain 05:57:02 : Le lecteur 05:57:03 : Le lecteur 05:57:04 : Le lecteur 05:57:05 : Le lecteur 05:57:06 : Le lecteur 05:57:07 : Le lecteur 05:57:08 : L'crivain 05:57:09 : L'crivain 05:57:10 : L'crivain 05:57:11 : L'crivain 05:57:12 : L'crivain 05:57:13 : L'crivain 05:57:14 : Le lecteur 05:57:15 : Le lecteur 05:57:16 : Le lecteur 05:57:17 : Le lecteur 05:57:18 : Le lecteur

crivain_0 a crit le nombre crivain_0 a crit le nombre crivain_0 a crit le nombre crivain_0 a crit le nombre crivain_0 a crit le nombre crivain_0 a crit le nombre lecteur_0 a lu le nombre 459 lecteur_0 a lu le nombre 955 lecteur_0 a lu le nombre 212 lecteur_0 a lu le nombre 297 lecteur_0 a lu le nombre 37 lecteur_0 a lu le nombre 623 crivain_1 a crit le nombre crivain_1 a crit le nombre crivain_1 a crit le nombre crivain_1 a crit le nombre crivain_1 a crit le nombre crivain_1 a crit le nombre lecteur_1 a lu le nombre 549 lecteur_1 a lu le nombre 34 lecteur_1 a lu le nombre 781 lecteur_1 a lu le nombre 555 lecteur_1 a lu le nombre 812

459 955 212 297 37 623

549 34 781 555 812 406

Les threads d'excution

185

05:57:19 : Le lecteur lecteur_1 a lu le nombre 406 05:57:20 : L'crivain crivain_2 a crit le nombre 442 05:57:21 : L'crivain crivain_2 a crit le nombre 83 ^C

On peut remarquer les points suivants : on a bien 1 seul lecteur la fois bien que celui-ci perde le processeur dans la section critique lire on a bien 1 seul crivain la fois bien que celui-ci perde le processeur dans la section critique crire un lecteur ne lit que lorsqu'il y a quelque chose lire dans le tableau un crivain n'crit que lorsque le tableau a t entirement lu

Les threads d'excution

186

8.
8.1 8.1.1 Gnralits

Programmation TCP-IP

Les protocoles de l'Internet

Nous donnons ici une introduction aux protocoles de communication de l'Internet, appels aussi suite de protocoles TCP/IP (Transfer Control Protocol / Internet Protocol), du nom des deux principaux protocoles. Il est bon que le lecteur ait une comprhension globale du fonctionnement des rseaux et notamment des protocoles TCP/IP avant d'aborder la construction d'applications distribues. Le texte qui suit est une traduction partielle d'un texte que l'on trouve dans le document "Lan Workplace for Dos - Administrator's Guide" de NOVELL, document du dbut des annes 90. ----------------------------------Le concept gnral de crer un rseau d'ordinateurs htrognes vient de recherches effectues par le DARPA (Defense Advanced Research Projects Agency) aux Etats-Unis. Le DARPA a dvelopp la suite de protocoles connue sous le nom de TCP/IP qui permet des machines htrognes de communiquer entre elles. Ces protocoles ont t tests sur un rseau appel ARPAnet, rseau qui devint ultrieurement le rseau INTERNET. Les protocoles TCP/IP dfinissent des formats et des rgles de transmission et de rception indpendants de l'organisation des rseaux et des matriels utiliss. Le rseau conu par le DARPA et gr par les protocoles TCP/IP est un rseau commutation de paquets. Un tel rseau transmet l'information sur le rseau, en petits morceaux appels paquets. Ainsi, si un ordinateur transmet un gros fichier, ce dernier sera dcoup en petits morceaux qui seront envoys sur le rseau pour tre recomposs destination. TCP/IP dfinit le format de ces paquets, savoir : . . . . origine du paquet destination longueur type

8.1.2

Le modle OSI

Les protocoles TCP/IP suivent peu prs le modle de rseau ouvert appel OSI (Open Systems Interconnection Reference Model) dfini par l'ISO (International Standards Organisation). Ce modle dcrit un rseau idal o la communication entre machines peut tre reprsente par un modle sept couches :
7 6 5 4 3 2 1 |-------------------------------------| | Application | |-------------------------------------| | Prsentation | |-------------------------------------| | Session | |-------------------------------------| | Transport | |-------------------------------------| | Rseau | |-------------------------------------| | Liaison | |-------------------------------------| | Physique | |-------------------------------------|

Chaque couche reoit des services de la couche infrieure et offre les siens la couche suprieure. Supposons que deux applications situes sur des machines A et B diffrentes veulent communiquer : elles le font au niveau de la couche Application. Elles n'ont pas besoin de connatre tous les dtails du fonctionnement du rseau : chaque application remet l'information qu'elle souhaite transmettre la couche du dessous : la couche Prsentation. L'application n'a donc connatre que les rgles d'interfaage avec la couche Prsentation. Une fois l'information dans la couche Prsentation, elle est passe selon d'autres rgles la couche Session et ainsi de suite, jusqu' ce que l'information arrive sur le support physique et soit transmise physiquement la machine destination. L, elle subira le traitement inverse de celui qu'elle a subi sur la machine expditeur. Services WEB 187

A chaque couche, le processus expditeur charg d'envoyer l'information, l'envoie un processus rcepteur sur l'autre machine apartenant la mme couche que lui. Il le fait selon certaines rgles que l'on appelle le protocole de la couche. On a donc le schma de communication final suivant :
Machine A Machine B +-------------------------------------+ +----------------------------+ Application v ^ Application +------------------------------------ +--------------------------- Prsentation v ^ Prsentation +------------------------------------ +--------------------------- Session v ^ Session +------------------------------------ +--------------------------- Transport v ^ Transport +------------------------------------ +--------------------------- Rseau v ^ Rseau +------------------------------------ +--------------------------- Liaison v ^ Liaison +------------------------------------ +--------------------------- Physique v ^ Physique +------------------------------------+ +---------------------------+ ^ +-->------->------>-----+

7 6 5 4 3 2 1

Le rle des diffrentes couches est le suivant :


Physique

Assure la transmission de bits sur un support physique. On trouve dans cette couche des quipements terminaux de traitement des donnes (E.T.T.D.) tels que terminal ou ordinateur, ainsi que des quipements de terminaison de circuits de donnes (E.T.C.D.) tels que modulateur/dmodulateur, multiplexeur, concentrateur. Les points d'intrt ce niveau sont : . le choix du codage de l'information (analogique ou numrique) . le choix du mode de transmission (synchrone ou asynchrone).

Liaison de donnes Rseau

Masque les particularits physiques de la couche Physique. Dtecte et corrige les erreurs de transmission. Gre le chemin que doivent suivre les informations envoyes sur le rseau. On appelle cela le routage : dterminer la route suivre par une information pour qu'elle arrive son destinataire. Permet la communication entre deux applications alors que les couches prcdentes ne permettaient que la communication entre machines. Un service fourni par cette couche peut tre le multiplexage : la couche transport pourra utiliser une mme connexion rseau (de machine machine) pour transmettre des informations appartenant plusieurs applications. On va trouver dans cette couche des services permettant une application d'ouvrir et de maintenir une session de travail sur une machine distante. Elle vise uniformiser la reprsentation des donnes sur les diffrentes machines. Ainsi des donnes provenant d'une machine A, vont tre "habilles" par la couche Prsentation de la machine A, selon un format standard avant d'tre envoyes sur le rseau. Parvenues la couche Prsentation de la machine destinatrice B qui les reconnatra grce leur format standard, elles seront habilles d'une autre faon afin que l'application de la machine B les reconnaisse. A ce niveau, on trouve les applications gnralement proches de l'utilisateur telles que la messagerie lectronique ou le transfert de fichiers.

Transport

Session

Prsentation

Application

8.1.3

Le modle TCP/IP

Le modle OSI est un modle idal encore jamais ralis. La suite de protocoles TCP/IP s'en approche sous la forme suivante :

Services WEB

188

7 6 5 4 3 2 1

+-----------------------+ Application +----------------------- Prsentation +----------------------- Session +----------------------- Transport +----------------------- Rseau +----------------------- Liaison +----------------------- Physique +-----------------------+

+-------------------------------------------------------+ DNS Telnet FTP TFTP SMTP +------------------ Autres +-------------------------+----------------------------- TCP UDP +------------------------------------------------------- IP ICMP ARP RARP +------------------------------------------------------- MLID1 MLID2 MLID3 MLID4 +------------------------------------------------------- Ethernet Token-ring Autres +-------------------------------------------------------+

Couche Physique En rseau local, on trouve gnralement une technologie Ethernet ou Token-Ring. Nous ne prsentons ici que la technologie Ethernet. Ethernet C'est le nom donn une technologie de rseaux locaux commutation de paquets invente PARC Xerox au dbut des annes 1970 et normalise par Xerox, Intel et Digital Equipment en 1978. Le rseau est physiquement constitu d'un cble coaxial d'environ 1,27 cm de diamtre et d'une longueur de 500 m au plus. Il peut tre tendu au moyen de rpteurs, deux machines ne pouvant tre spares par plus de deux rpteurs. Le cble est passif : tous les lments actifs sont sur les machines raccordes au cble. Chaque machine est relie au cble par une carte d'accs au rseau comprenant : un transmetteur (transceiver) qui dtecte la prsence de signaux sur le cble et convertit les signaux analogiques en signaux numrique et inversement. un coupleur qui reoit les signaux numriques du transmetteur et les transmet l'ordinateur pour traitement ou inversement.

Les caractristiques principales de la technologie Ethernet sont les suivantes : Capacit de 10 Mgabits/seconde. Topologie en bus : toutes les machines sont raccordes au mme cble

--------------------------------------------------------- +--------+ +--------+ +-----------+ +--------+ +--------+ +-----------+ Machine A B C

Rseau diffusant - Une machine qui met transfre des informations sur le cble avec l'adresse de la machine destinatrice. Toutes les machines raccordes reoivent alors ces informations et seule celle qui elles sont destines les conserve. La mthode d'accs est la suivante : le transmetteur dsirant mettre coute le cble - il dtecte alors la prsence ou non d'une onde porteuse, prsence qui signifierait qu'une transmission est en cours. C'est la technique CSMA (Carrier Sense Multiple Access). En l'absence de porteuse, un transmetteur peut dcider de transmettre son tour. Ils peuvent tre plusieurs prendre cette dcision. Les signaux mis se mlangent : on dit qu'il y a collision. Le transmetteur dtecte cette situation : en mme temps qu'il met sur le cble, il coute ce qui passe rellement sur celui-ci. S'il dtecte que l'information transitant sur le cble n'est pas celle qu'il a mise, il en dduit qu'il y a collision et il s'arrtera d'mettre. Les autres transmetteurs qui mettaient feront de mme. Chacun reprendra son mission aprs un temps alatoire dpendant de chaque transmetteur. Cette technique est appele CD (Collision Detect). La mthode d'accs est ainsi appele CSMA/CD. un adressage sur 48 bits. Chaque machine a une adresse, appele ici adresse physique, qui est inscrite sur la carte qui la relie au cble. On appelle cet adresse, l'adresse Ethernet de la machine.

Couche Rseau Nous trouvons au niveau de cette couche, les protocoles IP, ICMP, ARP et RARP.
IP (Internet Protocol)

Dlivre des paquets entre deux noeuds du rseau 189

Services WEB

ICMP (Internet Control Message Protocol)

ICMP ralise la communication entre le programme du protocole IP d'une machine et celui d'une autre machine. C'est donc un protocole d'change de messages l'intrieur mme du protocole IP. fait la correspondance adresse Internet machine--> adresse physique machine

ARP (Address Resolution Protocol)

RARP (Reverse Address Resolution Protocol)

fait la correspondance adresse physique machine--> adresse Internet machine

Couches Transport/Session Dans cette couche, on trouve les protocoles suivants :


TCP (Transmission Control Protocol) UDP (User Datagram Protocol)

Assure une remise fiable d'informations entre deux clients Assure une remise non fiable d'informations entre deux clients

Couches Application/Prsentation/Session On trouve ici divers protocoles :


TELNET

Emulateur de terminal permettant une machine A de se connecter une machine B en tant que terminal permet des transferts de fichiers permet des transferts de fichiers

FTP (File Transfer Protocol)

TFTP (Trivial File Transfer Protocol)

SMTP (Simple Mail Transfer protocol)

permet l'change de messages entre utilisateurs du rseau

DNS (Domain Name System)

transforme un nom de machine en adresse Internet de la machine cr par sun MicroSystems, il spcifie une reprsentation standard des donnes, indpendante des machines dfini galement par Sun, c'est un protocole de communication entre applications distantes, indpendant de la couche transport. Ce protocole est important : il dcharge le programmeur de la connaissance des dtails de la couche transport et rend les applications portables. Ce protocole s'appuie sur sur le protocole XDR toujours dfini par Sun, ce protocole permet une machine, de "voir" le systme de fichiers d'une autre machine. Il s'appuie sur le protocole RPC prcdent

XDR (eXternal Data Representation)

RPC(Remote Procedures Call)

NFS (Network File System)

8.1.4

Fonctionnement des protocoles de l'Internet

Les applications dveloppes dans l'environnement TCP/IP utilisent gnralement plusieurs des protocoles de cet environnement. Un programme d'application communique avec la couche la plus leve des protocoles. Celle-ci passe l'information la couche du dessous et ainsi de suite jusqu' arriver sur le support physique. L, l'information est physiquement transfre la machine destinatrice o elle retraversera les mmes couches, en sens inverse cette fois-ci, jusqu' arriver l'application destinatrice des informations envoyes. Le schma suivant montre le parcours de l'information :

Services WEB

190

+----------------+ +---------------------------+ Application Application +----------------+ +---------------------------+ <----------- messages ou streams ----------> +----------------+ +---------------------------+ Transport Transport (Udp/Tcp) (Udp/tcp) +----------------+ +---------------------------+ <----------- datagrammes (UDP) -----------> +----------------+ ou +---------------------------+ Rseau (IP) segments (TCP) Rseau (IP) +----------------+ +---------------------------+ <----------- datagrammes IP --------------> +----------------+ +----------------------------+ Interface rseau Interface rseau +---------------+ +----------------------------+ <---------- trames rseau -------------> +----------------------------------------------+ rseau physique

Prenons un exemple : l'application FTP, dfinie au niveau de la couche Application et qui permet des transferts de fichiers entre machines. . . L'application dlivre une suite d'octets transmettre la couche transport. La couche transport dcoupe cette suite d'octets en segments TCP, et ajoute au dbut de chaque segment, le numro de celuici. Les segments sont passs la couche Rseau gouverne par le protocole IP. . La couche IP cre un paquet encapsulant le segment TCP reu. En tte de ce paquet, elle place les adresses Internet des machines source et destination. Elle dtermine galement l'adresse physique de la machine destinatrice. Le tout est pass la couche Liaison de donnes & Liaison physique, c'est dire la carte rseau qui couple la machine au rseau physique. . L, le paquet IP est encapsul son tour dans une trame physique et envoy son destinataire sur le cble. . Sur la machine destinatrice, la couche Liaison de donnes & Liaison physique fait l'inverse : elle dsencapsule le paquet IP de la trame physique et le passe la couche IP. . La couche IP vrifie que le paquet est correct : elle calcule une somme, fonction des bits reus (checksum), somme qu'elle doit retrouver dans l'en-tte du paquet. Si ce n'est pas le cas, celui-ci est rejet. . Si le paquet est dclar correct, la couche IP dsencapsule le segment TCP qui s'y trouve et le passe au-dessus la couche transport. . La couche transport, couche TCP dans notre exemple, examine le numro du segment afin de restituer le bon ordre des segments. . Elle calcule galement une somme de vrification pour le segment TCP. S'il est trouv correct, la couche TCP envoie un accus de rception la machine source, sinon le segment TCP est refus. . Il ne reste plus la couche TCP qu' transmettre la partie donnes du segment l'application destinatrice de celles-ci dans la couche du dessus.

8.1.5

L'adressage dans l'Internet

Un noeud d'un rseau peut tre un ordinateur, une imprimante intelligente, un serveur de fichiers, n'importe quoi en fait pouvant communiquer l'aide des protocoles TCP/IP. Chaque noeud a une adresse physique ayant un format dpendant du type du rseau. Sur un rseau Ethernet, l'adresse physique est code sur 6 octets. Une adresse d'un rseau X25 est un nombre 14 chiffres. L'adresse Internet d'un noeud est une adresse logique : elle est indpendante du matriel et du rseau utilis. C'est une adresse sur 4 octets identifiant la fois un rseau local et un noeud de ce rseau. L'adresse Internet est habituellement reprsente sous la forme de 4 nombres, valeurs des 4 octets, spars par un point. Ainsi l'adresse de la machine Lagaffe de la facult des Sciences d'Angers est note 193.49.144.1 et celle de la machine Liny 193.49.144.9. On en dduira que l'adresse Internet du rseau local est 193.49.144.0. On pourra avoir jusqu' 254 noeuds sur ce rseau. Parce que les adresses Internet ou adresses IP sont indpendantes du rseau, une machine d'un rseau A peut communiquer avec une machine d'un rseau B sans se proccuper du type de rseau sur lequel elle se trouve : il suffit qu'elle connaisse son adresse IP. Le protocole IP de chaque rseau se charge de faire la conversion adresse IP <--> adresse physique, dans les deux sens. Les adresses IP doivent tre toutes diffrentes. En France, c'est l'INRIA qui s'occupe d'affecter les adresses IP. En fait, cet organisme dlivre une adresse pour votre rseau local, par exemple 193.49.144.0 pour le rseau de la facult des sciences d'Angers. L'administrateur de ce rseau peut ensuite affecter les adresses IP 193.49.144.1 193.49.144.254 comme il l'entend. Cette adresse est gnralement inscrite dans un fichier particulier de chaque machine relie au rseau.

Services WEB

191

8.1.5.1
. .

Les classes d'adresses IP

Une adresse IP est une suite de 4 octets note souvent I1.I2.I3.I4, qui contient en fait deux adresses : l'adresse du rseau l'adresse d'un noeud de ce rseau

Selon la taille de ces deux champs, les adresses IP sont divises en 3 classes : classes A, B et C. Classe A L'adresse IP : I1.I2.I3.I4 a la forme R1.N1.N2.N3 o R1 N1.N2.N3 est l'adresse du rseau est l'adresse d'une machine dans ce rseau

Plus exactement, la forme d'une adresse IP de classe A est la suivante :


1 octet 3 octets +-------------------------------------------------------------------------------+ 0 adr. rseau adresse noeud +-------------------------------------------------------------------------------+

L'adresse rseau est sur 7 bits et l'adresse du noeud sur 24 bits. On peut donc avoir 127 rseaux de classe A, chacun comportant jusqu' 224 noeuds. Classe B Ici, l'adresse IP : I1.I2.I3.I4 a la forme R1.R2.N1.N2 o R1.R2 N1.N2 est l'adresse du rseau est l'adresse d'une machine dans ce rseau

Plus exactement, la forme d'une adresse IP de classe B est la suivante :


2 octets 2 octets +-------------------------------------------------------------------------------+ 10 adresse rseau adresse noeud +-------------------------------------------------------------------------------+

L'adresse du rseau est sur 2 octets (14 bits exactement) ainsi que celle du noeud. On peut donc avoir 2 14 rseaux de classe B chacun comportant jusqu' 216 noeuds. Classe C Dans cette classe, l'adresse IP : I1.I2.I3.I4 a la forme R1.R2.R3.N1 o R1.R2.R3 N1 est l'adresse du rseau est l'adresse d'une machine dans ce rseau

Plus exactement, la forme d'une adresse IP de classe C est la suivante :


3 octets 1 octet +-------------------------------------------------------------------------------+ 110 adresse rseau adr. noeud +-------------------------------------------------------------------------------+

L'adresse rseau est sur 3 octets (moins 3 bits) et l'adresse du noeud sur 1 octet. On peut donc avoir 2 21 rseaux de classe C comportant jusqu' 256 noeuds. L'adresse de la machine Lagaffe de la facult des sciences d'Angers tant 193.49.144.1, on voit que l'octet de poids fort vaut 193, c'est dire en binaire 11000001. On en dduit que le rseau est de classe C. Adresses rserves Services WEB 192

. . .

Certaines adresses IP sont des adresses de rseaux plutt que des adresses de noeuds dans le rseau. Ce sont celles, o l'adresse du noeud est mise 0. Ainsi, l'adresse 193.49.144.0 est l'adresse IP du rseau de la Facult des Sciences d'Angers. En consquence, aucun noeud d'un rseau ne peut avoir l'adresse zro. Lorsque dans une adresse IP, l'adresse du noeud ne comporte que des 1, on a alors une adresse de diffusion : cette adresse dsigne tous les noeuds du rseau. Dans un rseau de classe C, permettant thoriquement 28=256 noeuds, si on enlve les deux adresses interdites, on n'a plus que 254 adresses autorises.

8.1.5.2

Les protocoles de conversion Adresse Internet <--> Adresse physique

Nous avons vu que lors d'une mission d'informations d'une machine vers une autre, celles-ci la traverse de la couche IP taient encapsules dans des paquets. Ceux-ci ont la forme suivante :
. <---- En-tte paquet IP ---------------------------> . <---Donnes paquet IP ------------>. +---------------------------------------------------------------------------------------------+ Info Adresse Internet Adresse Internet Source Destination +---------------------------------------------------------------------------------------------+

Le paquet IP contient donc les adresses Internet des machines source et destination. Lorsque ce paquet va tre transmis la couche charge de l'envoyer sur le rseau physique, d'autres informations lui sont ajoutes pour former la trame physique qui sera finalement envoye sur le rseau. Par exemple, le format d'une trame sur un rseau Ethernet est le suivant :
. <---- En-tte trame Ethernet -----------------------> . <-Donnes trame Ethernet->. +----------------------------------------------------------------------------------------------------+ Info Adresse Physique Adresse Physique longueur Paquet IP Ethernet Source Destination paquet CRC +----------------------------------------------------------------------------------------------------+ 8 oct 6 6 2 46 1500 4

Dans la trame finale, il y a l'adresse physique des machines source et destination. Comment sont-elles obtenues ? La machine expditrice connaissant l'adresse IP de la machine avec qui elle veut communiquer obtient l'adresse physique de celle-ci en utilisant un protocole particulier appel ARP (Address Resolution Protocol). . . . . Elle envoie un paquet d'un type spcial appel paquet ARP contenant l'adresse IP de la machine dont on cherche l'adresse physique. Elle a pris soin galement d'y placer sa propre adresse IP ainsi que son adresse physique. Ce paquet est envoy tous les noeuds du rseau. Ceux-ci reconnaissent la nature spciale du paquet. Le noeud qui reconnat son adresse IP dans le paquet, rpond en envoyant l'expditeur du paquet son adresse physique. Comment le peut-il ? Il a trouv dans le paquet les adresses IP et physique de l'expditeur. L'expditeur reoit donc l'adresse physique qu'il cherchait. Il la stocke en mmoire afin de pouvoir l'utiliser ultrieurement si d'autres paquets sont envoyer au mme destinataire.

L'adresse IP d'une machine est normalement inscrite dans l'un de ses fichiers qu'elle peut donc consulter pour la connatre. Cette adresse peut tre change : il suffit d'diter le fichier. L'adresse physique elle, est inscrite dans une mmoire de la carte rseau et ne peut tre change. Lorsqu'un administrateur dsire d'organiser son rseau diffremment, il peut tre amen changer les adresses IP de tous les noeuds et donc diter les diffrents fichiers de configuration des diffrents noeuds. Cela peut tre fastidieux et une occasion d'erreurs s'il y a beaucoup de machines. Une mthode consiste ne pas affecter d'adresse IP aux machines : on inscrit alors un code spcial dans le fichier dans lequel la machine devrait trouver son adresse IP. Dcouvrant qu'elle n'a pas d'adresse IP, la machine la demande selon un protocole appel RARP (Reverse Address Resolution Protocol). Elle envoie alors sur un rseau un paquet spcial appel paquet RARP, analogue au paquet ARP prcdent, dans lequel elle met son adresse physique. Ce paquet est envoy tous les noeuds qui reconnaissent alors un paquet RARP. L'un d'entre-eux, appel serveur RARP, possde un fichier donnant la correspondance adresse physique <--> adresse IP de tous les noeuds. Il rpond alors l'expditeur du paquet RARP, en lui renvoyant son adresse IP. Un administrateur dsirant reconfigurer son rseau, n'a donc qu' diter le fichier de correspondances du serveur RARP. Celui-ci doit normalement avoir une adresse IP fixe qu'il doit pouvoir connatre sans avoir utiliser lui-mme le protocole RARP.

8.1.6

La couche rseau dite couche IP de l'internet

Le protocole IP (Internet Protocol) dfinit la forme que les paquets doivent prendre et la faon dont ils doivent tre grs lors de leur mission ou de leur rception. Ce type de paquet particulier est appel un datagramme IP. Nous l'avons dj prsent :

Services WEB

193

. <---- En-tte paquet IP ---------------------------> . <---Donnes paquet IP ------------>. +---------------------------------------------------------------------------------------------+ Info Adresse Internet Adresse Internet Source Destination +---------------------------------------------------------------------------------------------+

L'important est qu'outre les donnes transmettre, le datagramme IP contient les adresses Internet des machines source et destination. Ainsi la machine destinatrice sait qui lui envoie un message. A la diffrence d'une trame de rseau qui a une longueur dtermine par les caractristiques physiques du rseau sur lequel elle transite, la longueur du datagramme IP est elle fixe par le logiciel et sera donc la mme sur diffrents rseaux physiques. Nous avons vu qu'en descendant de la couche rseau dans la couche physique le datagramme IP tait encapsul dans une trame physique. Nous avons donn l'exemple de la trame physique d'un rseau Ethernet :
. <---- En-tte trame Ethernet -------------------------------->. <---Donnes trame Ethernet------>. +----------------------------------------------------------------------------------------------------+ Info Adresse Physique Adresse Physique Type du Paquet IP Ethernet Source Destination paquet CRC +----------------------------------------------------------------------------------------------------+

Les trames physiques circulent de noeud en noeud vers leur destination qui peut ne pas tre sur le mme rseau physique que la machine expditrice. Le paquet IP peut donc tre encapsul successivement dans des trames physiques diffrentes au niveau des noeuds qui font la jonction entre deux rseaux de type diffrent. Il se peut aussi que le paquet IP soit trop grand pour tre encapsul dans une trame physique. Le logiciel IP du noeud o se pose ce problme, dcompose alors le paquet IP en fragments selon des rgles prcises, chacun d'eux tant ensuite envoy sur le rseau physique. Ils ne seront rassembls qu' leur ultime destination.

8.1.6.1

Le routage

Le routage est la mthode d'acheminement des paquets IP leur destination. Il y a deux mthodes : le routage direct et le routage indirect. Routage direct Le routage direct dsigne l'acheminement d'un paquet IP directement de l'expditeur au destinataire l'intrieur du mme rseau : . . . La machine expditrice d'un datagramme IP a l'adresse IP du destinataire. Elle obtient l'adresse physique de ce dernier par le protocole ARP ou dans ses tables, si cette adresse a dj t obtenue. Elle envoie le paquet sur le rseau cette adresse physique.

Routage indirect Le routage indirect dsigne l'acheminement d'un paquet IP une destination se trouvant sur un autre rseau que celui auquel appartient l'expditeur. Dans ce cas, les parties adresse rseau des adresses IP des machines source et destination sont diffrentes. La machine source reconnat ce point. Elle envoie alors le paquet un noeud spcial appel routeur (router), noeud qui connecte un rseau local aux autres rseaux et dont elle trouve l'adresse IP dans ses tables, adresse obtenue initialement soit dans un fichier soit dans une mmoire permanente ou encore via des informations circulant sur le rseau. Un routeur est attach deux rseaux et possde une adresse IP l'intrieur de ces deux rseaux.
+------------+ rseau 2 routeur rseau 1 ----------------|193.49.144.6|-----------193.49.145.0 193.49.145.3 193.49.144.0 +------------+

Dans notre exemple ci-dessus : . . Le rseau n 1 a l'adresse Internet 193.49.144.0 et le rseau n 2 l'adresse 193.49.145.0. A l'intrieur du rseau n 1, le routeur a l'adresse 193.49.144.6 et l'adresse 193.49.145.3 l'intrieur du rseau n 2.

Le routeur a pour rle de mettre le paquet IP qu'il reoit et qui est contenu dans une trame physique typique du rseau n 1, dans une trame physique pouvant circuler sur le rseau n 2. Si l'adresse IP du destinataire du paquet est dans le rseau n 2, le routeur lui enverra le paquet directement sinon il l'enverra un autre routeur, connectant le rseau n 2 un rseau n 3 et ainsi de suite.

8.1.6.2

Messages d'erreur et de contrle

Toujours dans la couche rseau, au mme niveau donc que le protocole IP, existe le protocole ICMP (Internet Control Message Protocol). Il sert envoyer des messages sur le fonctionnement interne du rseau : noeuds en panne, embouteillage un routeur, etc ... Les messages ICMP sont encapsuls dans des paquets IP et envoys sur le rseau. Les couches IP des diffrents noeuds prennent les actions appropries selon les messages ICMP qu'elles reoivent. Ainsi, une application elle-mme, ne voit jamais ces problmes propres au rseau. Un noeud utilisera les informations ICMP pour mettre jour ses tables de routage. Services WEB 194

8.1.7 8.1.7.1

La couche transport : les protocoles UDP et TCP Le protocole UDP : User Datagram Protocol

Le protocole UDP permet un change non fiable de donnes entre deux points, c'est dire que le bon acheminement d'un paquet sa destination n'est pas garanti. L'application, si elle le souhaite peut grer cela elle-mme, en attendant par exemple aprs l'envoi d'un message, un accus de rception, avant d'envoyer le suivant. Pour l'instant, au niveau rseau, nous avons parl d'adresses IP de machines. Or sur une machine, peuvent coexister en mme temps diffrents processus qui tous peuvent communiquer. Il faut donc indiquer, lors de l'envoi d'un message, non seulement l'adresse IP de la machine destinatrice, mais galement le "nom" du processus destinataire. Ce nom est en fait un numro, appel numro de port. Certains numros sont rservs des applications standard : port 69 pour l'application tftp (trivial file transfer protocol) par exemple. Les paquets grs par le protocole UDP sont appels galement des datagrammes. Ils ont la forme suivante :
. <---- En-tte datagramme UDP ----------->. <---Donnes datagramme UDP-------->. +--------------------------------------------------------------------------------+ Port source Port destination +--------------------------------------------------------------------------------+

Ces datagrammes seront encapsuls dans des paquets IP, puis dans des trames physiques.

8.1.7.2

Le protocole TCP : Transfer Control Protocol

Pour des communications sres, le protocole UDP est insuffisant : le dveloppeur d'applications doit laborer lui-mme un protocole lui permettant de dtecter le bon acheminement des paquets. Le protocole TCP (Transfer Control Protocol) vite ces problmes. Ses caractristiques sont les suivantes : . Le processus qui souhaite mettre tablit tout d'abord une connexion avec le processus destinataire des informations qu'il va mettre. Cette connexion se fait entre un port de la machine mettrice et un port de la machine rceptrice. Il y a entre les deux ports un chemin virtuel qui est ainsi cr et qui sera rserv aux deux seuls processus ayant ralis la connexion. Tous les paquets mis par le processus source suivent ce chemin virtuel et arrivent dans l'ordre o ils ont t mis ce qui n'tait pas garanti dans le protocole UDP puisque les paquets pouvaient suivre des chemins diffrents. L'information mise a un aspect continu. Le processus metteur envoie des informations son rhythme. Celles-ci ne sont pas ncessairement envoyes tout de suite : le protocole TCP attend d'en avoir assez pour les envoyer. Elles sont stockes dans une structure appele segment TCP. Ce segment une fois rempli sera transmis la couche IP o il sera encapsul dans un paquet IP. Chaque segment envoy par le protocole TCP est numrot. Le protocole TCP destinataire vrifie qu'il reoit bien les segments en squence. Pour chaque segment correctement reu, il envoie un accus de rception l'expditeur. Lorsque ce dernier le reoit, il l'indique au processus metteur. Celui-ci peut donc savoir qu'un segment est arriv bon port, ce qui n'tait pas possible avec le protocole UDP. Si au bout d'un certain temps, le protocole TCP ayant mis un segment ne reoit pas d'accus de rception, il retransmet le segment en question, garantissant ainsi la qualit du service d'acheminement de l'information. Le circuit virtuel tabli entre les deux processus qui communiquent est full-duplex : cela signifie que l'information peut transiter dans les deux sens. Ainsi le processus destination peut envoyer des accuss de rception alors mme que le processus source continue d'envoyer des informations. Cela permet par exemple au protocole TCP source d'envoyer plusieurs segments sans attendre d'accus de rception. S'il ralise au bout d'un certain temps qu'il n'a pas reu l'accus de rception d'un certain segment n n, il reprendra l'mission des segments ce point.

. . . .

8.1.8

La couche Applications

Au-dessus des protocoles UDP et TCP, existent divers protocoles standard : TELNET Ce protocole permet un utilisateur d'une machine A du rseau de se connecter sur une machine B (appele souvent machine hte). TELNET mule sur la machine A un terminal dit universel. L'utilisateur se comporte donc comme s'il disposait d'un terminal connect la machine B. Telnet s'appuie sur le protocole TCP. FTP : (File Transfer protocol) Ce protocole permet l'change de fichiers entre deux machines distantes ainsi que des manipulations de fichiers tels que des crations de rpertoire par exemple. Il s'appuie sur le protocole TCP. TFTP: (Trivial File Transfer Control) Ce protocole est une variante de FTP. Il s'appuie sur le protocole UDP et est moins sophistiqu que FTP. DNS : (Domain Name System) Services WEB 195

Lorsqu'un utilisateur dsire changer des fichiers avec une machine distante, par FTP par exemple, il doit connatre l'adresse Internet de cette machine. Par exemple, pour faire du FTP sur la machine Lagaffe de l'universit d'Angers, il faudrait lancer FTP comme suit : FTP 193.49.144.1 Cela oblige avoir un annuaire faisant la correspondance machine <--> adresse IP. Probablement que dans cet annuaire les machines seraient dsignes par des noms symboliques tels que : machine DPX2/320 de l'universit d'Angers machine Sun de l'ISERPA d'Angers On voit bien qu'il serait plus agrable de dsigner une machine par un nom plutt que par son adresse IP. Se pose alors le problme de l'unicit du nom : il y a des millions de machines interconnectes. On pourrait imaginer qu'un organisme centralis attribue les noms. Ce serait sans doute assez lourd. Le contrle des noms a t en fait distribu dans des domaines. Chaque domaine est gr par un organisme gnralement trs lger qui a toute libert quant au choix des noms de machines. Ainsi les machines en France appartiennent au domaine fr, domaine gr par l'Inria de Paris. Pour continuer simplifier les choses, on distribue encore le contrle : des domaines sont crs l'intrieur du domaine fr. Ainsi l'universit d'Angers appartient au domaine univ-Angers. Le service grant ce domaine a toute libert pour nommer les machines du rseau de l'Universit d'Angers. Pour l'instant ce domaine n'a pas t subdivis. Mais dans une grande universit comportant beaucoup de machines en rseau, il pourrait l'tre. La machine DPX2/320 de l'universit d'Angers a t nomme Lagaffe alors qu'un PC 486DX50 a t nomm liny. Comment rfrencer ces machines de l'extrieur ? En prcisant la hirarchie des domaines auxquelles elles appartiennent. Ainsi le nom complet de la machine Lagaffe sera : Lagaffe.univ-Angers.fr A l'intrieur des domaines, on peut utiliser des noms relatifs. Ainsi l'intrieur du domaine fr et en dehors du domaine univAngers, la machine Lagaffe pourra tre rfrence par Lagaffe.univ-Angers Enfin, l'intrieur du domaine univ-Angers, elle pourra tre rfrence simplement par Lagaffe Une application peut donc rfrencer une machine par son nom. Au bout du compte, il faut quand mme obtenir l'adresse Internet de cette machine. Comment cela est-il ralis ? Suposons que d'une machine A, on veuille communiquer avec une machine B. . . si la machine B appartient au mme domaine que la machine A, on trouvera probablement son adresse IP dans un fichier de la machine A. sinon, la machine A trouvera dans un autre fichier ou le mme que prcdemment, une liste de quelques serveurs de noms avec leurs adresses IP. Un serveur de noms est charg de faire la correspondance entre un nom de machine et son adresse IP. La machine A va envoyer une requte spciale au premier serveur de nom de sa liste, appel requte DNS incluant donc le nom de la machine recherche. Si le serveur interrog a ce nom dans ses tablettes, il enverra la machine A, l'adresse IP correspondante. Sinon, le serveur trouvera lui aussi dans ses fichiers, une liste de serveurs de noms qu'il peut interroger. Il le fera alors. Ainsi un certain nombre de serveurs de noms vont tre interrogs, pas de faon anarchique mais d'une faon minimiser les requtes. Si la machine est finalement trouve, la rponse redescendra jusqu' la machine A.

XDR : (eXternal Data Representation) Cr par sun MicroSystems, ce protocole spcifie une reprsentation standard des donnes, indpendante des machines. RPC : (Remote Procedure Call) Dfini galement par sun, c'est un protocole de communication entre applications distantes, indpendant de la couche transport. Ce protocole est important : il dcharge le programmeur de la connaissance des dtails de la couche transport et rend les applications portables. Ce protocole s'appuie sur sur le protocole XDR NFS : Network File System Toujours dfini par Sun, ce protocole permet une machine, de "voir" le systme de fichiers d'une autre machine. Il s'appuie sur le protocole RPC prcdent.

8.1.9
Titre Auteur Services WEB

Conclusion
TCP/IP : Architecture, Protocoles, Applications. Douglas COMER 196

Nous avons prsent dans cette introduction quelques grandes lignes des protocoles Internet. Pour approfondir ce domaine, on pourra lire l'excellent livre de Douglas Comer :

Editeur

InterEditions

8.2

Gestion des adresses rseau

Une machine sur le rseau Internet est dfinie de faon unique par une adresse IP (Internet Protocol) de la forme I1.I2.I3.I4 o I n est un nombre entre 1 et 254. Elle peut tre galement dfinie par un nom galement unique. Ce nom n'est pas obligatoire, les applications utilisant toujours au final les adresses IP des machines. lls sont l pour faciliter la vie des utilisateurs. Ainsi il est plus facile, avec un navigateur, de demander l'URL http://www.ibm.com que l'URL http://129.42.17.99 bien que les deux mthodes soient possibles. L'association adresse IP <--> nomMachine est assure par un service distribu de l'internet appel DNS (Domain Name System). La plate-forme .NET offre la classe Dns pour grer les adresses internet :

La plupart des mthodes de la classe sont statiques. Regardons celles qui nous intressent :
Overloads Public Shared Function GetHostByAddress(ByVal address As String) As IPHostEntry Public Shared Function GetHostByName(ByVal hostName As String) As IPHostEntry Public Shared Function GetHostName() As String

rend une adresse IPHostEntry partir d'une adresse IP sous la forme "I1.I2.I3.I4". Lance une exception si la machine address ne peut tre trouve. rend une adresse IPHostEntry partir d'un nom de machine. Lance une exception si la machine name ne peut tre trouve. rend le nom de la machine sur laquelle s'excute le programme qui joue cette instruction

Les adresses rseau de type IPHostEntry ont la forme suivante :

Les proprits qui nous intressent : Services WEB 197

Public Property AddressList As IPAddress ()

liste des adresses IP d'une machine. Si une adresse IP dsigne une et une seule machine physique, une machine physique peut elle avoir plusieurs adresses IP. Ce sera le cas si elle a plusieurs cartes rseau qui la connectent des rseaux diffrents. liste des alias d'une machine, pouvant tre dsigne par un nom principal et des alias le nom de la machine si elle en a un

Public Property Aliases As String () Public Property HostName As String

De la classe IPAddress nous retiendrons le constructeur, les proprits et mthodes suivantes :

Un objet [IPAddress] peut tre transform en chane I1.I2.I3.I4 avec la mthode ToString(). Inversement, on peut obtenir un objet IPAddress partir d'une chane I1.I2.I3.I4 avec la mthode statique IPAddress.Parse("I1.I2.I3.I4"). Considrons le programme suivant qui affiche le nom de la machine sur laquelle il s'excute puis de faon interactive donne les correspondances adresse IP <--> nom Machine :
dos>address1 Machine Locale=tahe Machine recherche (fin pour arrter) : istia.univ-angers.fr Machine : istia.univ-angers.fr Adresses IP : 193.49.146.171 Machine recherche (fin pour arrter) : 193.49.146.171 Machine : istia.istia.univ-angers.fr Adresses IP : 193.49.146.171 Alias : 171.146.49.193.in-addr.arpa Machine recherche (fin pour arrter) : www.ibm.com Machine : www.ibm.com Adresses IP : 129.42.17.99,129.42.18.99,129.42.19.99,129.42.16.99 Machine recherche (fin pour arrter) : 129.42.17.99 Machine : www.ibm.com Adresses IP : 129.42.17.99 Machine recherche (fin pour arrter) : x.y.z Impossible de trouver la machine [x.y.z] Machine recherche (fin pour arrter) : localhost Machine : tahe Adresses IP : 127.0.0.1 Machine recherche (fin pour arrter) : 127.0.0.1 Machine : tahe Adresses IP : 127.0.0.1 Machine recherche (fin pour arrter) : tahe Machine : tahe Adresses IP : 127.0.0.1 Machine recherche (fin pour arrter) : fin

Le programme est le suivant :


' options Option Explicit On Option Strict On ' espaces de noms

Services WEB

198

Imports System Imports System.Net Imports System.Text.RegularExpressions ' module de test Public Module adresses Sub Main() ' affiche le nom de la machine locale ' puis donne interactivement des infos sur les machines rseau ' identifies par un nom ou une adresse IP ' machine locale Dim localHost As String = Dns.GetHostName() Console.Out.WriteLine(("Machine Locale=" + localHost)) ' question-rponses interactives Dim machine As String Dim adresseMachine As IPHostEntry While True ' saisie du nom de la machine recherche Console.Out.Write("Machine recherche (fin pour arrter) : ") machine = Console.In.ReadLine().Trim().ToLower() ' fini ? If machine = "fin" Then Exit While End If ' adresse I1.I2.I3.I4 ou nom de machine ? Dim isIPV4 As Boolean = Regex.IsMatch(machine, "^\s*\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\s*$") ' gestion exception Try If isIPV4 Then adresseMachine = Dns.GetHostByAddress(machine) Else adresseMachine = Dns.GetHostByName(machine) End If ' le nom Console.Out.WriteLine(("Machine : " + adresseMachine.HostName)) ' les adresses IP Console.Out.Write(("Adresses IP : " + adresseMachine.AddressList(0).ToString)) Dim i As Integer For i = 1 To adresseMachine.AddressList.Length - 1 Console.Out.Write(("," + adresseMachine.AddressList(i).ToString)) Next i Console.Out.WriteLine() ' les alias If adresseMachine.Aliases.Length <> 0 Then Console.Out.Write(("Alias : " + adresseMachine.Aliases(0))) For i = 1 To adresseMachine.Aliases.Length - 1 Console.Out.Write(("," + adresseMachine.Aliases(i))) Next i Console.Out.WriteLine() End If Catch ' la machine n'existe pas Console.Out.WriteLine("Impossible de trouver la machine [" + machine + "]") End Try End While End Sub End Module

8.3 8.3.1

Programmation TCP-IP Gnralits

Considrons la communication entre deux machines distantes A et B : Machine A Machine B

Port PA
Rseau

Port PB

Services WEB

199

Lorsque une application AppA d'une machine A veut communiquer avec une application AppB d'une machine B de l'Internet, elle doit connatre plusieurs choses : l'adresse IP ou le nom de la machine B le numro du port avec lequel travaille l'application AppB. En effet la machine B peut supporter de nombreuses applications qui travaillent sur l'Internet. Lorsqu'elle reoit des informations provenant du rseau, elle doit savoir quelle application sont destines ces informations. Les applications de la machine B ont accs au rseau via des guichets appels galement des ports de communication. Cette information est contenue dans le paquet reu par la machine B afin qu'il soit dlivr la bonne application. les protocoles de communication compris par la machine B. Dans notre tude, nous utiliserons uniquement les protocoles TCP-IP. le protocole de dialogue accept par l'application AppB. En effet, les machines A et B vont se "parler". Ce qu'elles vont dire va tre encapsul dans les protocoles TCP-IP. Nanmoins, lorsqu'au bout de la chane, l'application AppB va recevoir l'information envoye par l'applicaton AppA, il faut qu'elle soit capable de l'interprter. Ceci est analogue la situation o deux personnes A et B communiquent par tlphone : leur dialogue est transport par le tlphone. La parole va tre code sous forme de signaux par le tlphone A, transporte par des lignes tlphoniques, arriver au tlphone B pour y tre dcode. La personne B entend alors des paroles. C'est l qu'intervient la notion de protocole de dialogue : si A parle franais et que B ne comprend pas cette langue, A et B ne pourront dialoguer utilement. Aussi les deux applications communicantes doivent -elles tre d'accord sur le type de dialogue qu'elles vont adopter. Par exemple, le dialogue avec un service ftp n'est pas le mme qu'avec un service pop : ces deux services n'acceptent pas les mmes commandes. Elles ont un protocole de dialogue diffrent.

8.3.2

Les caractristiques du protocole TCP


Le processus qui souhaite mettre tablit tout d'abord une connexion avec le processus destinataire des informations qu'il va mettre. Cette connexion se fait entre un port de la machine mettrice et un port de la machine rceptrice. Il y a entre les deux ports un chemin virtuel qui est ainsi cr et qui sera rserv aux deux seuls processus ayant ralis la connexion. Tous les paquets mis par le processus source suivent ce chemin virtuel et arrivent dans l'ordre o ils ont t mis L'information mise a un aspect continu. Le processus metteur envoie des informations son rythme. Celles-ci ne sont pas ncessairement envoyes tout de suite : le protocole TCP attend d'en avoir assez pour les envoyer. Elles sont stockes dans une structure appele segment TCP. Ce segment une fois rempli sera transmis la couche IP o il sera encapsul dans un paquet IP. Chaque segment envoy par le protocole TCP est numrot. Le protocole TCP destinataire vrifie qu'il reoit bien les segments en squence. Pour chaque segment correctement reu, il envoie un accus de rception l'expditeur. Lorsque ce dernier le reoit, il l'indique au processus metteur. Celui-ci peut donc savoir qu'un segment est arriv bon port. Si au bout d'un certain temps, le protocole TCP ayant mis un segment ne reoit pas d'accus de rception, il retransmet le segment en question, garantissant ainsi la qualit du service d'acheminement de l'information. Le circuit virtuel tabli entre les deux processus qui communiquent est full-duplex : cela signifie que l'information peut transiter dans les deux sens. Ainsi le processus destination peut envoyer des accuss de rception alors mme que le processus source continue d'envoyer des informations. Cela permet par exemple au protocole TCP source d'envoyer plusieurs segments sans attendre d'accus de rception. S'il ralise au bout d'un certain temps qu'il n'a pas reu l'accus de rception d'un certain segment n n, il reprendra l'mission des segments ce point.

Nous n'tudierons ici que des communications rseau utilisant le protocole de transport TCP. Rappelons ici, les caractristiques de celui-ci :

8.3.3

La relation client-serveur

Souvent, la communication sur Internet est dissymtrique : la machine A initie une connexion pour demander un service la machine B : il prcise qu'il veut ouvrir une connexion avec le service SB1 de la machine B. Celle-ci accepte ou refuse. Si elle accepte, la machine A peut envoyer ses demandes au service SB1. Celles-ci doivent se conformer au protocole de dialogue compris par le service SB1. Un dialogue demande-rponse s'instaure ainsi entre la machine A qu'on appelle machine cliente et la machine B qu'on appelle machine serveur. L'un des deux partenaires fermera la connexion.

8.3.4

Architecture d'un client

L'architecture d'un programme rseau demandant les services d'une application serveur sera la suivante :
ouvrir la connexion avec le service SB1 de la machine B si russite alors tant que ce n'est pas fini prparer une demande l'mettre vers la machine B

Services WEB

200

attendre et rcuprer la rponse la traiter fin tant que finsi

8.3.5

Architecture d'un serveur

L'architecture d'un programme offrant des services sera la suivante :


ouvrir le service sur la machine locale tant que le service est ouvert se mettre l'coute des demandes de connexion sur un port dit port d'coute lorsqu'il y a une demande, la faire traiter par une autre tche sur un autre port dit port de service fin tant que

Le programme serveur traite diffremment la demande de connexion initiale d'un client de ses demandes ultrieures visant obtenir un service. Le programme n'assure pas le service lui-mme. S'il le faisait, pendant la dure du service il ne serait plus l'coute des demandes de connexion et des clients ne seraient alors pas servis. Il procde donc autrement : ds qu'une demande de connexion est reue sur le port d'coute puis accepte, le serveur cre une tche charge de rendre le service demand par le client. Ce service est rendu sur un autre port de la machine serveur appel port de service. On peut ainsi servir plusieurs clients en mme temps. Une tche de service aura la structure suivante :
tant que le service n'a pas t rendu totalement attendre une demande sur le port de service lorsqu'il y en a une, laborer la rponse transmettre la rponse via le port de service fin tant que librer le port de service

8.3.6

La classe TcpClient

La classe TcpClient est la classe qui convient pour reprsenter le client d'un service TCP. Elle est dfinie comme suit :

Les constructeurs, mthodes et proprits qui nous intressent sont les suivants :
Public Sub New(ByVal hostname As String,ByVal port As Integer) Public Sub Close() Public Function GetStream() As NetworkStream

cre une liaison tcp avec le serveur oprant sur le port indiqu (port) de la machine indique (hostname). Par exemple new TcpClient("istia.univ-angers.fr",80) pour se connecter au port 80 de la machine istia.univ-angers.fr ferme la connexion au serveur Tcp obtient un flux NetworkStream de lecture et d'criture vers le serveur. C'est ce flux qui permet les changes client-serveur.

8.3.7

La classe NetworkStream

La classe NetworkStream reprsente le flux rseau entre le client et le serveur. La classe est dfinie comme suit :

La classe NetworkStream est drive de la classe Stream. Beaucoup d'applications client-serveur changent des lignes de texte termines par les caractres de fin de ligne "\r\n". Aussi il est intressant d'utiliser des objets StreamReader et StreamWriter pour lire et crire ces lignes dans le flux rseau. Lorsque deux machines communiquent, il y a chaque bout de la liaison un objet TcpClient. Services WEB 201

La mthode GetStream de cet objet permet d'avoir accs au flux rseau (NetworkStream) qui lie les deux machines. Ainsi si une machine M1 a tabli une liaison avec une machine M2 l'aide d'un objet TcpClient client1 qu'elles changent des lignes de texte, elle pourra crer ses flux de lecture et criture de la faon suivante :
Dim in1 as StreamReader=new StreamReader(client1.GetStream()) Dim out1 as StreamWriter=new StreamWriter(client1.GetStream()) out1.AutoFlush=true

L'instruction
out1.AutoFlush=true

signifie que le flux d'criture de client1 ne transitera pas par un buffer intermdiaire mais ira directement sur le rseau. Ce point est important. En gnral lorsque client1 envoie une ligne de texte son partenaire il en attend une rponse. Celle-ci ne viendra jamais si la ligne a t en ralit bufferise sur la machine M1 et jamais envoye. Pour envoyer une ligne de texte la machine M2, on crira :
client1.WriteLine("un texte")

Pour lire la rponse de M2, on crira :


Dim rponse as String=client1.ReadLine()

8.3.8
Dim Dim Dim Dim Dim

Architecture de base d'un client internet


client As TcpClient = Nothing ' le client [IN] As StreamReader = Nothing ' le flux de lecture du client OUT As StreamWriter = Nothing ' le flux d'criture du client demande As String = Nothing ' demande du client rponse As String = Nothing ' rponse du serveur

Nous avons maintenant les lments pour crire l'architecture de base d'un client internet :

Try ' on se connecte au service officiant sur le port P de la machine M client = New TcpClient(nomServeur, port) ' on cre les flux d'entre-sortie du client TCP [IN] = New StreamReader(client.GetStream()) OUT = New StreamWriter(client.GetStream()) OUT.AutoFlush = True ' boucle demande - rponse While True ' on prpare la demande demande = ... ' on l'envoie au serveur OUT.WriteLine(demande) ' on lit la rponse du serveur rponse = [IN].ReadLine() ' on traite la rponse ... End While ' c'est fini client.Close() Catch ex As Exception ' on gre l'exception ... End Try

8.3.9

La classe TcpListener

La classe TcpListener est la classe qui convient pour reprsenter un service TCP. Elle est dfinie comme suit :

Les constructeurs, mthodes et proprits qui nous intressent sont les suivants :
Public Sub New(ByVal localaddr As IPAddress,ByVal port As Integer)

cre un service TCP qui va attendre (listen) les demandes des clients sur un port pass en paramtre (port) appel port d'coute de la machine locale d'adresse IP localadr. 202

Services WEB

Public Function AcceptTcpClient() As TcpClient Public Sub Start() Public Sub Stop()

accepte la demande d'un client. Rend comme rsultat un objet TcpClient associ un autre port, appel port de service. lance l'coute des demandes clients arrte d'couter les demandes clients

8.3.10

Architecture de base d'un serveur Internet

De ce qui a t vu prcdemment, on peut dduire la structure de base d'un serveur :


' on cre le servive d'coute Dim ecoute As TcpListener = Nothing Dim port As Integer = ... Try ' on cre le service ecoute = New TcpListener(IPAddress.Parse("127.0.0.1"), port) ' on le lance ecoute.Start() ' boucle de service Dim liaisonClient As TcpClient = Nothing While not fini ' attente d'un client liaisonClient = ecoute.AcceptTcpClient() ' le service est assur par une autre tche Dim tache As Thread = New Thread(New ThreadStart(AddressOf [mthode])) tache.Start() End While Catch ex As Exception ' on signale l'erreur .... End Try ' fin du service ecoute.Stop()

La classe Service est un thread qui pourrait avoir l'allure suivante :


Public Class Service Private liaisonClient As TcpClient ' liaison avec le client Private [IN] As StreamReader ' flux d'entre Private OUT As StreamWriter ' flux de sortie ' constructeur Public Sub New(ByVal liaisonClient As TcpClient, ...) Me.liaisonClient = liaisonClient ... End Sub ' mthode run Public Sub Run() ' rend le service au client Try ' flux d'entre [IN] = New StreamReader(liaisonClient.GetStream()) ' flux de sortie OUT = New StreamWriter(liaisonClient.GetStream()) OUT.AutoFlush = True ' boucle lecture demande/criture rponse Dim demande As String = Nothing Dim reponse As String = Nothing demande = [IN].ReadLine While Not (demande Is Nothing) ' on traite la demande ... ' on envoie la rponse reponse = "[" + demande + "]" OUT.WriteLine(reponse) ' demande suivante demande = [IN].ReadLine End While ' fin liaison liaisonClient.Close() Catch e As Exception ... End Try ' fin du service End Sub

Services WEB

203

8.4 8.4.1

Exemples Serveur d'cho

Nous nous proposons d'crire un serveur d'cho qui sera lanc depuis une fentre DOS par la commande : serveurEcho port Le serveur officie sur le port pass en paramtre. Il se contente de renvoyer au client la demande que celui-ci lui a envoye. Le programme est le suivant :
' options Option Explicit On Option Strict On ' espaces de noms Imports System.Net.Sockets Imports System.Net Imports System Imports System.IO Imports System.Threading Imports Microsoft.VisualBasic ' appel : serveurEcho port ' serveur d'cho ' renvoie au client la ligne que celui-ci lui a envoye Public Class serveurEcho Private Shared syntaxe As String = "Syntaxe : serveurEcho port" ' programme principal Public Shared Sub Main(ByVal args() As String) ' y-a-t-il un argument If args.Length <> 1 Then erreur(syntaxe, 1) End If ' cet argument doit tre entier >0 Dim port As Integer = 0 Dim erreurPort As Boolean = False Dim E As Exception = Nothing Try port = Integer.Parse(args(0)) Catch ex As Exception E = ex erreurPort = True End Try erreurPort = erreurPort Or port <= 0 If erreurPort Then erreur(syntaxe + ControlChars.Lf + "Port incorrect (" + E.ToString + ")", 2) End If ' on cre le servive d'coute Dim ecoute As TcpListener = Nothing Dim nbClients As Integer = 0 ' nbre de clients traits Try ' on cre le service ecoute = New TcpListener(IPAddress.Parse("127.0.0.1"), port) ' on le lance ecoute.Start() ' suivi Console.Out.WriteLine(("Serveur d'cho lanc sur le port " & port)) Console.Out.WriteLine(ecoute.LocalEndpoint) ' boucle de service Dim liaisonClient As TcpClient = Nothing While True ' boucle infinie - sera arrte par Ctrl-C ' attente d'un client liaisonClient = ecoute.AcceptTcpClient() ' le service est assur par une autre tche nbClients += 1 Dim tache As Thread = New Thread(New ThreadStart(AddressOf New traiteClientEcho(liaisonClient, nbClients).Run)) tache.Start() End While ' on retourne l'coute des demandes

Services WEB

204

Catch ex As Exception ' on signale l'erreur erreur("L'erreur suivante s'est produite : " + ex.Message, 3) End Try ' fin du service ecoute.Stop() End Sub ' affichage des erreurs Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer) ' affichage erreur System.Console.Error.WriteLine(msg) ' arrt avec erreur Environment.Exit(exitCode) End Sub End Class ' ------------------------------------------------------' assure le service un client du serveur d'cho Public Class traiteClientEcho Private Private Private Private liaisonClient As TcpClient ' liaison avec le client numClient As Integer ' n de client [IN] As StreamReader ' flux d'entre OUT As StreamWriter ' flux de sortie

' constructeur Public Sub New(ByVal liaisonClient As TcpClient, ByVal numClient As Integer) Me.liaisonClient = liaisonClient Me.numClient = numClient End Sub ' mthode run Public Sub Run() ' rend le service au client Console.Out.WriteLine(("Dbut de service au client " & numClient)) Try ' flux d'entre [IN] = New StreamReader(liaisonClient.GetStream()) ' flux de sortie OUT = New StreamWriter(liaisonClient.GetStream()) OUT.AutoFlush = True ' boucle lecture demande/criture rponse Dim demande As String = Nothing Dim reponse As String = Nothing demande = [IN].ReadLine While Not (demande Is Nothing) ' suivi Console.Out.WriteLine(("Client " & numClient & " : " & demande)) ' le service s'arrte lorsque le client envoie une marque de fin de fichier reponse = "[" + demande + "]" OUT.WriteLine(reponse) ' le service s'arrte lorsque le client envoie "fin" If demande.Trim().ToLower() = "fin" Then Exit While End If ' demande suivante demande = [IN].ReadLine End While ' fin liaison liaisonClient.Close() Catch e As Exception erreur("Erreur lors de la fermeture de la liaison client (" + e.ToString + ")", 2) End Try ' fin du service Console.Out.WriteLine(("Fin de service au client " & numClient)) End Sub ' affichage des erreurs Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer) ' affichage erreur System.Console.Error.WriteLine(msg) ' arrt avec erreur Environment.Exit(exitCode) End Sub End Class

La structure du serveur est conforme l'architecture gnrale des serveurs tcp.

Services WEB

205

8.4.2

Un client pour le serveur d'cho

Nous crivons maintenant un client pour le serveur prcdent. Il sera appel de la faon suivante : clientEcho nomServeur port Il se connecte la machine nomServeur sur le port port puis envoie au serveur des lignes de texte que celui-ci lui renvoie en cho.
' options Option Explicit On Option Strict On ' espaces de noms Imports System.Net.Sockets Imports System.Net Imports System Imports System.IO Imports System.Threading Imports Microsoft.VisualBasic Public Class clientEcho ' se connecte un serveur d'cho ' toute ligne tape au clavier est alors reue en cho Public Shared Sub Main(ByVal args() As String) ' syntaxe Const syntaxe As String = "pg machine port" ' nombre d'arguments If args.Length <> 2 Then erreur(syntaxe, 1) End If ' on note le nom du serveur Dim nomServeur As String = args(0) ' le port doit tre entier >0 Dim port As Integer = 0 Dim erreurPort As Boolean = False Dim E As Exception = Nothing Try port = Integer.Parse(args(1)) Catch ex As Exception E = ex erreurPort = True End Try erreurPort = erreurPort Or port <= 0 If erreurPort Then erreur(syntaxe + ControlChars.Lf + "Port incorrect (" + E.ToString + ")", 2) End If ' on peut travailler Dim client As TcpClient = Nothing ' le client Dim [IN] As StreamReader = Nothing ' le flux de lecture du client Dim OUT As StreamWriter = Nothing ' le flux d'criture du client Dim demande As String = Nothing ' demande du client Dim rponse As String = Nothing ' rponse du serveur Try ' on se connecte au service officiant sur le port P de la machine M client = New TcpClient(nomServeur, port) ' on cre les flux d'entre-sortie du client TCP [IN] = New StreamReader(client.GetStream()) OUT = New StreamWriter(client.GetStream()) OUT.AutoFlush = True ' boucle demande - rponse While True ' la demande vient du clavier Console.Out.Write("demande (fin pour arrter) : ") demande = Console.In.ReadLine() ' on l'envoie au serveur OUT.WriteLine(demande) ' on lit la rponse du serveur rponse = [IN].ReadLine() ' on traite la rponse Console.Out.WriteLine(("Rponse : " + rponse)) ' fini ? If demande.Trim().ToLower() = "fin" Then

Services WEB

206

Exit While End If End While ' c'est fini client.Close() Catch ex As Exception ' on gre l'exception erreur(ex.Message, 3) End Try End Sub ' affichage des erreurs Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer) ' affichage erreur System.Console.Error.WriteLine(msg) ' arrt avec erreur Environment.Exit(exitCode) End Sub End Class

La structure de ce client est conforme l'architecture gnrale des clients tcp.Voici les rsultats obtenus dans la configuration suivante : le serveur est lanc sur le port 100 dans une fentre Dos sur la mme machine deux clients sont lancs dans deux autres fentres Dos Dans la fentre du client 1 on a les rsultats suivants :
dos>clientEcho localhost 100 demande (fin pour arrter) : Rponse : [ligne1] demande (fin pour arrter) : Rponse : [ligne1B] demande (fin pour arrter) : Rponse : [ligne1C] demande (fin pour arrter) : Rponse : [fin] ligne1 ligne1B ligne1C fin

Dans celle du client 2 :


dos>clientEcho localhost 100 demande (fin pour arrter) : ligne2A Rponse : [ligne2A] demande (fin pour arrter) : ligne2B Rponse : [ligne2B] demande (fin pour arrter) : fin Rponse : [fin]

Dans celle du serveur :


dos>serveurEcho 100 Serveur d'cho lanc sur le port 100 0.0.0.0:100 Dbut de service au client 1 Client 1 : ligne1 Dbut de service au client 2 Client 2 : ligne2A Client 2 : ligne2B Client 1 : ligne1B Client 1 : ligne1C Client 2 : fin Fin de service au client 2 Client 1 : fin Fin de service au client 1 ^C

On remarquera que le serveur a bien t capable de servir deux clients simultanment.

8.4.3

Un client TCP gnrique

Beaucoup de services crs l'origine de l'Internet fonctionnent selon le modle du serveur d'cho tudi prcdemment : les changes client-serveur se font pas changes de lignes de texte. Nous allons crire un client tcp gnrique qui sera lanc de la faon suivante : cltgen serveur port Ce client TCP se connectera sur le port port du serveur serveur. Ceci fait, il crera deux threads : 1. un thread charg de lire des commandes tapes au clavier et de les envoyer au serveur Services WEB 207

2.

un thread charg de lire les rponses du serveur et de les afficher l'cran

Pourquoi deux threads alors que dans l'application prcdente ce besoin ne s'tait pas fait ressentir ? Dans cette dernire, le protocole du dialogue tait connu : le client envoyait une seule ligne et le serveur rpondait par une seule ligne. Chaque service a son protocole particulier et on trouve galement les situations suivantes : le client doit envoyer plusieurs lignes de texte avant d'avoir une rponse la rponse d'un serveur peut comporter plusieurs lignes de texte Aussi la boucle envoi d'une unique ligne au seveur - rception d'une unique ligne envoye par le serveur ne convient-elle pas toujours. On va donc crer deux boucles dissocies : une boucle de lecture des commandes tapes au clavier pour tre envoyes au serveur. L'utilisateur signalera la fin des commandes avec le mot cl fin. une boucle de rception et d'affichage des rponses du serveur. Celle-ci sera une boucle infinie qui ne sera interrompue que par la fermeture du flux rseau par le serveur ou par l'utilisateur au clavier qui tapera la commande fin. Pour avoir ces deux boucles dissocies, il nous faut deux threads indpendants. Montrons un exemple d'exccution o notre client tcp gnrique se connecte un service SMTP (SendMail Transfer Protocol). Ce service est responsable de l'acheminement du courrier lectronique aux destinataires. Il fonctionne sur le port 25 et a un protocole de dialogue de type changes de lignes de texte.
dos>cltgen istia.univ-angers.fr 25 Commandes : <-- 220 istia.univ-angers.fr ESMTP Sendmail 8.11.6/8.9.3; Mon, 13 May 2002 08:37:26 +0200 help <-- 502 5.3.0 Sendmail 8.11.6 -- HELP not implemented mail from: machin@univ-angers.fr <-- 250 2.1.0 machin@univ-angers.fr... Sender ok rcpt to: serge.tahe@istia.univ-angers.fr <-- 250 2.1.5 serge.tahe@istia.univ-angers.fr... Recipient ok data <-- 354 Enter mail, end with "." on a line by itself Subject: test ligne1 ligne2 ligne3 . <-- 250 quit <-- 221 [fin du fin [fin du

2.0.0 g4D6bks25951 Message accepted for delivery 2.0.0 istia.univ-angers.fr closing connection thread de lecture des rponses du serveur] thread d'envoi des commandes au serveur]

Commentons ces changes client-serveur : le service SMTP envoie un message de bienvenue lorsqu'un client se connecte lui :

<-- 220 istia.univ-angers.fr ESMTP Sendmail 8.11.6/8.9.3; Mon, 13 May 2002 08:37:26 +0200

certains services ont une commande help donnant des indications sur les commandes utilisables avec le service. Ici ce n'est pas le cas. Les commandes SMTP utilises dans l'exemple sont les suivantes : o mail from: expditeur, pour indiquer l'adresse lectronique de l'expditeur du message o rcpt to: destinataire, pour indiquer l'adresse lectronique du destinataire du message. S'il y a plusieurs destinataires, on r-met autant de fois que ncessaire la commande rcpt to: pour chacun des destinataires. o data qui signale au serveur SMTP qu'on va envoyer le message. Comme indiqu dans la rponse du serveur, celui-ci est une suite de lignes termine par une ligne contenant le seul caractre point. Un message peut avoir des enttes spars du corps du message par une ligne vide. Dans notre exemple, nous avons mis un sujet avec le mot cl Subject: une fois le message envoy, on peut indiquer au serveur qu'on a termin avec la commande quit. Le serveur ferme alors la connexion rseau.Le thread de lecture peut dtecter cet vnement et s'arrter. l'utilisateur tape alors fin au clavier pour arrter galement le thread de lecture des commandes tapes au clavier.

Si on vrifie le courrier reu, nous avons la chose suivante (Outlook) :

Services WEB

208

On remarquera que le service SMTP ne peut dtecter si un expditeur est valide ou non. Aussi ne peut-on jamais faire confiance au champ from d'un message. Ici l'expditeur machin@univ-angers.fr n'existait pas. Ce client tcp gnrique peut nous permettre de dcouvrir le protocole de dialogue de services internet et partir de l construire des classes spcialises pour des clients de ces services. Dcouvrons le protocole de dialogue du service POP (Post Office Protocol) qui permet de retrouver ses mls stocks sur un serveur. Il travaille sur le port 110.
dos>cltgen istia.univ-angers.fr 110 Commandes : <-- +OK Qpopper (version 4.0.3) at istia.univ-angers.fr starting. help <-- -ERR Unknown command: "help". user st <-- +OK Password required for st. pass monpassword <-- +OK st has 157 visible messages (0 hidden) in 11755927 octets. list <-- +OK 157 visible messages (11755927 octets) <-- 1 892847 <-- 2 171661 ... <-- 156 2843 <-- 157 2796 <-- . retr 157 <-- +OK 2796 octets <-- Received: from lagaffe.univ-angers.fr (lagaffe.univ-angers.fr [193.49.144.1]) <-by istia.univ-angers.fr (8.11.6/8.9.3) with ESMTP id g4D6wZs26600; <-Mon, 13 May 2002 08:58:35 +0200 <-- Received: from jaume ([193.49.146.242]) <-by lagaffe.univ-angers.fr (8.11.1/8.11.2/GeO20000215) with SMTP id g4D6wSd37691; <-Mon, 13 May 2002 08:58:28 +0200 (CEST) ... <-- -----------------------------------------------------------------------<-- NOC-RENATER2 Tl. : 0800 77 47 95 <-- Fax : (+33) 01 40 78 64 00 , Email : noc-r2@cssi.renater.fr <-- -----------------------------------------------------------------------<-<-- . quit <-- +OK Pop server at istia.univ-angers.fr signing off. [fin du thread de lecture des rponses du serveur] fin [fin du thread d'envoi des commandes au serveur]

Les principales commandes sont les suivantes : user login, o on donne son login sur la machine qui dtient nos mls pass password, o on donne le mot de passe associ au login prcdent list, pour avoir la liste des messages sous la forme numro, taille en octets retr i, pour lire le message n i quit, pour arrter le dialogue.

Dcouvrons maintenant le protocole de dialogue entre un client et un serveur Web qui lui travaille habituellement sur le port 80 :
dos>cltgen istia.univ-angers.fr 80 Commandes : GET /index.html HTTP/1.0 <-<-<-<-<-<-<-HTTP/1.1 200 OK Date: Mon, 13 May 2002 07:30:58 GMT Server: Apache/1.3.12 (Unix) (Red Hat/Linux) PHP/3.0.15 mod_perl/1.21 Last-Modified: Wed, 06 Feb 2002 09:00:58 GMT ETag: "23432-2bf3-3c60f0ca" Accept-Ranges: bytes Content-Length: 11251

Services WEB

209

<-- Connection: close <-- Content-Type: text/html <-<-- <html> <-<-- <head> <-- <meta http-equiv="Content-Type" <-- content="text/html; charset=iso-8859-1"> <-- <meta name="GENERATOR" content="Microsoft FrontPage Express 2.0"> <-- <title>Bienvenue a l'ISTIA - Universite d'Angers</title> <-- </head> .... <-- face="Verdana"> - Dernire mise jour le <b>10 janvier 2002</b></font></p> <-- </body> <-- </html> <-[fin du thread de lecture des rponses du serveur] fin [fin du thread d'envoi des commandes au serveur]

Un client Web envoie ses commandes au serveur selon le schma suivant : commande1 commande2 ... commanden [ligne vide] Ce n'est qu'aprs avoir reu la ligne vide que le serveur Web rpond. Dans l'exemple nous n'avons utilis qu'une commande :
GET /index.html HTTP/1.0

qui demande au serveur l'URL /index.html et indique qu'il travaille avec le protocole HTTP version 1.0. La version la plus rcente de ce protocole est 1.1. L'exemple montre que le serveur a rpondu en renvoyant le contenu du fichier index.html puis qu'il a ferm la connexion puisqu'on voit le thread de lecture des rponses se terminer. Avant d'envoyer le contenu du fichier index.html, le serveur web a envoy une srie d'enttes termine par une ligne vide :
<-<-<-<-<-<-<-<-<-<-<-HTTP/1.1 200 OK Date: Mon, 13 May 2002 07:30:58 GMT Server: Apache/1.3.12 (Unix) (Red Hat/Linux) PHP/3.0.15 mod_perl/1.21 Last-Modified: Wed, 06 Feb 2002 09:00:58 GMT ETag: "23432-2bf3-3c60f0ca" Accept-Ranges: bytes Content-Length: 11251 Connection: close Content-Type: text/html <html>

La ligne <html> est la premire ligne du fichier /index.html. Ce qui prcde s'appelle des enttes HTTP (HyperText Transfer Protocol). Nous n'allons pas dtailler ici ces enttes mais on se rappellera que notre client gnrique y donne accs, ce qui peut tre utile pour les comprendre. La premire ligne par exemple :
<-- HTTP/1.1 200 OK

indique que le serveur Web contact comprend le protocole HTTP/1.1 et qu'il a bien trouv le fichier demand (200 OK), 200 tant un code de rponse HTTP. Les lignes
<-- Content-Length: 11251 <-- Connection: close <-- Content-Type: text/html

disent au client qu'il va recevoir 11251 octets reprsentant du texte HTML (HyperText Markup Language) et qu' la fin de l'envoi, la connexion sera ferme. On a donc l un client tcp trs pratique. En fait, ce client existe dj sur les machines o il s'appelle telnet mais il tait intressant de l'crire nous-mmes. Le programme du client tcp gnrique est le suivant :
' espaces de noms Imports System Imports System.Net.Sockets Imports System.IO Imports System.Threading Imports Microsoft.VisualBasic ' la classe Public Class clientTcpGnrique

Services WEB

210

' reoit en paramtre les caractristiques d'un service sous la forme ' serveur port ' se connecte au service ' cre un thread pour lire des commandes tapes au clavier ' celles-ci seront envoyes au serveur ' cre un thread pour lire les rponses du serveur ' celles-ci seront affiches l'cran ' le tout se termine avec la commande fin tape au clavier Public Shared Sub Main(ByVal args() As String) ' syntaxe Const syntaxe As String = "pg serveur port" ' nombre d'arguments If args.Length <> 2 Then erreur(syntaxe, 1) End If ' on note le nom du serveur Dim serveur As String = args(0) ' le port doit tre entier >0 Dim port As Integer = 0 Dim erreurPort As Boolean = False Dim E As Exception = Nothing Try port = Integer.Parse(args(1)) Catch ex As Exception E = ex erreurPort = True End Try erreurPort = erreurPort Or port <= 0 If erreurPort Then erreur(syntaxe + ControlChars.Lf + "Port incorrect (" + E.ToString + ")", 2) End If Dim client As TcpClient = Nothing ' il peut y avoir des problmes Try ' on se connecte au service client = New TcpClient(serveur, port) Catch ex As Exception ' erreur Console.Error.WriteLine(("Impossible de se connecter au service (" & serveur & "," & port & "), erreur : " & ex.Message)) ' fin Return End Try ' on cre les threads de lecture/criture Dim thReceive As New Thread(New ThreadStart(AddressOf New clientReceive(client).Run)) Dim thSend As New Thread(New ThreadStart(AddressOf New clientSend(client).Run)) ' on lance l'excution des deux threads thSend.Start() thReceive.Start() ' fin du thread principal Return End Sub ' affichage des erreurs Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer) ' affichage erreur System.Console.Error.WriteLine(msg) ' arrt avec erreur Environment.Exit(exitCode) End Sub End Class Public Class clientSend ' classe charge de lire des commandes tapes au clavier ' et de les envoyer un serveur via un client tcp pass au constructeur Private client As TcpClient ' le client tcp ' constructeur Public Sub New(ByVal client As TcpClient) ' on note le client tcp Me.client = client End Sub ' mthode Run du thread

Services WEB

211

Public Sub Run() ' donnes locales Dim OUT As StreamWriter = Nothing ' flux d'criture rseau Dim commande As String = Nothing ' commande lue au clavier ' gestion des erreurs Try ' cration du flux d'criture rseau OUT = New StreamWriter(client.GetStream()) OUT.AutoFlush = True ' boucle saisie-envoi des commandes Console.Out.WriteLine("Commandes : ") While True ' lecture commande tape au clavier commande = Console.In.ReadLine().Trim() ' fini ? If commande.ToLower() = "fin" Then Exit While End If ' envoi commande au serveur OUT.WriteLine(commande) End While Catch ex As Exception ' erreur Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message)) End Try ' fin - on ferme les flux Try OUT.Close() client.Close() Catch End Try ' on signale la fin du thread Console.Out.WriteLine("[fin du thread d'envoi des commandes au serveur]") End Sub End Class Public Class clientReceive ' classe charge de lire les lignes de texte destines un ' client tcp pass au constructeur Private client As TcpClient ' le client tcp ' constructeur Public Sub New(ByVal client As TcpClient) ' on note le client tcp Me.client = client End Sub 'constructeur ' mthode Run du thread Public Sub Run() ' donnes locales Dim [IN] As StreamReader = Nothing ' flux lecture rseau Dim rponse As String = Nothing ' rponse serveur ' gestion des erreurs Try ' cration du flux lecture rseau [IN] = New StreamReader(client.GetStream()) ' boucle lecture lignes de texte du flux IN While True ' lecture flux rseau rponse = [IN].ReadLine() ' flux ferm ? If rponse Is Nothing Then Exit While End If ' affichage Console.Out.WriteLine(("<-- " + rponse)) End While Catch ex As Exception ' erreur Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message)) End Try ' fin - on ferme les flux Try [IN].Close() client.Close() Catch End Try

Services WEB

212

' on signale la fin du thread Console.Out.WriteLine("[fin du thread de lecture des rponses du serveur]") End Sub End Class

8.4.4

Un serveur Tcp gnrique

Maintenant nous nous intressons un serveur qui affiche l'cran les commandes envoyes par ses clients leur envoie comme rponse les lignes de texte tapes au clavier par un utilisateur. C'est donc ce dernier qui fait office de serveur. Le programme est lanc par : srvgen portEcoute, o portEcoute est le port sur lequel les clients doivent se connecter. Le service au client sera assur par deux threads : un thread se consacrant exclusivement la lecture des lignes de texte envoyes par le client un thread se consacrant exclusivement la lecture des rponses tapes au clavier par l'utilisateur. Celui-ci signalera par la commande fin qu'il clt la connexion avec le client. Le serveur cre deux threads par client. S'il y a n clients, il y aura 2n threads actifs en mme temps. Le serveur lui ne s'arrte jamais sauf par un Ctrl-C tap au clavier par l'utilisateur. Voyons quelques exemples. Le serveur est lanc sur le port 100 et on utilise le client gnrique pour lui parler. La fentre du client est la suivante :
dos>cltgen localhost 100 Commandes : commande 1 du client 1 <-- rponse 1 au client 1 commande 2 du client 1 <-- rponse 2 au client 1 fin L'erreur suivante s'est produite : Impossible de lire les donnes de la connexion de transport. [fin du thread de lecture des rponses du serveur] [fin du thread d'envoi des commandes au serveur]

Les lignes commenant par <-- sont celles envoyes du serveur au client, les autres celles du client vers le serveur. La fentre du serveur est la suivante :
dos>srvgen 100 Serveur gnrique lanc sur le port 100 Thread de lecture des rponses du serveur au client 1 lanc 1 : Thread de lecture des demandes du client 1 lanc <-- commande 1 du client 1 rponse 1 au client 1 1 : <-- commande 2 du client 1 rponse 2 au client 1 1 : [fin du Thread de lecture des demandes du client 1] fin [fin du Thread de lecture des rponses du serveur au client 1]

Les lignes commenant par <-- sont celles envoyes du client au serveur. Les lignes N : sont les lignes envoyes du serveur au client n N. Le serveur ci-dessus est encore actif alors que le client 1 est termin. On lance un second client pour le mme serveur :
dos>cltgen localhost 100 Commandes : commande 3 du client 2 <-- rponse 3 au client 2 fin L'erreur suivante s'est produite : Impossible de lire les donnes de la connexion de transport. [fin du thread de lecture des rponses du serveur] [fin du thread d'envoi des commandes au serveur]

La fentre du serveur est alors celle-ci :


dos>srvgen 100 Serveur gnrique lanc sur le port 100 Thread de lecture des rponses du serveur au client 1 lanc 1 : Thread de lecture des demandes du client 1 lanc <-- commande 1 du client 1 rponse 1 au client 1 1 : <-- commande 2 du client 1 rponse 2 au client 1 1 : [fin du Thread de lecture des demandes du client 1] fin [fin du Thread de lecture des rponses du serveur au client 1]

Services WEB

213

Thread de lecture des rponses du serveur au client 2 lanc 2 : Thread de lecture des demandes du client 2 lanc <-- commande 3 du client 2 rponse 3 au client 2 2 : [fin du Thread de lecture des demandes du client 2] fin [fin du Thread de lecture des rponses du serveur au client 2] ^C

Simulons maintenant un serveur web en lanant notre serveur gnrique sur le port 88 :
dos>srvgen 88 Serveur gnrique lanc sur le port 88

Prenons maintenant un navigateur et demandons l'URL http://localhost:88/exemple.html. Le navigateur va alors se connecter sur le port 88 de la machine localhost puis demander la page /exemple.html :

Regardons maintenant la fentre de notre serveur :


dos>srvgen 88 Serveur gnrique lanc sur le port 88 Thread de lecture des rponses du serveur au client 2 lanc 2 : Thread de lecture des demandes du client 2 lanc <-- GET /exemple.html HTTP/1.1 <-- Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/msword, */* <-- Accept-Language: fr <-- Accept-Encoding: gzip, deflate <-- User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; .NET CLR 1.0.3705; .NET CLR 1.0.2 914) <-- Host: localhost:88 <-- Connection: Keep-Alive <--

On dcouvre ainsi les enttes HTTP envoys par le navigateur. Cela nous permet de dcouvrir peu peu le protocole HTTP. Lors d'un prcdent exemple, nous avions cr un client Web qui n'envoyait que la seule commande GET. Cela avait t suffisant. On voit ici que le navigateur envoie d'autres informations au serveur. Elles ont pour but d'indiquer au serveur quel type de client il a en face de lui. On voit aussi que les enttes HTTP se terminent par une ligne vide. Elaborons une rponse notre client. L'utilisateur au clavier est ici le vritable serveur et il peut laborer une rponse la main. Rappelons-nous la rponse faite par un serveur Web dans un prcdent exemple :
<-<-<-<-<-<-<-<-<-<-<-HTTP/1.1 200 OK Date: Mon, 13 May 2002 07:30:58 GMT Server: Apache/1.3.12 (Unix) (Red Hat/Linux) PHP/3.0.15 mod_perl/1.21 Last-Modified: Wed, 06 Feb 2002 09:00:58 GMT ETag: "23432-2bf3-3c60f0ca" Accept-Ranges: bytes Content-Length: 11251 Connection: close Content-Type: text/html <html>

Essayons de donner une rponse analogue :


... <-<-<-2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : Host: localhost:88 Connection: Keep-Alive HTTP/1.1 200 OK Server: serveur tcp generique Connection: close Content-Type: text/html <html> <head><title>Serveur generique</title></head> <body> <center>

Services WEB

214

2 : <h2>Reponse du serveur generique</h2> 2 : </center> 2 : </body> 2 : </html> 2 : fin L'erreur suivante s'est produite : Impossible de lire les donnes de la connexion de transport. [fin du Thread de lecture des demandes du client 2] [fin du Thread de lecture des rponses du serveur au client 2]

Les lignes commenant par 2 : sont envoyes du serveur au client n 2. La commande fin clt la connexion du serveur au client. Nous nous sommes limits dans notre rponse aux enttes HTTP suivants :
HTTP/1.1 200 OK 2 : Server: serveur tcp generique 2 : Connection: close 2 : Content-Type: text/html 2 :

Nous ne donnons pas la taille du fichier que nous allons envoyer (Content-Length) mais nous contentons de dire que nous allons fermer la connexion (Connection: close) aprs envoi de celui-ci. Cela est suffisant pour le navigateur. En voyant la connexion ferme, il saura que la rponse du serveur est termine et affichera la page HTML qui lui a t envoye. Cette dernire est la suivante :
2 2 2 2 2 2 2 2 : <html> : <head><title>Serveur generique</title></head> : <body> : <center> : <h2>Reponse du serveur generique</h2> : </center> : </body> : </html>

L'utilisateur ferme ensuite la connexion au client en tapant la commande fin. Le navigateur sait alors que la rponse du serveur est termine et peut alors l'afficher :

Si ci-dessus, on fait Affichage/Source pour voir ce qu'a reu le navigateur, on obtient :

c'est dire exactement ce qu'on a envoy depuis le serveur gnrique. Le code du serveur TCP gnrique est le suivant :
' espaces de noms Imports System Imports System.Net Imports System.Net.Sockets Imports System.IO Imports System.Threading Imports Microsoft.VisualBasic Public Class serveurTcpGnrique ' programme principal Public Shared Sub Main(ByVal args() As String)

Services WEB

215

' ' ' ' ' '

reoit le port d'coute des demandes des clients cre un thread pour lire les demandes du client celles-ci seront affiches l'cran cre un thread pour lire des commandes tapes au clavier celles-ci seront envoyes comme rponse au client le tout se termine avec la commande fin tape au clavier

Const syntaxe As String = "Syntaxe : pg port" ' y-a-t-il un argument If args.Length <> 1 Then erreur(syntaxe, 1) End If ' cet argument doit tre entier >0 Dim port As Integer = 0 Dim erreurPort As Boolean = False Dim E As Exception = Nothing Try port = Integer.Parse(args(0)) Catch ex As Exception E = ex erreurPort = True End Try erreurPort = erreurPort Or port <= 0 If erreurPort Then erreur(syntaxe + ControlChars.Lf + "Port incorrect (" + E.ToString + ")", 2) End If ' on cre le servive d'coute Dim ecoute As TcpListener = Nothing Dim nbClients As Integer = 0 ' nbre de clients traits Try ' on cre le service ecoute = New TcpListener(IPAddress.Parse("127.0.0.1"), port) ' on le lance ecoute.Start() ' suivi Console.Out.WriteLine(("Serveur gnrique lanc sur le port " & port)) ' boucle de service aux clients Dim client As TcpClient = Nothing While True ' boucle infinie - sera arrte par Ctrl-C ' attente d'un client client = ecoute.AcceptTcpClient() ' le service est assur des threads spars nbClients += 1 ' thread de lecture des demandes clients Dim thReceive As New Thread(New ThreadStart(AddressOf New serveurReceive(client, nbClients).Run)) ' thread de lecture des rponses tapes au clavier par l'utilisateur Dim thSend As New Thread(New ThreadStart(AddressOf New serveurSend(client, nbClients).Run)) ' on lance l'excution des deux threads thSend.Start() thReceive.Start() End While ' on retourne l'coute des demandes Catch ex As Exception ' on signale l'erreur erreur("L'erreur suivante s'est produite : " + ex.Message, 3) End Try End Sub ' affichage des erreurs Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer) ' affichage erreur System.Console.Error.WriteLine(msg) ' arrt avec erreur Environment.Exit(exitCode) End Sub End Class Public Class serveurSend ' classe charge de lire des rponses tapes au clavier ' et de les envoyer un client via un client tcp pass au constructeur Private client As TcpClient ' le client tcp Private numClient As Integer ' n de client ' constructeur Public Sub New(ByVal client As TcpClient, ByVal numClient As Integer)

Services WEB

216

' on note le client tcp Me.client = client ' et son n Me.numClient = numClient End Sub ' mthode Run du thread Public Sub Run() ' donnes locales Dim OUT As StreamWriter = Nothing ' flux d'criture rseau Dim rponse As String = Nothing ' rponse lue au clavier ' suivi Console.Out.WriteLine(("Thread de lecture des rponses du serveur au client " & numClient & " lanc")) ' gestion des erreurs Try ' cration du flux d'criture rseau OUT = New StreamWriter(client.GetStream()) OUT.AutoFlush = True ' boucle saisie-envoi des commandes While True ' identification client Console.Out.Write((numClient & " : ")) ' lecture rponse tape au clavier rponse = Console.In.ReadLine().Trim() ' fini ? If rponse.ToLower() = "fin" Then Exit While End If ' envoi rponse au serveur OUT.WriteLine(rponse) End While ' rponse suivante Catch ex As Exception ' erreur Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message)) End Try ' fin - on ferme les flux Try OUT.Close() client.Close() Catch End Try ' on signale la fin du thread Console.Out.WriteLine(("[fin du Thread de lecture des rponses du serveur au client " & numClient & "]")) End Sub End Class Public Class serveurReceive ' classe charge de lire les lignes de texte envoyes au serveur ' via un client tcp pass au constructeur Private client As TcpClient ' le client tcp Private numClient As Integer ' n de client ' constructeur Public Sub New(ByVal client As TcpClient, ByVal numClient As Integer) ' on note le client tcp Me.client = client ' et son n Me.numClient = numClient End Sub ' mthode Run du thread Public Sub Run() ' donnes locales Dim [IN] As StreamReader = Nothing ' flux lecture rseau Dim rponse As String = Nothing ' rponse serveur ' suivi Console.Out.WriteLine(("Thread de lecture des demandes du client " & numClient & " lanc")) ' gestion des erreurs Try ' cration du flux lecture rseau [IN] = New StreamReader(client.GetStream()) ' boucle lecture lignes de texte du flux IN While True ' lecture flux rseau rponse = [IN].ReadLine() ' flux ferm ? If rponse Is Nothing Then

Services WEB

217

Exit While End If ' affichage Console.Out.WriteLine(("<-- " + rponse)) End While Catch ex As Exception ' erreur Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message)) End Try ' fin - on ferme les flux Try [IN].Close() client.Close() Catch End Try ' on signale la fin du thread Console.Out.WriteLine(("[fin du Thread de lecture des demandes du client " & numClient & "]")) End Sub End Class

8.4.5

Un client Web

Nous avons vu dans l'exemple prcdent, certains des enttes HTTP qu'envoyait un navigateur :
<-- GET /exemple.html HTTP/1.1 <-- Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/msword, */* <-- Accept-Language: fr <-- Accept-Encoding: gzip, deflate <-- User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; .NET CLR 1.0.3705; .NET CLR 1.0.2 914) <-- Host: localhost:88 <-- Connection: Keep-Alive <--

Nous allons crire un client Web auquel on passerait en paramtre une URL et qui afficherait l'cran le texte envoy par le serveur. Nous supposerons que celui-ci supporte le protocole HTTP 1.1. Des enttes prcdents, nous n'utiliserons que les suivants :
<-- GET /exemple.html HTTP/1.1 <-- Host: localhost:88 <-- Connection: close

le premier entte indique quelle page nous dsirons le second quel serveur nous interrogeons le troisime que nous souhaitons que le serveur ferme la connexion aprs nous avoir rpondu.

Si ci-dessus, nous remplaons GET par HEAD, le serveur ne nous enverra que les enttes HTTP et pas la page HTML. Notre client web sera appel de la faon suivante : clientweb URL cmd, o URL est l'URL dsire et cmd l'un des deux mots cls GET ou HEAD pour indiquer si on souhaite seulement les enttes (HEAD) ou galement le contenu de la page (GET). Regardons un premier exemple. Nous lanons le serveur IIS puis le client web sur la mme machine :
dos>clientweb http://localhost HEAD HTTP/1.1 302 Object moved Server: Microsoft-IIS/5.0 Date: Mon, 13 May 2002 09:23:37 GMT Connection: close Location: /IISSamples/Default/welcome.htm Content-Length: 189 Content-Type: text/html Set-Cookie: ASPSESSIONIDGQQQGUUY=HMFNCCMDECBJJBPPBHAOAJNP; path=/ Cache-control: private

La rponse
HTTP/1.1 302 Object moved

signifie que la page demande a chang de place (donc d'URL). La nouvelle URL est donne par l'entte Location:
Location: /IISSamples/Default/welcome.htm

Si nous utilisons GET au lieu de HEAD dans l'appel au client Web : Services WEB 218

dos>clientweb http://localhost GET HTTP/1.1 302 Object moved Server: Microsoft-IIS/5.0 Date: Mon, 13 May 2002 09:33:36 GMT Connection: close Location: /IISSamples/Default/welcome.htm Content-Length: 189 Content-Type: text/html Set-Cookie: ASPSESSIONIDGQQQGUUY=IMFNCCMDAKPNNGMGMFIHENFE; path=/ Cache-control: private <head><title>L'objet a chang d'emplacement</title></head> <body><h1>L'objet a chang d'emplacement</h1>Cet objet peut tre trouv <a HREF="/IISSamples/Default/we lcome.htm">ici</a>.</body>

Nous obtenons le mme rsultat qu'avec HEAD avec de plus le corps de la page HTML. Le programme est le suivant :
' espaces de noms Imports System Imports System.Net.Sockets Imports System.IO Public Class clientWeb1 ' demande une URL ' affiche le contenu de celle-ci l'cran Public Shared Sub Main(ByVal args() As String) ' syntaxe Const syntaxe As String = "pg URI GET/HEAD" ' nombre d'arguments If args.Length <> 2 Then erreur(syntaxe, 1) End If ' on note l'URI demande Dim URIstring As String = args(0) Dim commande As String = args(1).ToUpper() ' vrification validit de l'URI Dim uri As Uri = Nothing Try uri = New Uri(URIstring) Catch ex As Exception ' URI incorrecte erreur("L'erreur suivante s'est produite : " + ex.Message, 2) End Try ' vrification de la commande If commande <> "GET" And commande <> "HEAD" Then ' commande incorrecte erreur("Le second paramtre doit tre GET ou HEAD", 3) End If ' on peut travailler Dim client As TcpClient = Nothing ' le client Dim [IN] As StreamReader = Nothing ' le flux de lecture du client Dim OUT As StreamWriter = Nothing ' le flux d'criture du client Dim rponse As String = Nothing ' rponse du serveur Try ' on se connecte au serveur client = New TcpClient(uri.Host, uri.Port) ' on cre les flux d'entre-sortie du client TCP [IN] = New StreamReader(client.GetStream()) OUT = New StreamWriter(client.GetStream()) OUT.AutoFlush = True ' on demande l'URL - envoi des enttes HTTP OUT.WriteLine((commande + " " + uri.PathAndQuery + " HTTP/1.1")) OUT.WriteLine(("Host: " + uri.Host + ":" & uri.Port)) OUT.WriteLine("Connection: close") OUT.WriteLine() ' on lit la rponse rponse = [IN].ReadLine() While Not (rponse Is Nothing) ' on traite la rponse Console.Out.WriteLine(rponse) ' on lit la rponse rponse = [IN].ReadLine()

Services WEB

219

End While ' c'est fini client.Close() Catch e As Exception ' on gre l'exception erreur(e.Message, 4) End Try End Sub ' affichage des erreurs Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer) ' affichage erreur System.Console.Error.WriteLine(msg) ' arrt avec erreur Environment.Exit(exitCode) End Sub End Class

La seule nouveaut dans ce programme est l'utilisation de la classe Uri. Le programme reoit une URL (Uniform Resource Locator) ou URI (Uniform Resource Identifier) de la forme http://serveur:port/cheminPageHTML?param1=val1;param2=val2;.... La classe Uri nous permet de dcomposer la chane de l'URL en ses diffrents lments. Un objet Uri est construit partir de la chane URIstring reue en paramtre :
' vrification validit de l'URI Dim uri As Uri = Nothing Try uri = New Uri(URIstring) Catch ex As Exception ' URI incorrecte erreur("L'erreur suivante s'est produite : " + ex.Message, 2) End Try

Si la chane URI reue en paramtre n'est pas une URI valide (absence du protocole, du serveur, ...), une exception est lance. Cela nous permet de vrifier la validit du paramtre reu. Une fois l'objet Uri construit, on a accs aux diffrents lments de cette Uri. Ainsi si l'objet uri du code prcdent a t construit partir de la chane http://serveur:port/cheminPageHTML?param1=val1;param2=val2;... on aura : uri.Host=serveur, uri.Port=port, uri.Path=cheminPageHTML, uri.Query=param1=val1;param2=val2;..., uri.pathAndQuery= cheminPageHTML?param1=val1;param2=val2;..., uri.Scheme=http.

8.4.6
1. 2. 3.

Client Web grant les redirections


il lit la premire ligne des enttes HTTP envoys par le serveur pour vrifier si on y trouve la chane 302 Object moved qui signale une redirection il lit les enttes suivants. S'il y a redirection, il recherche la ligne Location: url qui donne la nouvelle URL de la page demande et note cette URL. il affiche le reste de la rponse du serveur. S'il y a redirection, les tapes 1 3 sont rptes avec la nouvelle URL. Le programme n'accepte pas plus d'une redirection. Cette limite fait l'objet d'une constante qui peut tre modifie.

Le client Web prcdent ne gre pas une ventuelle redirection de l'URL qu'il a demande. Le client suivant la gre.

Voici un exemple :
dos>clientweb2 http://localhost GET HTTP/1.1 302 Object moved Server: Microsoft-IIS/5.0 Date: Mon, 13 May 2002 11:38:55 GMT Connection: close Location: /IISSamples/Default/welcome.htm Content-Length: 189 Content-Type: text/html Set-Cookie: ASPSESSIONIDGQQQGUUY=PDGNCCMDNCAOFDMPHCJNPBAI; path=/ Cache-control: private <head><title>L'objet a chang d'emplacement</title></head> <body><h1>L'objet a chang d'emplacement</h1>Cet objet peut tre trouv <a HREF="/IISSamples/Default/we lcome.htm">ici</a>.</body> <--Redirection vers l'URL http://localhost:80/IISSamples/Default/welcome.htm--> HTTP/1.1 200 OK Server: Microsoft-IIS/5.0 Connection: close Date: Mon, 13 May 2002 11:38:55 GMT

Services WEB

220

Content-Type: text/html Accept-Ranges: bytes Last-Modified: Mon, 16 Feb 1998 21:16:22 GMT ETag: "0174e21203bbd1:978" Content-Length: 4781 <html> <head> <title>Bienvenue dans le Serveur Web personnel</title> </head> .... </body> </html>

Le programme est le suivant :


' espaces de noms Imports System Imports System.Net.Sockets Imports System.IO Imports System.Text.RegularExpressions Imports Microsoft.VisualBasic ' classe client web Public Class clientWeb ' demande une URL et affiche le contenu de celle-ci l'cran Public Shared Sub Main(ByVal args() As String) ' syntaxe Const syntaxe As String = "pg URI GET/HEAD" ' nombre d'arguments If args.Length <> 2 Then erreur(syntaxe, 1) End If ' on note l'URI demande Dim URIstring As String = args(0) Dim commande As String = args(1).ToUpper() ' vrification validit de l'URI Dim uri As Uri = Nothing Try uri = New Uri(URIstring) Catch ex As Exception ' URI incorrecte erreur("L'erreur suivante s'est produite : " + ex.Message, 2) End Try 'catch ' vrification de la commande If commande <> "GET" And commande <> "HEAD" Then ' commande incorrecte erreur("Le second paramtre doit tre GET ou HEAD", 3) End If ' on peut travailler Dim client As TcpClient = Nothing ' le client Dim [IN] As StreamReader = Nothing ' le flux de lecture du client Dim OUT As StreamWriter = Nothing ' le flux d'criture du client Dim rponse As String = Nothing ' rponse du serveur Const nbRedirsMax As Integer = 1 ' pas plus d'une redirection accepte Dim nbRedirs As Integer = 0 ' nombre de redirections en cours Dim premireLigne As String ' 1re ligne de la rponse Dim redir As Boolean = False ' indique s'il y a redirection ou non Dim locationString As String = "" ' la chane URI d'une ventuelle redirection ' expression rgulire pour trouver une URL de redirection Dim location As New Regex("^Location: (.+?)$") ' ' gestion des erreurs Try ' on peut avoir plusieurs URL demander s'il y a des redirections While nbRedirs <= nbRedirsMax ' on se connecte au serveur client = New TcpClient(uri.Host, uri.Port) ' on cre les flux d'entre-sortie du client TCP [IN] = New StreamReader(client.GetStream()) OUT = New StreamWriter(client.GetStream()) OUT.AutoFlush = True ' on envoie les enttes HTTP pour demander l'URL OUT.WriteLine((commande + " " + uri.PathAndQuery + " HTTP/1.1"))

Services WEB

221

OUT.WriteLine(("Host: " + uri.Host + ":" & uri.Port)) OUT.WriteLine("Connection: close") OUT.WriteLine() ' on lit la premire ligne de la rponse premireLigne = [IN].ReadLine() ' cho cran Console.Out.WriteLine(premireLigne) ' redirection ? If Regex.IsMatch(premireLigne, "302 Object moved$") Then ' il y a une redirection redir = True nbRedirs += 1 End If ' enttes HTTP suivants jusqu' trouver la ligne vide signalant la fin des enttes Dim locationFound As Boolean = False rponse = [IN].ReadLine() While rponse <> "" ' on affiche la rponse Console.Out.WriteLine(rponse) ' s'il y a redirection, on recherche l'entte Location If redir And Not locationFound Then ' on compare la ligne l'expression relationnelle location Dim rsultat As Match = location.Match(rponse) If rsultat.Success Then ' si on a trouv on note l'URL de redirection locationString = rsultat.Groups(1).Value ' on note qu'on a trouv locationFound = True End If End If ' ligne suivante rponse = [IN].ReadLine() End While ' lignes suivantes de la rponse Console.Out.WriteLine(rponse) rponse = [IN].ReadLine() While Not (rponse Is Nothing) ' on affiche la rponse Console.Out.WriteLine(rponse) ' ligne suivante rponse = [IN].ReadLine() End While ' on ferme la connexion client.Close() ' a-t-on fini ? If Not locationFound Or nbRedirs > nbRedirsMax Then Exit While End If ' il y a une redirection oprer - on construit la nouvelle Uri URIstring = uri.Scheme + "://" & uri.Host & ":" & uri.Port & locationString uri = New Uri(URIstring) ' suivi Console.Out.WriteLine((ControlChars.Lf + "<--Redirection vers l'URL " + URIstring + "-->" + ControlChars.Lf)) End While Catch e As Exception ' on gre l'exception erreur(e.Message, 4) End Try End Sub ' affichage des erreurs Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer) ' affichage erreur System.Console.Error.WriteLine(msg) ' arrt avec erreur Environment.Exit(exitCode) End Sub End Class

Services WEB

222

8.4.7

Serveur de calcul d'impts

Nous reprenons l'exercice IMPOTS dj trait sous diverses formes. Rappelons la dernire mouture. Une classe impt a t cre. Ses attributs sont trois tableaux de nombres :
Public Class impt ' les donnes ncessaires au calcul de l'impt ' proviennent d'une source extrieure Private limites(), coeffR(), coeffN() as double

La classe a deux constructeurs : un constructeur qui on passe les trois tableaux de donnes ncessaires au calcul de l'impt
// constructeur 1 Public Sub New(ByVal LIMITES() As Decimal, ByVal COEFFR() As Decimal, ByVal COEFFN() As Decimal) ' initialise les trois tableaux limites, coeffR, coeffN partir ' des paramtres passs au constructeur

un constructeur qui on passe le nom DSN d'une base de donnes ODBC

' constructeur 2 Public Sub New(ByVal DSNimpots As String, ByVal Timpots As String, ByVal colLimites As String, ByVal colCoeffR As String, ByVal colCoeffN As String) ' initialise les trois tableaux limites, coeffR, coeffN partir ' du contenu de la table Timpots de la base ODBC DSNimpots ' colLimites, colCoeffR, colCoeffN sont les trois colonnes de cette table ' peut lancer une exception

Un programme de test avait t crit :


dos>vbc /r:impots.dll testimpots.vb dos>test mysql-impots timpots limites coeffr coeffn Paramtres du calcul de l'impt au format mari nbEnfants impt=22506 F Paramtres du calcul de l'impt au format mari nbEnfants impt=33388 F Paramtres du calcul de l'impt au format mari nbEnfants impt=16400 F Paramtres du calcul de l'impt au format mari nbEnfants impt=50082 F Paramtres du calcul de l'impt au format mari nbEnfants impt=22506 F

salaire ou rien pour arrter :o 2 200000 salaire ou rien pour arrter :n 2 200000 salaire ou rien pour arrter :o 3 200000 salaire ou rien pour arrter :n 3 300000 salaire ou rien pour arrter :n 3 200000

Ici le programme de test et l'objet impt taient sur la mme machine. Nous nous proposons de mettre le programme de test et l'objet impt sur des machines diffrentes. Nous aurons une application client-serveur o l'objet impt distant sera le serveur. La nouvelle classe s'appelle ServeurImpots et est drive de la classe impt :
Public Class ServeurImpots Inherits impt ' attributs Private portEcoute As Integer ' le port d'coute des demandes clients Private actif As Boolean ' tat du serveur ' constructeur Public Sub New(ByVal portEcoute As Integer, ByVal DSNimpots As String, ByVal Timpots As String, ByVal colLimites As String, ByVal colCoeffR As String, ByVal colCoeffN As String) MyBase.New(DSNimpots, Timpots, colLimites, colCoeffR, colCoeffN) ' on note le port d'coute Me.portEcoute = portEcoute ' pour l'instant inactif actif = False ' cre et lance un thread de lecture des commandes tapes au clavier ' le serveur sera gr partir de ces commandes Dim threadLecture As Thread = New Thread(New ThreadStart(AddressOf admin)) threadLecture.Start() End Sub

Le seul paramtre nouveau dans le constructeur est le port d'coute des demandes des clients. Les autres paramtres sont passs directement la classe de base impt. Le serveur d'impts est contrl par des commandes tapes au clavier. Aussi cre-t-on un thread pour lire ces commandes. Il y en aura deux possibles : start pour lancer le service, stop pour l'arrter dfinitivement. La mthode admin qui gre ces commandes est la suivante :
Public Sub admin()

Services WEB

223

' lit les commandes d'administration du serveur tapes au clavier ' dans une boucle sans fin Dim commande As String = Nothing While True ' invite Console.Out.Write("Serveur d'impts>") ' lecture commande commande = Console.In.ReadLine().Trim().ToLower() ' excution commande If commande = "start" Then ' actif ? If actif Then 'erreur Console.Out.WriteLine("Le serveur est dj actif") Else ' on lance le service d'coute Dim threadEcoute As Thread = New Thread(New ThreadStart(AddressOf ecoute)) threadEcoute.Start() End If Else If commande = "stop" Then ' fin de tous les threads d'excution Environment.Exit(0) Else ' erreur Console.Out.WriteLine("Commande incorrecte. Utilisez (start,stop)") End If End If End While End Sub

Si la commande tape au clavier est start, un thread d'coute des demandes clients est lanc. Si la commande tape est stop, tous les threads sont arrts. Le thread d'coute excute la mthode ecoute :
Public Sub ecoute() ' thread d'coute des demandes des clients ' on cre le service d'coute Dim ecoute As TcpListener = Nothing Try ' on cre le service ecoute = New TcpListener(IPAddress.Parse("127.0.0.1"), portEcoute) ' on le lance ecoute.Start() ' suivi Console.Out.WriteLine(("Serveur d'cho lanc sur le port " & portEcoute)) ' boucle de service Dim liaisonClient As TcpClient = Nothing While True ' boucle infinie ' attente d'un client liaisonClient = ecoute.AcceptTcpClient() ' le service est assur par une autre tche Dim threadClient As Thread = New Thread(New ThreadStart(AddressOf New traiteClientImpots(liaisonClient, Me).Run)) threadClient.Start() End While ' on retourne l'coute des demandes Catch ex As Exception ' on signale l'erreur erreur("L'erreur suivante s'est produite : " + ex.Message, 3) End Try End Sub ' affichage des erreurs Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer) ' affichage erreur System.Console.Error.WriteLine(msg) ' arrt avec erreur Environment.Exit(exitCode) End Sub

On retrouve un serveur tcp classique coutant sur le port portEcoute. Les demandes des clients sont traites par la mthode Run d'un objet auquel on passe deux paramtres : 1. l'objet TcpClient qui va permettre d'atteindre le client 2. l'objet impt this qui va donner accs la mthode this.calculer de calcul de l'impt.
' ------------------------------------------------------' assure le service un client du serveur d'impts Public Class traiteClientImpots

Services WEB

224

Private Private Private Private

liaisonClient As TcpClient [IN] As StreamReader ' flux OUT As StreamWriter ' flux objImpt As impt ' objet

' liaison avec le client d'entre de sortie Impt

' constructeur Public Sub New(ByVal liaisonClient As TcpClient, ByVal objImpt As impt) Me.liaisonClient = liaisonClient Me.objImpt = objImpt End Sub

La mthode Run traite les demandes des clients. Celles-ci peuvent avoir deux formes : 1. calcul mari(o/n) nbEnfants salaireAnnuel 2. fincalculs La forme 1 permet le calcul d'un impt, la forme 2 clt la liaison client-serveur.
' mthode Run Public Sub Run() ' rend le service au client Try ' flux d'entre [IN] = New StreamReader(liaisonClient.GetStream()) ' flux de sortie OUT = New StreamWriter(liaisonClient.GetStream()) OUT.AutoFlush = True ' envoi d'un msg de bienvenue au client OUT.WriteLine("Bienvenue sur le serveur d'impts") ' boucle lecture demande/criture rponse Dim demande As String = Nothing Dim champs As String() = Nothing ' les lments de la demande Dim commande As String = Nothing ' la commande du client : calcul ou fincalculs demande = [IN].ReadLine() While Not (demande Is Nothing) ' on dcompose la demande en champs champs = Regex.Split(demande.Trim().ToLower(), "\s+") ' deux demandes acceptes : calcul et fincalculs commande = champs(0) Dim erreur As Boolean = False If commande <> "calcul" And commande <> "fincalculs" Then ' erreur client OUT.WriteLine("Commande incorrecte. Utilisez (calcul,fincalculs).") End If If commande = "calcul" Then calculerImpt(champs) End If If commande = "fincalculs" Then ' msg d'au-revoir au client OUT.WriteLine("Au revoir...") ' libration des ressources Try OUT.Close() [IN].Close() liaisonClient.Close() Catch End Try ' fin Return End If ' nouvelle demande demande = [IN].ReadLine() End While Catch e As Exception erreur("L'erreur suivante s'est produite (" + e.ToString + ")", 2) End Try End Sub

Le calcul de l'impt est effectu par la mthode calculerImpt qui reoit en paramtre le tableau des champs de la demande faite par le client. La validit de la demande est vrifie et ventuellement l'impt calcul et renvoy au client.
' calcul d'impts Public Sub calculerImpt(ByVal champs() As String) ' traite la demande : calcul mari nbEnfants salaireAnnuel ' dcompose en champs dans le tableau champs Dim mari As String = Nothing Dim nbEnfants As Integer = 0 Dim salaireAnnuel As Integer = 0

Services WEB

225

' validit des arguments Try ' il faut au moins 4 champs If champs.Length <> 4 Then Throw New Exception End If ' mari mari = champs(1) If mari <> "o" And mari <> "n" Then Throw New Exception End If ' enfants nbEnfants = Integer.Parse(champs(2)) ' salaire salaireAnnuel = Integer.Parse(champs(3)) Catch OUT.WriteLine(" syntaxe : calcul mari(O/N) nbEnfants salaireAnnuel") ' fini Exit Sub End Try ' on peut calculer l'impt Dim impot As Long = objImpt.calculer(mari = "o", nbEnfants, salaireAnnuel) ' on envoie la rponse au client OUT.WriteLine(impot.ToString) End Sub ' affichage des erreurs Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer) ' affichage erreur System.Console.Error.WriteLine(msg) ' arrt avec erreur Environment.Exit(exitCode) End Sub

Cette classe est compile par


dos>vbc /r:impots.dll /r:system.dll /t:library srvimpots.vb

o impots.dll contient le code de la classe impt. Un programme de test pourrait tre le suivant :
' espaces de noms Imports System Imports System.IO Imports Microsoft.VisualBasic Public Class testServeurImpots Public Shared syntaxe As String = "Syntaxe : pg port dsnImpots Timpots colLimites colCoeffR colCoeffN" ' programme principal Public Shared Sub Main(ByVal args() As String) ' il faut6 arguments If args.Length <> 6 Then erreur(syntaxe, 1) End If ' le port doit tre entier >0 Dim port As Integer = 0 Dim erreurPort As Boolean = False Dim E As Exception = Nothing Try port = Integer.Parse(args(0)) Catch ex As Exception E = ex erreurPort = True End Try erreurPort = erreurPort Or port <= 0 If erreurPort Then erreur(syntaxe + ControlChars.Lf + "Port incorrect (" + E.ToString + ")", 2) End If ' on cre le serveur d'impts Try Dim srvimots As ServeurImpots = New ServeurImpots(port, args(1), args(2), args(3), args(4), args(5)) Catch ex As Exception 'erreur Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message)) End Try End Sub ' affichage des erreurs

Services WEB

226

Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer) ' affichage erreur System.Console.Error.WriteLine(msg) ' arrt avec erreur Environment.Exit(exitCode) End Sub End Class

On passe au programme de test les donnes ncessaires la construction d'un objet ServeurImpots et partir de l il cre cet objet. Ce programme de test est compil par :
dos>vbc /r:srvimpots.dll /r:impots.dll testimpots.vb

Voici un premier test :


dos>testimpots 124 odbc-mysql-dbimpots impots limites coeffr coeffn Serveur d'impts>Serveur d'impts>start Serveur d'impts>Serveur d'cho lanc sur le port 124 stop

La ligne
dos>testimpots 124 odbc-mysql-dbimpots impots limites coeffr coeffn

cre un objet ServeurImpots qui n'coute pas encore les demandes des clients. C'est la commande start tape au clavier qui lance cette coute. La commande stop arrte le serveur. Utilisons maintenant un client. Nous utiliserons le client gnrique cr prcdemment. Le serveur est lanc :
dos>testimpots 124 odbc-mysql-dbimpots impots limites coeffr coeffn Serveur d'impts>Serveur d'impts>start Serveur d'impts>Serveur d'cho lanc sur le port 124

Le client gnrique est lanc dans une autre fentre Dos :


dos> clttcpgenerique localhost 124Commandes : <-- Bienvenue sur le serveur d'impts

On voit que le client a bien rcupr le message de bienvenue du serveur. On envoie d'autres commandes :
x <-- Commande incorrecte. Utilisez (calcul,fincalculs). calcul <-- syntaxe : calcul mari(O/N) nbEnfants salaireAnnuel calcul o 2 200000 <-- 22506 calcul n 2 200000 <-- 33388 fincalculs <-- Au revoir... [fin du thread de lecture des rponses du serveur] fin [fin du thread d'envoi des commandes au serveur]

On retourne dans la fentre du serveur pour l'arrter :


dos>testimpots 124 odbc-mysql-dbimpots impots limites coeffr coeffn Serveur d'impts>Serveur d'impts>start Serveur d'impts>Serveur d'cho lanc sur le port 124 stop

Services WEB

227

9.
9.1 Introduction

Services Web

Nous avons prsent dans le chapitre prcdent plusieurs applications client-serveur tcp-ip. Dans la mesure o les clients et le serveur changent des lignes de texte, ils peuvent tre crits en n'importe quel langage. Le client doit simplement connatre le protocole de dialogue attendu par le serveur. Les services Web sont des applications serveur tcp-ip prsentant les caractristiques suivantes : Elles sont hberges par des serveurs web et le protocole d'changes client-serveur est donc HTTP (HyperText Transport Protocol), un protocole au-dessus de TCP-IP. Le service Web a un protocole de dialogue standard quelque soit le service assur. Un service Web offre divers services S1, S2, .., Sn. Chacun d'eux attend des paramtres fournis par le client et rend celui-ci un rsultat. Pour chaque service, le client a besoin de savoir : o le nom exact du service Si o la liste des paramtres qu'il faut lui fournir et leur type o le type de rsultat retourn par le service Une fois, ces lments connus, le dialogue client-serveur suit le mme format quelque soit le service web interrog. L'criture des clients est ainsi normalise. Pour des raisons de scurit vis vis des attaques venant de l'internet, beaucoup d'organisations ont des rseaux privs et n'ouvrent sur Internet que certains ports de leurs serveurs : essentiellement le port 80 du service web. Tous les autres ports sont verrouills. Aussi les applications client-serveur telles que prsentes dans le chapitre prcdent sont-elles construites au sein du rseau priv (intranet) et ne sont en gnral pas accessibles de l'extrieur. Loger un service au sein d'un serveur web le rend accessible toute la communaut internet. Le service Web peut tre modlis comme un objet distant. Les services offerts deviennent alors des mthodes de cet objet. Un client peut avoir accs cet objet distant comme s'il tait local. Cela cache toute la partie communication rseau et permet de construire un client indpendant de cette couche. Si celle-ci vient changer, le client n'a pas tre modifi. C'est l un norme avantage et probablement le principal atout des services Web. Comme pour les applications client-serveur tcp-ip prsentes dans le chapitre prcdent, le client et le serveur peuvent tre crits dans un langage quelconque. Ils changent des lignes de texte. Celles-ci comportent deux parties : o les enttes ncessaires au protocole HTTP o le corps du message. Pour une rponse du serveur au client, celui-ci est au format XML (eXtensible Markup Language). Pour une demande du client au serveur, le corps du message peut avoir plusieurs formes dont XML. La demande XML du client peut avoir un format particulier appel SOAP (Simple Object Access Protocol). Dans ce cas, la rponse du serveur suit aussi le format SOAP.

9.2

Les navigateurs et XML

Les services Web envoient du XML leurs clients. Les navigateurs peuvent ragir diffremment la rception de ce flux XML. Internet Explorer a une feuille de style prdfinie qui permet de l'afficher. Netscape Communicator n'a pas lui cette feuille de style et n'affiche pas le code XML reu. Il faut visualiser le code source de la page reue pour avoir accs au XML. Voici un exemple. pour le code XML suivant :
<?xml version="1.0" encoding="utf-8"?> <string xmlns="st.istia.univ-angers.fr">bonjour de nouveau !</string>

Internet Explorer affichera la page suivante :

alors que Netscape Navigator affichera : Services WEB 228

Si on visualise le code source de la page reue par Netscape, on obtient :

Netscape a bien reu la mme chose que Internet Explorer mais il l'a affich diffremment. Dans la suite, nous utiliserons Internet Explorer pour les copies d'cran.

9.3 9.3.1

Un premier service Web Version 1

Nous allons dcouvrir les services web au travers d'un exemple simplissime dclin en trois versions.

Pour cette premire version nous allons utiliser VS.NET qui prsente l'avantage de pouvoir gnrer un squelette de service web immdiatement oprationnel. Une fois comprise cette architecture, nous pourrons commencer voler de nos propres ailes. Ce sera l'objet des versions suivantes. Avec VS.NET, construisons un nouveau projet avec l'option [Fichier/Nouveau/Projet] :

On notera les points suivants : le type du projet est Visual Basic (cadre de gauche) le modle du projet est Service Web ASP.NET (cadre de droite) l'emplacement est libre. Ici, le service web sera hberg par un serveur Web IIS local. Son URL sera donc http://localhost/[chemin] o [chemin] est dfinir. Ici, nous choisissons le chemin http://localhost/polyvbnet/demo. VS.NET va alors crer un dossier pour ce projet. O ? Le serveur IIS a une racine pour l'arborescence des documents web qu'il dlivre. Appelons cette racine <IISroot>. Elle correspond l'URL http://localhost. On en dduit que l'URL http://localhost/polyvbnet/demo sera associe au dossier <IISroot>/polyvbnet/demo. <IISroot> est normalement le dossier \inetpub\wwwroot sur le disque o a t install IIS. Dans notre exemple c'est le disque E. Le dossier cr par VS.NET est donc le dossier e:\inetpub\wwwroot\polyvbnet\demo :

Services WEB

229

Comme toujours, il y a une surabondance de dossiers crs. Ils n'ont pas toujours un intrt. Nous n'expliciterons que ceux dont nous avons besoin un moment donn. VS.NET a cr un projet :

Nous retrouvons certains des fichiers prsents dans le dosier physique du projet. Le plus intressant pour nous est le fichier de suffixe asmx. C'est le suffixe des services web. Un service web est gr par VS.NET comme une application windows, c.a.d. une application qui a une interface graphique et du code pour la grer. C'est pourquoi, nous avons une fentre de conception :

Un service web n'a normalement pas d'interface graphique. Il reprsente un objet qu'on peut appeler distance. Il possde des mthodes et les applications appellent celles-ci. Nous le verrons donc comme un objet classique avec cette particularit qu'il a de pouvoir tre instanci distance via le rseau. Aussi, n'utiliserons-nous pas la fentre de conception prsente par VS.NET. Intressons-nous plutt au code du service en utilisant l'option Affichage/Code :

Plusieurs points sont noter : le fichier s'appelle Service1.asmx.vb et non Service1.asmx. Nous reviendrons sur le contenu du fichier Service1.asmx un peu plus loin. 230

Services WEB

on retrouve une fentre de code analogue celle qu'on avait lorsqu'on construisait des applications windows avec VS.NET

Le code gnr par VS.NET est le suivant :


Imports System.Web.Services <System.Web.Services.WebService(Namespace := "http://tempuri.org/demo/Service1")> _ Public Class Service1 Inherits System.Web.Services.WebService #Region " Code gnr par le Concepteur des services Web " Public Sub New() MyBase.New() 'Cet appel est requis par le Concepteur des services Web. InitializeComponent() 'Ajoutez votre code d'initialisation aprs l'appel InitializeComponent() End Sub 'Requis par le Concepteur des services Web Private components As System.ComponentModel.IContainer 'REMARQUE : la procdure suivante est requise par le Concepteur des services Web 'Elle peut tre modifie en utilisant le Concepteur des services Web. 'Ne la modifiez pas en utilisant l'diteur de code. <System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent() components = New System.ComponentModel.Container() End Sub Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean) 'CODEGEN : cette procdure est requise par le Concepteur des services Web 'Ne la modifiez pas en utilisant l'diteur de code. If disposing Then If Not (components Is Nothing) Then components.Dispose() End If End If MyBase.Dispose(disposing) End Sub #End Region ' EXEMPLE DE SERVICE WEB ' L'exemple de service HelloWorld() retourne la chane Hello World. ' Pour gnrer, ne commentez pas les lignes suivantes, puis enregistrez et gnrez le projet. ' Pour tester ce service Web, assurez-vous que le fichier .asmx est la page de dmarrage ' et appuyez sur F5. ' '<WebMethod()> Public Function HelloWorld() As String ' HelloWorld = "Hello World" ' End Function End Class

Tout d'abord, remarquons que nous avons l une classe, la classe Service1 qui drive de la classe WebService :
Public Class Service1 Inherits System.Web.Services.WebService

Cela nous amne importer l'espace de noms System.Web.Services :


Imports System.Web.Services

La dclaration de la classe est prcde d'un attribut de compilation :


<System.Web.Services.WebService(Namespace := "http://tempuri.org/demo/Service1")> _ Public Class Service1 Inherits System.Web.Services.WebService

L'attribut System.Web.Services.WebService() indique que la classe qui suit est un service web. Cet attribut admet divers paramtres dont un appel NameSpace. Il sert placer le service web dans un espace de noms. En effet, on peut imaginer qu'il y ait plusieurs services web appels meteo dans le monde. Il nous faut un moyen de les diffrentier. C'est l'espace de noms qui le permet. L'un Services WEB 231

pourra s'appeler [espacenom1].meteo et un autre [espacenom2].meteo. On retrouve l, un concept analogue aux espaces de noms des classes. VS.NET a automatiquement gnr du code qu'il a mise dans une rgion du source :
#Region " Code gnr par le Concepteur des services Web "

Si on regarde ce code, on retrouve celui que le concepteur gnrait lorsqu'on construisait des applications windows. C'est un code que l'on pourra purement et simplement supprimer si on n'a pas d'interface graphique, ce qui sera notre cas pour les services web. La classe se termine par un exemple de ce que pourrait tre un service web :
#End Region ' EXEMPLE DE SERVICE WEB ' L'exemple de service HelloWorld() retourne la chane Hello World. ' Pour gnrer, ne commentez pas les lignes suivantes, puis enregistrez et gnrez le projet. ' Pour tester ce service Web, assurez-vous que le fichier .asmx est la page de dmarrage ' et appuyez sur F5. ' '<WebMethod()> Public Function HelloWorld() As String ' HelloWorld = "Hello World" ' End Function

Fort de ce qui vient d'tre dit, nous nettoyons le code pour qu'il devienne le suivant :
Imports System.Web.Services <System.Web.Services.WebService(Namespace:="st.istia.univ-angers.fr")> _ Public Class Bonjour Inherits System.Web.Services.WebService <WebMethod()> Public Function Bonjour() As String Return "bonjour !" End Function End Class

Nous y voyons un peu plus clair. un service web est une classe drivant de la classe WebService la classe est qualifie par l'attribut <System.Web.Services.WebService(Namespace:="st.istia.univ-angers.fr")>. On place donc notre service dans l'espace de noms st.istia.univ-angers.fr. les mthodes de la classe sont qualifies par un attribut <WebMethod()> indiquant qu'on a affaire une mthode qui peut tre appele distance via le rseau La classe assurant notre service web s'appelle donc Bonjour et a une seule mthode s'appelant elle-aussi Bonjour qui rend une chane de caractres. Nous sommes prts pour un premier test. lanons le serveur web IIS si ce n'est fait utilisons l'option Dboguer/Excuter sans dbogage. VS.NET VS.NET va alors compiler l'ensemble de l'application, lancer un navigateur (souvent Internet Explorer s'il est prsent), et afficher l'url http://localhost/polyvbnet/demo/Service1.asmx :

Pourquoi l'url http://localhost/polyvbnet/demo/Service1.asmx ? Parce que c'tait le seul fichier .asmx du projet :

Services WEB

232

S'il y avait eu plusieurs fichiers .asmx, il nous aurait fallu prciser celui qui devait tre excut en premier. Cela se fait en cliquant droit sur le fichier .asmx concern et en prenant l'option [Dfinir comme page de dmarrage].

On pourrait tre intress par savoir ce que contient le fichier service1.asmx. En effet, avec VS.NET nous avons travaill sur le fichier service1.asmx.vb et non sur le fichier service1.asmx. Ce fichier se trouve dans le dossier du projet :

Ouvrons avec un diteur de texte (notepad ou autre). On obtient le contenu suivant :


<%@ WebService Language="vb" Codebehind="Service1.asmx.vb" Class="demo.Bonjour" %>

Le fichier contient une simple directive l'intention du serveur IIS indiquant : qu'on a affaire un service web (mot cl WebService) que le langage de la classe de ce service est Visual Basic (Language="vb") que le source de cette classe sera trouve dans le fichier Service1.asmx.vb (Codebehind="Service1.asmx.vb") que la classe implmentant le service s'appelle demo.Bonjour (Class="demo.Bonjour"). On remarquera que VS.NET a plac la classe Bonjour dans l'espace de noms demo qui est aussi le nom du projet. Services WEB 233

Revenons la page obtenue l'url http://localhost/polyvbnet/demo/Service1.asmx :

Qui a crit le code HTML de la page ci-dessus ? Pas nous, nous le savons. C'est IIS, qui prsente les services web d'une faon standard. Cette page nous propose deux liens. Suivons le premier [Description du service] :

Ooops... c'est du XML plutt abscons. Remarquons quand mme l'URL http://localhost/polyvbnet/demo/Service1.asmx?WSDL. Prenez un navigateur, et tapez directement cette url. Vous obtenez la mme chose que prcdemment. On se rappellera donc que l'url http://serviceweb?WSDL donne accs la description XML du service web. Revenons la page de dpart et prenons le lien [Bonjour]. Rappelons-nous que Bonjour est une mthode du service web. Si nous avions eu plusieurs mthodes, elles auraient t toutes prsentes ici. Nous obtenons la nouvelle page suivante :

Nous avons volontairement tronqu la page obtenue pour ne pas alourdir notre dmonstration. Remarquons de nouveau l'url obtenue :
http://localhost/polyvbnet/demo/Service1.asmx?op=Bonjour

Si nous tapons directement cette url dans un navigateur, nous obtiendrons la mme chose que ci-dessus. On nous incite utiliser le bouton [Appeler]. Faisons-le. Nous obtenons une nouvelle page :

Services WEB

234

C'est de nouveau du XML. On y retrouve deux informations qui taient prsentes dans notre service web : l'espace de noms st.istia.univ-angers.fr de notre service
<System.Web.Services.WebService(Namespace:="st.istia.univ-angers.fr")>

la valeur rendue par la mthode Bonjour :


Return "bonjour !"

Qu'avons-nous appris ? la faon d'crire un service web S la faon de l'appeler Nous nous intressons maintenant l'criture d'un service web sans l'aide de VS.NET.

9.3.2

Version 2

Dans l'exemple prcdent, VS.NET a fait beaucoup de choses tout seul. Est-il possible de construire un service web sans cet outil ? La rponse est oui et nous le montrons maintenant. Avec un diteur de texte, nous construisons le service web suivant :
Imports System.Web.Services <System.Web.Services.WebService(Namespace:="st.istia.univ-angers.fr")> _ Public Class Bonjour2 Inherits System.Web.Services.WebService <WebMethod()> Public Function getBonjour() As String Return "bonjour de nouveau !" End Function End Class

La classe s'appelle Bonjour2 et a une mthode qui s'appelle getBonjour. Elle a t place dans le fichier demo2.vb lui mme plac dans l'arborescence du serveur IIS dans le dossier E:\Inetpub\wwwroot\polyvbnet\demo2. C'est une classe VB.NET classique qu'on peut donc compiler :
dos>vbc /out:demo2 /t:library /r:system.dll /r:system.web.services.dll demo2.vb dos>dir 02/03/2004 02/03/2004 02/03/2004 18:04 18:10 18:12 286 demo2.vb 77 demo2.asmx 3 072 demo2.dll

Nous mettons l'assemblage demo2.dll dans un dossier bin (ce nom est obligatoire) :
dos>dir bin 02/03/2004 18:12 3 072 demo2.dll

Nous crons maintenant le fichier demo2.asmx. C'est lui qui sera appel par les clients web. Son contenu est le suivant :
<%@ WebService Language="vb" class="Bonjour2,demo2"%>

Nous avons dj rencontr cette directive. Elle indique que : la classe du service web s'appelle Bonjour2 et se trouve dans l'assemblage demo2.dll. IIS cherchera cet assemblage dans diffrents endroits et notamment dans le dossier bin du service web. C'est pourquoi, nous avons plac l l'assemblage demo2.dll. Maintenant nous pouvons faire divers tests. On s'assure que IIS est actif et on demande avec un navigateur l'url http://localhost/polyvbnet/demo2/demo2.asmx :

Services WEB

235

Puis l'url http://localhost/polyvbnet/demo2/demo2.asmx?WSDL

Puis l'URL http://localhost/polyvbnet/demo2/demo2.asmx?op=getBonjour, o getBonjour est le nom de l'unique mthode de notre service web :

Nous utilisons le bouton [Appeler] ci-dessus :

Services WEB

236

Nous obtenons bien le rsultat de l'appel la mthode getBonjour du service web. Nous savons maintenant comment construire un service web sans vs.net. Nous ferons dsormais abstraction de la faon dont est construit le service web pour ne nous intresser qu'aux fichiers fondamentaux.

9.3.3

Version 3

Les deux versions prcdentes du service web [Bonjour] utilisaient deux fichiers : - un fichier .asmx, point d'entre du service web - un fichier .vb, code source du service web Nous montrons ici, qu'on peut se contenter du seul fichier .asmx. Le code du service demo3.asmx est le suivant :
<%@ WebService Language="vb" class="Bonjour3"%> Imports System.Web.Services <System.Web.Services.WebService(Namespace:="st.istia.univ-angers.fr")> _ Public Class Bonjour3 Inherits System.Web.Services.WebService <WebMethod()> Public Function getBonjour() As String Return "bonjour en version3 !" End Function End Class

Nous constatons que le code source du service est maintenant directement dans le fichier source du fichier demo3.asmx. La directive
<%@ WebService Language="vb" class="Bonjour3"%>

ne rfrence plus une classe dans un assemblage externe, mais une classe se trouvant dans le mme fichier source. Plaons celui-ci dans le dossier <IISroot>\polyvbnet\demo3 :

Lanons IIS etd emandons l'url http://localhost/polyvbnet/demo3/demo3.asmx :

Nous constatons une diffrence importante par rapport la version prcdente : nous n'avons pas eu compiler le code VB du service. IIS a opr cette compilation lui-mme par l'intermdiaire du compilateur VB.NET install sur la mme machine. Puis il a dlivr la page. S'il y a une erreur de compilation, celle-ci sera signale par IIS :

Services WEB

237

9.3.4

Version 4

Nous nous intressons ici la configuration du serveur IIS. Nous avons toujours, jusqu' maintenant, plac nos services web dans l'arborescence de racine <IISroot> du serveur IIS, ici [e:\inetpub\wwwroot]. Nous montrons ici que nous pouvons placer le service web n'importe o. Cela se fait l'aide des dossiers virtuels de IIS. Plaons notre service dans le dossier suivant :

Le dossier [D:\data\devel\vbnet\poly\chap9\demo3] ne se trouve pas dans l'arborescence du serveur IIS. On doit l'indiquer celui-ci en crant un dossier IIS virtuel. Lanons IIS et prenons l'option [Avanc] ci-dessous :

Nous avons une liste de rpertoires virtuels qui nous est prsente. Nous ne nous attarderons pas sur celle-ci. On cre un nouveau rpertoire virtuel avec le bouton [Ajouter] ci-dessus :

Services WEB

238

A l'aide du bouton [Parcourir], nous dsignons le dossier physique contenant le service web, ici le dossier [D:\data\devel\vbnet\poly\chap9\demo3]. Nous donnons un nom logique (virtuel) ce dossier : [virdemo3]. Cela signifie que les documents l'intrieur du dossier physique [D:\data\devel\vbnet\poly\chap9\demo3] seront accessibles sur le rseau via l'url [http://<machine>/virdemo3]. La bote de dialogue ci-dessus comporte d'autres paramtres qu'on laisse en l'tat. Nous validons la bote. Le nouveau dossier virtuel apparat dans la liste des dossiers virtuels de IIS :

Maintenant, nous prenons un navigateur et nous demandons l'url [http://localhost/virdemo3/demo3.asmx]. Nous obtenons la mme chose qu'auparavant :

9.3.5

Conclusion

Nous avons montr plusieurs faons de procder pour crer un service web. Par la suite, nous utiliserons la mthode de la version 3 pour la cration du service et la mthode 4 pour sa localisation. Nous n'aurons pas ainsi besoin de VS.NET. Nanmoins notons l'intrt d'utiliser VS.NET pour l'aide qu'il apporte au dbogage. Il existe des outils gratuits pour dvelopper des application web, notamment le produit WebMatrix sponsoris par Microsoft et qu'on trouvera l'URL [http://www.asp.net/webmatrix]. C'est un outil excellent pour dmarrer la programmation web sans investissement pralable.

9.4
1. 2. 3. 4. 5.

Un service web d'oprations


ajouter(a,b) qui rendra a+b soustraire(a,b) qui rendra a-b multiplier(a,b) qui rendra a*b diviser(a,b) qui rendra a/b toutfaire(a,b) qui rendra le tableau [a+b,a-b,a*b,a/b]

Nous considrons un service Web qui offre cinq fonctions :

Le code VB.NET de ce service est le suivant : Services WEB 239

<%@ WebService language="VB" class=operations %> imports system.web.services <WebService(Namespace:="st.istia.univ-angers.fr")> _ Public Class operations Inherits WebService <WebMethod> _ Function ajouter(a As Double, b As Double) As Double Return a + b End Function <WebMethod> _ Function soustraire(a As Double, b As Double) As Double Return a - b End Function <WebMethod> _ Function multiplier(a As Double, b As Double) As Double Return a * b End Function <WebMethod> _ Function diviser(a As Double, b As Double) As Double Return a / b End Function <WebMethod> _ Function toutfaire(a As Double, b As Double) As Double() Return New Double() {a + b, a - b, a * b, a / b} End Function End Class

Nous reprenons ici certaines explications dj donnes mais qui mritent d'tre rappeles ou compltes. La classe operations ressemble une classe VB.NET avec cependant quelques points noter : les mthodes sont prcdes d'un attribut <WebMethod()> qui indique au compilateur les mthodes qui doivent tre "publies" c.a.d. rendues disponibles au client. Une mthode non prcde de cet attribut serait invisible aux clients distants. Ce pourrait tre une mthode interne utilise par d'autres mthodes mais pas destine tre publie. la classe drive de la classe WebService dfinie dans l'espace de noms System.Web.Services. Cet hritage n'est pas toujours obligatoire. Dans cet exemple notamment on pourrait s'en passer. la classe elle-mme est prcde d'un attribut <WebService(Namespace="st.istia.univ-angers.fr")> destin donner un espace de noms au service web. Un vendeur de classes donne un espace de noms ses classes afin de leur donner un nom unique et viter ainsi des conflits avec des classes d'autres vendeurs qui pourraient porter le mme nom. Pour les services Web, c'est pareil. Chaque service web doit pouvoir tre identifi par un nom unique, ici par st.istia.univangers.fr. nous n'avons pas dfini de constructeur. C'est donc implicitement le constructeur de la classe parent qui sera utilis.

Le code source prcdent n'est pas destin directement au compilateur VB.NET mais au serveur Web IIS. Il doit porter le suffixe .asmx et sauvegard dans l'arborescence du serveur Web. Ici nous le sauvegardons sous le nom operations.asmx dans le dossier <IISroot>\polyvbnet\operations :

Nous associons ce dossier physique, le dossier virtuel IIS [operations] :

Services WEB

240

Accdons au service avec un navigateur. L'URl demander est [http://localhost/operations/operations.asmx] :

Nous obtenons un document Web avec un lien pour chacune des mthodes dfinies dans le service web operations. Suivons le lien ajouter :

La page obtenue nous propose de tester la mthode ajouter en lui fournissant les deux arguments a et b dont elle a besoin. Rappelons la dfinition de la mthode ajouter :
<WebMethod> _ Function ajouter(a As Double, b As Double) As Double Return a + b End Function

On notera que la page a repris les noms des arguments a et b utiliss dans la dfinition de la mthode. On utilise le bouton Appeler et on obtient la rponse suivante dans une fentre spare du navigateur :

Services WEB

241

Si ci-dessus, on fait [Affichage/Source] on obtient le code suivant :

Refaisons l'opration pour la mthode [toutfaire] :

Nous obtenons la page suivante :

Services WEB

242

Utilisons le bouton [Appeler] ci-dessus :

Dans tous les cas, la rponse du serveur a la forme : <?xml version="1.0" encoding="utf-8"?> [rponse au format XML] la rponse est au format XML la ligne 1 est standard et est toujours prsente dans la rponse les lignes suivantes dpendent du type de rsultat (double,ArrayOfDouble), du nombre de rsultats, et de l'espace de noms du service web (st.istia.univ-angers.fr ici).

Il existe plusieurs mthodes interroger un service web et obtenir sa rponse . Revenons l'URL du service :

Services WEB

243

et suivons le lien [ajouter]. Dans page prsente, sont exposes deux mthodes pour interroger la fonction [ajouter] du service web :

Services WEB

244

Ces deux mthodes d'accs aux fonctions d'un service web sont appeles respectivement : HTTP-POST et SOAP. Nous les examinons maintenant l'une aprs l'autre. Note : dans les premires versions de VS.NET, il existait une 3ime mthode appele HTTP-GET. Au jour d'criture de ce document (mars 2004), cette mthode ne semble plus tre disponible. Cela veut dire que le service web gnr par VS.NET n'accepte pas de requtes GET. Cela ne veut pas dire qu'on ne peut pas crire de services web acceptant les requtes GET, notamment avec d'autres outils que VS.NET ou simplement la main.

9.5

Un client HTTP-POST

Nous suivons la mthode propose par le service web :

Commentons ce qui est crit. Tout da'bord le client web doit envoyer les enttes HTTP suivants :
POST /operations/operations.asmx/ajouter HTTP/1.1

Le client web fait une requte POST l'URL /operations/operations.asmx/ajouter selon le protocole HTTP version 1.1 On prcise la machine cible de la requte. Ici localhost. Cet entte a t rendu obligatoire par la version 1.1 du protocole HTTP On prcise ici qu'aprs les enttes HTTP on va envoyer des paramtres supplmentaires au format urlencoded. Ce format remplace certains caractres par leur code hexadcimal. C'est la taille en caractres de la chane de paramtres qui sera envoye aprs les enttes HTTP.

HOST: localhost

Content-Type: application/x-www-form-urlencoded

Content-length: 7

Les enttes HTTP sont suivis d'une ligne vide puis de la chane de paramtres du POST de [Content-Length] caractres sous la forme a=XX&b=YY o XX et YY sont les chanes "urlencodes" des valeurs des paramtres a et b. Nous en savons assez pour reproduire ce qui ci-dessus avec notre client tcp gnrique dj utilis dans le chapitre sur la programmation tcp-ip : nous lanons IIS le service est disponible l'url [http://localhost/operations/operations.asmx] nous utilisons le client tcp gnrique dans une fentre DOS 245

Services WEB

dos>clttcpgenerique localhost 80 Commandes : POST /operations/operations.asmx/ajouter HTTP/1.1 HOST: localhost Connection: close Content-Type: application/x-www-form-urlencoded Content-length: 7 <-- HTTP/1.1 100 Continue <-- Server: Microsoft-IIS/5.0 <-- Date: Wed, 03 Mar 2004 13:55:17 GMT <-- X-Powered-By: ASP.NET <-a=2&b=3 <-- HTTP/1.1 200 OK <-- Server: Microsoft-IIS/5.0 <-- Date: Wed, 03 Mar 2004 13:55:26 GMT <-- X-Powered-By: ASP.NET <-- Connection: close <-- X-AspNet-Version: 1.1.4322 <-- Cache-Control: private, max-age=0 <-- Content-Type: text/xml; charset=utf-8 <-- Content-Length: 90 <-<-- <?xml version="1.0" encoding="utf-8"?> <-- <double xmlns="st.istia.univ-angers.fr">5</double> [fin du thread de lecture des rponses du serveur] fin [fin du thread d'envoi des commandes au serveur]

Remarquons tout d'abord que nous avons ajout l'entte [Connection: close] pour demander au serveur de fermer la connexion aprs avoir envoy la rponse. Cela est ncessaire ici. Si on ne le dit pas, par dfaut le serveur va garder la connexion ouverte. Or sa rponse est une suite de lignes de texte dont la dernire n'est pas termine par une marque de fin de ligne. Il se trouve que notre client TCP gnrique lit des lignes de texte termines par la marque de fin de ligne avec la mthode ReadLine. Si le serveur ne ferme pas la connexion aprs envoi de la dernire ligne, le client est bloqu parce qu'il attend une marque de fin de ligne qui ne vient pas. Si le serveur ferme la connexion, la mthode ReadLine du client se termine et le client ne reste pas bloqu. Aussitt aprs avoir reu la ligne vide signalant la fin des enttes HTTP, le serveur IIS envoie une premire rponse :
<-<-<-<-<-HTTP/1.1 100 Continue Server: Microsoft-IIS/5.0 Date: Wed, 03 Mar 2004 13:55:17 GMT X-Powered-By: ASP.NET

Cette rponse forme uniquement d'enttes HTTP indique au client qu'il peut envoyer les 7 caractres qu'il a dit vouloir envoyer. Ce que nous faisons :
a=2&b=3

Il faut voir ici que notre client tcp envoie plus de 7 caractres puisqu'il les envoie avec une marque de fin de ligne (WriteLine). Ca ne gne pas le serveur qui des caractres reus ne prendra que les 7 premiers et parce qu'ensuite la connexion est ferme (Connection: close). Ces caractres en trop auraient t gnants si la connexion tait reste ouverte car alors ils auraient t pris comme venant de la commande suivante du client. Une fois les paramtres reus, le serveur envoie sa rponse :
<-<-<-<-<-<-<-<-<-<-<-<-HTTP/1.1 200 OK Server: Microsoft-IIS/5.0 Date: Wed, 03 Mar 2004 13:55:26 GMT X-Powered-By: ASP.NET Connection: close X-AspNet-Version: 1.1.4322 Cache-Control: private, max-age=0 Content-Type: text/xml; charset=utf-8 Content-Length: 90 <?xml version="1.0" encoding="utf-8"?> <double xmlns="st.istia.univ-angers.fr">5</double>

Nous avons maintenant les lments pour crire un client programm pour notre service web. Ce sera un client console appel httpPost2 et s'utilisant comme suit :
dos>httpPost2 http://localhost/operations/operations.asmx Tapez vos commandes au format : [ajouter|soustraire|multiplier|diviser] a b

Services WEB

246

ajouter 6 7 --> POST /operations/operations.asmx/ajouter HTTP/1.1 --> Host: localhost:80 --> Content-Type: application/x-www-form-urlencoded --> Content-Length: 7 --> Connection: Keep-Alive --> <-- HTTP/1.1 100 Continue <-- Server: Microsoft-IIS/5.0 <-- Date: Wed, 03 Mar 2004 14:56:38 GMT <-- X-Powered-By: ASP.NET <---> a=6&b=7 <-- HTTP/1.1 200 OK <-- Server: Microsoft-IIS/5.0 <-- Date: Wed, 03 Mar 2004 14:56:38 GMT <-- X-Powered-By: ASP.NET <-- X-AspNet-Version: 1.1.4322 <-- Cache-Control: private, max-age=0 <-- Content-Type: text/xml; charset=utf-8 <-- Content-Length: 91 <-<-- <?xml version="1.0" encoding="utf-8"?> <-- <double xmlns="st.istia.univ-angers.fr">13</double> [rsultat=13] soustraire 8 9 --> POST /operations/operations.asmx/soustraire HTTP/1.1 --> Host: localhost:80 --> Content-Type: application/x-www-form-urlencoded --> Content-Length: 7 --> Connection: Keep-Alive --> <-- HTTP/1.1 100 Continue <-- Server: Microsoft-IIS/5.0 <-- Date: Wed, 03 Mar 2004 14:56:47 GMT <-- X-Powered-By: ASP.NET <---> a=8&b=9 <-- HTTP/1.1 200 OK <-- Server: Microsoft-IIS/5.0 <-- Date: Wed, 03 Mar 2004 14:56:47 GMT <-- X-Powered-By: ASP.NET <-- X-AspNet-Version: 1.1.4322 <-- Cache-Control: private, max-age=0 <-- Content-Type: text/xml; charset=utf-8 <-- Content-Length: 91 <-<-- <?xml version="1.0" encoding="utf-8"?> <-- <double xmlns="st.istia.univ-angers.fr">-1</double> [rsultat=-1] fin dos>

Le client est appel en lui passant l'URL du service web :


dos>httpPost2 http://localhost/operations/operations.asmx

Ensuite, le client lit les commandes tapes au clavier et les excute. Celles-ci sont au format : fonction a b o fonction est la fonction du service web appele (ajouter, soustraire, multiplier, diviser) et a et b les valeurs sur lesquelles va oprer cette fonction. Par exemple :
ajouter 6 7

A partir de l, le client va faire la requte HTTP ncessaire au serveur Web et obtenir une rponse. Les changes client-serveur sont dupliqus l'cran pour une meilleure comprhension du processus :
ajouter 6 7 --> POST /operations/operations.asmx/ajouter HTTP/1.1 --> Host: localhost:80 --> Content-Type: application/x-www-form-urlencoded --> Content-Length: 7 --> Connection: Keep-Alive -->

Services WEB

247

<-- HTTP/1.1 100 Continue <-- Server: Microsoft-IIS/5.0 <-- Date: Wed, 03 Mar 2004 14:56:38 GMT <-- X-Powered-By: ASP.NET <---> a=6&b=7 <-- HTTP/1.1 200 OK <-- Server: Microsoft-IIS/5.0 <-- Date: Wed, 03 Mar 2004 14:56:38 GMT <-- X-Powered-By: ASP.NET <-- X-AspNet-Version: 1.1.4322 <-- Cache-Control: private, max-age=0 <-- Content-Type: text/xml; charset=utf-8 <-- Content-Length: 91 <-<-- <?xml version="1.0" encoding="utf-8"?> <-- <double xmlns="st.istia.univ-angers.fr">13</double> [rsultat=13]

On retrouve ci-dessus l'change dj rencontr avec le client tcp gnrique une diffrence prs : l'entte HTTP Connection: Keep-Alive demande au serveur de ne pas fermer la connexion. Celle-ci reste donc ouverte pour l'opration suivante du client qui n'a donc pas besoin de se reconnecter de nouveau au serveur. Cela l'oblige cependant utiliser une autre mthode que ReadLine() pour lire la rponse du serveur puisqu'on sait que celle-ci est une suite de lignes dont la dernire n'est pas termine par une marque de fin de ligne. Une fois toute la rponse du serveur obtenue, le client l'analyse pour y trouver le rsultat de l'opration demande et l'afficher :
[rsultat=13]

Examinons le code de notre client :


' espaces de noms Imports System Imports System.Net.Sockets Imports System.IO Imports System.Text.RegularExpressions Imports System.Collections Imports Microsoft.VisualBasic Imports System.Web ' client d'un service web operations Public Module clientPOST Public Sub Main(ByVal args() As String) ' syntaxe Const syntaxe As String = "pg URI" Dim fonctions As String() = {"ajouter", "soustraire", "multiplier", "diviser"} ' nombre d'arguments If args.Length <> 1 Then erreur(syntaxe, 1) End If ' on note l'URI demande Dim URIstring As String = args(0) ' on se connecte au serveur Dim uri As Uri = Nothing ' l'URI du service web Dim client As TcpClient = Nothing ' la liaison tcp du client avec le serveur Dim [IN] As StreamReader = Nothing ' le flux de lecture du client Dim OUT As StreamWriter = Nothing ' le flux d'criture du client Try ' connexion au serveur uri = New Uri(URIstring) client = New TcpClient(uri.Host, uri.Port) ' on cre les flux d'entre-sortie du client TCP [IN] = New StreamReader(client.GetStream()) OUT = New StreamWriter(client.GetStream()) OUT.AutoFlush = True Catch ex As Exception ' URI incorrecte ou autre problme erreur("L'erreur suivante s'est produite : " + ex.Message, 2) End Try ' cration d'un dictionnaire des fonctions du service web Dim dicoFonctions As New Hashtable Dim i As Integer For i = 0 To fonctions.Length - 1 dicoFonctions.Add(fonctions(i), True) Next i

Services WEB

248

' les demandes de l'utilisateur sont tapes au clavier ' sous la forme fonction a b ' elles se terminent avec la commande fin Dim commande As String = Nothing ' commande tape au clavier Dim champs As String() = Nothing ' champs d'une ligne de commande Dim fonction As String = Nothing ' nom d'une fonction du service web Dim a, b As String ' les arguments des fonctions du service web ' invite l'utilisateur Console.Out.WriteLine("Tapez vos commandes au format : [ajouter|soustraire|multiplier|diviser] a b") ' gestion des erreurs Dim erreurCommande As Boolean Try ' boucle de saisie des commandes tapes au clavier While True ' pas d'erreur au dpart erreurCommande = False ' lecture commande commande = Console.In.ReadLine().Trim().ToLower() ' fini ? If commande Is Nothing Or commande = "fin" Then Exit While End If ' dcomposition de la commande en champs champs = Regex.Split(commande, "\s+") Try ' il faut trois champs If champs.Length <> 3 Then Throw New Exception End If ' le champ 0 doit tre une fonction reconnue fonction = champs(0) If Not dicoFonctions.ContainsKey(fonction) Then Throw New Exception End If ' le champ 1 doit tre un nombre valide a = champs(1) Double.Parse(a) ' le champ 2 doit tre un nombre valide b = champs(2) Double.Parse(b) Catch ' commande invalide Console.Out.WriteLine("syntaxe : [ajouter|soustraire|multiplier|diviser] a b") erreurCommande = True End Try ' on fait la demande au service web If Not erreurCommande Then executeFonction([IN], OUT, uri, fonction, a, b) End While Catch e As Exception Console.Out.WriteLine(("L'erreur suivante s'est produite : " + e.Message)) End Try ' fin liaison client-serveur Try [IN].Close() OUT.Close() client.Close() Catch End Try End Sub ........... ' affichage des erreurs Public Sub erreur(ByVal msg As String, ByVal exitCode As Integer) ' affichage erreur System.Console.Error.WriteLine(msg) ' arrt avec erreur Environment.Exit(exitCode) End Sub End Module

On a l des choses dj rencontres plusieurs fois et qui ne ncessitent pas de commentaires particuliers. Examinons maintenant le code de la mthode executeFonction o rsident les nouveauts :
' executeFonction Public Sub executeFonction(ByVal [IN] As StreamReader, ByVal OUT As StreamWriter, ByVal uri As Uri, ByVal fonction As String, ByVal a As String, ByVal b As String) ' excute fonction(a,b) sur le service web d'URI uri ' les changes client-serveur se font via les flux IN et OUT ' le rsultat de la fonction est dans la ligne

Services WEB

249

' <double xmlns="st.istia.univ-angers.fr">double</double> ' envoye par le serveur ' construction de la chane de requte Dim requte As String = "a=" + HttpUtility.UrlEncode(a) + "&b=" + HttpUtility.UrlEncode(b) Dim nbChars As Integer = requte.Length ' construction du tableau des enttes HTTP envoyer Dim entetes(5) As String entetes(0) = "POST " + uri.AbsolutePath + "/" + fonction + " HTTP/1.1" entetes(1) = "Host: " & uri.Host & ":" & uri.Port entetes(2) = "Content-Type: application/x-www-form-urlencoded" entetes(3) = "Content-Length: " & nbChars entetes(4) = "Connection: Keep-Alive" entetes(5) = "" ' on envoie les enttes HTTP au serveur Dim i As Integer For i = 0 To entetes.Length - 1 ' envoi au serveur OUT.WriteLine(entetes(i)) ' cho cran Console.Out.WriteLine(("--> " + entetes(i))) Next i ' on lit la 1ere rponse du serveur Web HTTP/1.1 100 Dim ligne As String = Nothing ' une ligne du flux de lecture ligne = [IN].ReadLine() While ligne <> "" 'cho Console.Out.WriteLine(("<-- " + ligne)) ' ligne suivante ligne = [IN].ReadLine() End While 'cho dernire ligne Console.Out.WriteLine(("<-- " + ligne)) ' envoi paramtres de la requte OUT.Write(requte) ' echo Console.Out.WriteLine(("--> " + requte)) ' construction de l'expression rgulire permettant de retrouver la taille de la rponse XML ' dans le flux de la rponse du serveur web Dim modleLength As String = "^Content-Length: (.+?)\s*$" Dim RegexLength As New Regex(modleLength) ' Dim MatchLength As Match = Nothing Dim longueur As Integer = 0 ' lecture seconde rponse du serveur web aprs envoi de la requte ' on mmorise la valeur de la ligne Content-Length ligne = [IN].ReadLine() While ligne <> "" ' cho cran Console.Out.WriteLine(("<-- " + ligne)) ' Content-Length ? MatchLength = RegexLength.Match(ligne) If MatchLength.Success Then longueur = Integer.Parse(MatchLength.Groups(1).Value) End If ' ligne suivante ligne = [IN].ReadLine() End While ' cho dernire ligne Console.Out.WriteLine("<--") ' construction de l'expression rgulire permettant de retrouver le rsultat ' dans le flux de la rponse du serveur web Dim modle As String = "<double xmlns=""st.istia.univ-angers.fr"">(.+?)</double>" Dim ModleRsultat As New Regex(modle) Dim MatchRsultat As Match = Nothing ' on lit le reste de la rponse du serveur web Dim chrRponse(longueur) As Char [IN].Read(chrRponse, 0, longueur) Dim strRponse As String = New [String](chrRponse) ' on dcompose la rponse en lignes de texte Dim lignes As String() = Regex.Split(strRponse, ControlChars.Lf)

Services WEB

250

' on parcourt les lignes de texte la recherche du rsultat Dim strRsultat As String = "?" ' rsultat de la fonction For i = 0 To lignes.Length - 1 ' suivi Console.Out.WriteLine(("<-- " + lignes(i))) ' comparaison ligne courante au modle MatchRsultat = ModleRsultat.Match(lignes(i)) ' a-t-on trouv ? If MatchRsultat.Success Then ' on note le rsultat strRsultat = MatchRsultat.Groups(1).Value End If Next i ' on affiche le rsultat Console.Out.WriteLine(("[rsultat=" + strRsultat + "]" + ControlChars.Lf)) End Sub

Tout d'abord, le client HTTP-POST envoie sa demande au format POST :


' construction de la chane de requte Dim requte As String = "a=" + HttpUtility.UrlEncode(a) + "&b=" + HttpUtility.UrlEncode(b) Dim nbChars As Integer = requte.Length ' construction du tableau des enttes HTTP envoyer Dim entetes(5) As String entetes(0) = "POST " + uri.AbsolutePath + "/" + fonction + " HTTP/1.1" entetes(1) = "Host: " & uri.Host & ":" & uri.Port entetes(2) = "Content-Type: application/x-www-form-urlencoded" entetes(3) = "Content-Length: " & nbChars entetes(4) = "Connection: Keep-Alive" entetes(5) = "" ' on envoie les enttes HTTP au serveur Dim i As Integer For i = 0 To entetes.Length - 1 ' envoi au serveur OUT.WriteLine(entetes(i)) ' cho cran Console.Out.WriteLine(("--> " + entetes(i))) Next i

Dans l'entte
--> Content-Length: 7

on doit indiquer la taille des paramtres qui seront envoys par le client derrire les enttes HTTP :
--> a=6&b=7

Pour cela on utilise le code suivant :


' construction de la chane de requte Dim requte As String = "a=" + HttpUtility.UrlEncode(a) + "&b=" + HttpUtility.UrlEncode(b) Dim nbChars As Integer = requte.Length

La mthode HttpUtility.UrlEncode(string chaine) transforme certains des caractres de chane en %n1n2 o n1n2 est le code ASCII du caractre transform. Les caractres viss par cette transformation sont tous les caractres ayant un sens particulier dans une requte POST (l'espace, le signe =, le signe &, ...). Ici la mthode HttpUtility.UrlEncode est normalement inutile puisque a et b sont des nombres qui ne contiennent aucun de ces caractres particuliers. Elle est ici employe titre d'exemple. Elle a besoin de l'espace de noms System.Web. Une fois que le client a envoy ses enttes HTTP :
--> --> --> --> --> --> POST /operations/operations.asmx/ajouter HTTP/1.1 Host: localhost:80 Content-Type: application/x-www-form-urlencoded Content-Length: 7 Connection: Keep-Alive

le serveur rpond par l'entte HTTP 100 Continue :


<-<-<-<-<-HTTP/1.1 100 Continue Server: Microsoft-IIS/5.0 Date: Wed, 03 Mar 2004 14:56:47 GMT X-Powered-By: ASP.NET

Services WEB

251

Le code se contente de lire et d'afficher l'cran cette premire rponse :


' on lit la 1ere rponse du serveur Web HTTP/1.1 100 Dim ligne As String = Nothing ' une ligne du flux de lecture ligne = [IN].ReadLine() While ligne <> "" 'cho Console.Out.WriteLine(("<-- " + ligne)) ' ligne suivante ligne = [IN].ReadLine() End While 'cho dernire ligne Console.Out.WriteLine(("<-- " + ligne))

Une fois cette premire rponse lue, le client doit envoyer ses paramtres :
--> a=6&b=7

Il le fait avec le code suivant :


' envoi paramtres de la requte OUT.Write(requte) ' echo Console.Out.WriteLine(("--> " + requte))

Le serveur va alors envoyer sa rponse. Celle-ci est compose de deux parties : 1. des enttes HTTP termins par une ligne vide 2. la rponse au format XML
<-<-<-<-<-<-<-<-<-<-<-HTTP/1.1 200 OK Server: Microsoft-IIS/5.0 Date: Wed, 03 Mar 2004 14:56:38 GMT X-Powered-By: ASP.NET X-AspNet-Version: 1.1.4322 Cache-Control: private, max-age=0 Content-Type: text/xml; charset=utf-8 Content-Length: 91 <?xml version="1.0" encoding="utf-8"?> <double xmlns="st.istia.univ-angers.fr">13</double>

Dans un premier temps, le client lit les enttes HTTP pour y trouver la ligne Content-Length et rcuprer la taille de la rponse XML (ici 90). Celle-ci est rcupre au moyen d'une expression rgulire. On aurait pu faire autrement et sans doute de faon plus efficace.
' construction de l'expression rgulire permettant de retrouver la taille de la rponse XML ' dans le flux de la rponse du serveur web Dim modleLength As String = "^Content-Length: (.+?)\s*$" Dim RegexLength As New Regex(modleLength) ' Dim MatchLength As Match = Nothing Dim longueur As Integer = 0 ' lecture seconde rponse du serveur web aprs envoi de la requte ' on mmorise la valeur de la ligne Content-Length ligne = [IN].ReadLine() While ligne <> "" ' cho cran Console.Out.WriteLine(("<-- " + ligne)) ' Content-Length ? MatchLength = RegexLength.Match(ligne) If MatchLength.Success Then longueur = Integer.Parse(MatchLength.Groups(1).Value) End If ' ligne suivante ligne = [IN].ReadLine() End While ' cho dernire ligne Console.Out.WriteLine("<--")

Une fois qu'on a la longueur N de la rponse XML, on n'a plus qu' lire N caractres dans le flux IN de la rponse du serveur. Cette chane de N caractres est redcompose en lignes de texte pour les besoins du suivi cran. Parmi ces lignes on cherche la ligne du rsultat :
<-- <double xmlns="st.istia.univ-angers.fr">13</double>

Services WEB

252

au moyen l encore d'une expression rgulire. Une fois le rsultat trouv, il est affich.
[rsultat=13]

La fin du code du client est la suivante :


' construction de l'expression rgulire permettant de retrouver le rsultat ' dans le flux de la rponse du serveur web Dim modle As String = "<double xmlns=""st.istia.univ-angers.fr"">(.+?)</double>" Dim ModleRsultat As New Regex(modle) Dim MatchRsultat As Match = Nothing ' on lit le reste de la rponse du serveur web Dim chrRponse(longueur) As Char [IN].Read(chrRponse, 0, longueur) Dim strRponse As String = New [String](chrRponse) ' on dcompose la rponse en lignes de texte Dim lignes As String() = Regex.Split(strRponse, ControlChars.Lf) ' on parcourt les lignes de texte la recherche du rsultat Dim strRsultat As String = "?" ' rsultat de la fonction For i = 0 To lignes.Length - 1 ' suivi Console.Out.WriteLine(("<-- " + lignes(i))) ' comparaison ligne courante au modle MatchRsultat = ModleRsultat.Match(lignes(i)) ' a-t-on trouv ? If MatchRsultat.Success Then ' on note le rsultat strRsultat = MatchRsultat.Groups(1).Value End If Next i ' on affiche le rsultat Console.Out.WriteLine(("[rsultat=" + strRsultat + "]" + ControlChars.Lf)) End Sub

9.6

Un client SOAP

Nous tudions ici un second client qui va lui utiliser un dialogue client-serveur de type SOAP (Simple Object Access Protocol). Un exemple de dialogue nous est prsent pour la fonction ajouter :

Services WEB

253

La demande du client est une demande POST. On va donc retrouver certains des mcanismes du client prcdent. La principale diffrence est qu'alors que le client HTTP-POST envoyait les paramtres a et b sous la forme a=A&b=B le client SOAP les envoie dans un format XML plus complexe :
POST /operations/operations.asmx HTTP/1.1 Host: localhost Content-Type: text/xml; charset=utf-8 Content-Length: length SOAPAction: "st.istia.univ-angers.fr/ajouter" <?xml version="1.0" encoding="utf-8"?> <soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Body> <ajouter xmlns="st.istia.univ-angers.fr"> <a>double</a> <b>double</b> </ajouter> </soap:Body> </soap:Envelope>

Il reoit en retour une rponse XML galement plus complexe que les rponses vues prcdemment :
HTTP/1.1 200 OK Content-Type: text/xml; charset=utf-8 Content-Length: length <?xml version="1.0" encoding="utf-8"?> <soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Body> <ajouterResponse xmlns="st.istia.univ-angers.fr"> <ajouterResult>double</ajouterResult> </ajouterResponse> </soap:Body> </soap:Envelope>

Mme si la demande et la rponse sont plus complexes, il s'agit bien du mme mcanisme HTTP que pour le client HTTP-POST. L'criture du client SOAP peut tre ainsi calqu sur celle du client HTTP-POST. Voici un exemple d'excution :
dos>clientsoap1 http://localhost/operations/operations.asmx Tapez vos commandes au format : [ajouter|soustraire|multiplier|diviser] a b ajouter 3 4 --> POST /operations/operations.asmx HTTP/1.1 --> Host: localhost:80 --> Content-Type: text/xml; charset=utf-8 --> Content-Length: 321 --> Connection: Keep-Alive --> SOAPAction: "st.istia.univ-angers.fr/ajouter" --> <-- HTTP/1.1 100 Continue <-- Server: Microsoft-IIS/5.0 <-- Date: Thu, 04 Mar 2004 07:28:29 GMT <-- X-Powered-By: ASP.NET <---> <?xml version="1.0" encoding="utf-8"?> <soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Body>

Services WEB

254

<ajouter xmlns="st.istia.univ-angers.fr"> <a>3</a> <b>4</b> </ajouter> </soap:Body> </soap:Envelope> <-- HTTP/1.1 200 OK <-- Server: Microsoft-IIS/5.0 <-- Date: Thu, 04 Mar 2004 07:28:33 GMT <-- X-Powered-By: ASP.NET <-- X-AspNet-Version: 1.1.4322 <-- Cache-Control: private, max-age=0 <-- Content-Type: text/xml; charset=utf-8 <-- Content-Length: 345 <-<-- <?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-inst ance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><ajouterResponse xmlns="st.istia.univangers.fr"><ajouterResult>7</ajouterResult></ajouterResponse ></soap:Body></soap:Envelope> [rsultat=7]

Seule la mthode executeFonction change. Le client SOAP envoie les enttes HTTP de sa demande. Ils sont simplement un peu plus complexes que ceux de HTTP-POST :
ajouter 3 4 --> POST /operations/operations.asmx HTTP/1.1 --> Host: localhost:80 --> Content-Type: text/xml; charset=utf-8 --> Content-Length: 321 --> Connection: Keep-Alive --> SOAPAction: "st.istia.univ-angers.fr/ajouter" -->

Le code qui les gnre :


' executeFonction Public Sub executeFonction(ByVal [IN] As StreamReader, ByVal OUT As StreamWriter, ByVal uri As Uri, ByVal fonction As String, ByVal a As String, ByVal b As String) ' excute fonction(a,b) sur le service web d'URI uri ' les changes client-serveur se font via les flux IN et OUT ' le rsultat de la fonction est dans la ligne ' <double xmlns="st.istia.univ-angers.fr">double</double> ' envoye par le serveur ' construction de la chane de requte SOAP Dim requteSOAP As String = "<?xml version=" + """1.0"" encoding=""utf-8""?>" + ControlChars.Lf requteSOAP += "<soap:Envelope xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"" xmlns:xsd=""http://www.w3.org/2001/XMLSchema"" xmlns:soap=""http://schemas.xmlsoap.org/soap/envelope/"">" + ControlChars.Lf requteSOAP += "<soap:Body>" + ControlChars.Lf requteSOAP += "<" + fonction + " xmlns=""st.istia.univ-angers.fr"">" + ControlChars.Lf requteSOAP += "<a>" + a + "</a>" + ControlChars.Lf requteSOAP += "<b>" + b + "</b>" + ControlChars.Lf requteSOAP += "</" + fonction + ">" + ControlChars.Lf requteSOAP += "</soap:Body>" + ControlChars.Lf requteSOAP += "</soap:Envelope>" Dim nbCharsSOAP As Integer = requteSOAP.Length ' construction du tableau des enttes HTTP envoyer Dim entetes(6) As String entetes(0) = "POST " + uri.AbsolutePath + " HTTP/1.1" entetes(1) = "Host: " & uri.Host & ":" & uri.Port entetes(2) = "Content-Type: text/xml; charset=utf-8" entetes(3) = "Content-Length: " & nbCharsSOAP entetes(4) = "Connection: Keep-Alive" entetes(5) = "SOAPAction: ""st.istia.univ-angers.fr/" + fonction + """" entetes(6) = "" ' on envoie les enttes HTTP au serveur Dim i As Integer For i = 0 To entetes.Length - 1 ' envoi au serveur OUT.WriteLine(entetes(i)) ' cho cran Console.Out.WriteLine(("--> " + entetes(i))) Next i

En recevant cette demande, le serveur envoie sa premire rponse que le client affiche : Services WEB 255

<-<-<-<-<--

HTTP/1.1 100 Continue Server: Microsoft-IIS/5.0 Date: Thu, 04 Mar 2004 07:28:29 GMT X-Powered-By: ASP.NET

Le code de lecture de cette premire rponse est le suivant :


' on lit la 1ere rponse du serveur Web HTTP/1.1 100 Dim ligne As String = Nothing ' une ligne du flux de lecture ligne = [IN].ReadLine() While ligne <> "" 'cho Console.Out.WriteLine(("<-- " + ligne)) ' ligne suivante ligne = [IN].ReadLine() End While 'while 'cho dernire ligne Console.Out.WriteLine(("<-- " + ligne))

Le client va maintenant envoyer ses paramtres au format XML dans quelque chose qu'on appelle une enveloppe SOAP :
--> <?xml version="1.0" encoding="utf-8"?> <soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Body> <ajouter xmlns="st.istia.univ-angers.fr"> <a>3</a> <b>4</b> </ajouter> </soap:Body> </soap:Envelope>

Le code :
' envoi paramtres de la requte OUT.Write(requteSOAP) ' echo Console.Out.WriteLine(("--> " + requteSOAP))

Le serveur va alors envoyer sa rponse dfinitive :


<-- HTTP/1.1 200 OK <-- Server: Microsoft-IIS/5.0 <-- Date: Thu, 04 Mar 2004 07:28:33 GMT <-- X-Powered-By: ASP.NET <-- X-AspNet-Version: 1.1.4322 <-- Cache-Control: private, max-age=0 <-- Content-Type: text/xml; charset=utf-8 <-- Content-Length: 345 <-<-- <?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-inst ance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><ajouterResponse xmlns="st.istia.univangers.fr"><ajouterResult>7</ajouterResult></ajouterResponse ></soap:Body></soap:Envelope>

Le client affiche l'cran les enttes HTTP reus tout en y cherchant la ligne Content-Length :
' construction de l'expression rgulire permettant de retrouver la taille de la rponse XML ' dans le flux de la rponse du serveur web Dim modleLength As String = "^Content-Length: (.+?)\s*$" Dim RegexLength As New Regex(modleLength) ' Dim MatchLength As Match = Nothing Dim longueur As Integer = 0 ' lecture seconde rponse du serveur web aprs envoi de la requte ' on mmorise la valeur de la ligne Content-Length ligne = [IN].ReadLine() While ligne <> "" ' cho cran Console.Out.WriteLine(("<-- " + ligne)) ' Content-Length ? MatchLength = RegexLength.Match(ligne) If MatchLength.Success Then longueur = Integer.Parse(MatchLength.Groups(1).Value)

Services WEB

256

End If ' ligne suivante ligne = [IN].ReadLine() End While 'while ' cho dernire ligne Console.Out.WriteLine("<--")

Une fois la taille N de la rponse XML connue, le client lit N caractres dans le flux de la rponse du serveur, dcompose la chane rcupre en lignes de texte pour les afficher l'cran et y chercher la balise XML du rsultat : <ajouterResult>7</ajouterResult> et afficher ce dernier :
' construction de l'expression rgulire permettant de retrouver le rsultat ' dans le flux de la rponse du serveur web Dim modle As String = "<" + fonction + "Result>(.+?)</" + fonction + "Result>" Dim ModleRsultat As New Regex(modle) Dim MatchRsultat As Match = Nothing ' on lit le reste de la rponse du serveur web Dim chrRponse(longueur) As Char [IN].Read(chrRponse, 0, longueur) Dim strRponse As String = New [String](chrRponse) ' on dcompose la rponse en lignes de texte Dim lignes As String() = Regex.Split(strRponse, ControlChars.Lf) ' on parcourt les lignes de texte la recherche du rsultat Dim strRsultat As String = "?" ' rsultat de la fonction For i = 0 To lignes.Length - 1 ' suivi Console.Out.WriteLine(("<-- " + lignes(i))) ' comparaison ligne courante au modle MatchRsultat = ModleRsultat.Match(lignes(i)) ' a-t-on trouv ? If MatchRsultat.Success Then ' on note le rsultat strRsultat = MatchRsultat.Groups(1).Value End If 'ligne suivante Next i ' on affiche le rsultat Console.Out.WriteLine(("[rsultat=" + strRsultat + "]" + ControlChars.Lf)) End Sub

9.7

Encapsulation des changes client-serveur

Imaginons que notre service web operations soit utilis par diverses applications. Il serait intressant de mettre disposition de cellesci une classe qui ferait l'interface entre l'application cliente et le service web et qui cacherait la majeure partie des changes rseau qui, pour la plupart des dveloppeurs, ne sont pas triviaux. On aurait ainsi le schma suivant :

Machine cliente Application cliente Interface client-serveur

Machine serveur Service Web internet

L'application cliente s'adresserait l'interface client-serveur pour faire ses demandes au service web. Celle-ci ferait tous les changes rseau ncessaires avec le serveur et rendrait le rsultat obtenu l'application cliente. Celle-ci n'aurait plus s'occuper des changes avec le serveur ce qui faciliterait grandement son criture.

9.7.1

La classe d'encapsulation

Aprs ce qui a t vu dans les paragraphes prcdents, nous connaissons bien maintenant les changes rseau entre le client et le serveur. Nous avons mme vu trois mthodes. Nous choisissons d'encapsuler la mthode SOAP. La classe est la suivante :
' espaces de noms Imports System Imports System.Net.Sockets Imports System.IO Imports System.Text.RegularExpressions

Services WEB

257

Imports System.Collections Imports System.Web Imports Microsoft.VisualBasic ' clientSOAP du service Web operations Public Class clientSOAP ' variables d'instance Private uri As uri = Nothing ' l'URI du service web Private client As TcpClient = Nothing ' la liaison tcp du client avec le serveur Private [IN] As StreamReader = Nothing ' le flux de lecture du client Private OUT As StreamWriter = Nothing ' le flux d'criture du client ' dictionnaire des fonctions Private dicoFonctions As New Hashtable ' liste des fonctions Private fonctions As String() = {"ajouter", "soustraire", "multiplier", "diviser"} ' verbose Private verbose As Boolean = False ' vrai, affiche l'cran les changes client-serveur ' constructeur Public Sub New(ByVal uriString As String, ByVal verbose As Boolean) ' on note verbose Me.verbose = verbose ' connexion au serveur uri = New Uri(uriString) client = New TcpClient(uri.Host, uri.Port) ' on cre les flux d'entre-sortie du client TCP [IN] = New StreamReader(client.GetStream()) OUT = New StreamWriter(client.GetStream()) OUT.AutoFlush = True ' cration du dictionnaire des fonctions du service web Dim i As Integer For i = 0 To fonctions.Length - 1 dicoFonctions.Add(fonctions(i), True) Next i End Sub ' fermeture de la connexion au serveur Public Sub Close() ' fin liaison client-serveur [IN].Close() OUT.Close() client.Close() End Sub ' executeFonction Public Function executeFonction(ByVal fonction As String, ByVal a As String, ByVal b As String) As String ' excute fonction(a,b) sur le service web d'URI uri ' les changes client-serveur se font via les flux IN et OUT ' le rsultat de la fonction est dans la ligne ' <double xmlns="st.istia.univ-angers.fr">double</double> ' envoye par le serveur ' fonction valide ? fonction = fonction.Trim().ToLower() If Not dicoFonctions.ContainsKey(fonction) Then Return "[fonction [" + fonction + "] indisponible : (ajouter, soustraire,multiplier,diviser)]" End If ' arguments a et b valides ? Dim doubleA As Double = 0 Try doubleA = Double.Parse(a) Catch Return "[argument [" + a + "] incorrect (double)]" End Try Dim doubleB As Double = 0 Try doubleB = Double.Parse(b) Catch Return "[argument [" + b + "] incorrect (double)]" End Try ' division par zro ? If fonction = "diviser" And doubleB = 0 Then Return "[division par zro]"

Services WEB

258

End If ' construction de la chane de requte SOAP Dim requteSOAP As String = "<?xml version=" + """1.0"" encoding=""utf-8""?>" + ControlChars.Lf requteSOAP += "<soap:Envelope xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"" xmlns:xsd=""http://www.w3.org/2001/XMLSchema"" xmlns:soap=""http://schemas.xmlsoap.org/soap/envelope/"">" + ControlChars.Lf requteSOAP += "<soap:Body>" + ControlChars.Lf requteSOAP += "<" + fonction + " xmlns=""st.istia.univ-angers.fr"">" + ControlChars.Lf requteSOAP += "<a>" + a + "</a>" + ControlChars.Lf requteSOAP += "<b>" + b + "</b>" + ControlChars.Lf requteSOAP += "</" + fonction + ">" + ControlChars.Lf requteSOAP += "</soap:Body>" + ControlChars.Lf requteSOAP += "</soap:Envelope>" Dim nbCharsSOAP As Integer = requteSOAP.Length ' construction du tableau des enttes HTTP envoyer Dim entetes(6) As String entetes(0) = "POST " + uri.AbsolutePath + " HTTP/1.1" entetes(1) = "Host: " + uri.Host + ":" + uri.Port.ToString entetes(2) = "Content-Type: text/xml; charset=utf-8" entetes(3) = "Content-Length: " + nbCharsSOAP.ToString entetes(4) = "Connection: Keep-Alive" entetes(5) = "SOAPAction: ""st.istia.univ-angers.fr/" + fonction + """" entetes(6) = "" ' on envoie les enttes HTTP au serveur Dim i As Integer For i = 0 To entetes.Length - 1 ' envoi au serveur OUT.WriteLine(entetes(i)) ' cho cran If verbose Then Console.Out.WriteLine(("--> " + entetes(i))) End If Next i ' on lit la 1ere rponse du serveur Web HTTP/1.1 100 Dim ligne As String = Nothing ' une ligne du flux de lecture ligne = [IN].ReadLine() While ligne <> "" 'cho If verbose Then Console.Out.WriteLine(("<-- " + ligne)) End If ' ligne suivante ligne = [IN].ReadLine() End While 'cho dernire ligne If verbose Then Console.Out.WriteLine(("<-- " + ligne)) End If ' envoi paramtres de la requte OUT.Write(requteSOAP) ' echo If verbose Then Console.Out.WriteLine(("--> " + requteSOAP)) End If ' construction de l'expression rgulire permettant de retrouver la taille de la rponse XML ' dans le flux de la rponse du serveur web Dim modleLength As String = "^Content-Length: (.+?)\s*$" Dim RegexLength As New Regex(modleLength) ' Dim MatchLength As Match = Nothing Dim longueur As Integer = 0 ' lecture seconde rponse du serveur web aprs envoi de la requte ' on mmorise la valeur de la ligne Content-Length ligne = [IN].ReadLine() While ligne <> "" ' cho cran If verbose Then Console.Out.WriteLine(("<-- " + ligne)) End If ' Content-Length ? MatchLength = RegexLength.Match(ligne) If MatchLength.Success Then longueur = Integer.Parse(MatchLength.Groups(1).Value) End If ' ligne suivante

Services WEB

259

ligne = [IN].ReadLine() End While ' cho dernire ligne If verbose Then Console.Out.WriteLine("<--") End If ' construction de l'expression rgulire permettant de retrouver le rsultat ' dans le flux de la rponse du serveur web Dim modle As String = "<" + fonction + "Result>(.+?)</" + fonction + "Result>" Dim ModleRsultat As New Regex(modle) Dim MatchRsultat As Match = Nothing ' on lit le reste de la rponse du serveur web Dim chrRponse(longueur) As Char [IN].Read(chrRponse, 0, longueur) Dim strRponse As String = New [String](chrRponse) ' on dcompose la rponse en lignes de texte Dim lignes As String() = Regex.Split(strRponse, ControlChars.Lf) ' on parcourt les lignes de texte la recherche du rsultat Dim strRsultat As String = "?" ' rsultat de la fonction For i = 0 To lignes.Length - 1 ' suivi If verbose Then Console.Out.WriteLine(("<-- " + lignes(i))) End If ' comparaison ligne courante au modle MatchRsultat = ModleRsultat.Match(lignes(i)) ' a-t-on trouv ? If MatchRsultat.Success Then ' on note le rsultat strRsultat = MatchRsultat.Groups(1).Value End If Next i ' on renvoie le rsultat Return strRsultat End Function End Class

Nous ne retrouvons rien de neuf par raport ce qui a t dj vu. Nous avons simplement repris le code du client SOAP tudi et l'avons ramnag quelque peu pour en faire une classe. Celle-ci a un constructeur et deux mthodes :
' constructeur Public Sub New(ByVal uriString As String, ByVal verbose As Boolean) ' executeFonction Public Function executeFonction(ByVal fonction As String, ByVal a As String, ByVal b As String) As String

' fermeture de la connexion au serveur Public Sub Close()

et a les attributs suivants :


' variables d'instance Private uri As Uri = Nothing ' l'URI du service web Private client As TcpClient = Nothing ' la liaison tcp du client avec le serveur Private [IN] As StreamReader = Nothing ' le flux de lecture du client Private OUT As StreamWriter = Nothing ' le flux d'criture du client ' dictionnaire des fonctions Private dicoFonctions As New Hashtable ' liste des fonctions Private fonctions As String() = {"ajouter", "soustraire", "multiplier", "diviser"} ' verbose Private verbose As Boolean = False ' vrai, affiche l'cran les changes client-serveur

On passe au constructeur deux paramtres : 1. l'URI du service web auquel il doit se connecter 2. un boolen verbose qui, vrai, demande que les changes rseau soient affichs l'cran, sinon ils ne le seront pas. Au cours de la construction, on construit les flux IN de lecture rseau, OUT d'criture rseau, ainsi que le dictionnaire des fonctions gres par le service. Une fois l'objet construit, la connexion client-serveur est ouverte et ses flux IN et OUT utilisables. Services WEB 260

La mthode Close permet de fermer la liaison avec le serveur. La mthode ExecuteFonction est celle qu'on a crite pour le client SOAP tudi, quelques dtails prs : 1. Les paramtres uri, IN et OUT qui taient auparavant passs en paramtres la mthode n'ont plus besoin de l'tre, puisque ce sont maintenant des attributs d'instance accessibles toutes les mthodes de l'instance 2. La mthode ExecuteFonction qui rendait auparavant un type void et affichait le rsultat de la fonction l'cran, rend maintenant ce rsultat et donc un type string. Typiquement un client utilisera la classe clientSOAP de la faon suivante : 1. 2. 3. cration d'un objet clientSOAP qui va crer la liaison avec le service web utilisation rpte de la mthode executeFonction fermeture de la liaison avec le service Web par la mthode Close.

Etudions un premier client.

9.7.2

Un client console

Nous reprenons ici le client SOAP tudi alors que la classe clientSOAP n'existait pas et nous le ramnageons afin qu'il utilise maintenant cette classe :
' espaces de noms Imports System Imports System.IO Imports System.Text.RegularExpressions Imports Microsoft.VisualBasic Public Module testClientSoap ' demande l'URI du service web operations ' excute de faon interactive les commandes tapes au clavier Public Sub Main(ByVal args() As String) ' syntaxe Const syntaxe As String = "pg URI [verbose]" ' nombre d'arguments If args.Length <> 1 And args.Length <> 2 Then erreur(syntaxe, 1) End If ' verbose ? Dim verbose As Boolean = False If args.Length = 2 Then verbose = args(1).ToLower() = "verbose" End If ' on se connecte au service web Dim client As clientSOAP = Nothing Try client = New clientSOAP(args(0), verbose) Catch ex As Exception ' erreur de connexion erreur("L'erreur suivante s'est produite lors de la connexion au service web : " + ex.Message, 2) End Try ' les demandes de l'utilisateur sont ' sous la forme fonction a b - elles Dim commande As String = Nothing ' Dim champs As String() = Nothing ' tapes au clavier se terminent avec la commande fin commande tape au clavier champs d'une ligne de commande

' invite l'utilisateur Console.Out.WriteLine("Tapez vos commandes au format : [ajouter|soustraire|multiplier|diviser] a b" + ControlChars.Lf) ' gestion des erreurs Dim erreurCommande As Boolean Try ' boucle de saisie des commandes tapes au clavier While True ' au dpart pas d'erreur erreurCommande = False ' lecture commande commande = Console.In.ReadLine().Trim().ToLower() ' fini ? If commande Is Nothing Or commande = "fin" Then Exit While End If

Services WEB

261

' dcomposition de la commande en champs champs = Regex.Split(commande, "\s+") ' il faut trois champs If champs.Length <> 3 Then Console.Out.WriteLine("syntaxe : [ajouter|soustraire|multiplier|diviser] a b") ' on note l'erreur erreurCommande = True End If ' on fait la demande au service web If Not erreurCommande Then Console.Out.WriteLine(("rsultat=" + client.executeFonction(champs(0).Trim().ToLower(), champs(1).Trim(), champs(2).Trim()))) ' demande suivante End While Catch e As Exception Console.Out.WriteLine(("L'erreur suivante s'est produite : " + e.Message)) End Try ' fin liaison client-serveur Try client.Close() Catch End Try End Sub ' affichage des erreurs Public Sub erreur(ByVal msg As String, ByVal exitCode As Integer) ' affichage erreur System.Console.Error.WriteLine(msg) ' arrt avec erreur Environment.Exit(exitCode) End Sub End Module

La client est maintenant considrablement plus simple et on n'y retrouve aucune communication rseau. Le client admet deux paramtres : 1. l'URI du service web operations 2. le mot cl facultatif verbose. S'il est prsent, les changes rseau seront affichs l'cran. Ces deux paramtres sont utiliss pour construire un objet clientSOAP qui va assurer les changes avec le service web.
' on se connecte au service web Dim client As clientSOAP = Nothing Try client = New clientSOAP(args(0), verbose) Catch ex As Exception ' erreur de connexion erreur("L'erreur suivante s'est produite lors de la connexion au service web : " + ex.Message, 2) End Try

Une fois ouverte la connexion avec le service web, le client peut envoyer ses demandes. Celles-ci sont tapes au clavier, analyses puis envoyes au serveur par appel la mthode executeFonction de l'objet clientSOAP.
' on fait la demande au service web If Not erreurCommande Then Console.Out.WriteLine(("rsultat=" + client.executeFonction(champs(0).Trim().ToLower(), champs(1).Trim(), champs(2).Trim())))

La classe clientSOAP est compile dans un "assemblage" :


dos>vbc /r:clientSOAP.dll testClientSOAP.vb dos>dir 04/03/2004 08:46 6 913 clientSOAP.vb 04/03/2004 09:07 7 168 clientSOAP.dll

L'application client testClientSoap est ensuite compile par :


dos>vbc /r:clientSOAP.dll /r:system.dll testClientSOAP.vb dos>dir 04/03/2004 09:08 2 711 testClientSOAP.vb 04/03/2004 09:08 4 608 testClientSOAP.exe

Voici un exemple d'excution non verbeux :


dos>testclientsoap http://localhost/st/operations/operations.asmx Tapez vos commandes au format : [ajouter|soustraire|multiplier|diviser] a b ajouter 1 3 rsultat=4

Services WEB

262

soustraire 6 7 rsultat=-1 multiplier 4 5 rsultat=20 diviser 1 2 rsultat=0.5 x syntaxe : [ajouter|soustraire|multiplier|diviser] a b x 1 2 rsultat=[fonction [x] indisponible : (ajouter, soustraire,multiplier,diviser)] ajouter a b rsultat=[argument [a] incorrect (double)] ajouter 1 b rsultat=[argument [b] incorrect (double)] diviser 1 0 rsultat=[division par zro] fin

On peut suivre les changes rseau en demandant une excutions "verbeuse" :


dos>testClientSOAP http://localhost/operations/operations.asmx verbose Tapez vos commandes au format : [ajouter|soustraire|multiplier|diviser] a b ajouter 4 8 --> POST /operations/operations.asmx HTTP/1.1 --> Host: localhost:80 --> Content-Type: text/xml; charset=utf-8 --> Content-Length: 321 --> Connection: Keep-Alive --> SOAPAction: "st.istia.univ-angers.fr/ajouter" --> <-- HTTP/1.1 100 Continue <-- Server: Microsoft-IIS/5.0 <-- Date: Thu, 04 Mar 2004 08:15:25 GMT <-- X-Powered-By: ASP.NET <---> <?xml version="1.0" encoding="utf-8"?> <soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Body> <ajouter xmlns="st.istia.univ-angers.fr"> <a>4</a> <b>8</b> </ajouter> </soap:Body> </soap:Envelope> <-- HTTP/1.1 200 OK <-- Server: Microsoft-IIS/5.0 <-- Date: Thu, 04 Mar 2004 08:15:25 GMT <-- X-Powered-By: ASP.NET <-- X-AspNet-Version: 1.1.4322 <-- Cache-Control: private, max-age=0 <-- Content-Type: text/xml; charset=utf-8 <-- Content-Length: 346 <-<-- <?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-inst ance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><ajouterResponse xmlns="st.istia.univangers.fr"><ajouterResult>12</ajouterResult></ajouterResponse></soap:Body></soap:Envelope> rsultat=12 fin

Construisons maintenant un client graphique.

9.7.3

Un client graphique windows

Nous allons maintenant interroger notre service web avec un client graphique qui utilisera lui aussi la classe clientSOAP. L'interface graphique sera la suivante :

Services WEB

263

1 2 3

5 7 8 9

Les contrles sont les suivants : n


1 2 3 4 5 6 7 8 9

type TextBox Button Button ComboBox TextBox TextBox TextBox Button TextBox

nom txtURI btnOuvrir btnFermer cmbFonctions txtA txtB txtRsultat btnCalculer txtErreur

rle l'URI du service web operations ouvre la liaison avec le service Web ferme la liaison avec le service Web la liste des fonction (ajouter, soustraire, multiplier, diviser) l'argument a des fonctions l'argument b des fonctions le rsultat de fonction(a,b) lance le calcul de fonction(a,b) affiche un msg d'tat de la liaison

Il y a quelques contraintes de fonctionnement : le bouton btnOuvrir n'est actif que si le champ txtURI est non vide et qu'une liaison n'est pas dj ouverte le bouton btnFermer n'est actif que lorsqu'une liaison avec le service web a t ouverte le bouton btnCalculer n'est actif que lorsqu'une liaison est ouverte et ques champs txtA et txtB sont non vides les champs txtRsultat et txtErreur ont l'attribut ReadOnly vrai Le client commence par ouvrir la connexion avec le service web l'aide du bouton [Ouvrir] :

Services WEB

264

Ensuite l'utilisateur peut choisir une fonction et des valeurs pour a et b :

Services WEB

265

Le code de l'application suit. Nous avons omis le code du formulaire qui ne prsente pas d'intrt ici.
'espaces de noms Imports System Imports System.Windows.Forms ' la classe du formulaire Public Class FormClientSOAP Inherits System.Windows.Forms.Form ' attributs d'instance Dim client As clientSOAP ' client SOAP du service web operations #Region " Code gnr par le Concepteur Windows Form " Public Sub New() MyBase.New() 'Cet appel est requis par le Concepteur Windows Form. InitializeComponent() ' autres initialisations myInit() End Sub 'La mthode substitue Dispose du formulaire pour nettoyer la liste des composants. Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean) .... End Sub ... Private Sub InitializeComponent() .... End Sub #End Region Private Sub myInit() ' init formulaire cmbFonctions.SelectedIndex = 0 btnOuvrir.Enabled = False btnFermer.Enabled = True btnCalculer.Enabled = False End Sub Private Sub txtURI_TextChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles txtURI.TextChanged ' le contenu du champ de saisie a chang - on fixe l'tat du bouton ouvrir btnOuvrir.Enabled = txtURI.Text.Trim <> "" End Sub Private Sub btnOuvrir_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnOuvrir.Click ' demande d'ouverture d'une connexion avec le service web Try ' cration d'un objet de type [clientSOAP]

Services WEB

266

client = New clientSOAP(txtURI.Text.Trim, False) ' tats des boutons btnOuvrir.Enabled = False btnFermer.Enabled = True ' l'URI ne peut plus tre modifie txtURI.ReadOnly = True ' tat client txtErreur.Text = "Liaison au service web ouverte" Catch ex As Exception ' il y a eu une erreur - on l'affiche txtErreur.Text = ex.Message End Try End Sub Private Sub btnFermer_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnFermer.Click ' fermeture le la connexion au service web client.Close() ' tats boutons btnOuvrir.Enabled = True btnFermer.Enabled = False ' URI txtURI.ReadOnly = False ' tat client txtErreur.Text = "Liaison au service web ferme" End Sub Private Sub btnCalculer_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnCalculer.Click ' calcul d'une fonction f(a,b) ' on efface le rsultat prcdent txtRsultat.Text = "" Try txtRsultat.Text = client.executeFonction(cmbFonctions.Text, txtA.Text.Trim, txtB.Text.Trim) Catch ex As Exception ' il y a eu une erreur rseau txtErreur.Text = ex.Message ' on ferme la liaison btnFermer_Click(Nothing, Nothing) End Try End Sub Private Sub txtA_TextChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles txtA.TextChanged ' changement de la valeur de A btnCalculer.Enabled = txtA.Text.Trim <> "" And txtB.Text.Trim <> "" End Sub Private Sub txtB_TextChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles txtB.TextChanged ' changement de la valeur de B txtA_TextChanged(Nothing, Nothing) End Sub ' mthode principale Public Shared Sub main() Application.Run(New FormClientSOAP) End Sub End Class

L encore la classe clientSOAP cache tout l'aspect rseau de l'application. L'application a t construite de la faon suivante : - l'assemblage clientSOAP.dll contenant la classe clientSOAP a t plac dans le dossier du projet - l'interface graphique clientsoapgui.vb a t construite avec VS.NET puis compile dans une fentre dos :
dos>vbc /r:system.dll /r:system.windows.forms.dll /r:system.drawing.dll /r:clientSOAP.dll clientsoapgui.vb dos>dir 04/03/2004 04/03/2004 04/03/2004 09:13 16:44 16:44 7 168 clientSOAP.dll 9 866 clientsoapgui.vb 11 264 clientsoapgui.exe

L'interface graphique a t ensuite lance par :


dos>clientsoapgui

Services WEB

267

9.8

Un client proxy

Rappelons ce qui vient d'tre fait. Nous avons cr une classe intermdiaire encapsulant les changes rseau entre un client et un service web selon le schma ci-dessous : Machine cliente Application cliente Intermdiaire client-serveur Machine serveur Service Web internet

La plate-forme .NET pousse cette logique plus loin. Une fois connu le service Web atteindre, nous pouvons gnrer automatiquement la classe qui va nous servir d'intermdiaire pour atteindre les fonctions du service Web et qui cachera toute la partie rseau. On appelle cette classe un proxy pour le service web pour lequel elle a t gnre. Comment gnrer la classe proxy d'un service web ? Un service web est toujours accompagn d'un fichier de description au format XML. Si l'URI de notre service web operations est http://localhost/operations/operations.asmx, son fichier de description est disponible l'URL http://localhost/operations/operations.asmx?wsdl comme le montre la copie d'cran suivante :

On a l un fichier XML dcrivant prcisment toutes les fonctions du service web avec pour chacune d'elles le type et le nombre de paramtres, le type du rsultat. On appelle ce fichier le fichier WSDL du service parce qu'il utilise le langage WSDL (Web Services Description Language). A partir de ce fichier, une classe proxy peut tre gnre l'aide de l'outil wsdl :
dos>wsdl http://localhost/operations/operations.asmx?wsdl /language=vb Microsoft (R) Web Services Description Language Utility [Microsoft (R) .NET Framework, Version 1.1.4322.573] Copyright (C) Microsoft Corporation 1998-2002. All rights reserved. criture du fichier 'D:\data\devel\vbnet\poly\chap9\clientproxy\operations.vb'. dos>dir 04/03/2004 17:17 6 663 operations.vb

L'outil wsdl gnre un fichier source VB.NET (option /language=vb) portant le nom de la classe implmentant le service web, ici operations. Examinons une partie du code gnr :
'-----------------------------------------------------------------------------' <autogenerated> ' This code was generated by a tool. ' Runtime Version: 1.1.4322.573 ' ' Changes to this file may cause incorrect behavior and will be lost if ' the code is regenerated. ' </autogenerated> '-----------------------------------------------------------------------------Option Strict Off Option Explicit On Imports Imports Imports Imports Imports Imports System System.ComponentModel System.Diagnostics System.Web.Services System.Web.Services.Protocols System.Xml.Serialization

' 'Ce code source a t automatiquement gnr par wsdl, Version=1.1.4322.573. '

Services WEB

268

'<remarks/> <System.Diagnostics.DebuggerStepThroughAttribute(), _ System.ComponentModel.DesignerCategoryAttribute("code"), _ System.Web.Services.WebServiceBindingAttribute(Name:="operationsSoap", [Namespace]:="st.istia.univangers.fr")> _ Public Class operations Inherits System.Web.Services.Protocols.SoapHttpClientProtocol '<remarks/> Public Sub New() MyBase.New Me.Url = "http://localhost/operations/operations.asmx" End Sub '<remarks/> <System.Web.Services.Protocols.SoapDocumentMethodAttribute("st.istia.univ-angers.fr/ajouter", RequestNamespace:="st.istia.univ-angers.fr", ResponseNamespace:="st.istia.univ-angers.fr", Use:=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle:=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)> _ Public Function ajouter(ByVal a As Double, ByVal b As Double) As Double Dim results() As Object = Me.Invoke("ajouter", New Object() {a, b}) Return CType(results(0),Double) End Function '<remarks/> Public Function Beginajouter(ByVal a As Double, ByVal b As Double, ByVal callback As System.AsyncCallback, ByVal asyncState As Object) As System.IAsyncResult Return Me.BeginInvoke("ajouter", New Object() {a, b}, callback, asyncState) End Function '<remarks/> Public Function Endajouter(ByVal asyncResult As System.IAsyncResult) As Double Dim results() As Object = Me.EndInvoke(asyncResult) Return CType(results(0),Double) End Function

....

Ce code est un peu complexe au premier abord. Nous n'avons pas besoin d'en comprendre les dtails pour pouvoir l'utiliser. Examinons tout d'abord la dclaration de la classe :
Public Class operations Inherits System.Web.Services.Protocols.SoapHttpClientProtocol

La classe porte le nom operations du service web pour lequel elle a t construite. Elle drive de la classe SoapHttpClientProtocol :

Notre classe proxy a un constructeur unique :


Public Sub New() MyBase.New Me.Url = "http://localhost/operations/operations.asmx" End Sub

Le constructeur affecte l'attibut url l'URL du service web associ au proxy. La classe operations ci-dessus ne dfinit pas elle-mme l'attribut url. Celui-ci est hrit de la classe dont drive le proxy : System.Web.Services.Protocols.SoapHttpClientProtocol. Examinons maintenant ce qui se rapporte la mthode ajouter :
Public Function ajouter(ByVal a As Double, ByVal b As Double) As Double Dim results() As Object = Me.Invoke("ajouter", New Object() {a, b}) Return CType(results(0),Double) End Function

Services WEB

269

On peut constater qu'elle a la mme signature que dans le service Web operations o elle tait dfinie comme suit :
<WebMethod> _ Function ajouter(a As Double, b As Double) As Double Return a + b End Function 'ajouter

La faon dont cette classe dialogue avec le service Web n'apparat pas ici. Ce dialogue est entirement pris en charge par la classe parent System.Web.Services.Protocols.SoapHttpClientProtocol. On ne trouve dans le proxy que ce qui le diffrencie des autres proxy : l'URL du service web associ la dfinition des mthodes du service associ.

Pour utiliser les mthodes du service web operations, un client n'a besoin que de la classe proxy operations gnre prcdemment. Compilons cette classe dans un fichier assembly :
dos>vbc /t:library /r:system.web.services.dll /r:system.xml.dll /r:system.dll operations.vb dos>dir 04/03/2004 04/03/2004

17:17 17:24

6 663 operations.vb 7 680 operations.dll

Maintenant crivons un client console. Il est appel sans paramtres et excute les demandes tapes au clavier :
dos>testclientproxy Tapez vos commandes au format : [ajouter|soustraire|multiplier|diviser|toutfaire] a b ajouter 4 5 rsultat=9 soustraire 9 8 rsultat=1 multiplier 10 4 rsultat=40 diviser 6 7 rsultat=0,857142857142857 toutfaire 10 20 rsultats=[30,-10,200,0,5] diviser 5 0 rsultat=+Infini fin

Le code du client est le suivant :


' espaces de noms Imports System Imports System.IO Imports System.Text.RegularExpressions Imports System.Collections Imports Microsoft.VisualBasic Public Module testClientProxy ' excute de faon interactive les commandes tapes au clavier ' et les envoie au service web operations Public Sub Main() ' il n'y a plus d'arguments - l'URL du service web tant code en dur dans le proxy ' cration d'un dictionnaire des fonctions du service web Dim fonctions As String() = {"ajouter", "soustraire", "multiplier", "diviser", "toutfaire"} Dim dicoFonctions As New Hashtable Dim i As Integer For i = 0 To fonctions.Length - 1 dicoFonctions.Add(fonctions(i), True) Next i ' on cre un objet proxy operations Dim myOperations As operations = Nothing Try myOperations = New operations Catch ex As Exception ' erreur de connexion erreur("L'erreur suivante s'est produite lors de la connexion au proxy dy service web : " + ex.Message, 2) End Try

Services WEB

270

' les demandes de l'utilisateur sont ' sous la forme fonction a b - elles Dim commande As String = Nothing ' Dim champs As String() = Nothing '

tapes au clavier se terminent avec la commande fin commande tape au clavier champs d'une ligne de commande

' invite l'utilisateur Console.Out.WriteLine("Tapez vos commandes au format : [ajouter|soustraire|multiplier|diviser| toutfaire] a b" + ControlChars.Lf) ' qqs donnes locales Dim erreurCommande As Boolean Dim fonction As String Dim a, b As Double ' boucle de saisie des commandes tapes au clavier While True ' au dpart pas d'erreur erreurCommande = False ' lecture commande commande = Console.In.ReadLine().Trim().ToLower() ' fini ? If commande Is Nothing Or commande = "fin" Then Exit While End If ' dcomposition de la commande en champs champs = Regex.Split(commande, "\s+") Try ' il faut trois champs If champs.Length <> 3 Then Throw New Exception End If ' le champ 0 doit tre une fonction reconnue fonction = champs(0) If Not dicoFonctions.ContainsKey(fonction) Then Throw New Exception End If ' le champ 1 doit tre un nombre valide a = Double.Parse(champs(1)) ' le champ 2 doit tre un nombre valide b = Double.Parse(champs(2)) Catch ' commande invalide Console.Out.WriteLine("syntaxe : [ajouter|soustraire|multiplier|diviser] a b") erreurCommande = True End Try ' on fait la demande au service web If Not erreurCommande Then Try Dim rsultat As Double Dim rsultats() As Double If fonction = "ajouter" Then rsultat = myOperations.ajouter(a, b) Console.Out.WriteLine(("rsultat=" + rsultat.ToString)) End If If fonction = "soustraire" Then rsultat = myOperations.soustraire(a, b) Console.Out.WriteLine(("rsultat=" + rsultat.ToString)) End If If fonction = "multiplier" Then rsultat = myOperations.multiplier(a, b) Console.Out.WriteLine(("rsultat=" + rsultat.ToString)) End If If fonction = "diviser" Then rsultat = myOperations.diviser(a, b) Console.Out.WriteLine(("rsultat=" + rsultat.ToString)) End If If fonction = "toutfaire" Then rsultats = myOperations.toutfaire(a, b) Console.Out.WriteLine(("rsultats=[" + rsultats(0).ToString + "," + rsultats(1).ToString + "," + _ rsultats(2).ToString + "," + rsultats(3).ToString + "]")) End If Catch e As Exception Console.Out.WriteLine(("L'erreur suivante s'est produite : " + e.Message)) End Try End If End While End Sub ' affichage des erreurs Public Sub erreur(ByVal msg As String, ByVal exitCode As Integer) ' affichage erreur

Services WEB

271

System.Console.Error.WriteLine(msg) ' arrt avec erreur Environment.Exit(exitCode) End Sub End Module

Nous n'examinons que le code propre l'utilisation de la classe proxy. Tout d'abord un objet proxy operations est cr :
' on cre un objet proxy operations Dim myOperations As operations = Nothing Try myOperations = New operations Catch ex As Exception ' erreur de connexion erreur("L'erreur suivante s'est produite lors de la connexion au proxy dy service web : " + ex.Message, 2) End Try

Des lignes fonction a b sont tapes au clavier. A partir de ces informations, on appelle les mthodes appropries du proxy :
' on fait la demande au service web If Not erreurCommande Then Try Dim rsultat As Double Dim rsultats() As Double If fonction = "ajouter" Then rsultat = myOperations.ajouter(a, b) Console.Out.WriteLine(("rsultat=" + rsultat.ToString)) End If If fonction = "soustraire" Then rsultat = myOperations.soustraire(a, b) Console.Out.WriteLine(("rsultat=" + rsultat.ToString)) End If If fonction = "multiplier" Then rsultat = myOperations.multiplier(a, b) Console.Out.WriteLine(("rsultat=" + rsultat.ToString)) End If If fonction = "diviser" Then rsultat = myOperations.diviser(a, b) Console.Out.WriteLine(("rsultat=" + rsultat.ToString)) End If If fonction = "toutfaire" Then rsultats = myOperations.toutfaire(a, b) Console.Out.WriteLine(("rsultats=[" + rsultats(0).ToString + "," + rsultats(1).ToString + "," + _ rsultats(2).ToString + "," + rsultats(3).ToString + "]")) End If Catch e As Exception Console.Out.WriteLine(("L'erreur suivante s'est produite : " + e.Message)) End Try

On traite ici pour la premire fois, l'opration toutfaire qui fait les quatre oprations. Elle avait t ignore jusqu' maintenant car elle envoie un tableau de nombres encapsul dans une enveloppe XML plus complique grer que les rponses XML simples des autres fonctions ne dlivrant qu'un unique rsultat. On voit qu'ici avec la classe proxy, utiliser la mthode toutfaire n'est pas plus compliqu qu'utiliser les autres mthodes. L'application a t compile dans une fentre dos de la faon suivante :
dos>vbc /r:operations.dll /r:system.dll /r:system.web.services.dll testClientProxy.vb dos>dir 04/03/2004 04/03/2004 04/03/2004 04/03/2004 17:17 17:24 17:41 17:41 6 7 4 5 663 680 099 632 operations.vb operations.dll testClientProxy.vb testClientProxy.exe

9.9

Configurer un service Web

Un service Web peut avoir besoin d'informations de configuration pour s'initialiser correctement. Avec le serveur IIS, ces informations peuvent tre places dans un fichier appel web.config et situ dans le mme dossier que le service web. Supposons qu'on veuille crer un service web ayant besoin de deux informations pour s'initialiser : un nom et un ge. Ces deux informations pevent tre places dans le fichier web.config sous la forme suivante :
<?xml version="1.0" encoding="UTF-8" ?> <configuration> <appSettings>

Services WEB

272

<add key="nom" value="tintin"/> <add key="age" value="27"/> </appSettings> </configuration>

Les paramtres d'initialisation sont places dans une enveloppe XML :


<configuration> <appSettings> ... </appSettings> </configuration>

Un paramtre d'initialisation de nom P ayant la valeur V sera dclare avec la ligne :


<add key="P" value="V"/>

Comment le service Web rcupre-t-il ces informations ? Lorsque IIS charge un service web, il regarde s'il y a dans le mme dossier un fichier web.config. Si oui, il le lit. La valeur V d'un paramtre P est obtenue par l'instruction :
String P=ConfigurationSettings.AppSettings["V"];

o ConfigurationSettings est une classe dans l'espace de noms System.Configuration. Vrifions cette technique sur le service web suivant :
<%@ WebService language="VB" class=personne %> Imports System.Web.Services imports System.Configuration <WebService([Namespace] := "st.istia.univ-angers.fr")> _ Public Class personne Inherits WebService ' attributs Private nom As String Private age As Integer ' constructeur Public Sub New() ' init attributs nom = ConfigurationSettings.AppSettings("nom") age = Integer.Parse(ConfigurationSettings.AppSettings("age")) End Sub <WebMethod> _ Function id() As String Return "[" + nom + "," + age.ToString + "]" End Function End Class

Le service web personne a deux attributs nom et age qui sont initialiss dans son constructeur sans paramtres partir des valeurs lues dans le fichier de configuration web.config du service personne. Ce fichier est le suivant :
<configuration> <appSettings> <add key="nom" value="tintin"/> <add key="age" value="27"/> </appSettings> </configuration>

Le service web a par ailleurs une <WebMethod> id sans paramtres et qui se contente de rendre les attributs nom et age. Le service est enregistr dans le fichier source personne.asmx qui est plac avec son fichier de configuration dans le dossier c:\inetpub\wwwroot\st\personne :
dos>dir 09/03/2004 09/03/2004 08:25 08:08 632 personne.asmx 186 web.config

Associons un dossier virtuel IIS /config au dossier physique prcdent. Lanons IIS puis avec un navigateur demandons l'url http://localhost/config/personne.asmx du service personne : Services WEB 273

Suivons le lien de l'unique mthode id :

La mthode id n'a pas de paramtres. Utilisons le bouton Appeler :

Nous avons bien rcupr les informations places dans le fichier web.config du service.

9.10

Le service Web IMPOTS

Nous reprenons l'application IMPOTS maintenant bien connue. La dernire fois que nous avons travaill avec, nous en avions fait un serveur distant qu'on pouvait appeler sur l'internet. Nous en faisons maintenant un service web.

9.10.1

Le service web

Nous partons de la classe impt cre dans le chapitre sur les bases de donnes et qui se construit partir des informations contenues dans une base de donnes ODBC :
' options Option Strict On Option Explicit On ' espaces de noms Imports System Imports System.Data Imports Microsoft.Data.Odbc Imports System.Collections Public Class impt ' les donnes ncessaires au calcul de l'impt ' proviennent d'une source extrieure Private limites(), coeffR(), coeffN() As Decimal

Services WEB

274

' constructeur Public Sub New(ByVal LIMITES() As Decimal, ByVal COEFFR() As Decimal, ByVal COEFFN() As Decimal) ' on vrifie que les 3 tablaeux ont la mme taille Dim OK As Boolean = LIMITES.Length = COEFFR.Length And LIMITES.Length = COEFFN.Length If Not OK Then Throw New Exception("Les 3 tableaux fournis n'ont pas la mme taille(" & LIMITES.Length & "," & COEFFR.Length & "," & COEFFN.Length & ")") End If ' c'est bon Me.limites = LIMITES Me.coeffR = COEFFR Me.coeffN = COEFFN End Sub ' constructeur 2 Public Sub New(ByVal DSNimpots As String, ByVal Timpots As String, ByVal colLimites As String, ByVal colCoeffR As String, ByVal colCoeffN As String) ' initialise les trois tableaux limites, coeffR, coeffN partir ' du contenu de la table Timpots de la base ODBC DSNimpots ' colLimites, colCoeffR, colCoeffN sont les trois colonnes de cette table ' peut lancer une exception Dim connectString As String = "DSN=" + DSNimpots + ";" ' chane de connexion la base Dim impotsConn As OdbcConnection = Nothing ' la connexion Dim sqlCommand As OdbcCommand = Nothing ' la commande SQL ' la requte SELECT Dim selectCommand As String = "select " + colLimites + "," + colCoeffR + "," + colCoeffN + " from " + Timpots ' tableaux pour rcuprer les donnes Dim tLimites As New ArrayList Dim tCoeffR As New ArrayList Dim tCoeffN As New ArrayList ' on tente d'accder la base de donnes impotsConn = New OdbcConnection(connectString) impotsConn.Open() ' on cre un objet command sqlCommand = New OdbcCommand(selectCommand, impotsConn) ' on excute la requte Dim myReader As OdbcDataReader = sqlCommand.ExecuteReader() ' Exploitation de la table rcupre While myReader.Read() ' les donnes de la ligne courante sont mis dans les tableaux tLimites.Add(myReader(colLimites)) tCoeffR.Add(myReader(colCoeffR)) tCoeffN.Add(myReader(colCoeffN)) End While ' libration des ressources myReader.Close() impotsConn.Close() ' les tableaux dynamiques sont mis dans des tableaux statiques Me.limites = New Decimal(tLimites.Count) {} Me.coeffR = New Decimal(tLimites.Count) {} Me.coeffN = New Decimal(tLimites.Count) {} Dim i As Integer For i = 0 To tLimites.Count - 1 limites(i) = Decimal.Parse(tLimites(i).ToString()) coeffR(i) = Decimal.Parse(tCoeffR(i).ToString()) coeffN(i) = Decimal.Parse(tCoeffN(i).ToString()) Next i End Sub ' calcul de l'impt Public Function calculer(ByVal mari As Boolean, ByVal nbEnfants As Integer, ByVal salaire As Integer) As Long ' calcul du nombre de parts Dim nbParts As Decimal If mari Then nbParts = CDec(nbEnfants) / 2 + 2 Else nbParts = CDec(nbEnfants) / 2 + 1 End If If nbEnfants >= 3 Then nbParts += 0.5D End If ' calcul revenu imposable & Quotient familial Dim revenu As Decimal = 0.72D * salaire Dim QF As Decimal = revenu / nbParts ' calcul de l'impt limites((limites.Length - 1)) = QF + 1 Dim i As Integer = 0

Services WEB

275

While QF > limites(i) i += 1 End While ' retour rsultat Return CLng(revenu * coeffR(i) - nbParts * coeffN(i)) End Function End Class

Dans le service web, on ne peut utiliser qu'un constructeur sans paramtres. Aussi le constructeur de la classe va-t-il devenir le suivant :
' constructeur Public Sub New() ' initialise les trois tableaux limites, coeffR, coeffN partir ' du contenu de la table Timpots de la base ODBC DSNimpots ' colLimites, colCoeffR, colCoeffN sont les trois colonnes de cette table ' peut lancer une exception ' on rcupre les paramtres de configuration du service Dim DSNimpots As String = ConfigurationSettings.AppSettings("DSN") Dim Timpots As String = ConfigurationSettings.AppSettings("TABLE") Dim colLimites As String = ConfigurationSettings.AppSettings("COL_LIMITES") Dim colCoeffR As String = ConfigurationSettings.AppSettings("COL_COEFFR") Dim colCoeffN As String = ConfigurationSettings.AppSettings("COL_COEFFN") ' on exploite la base de donnes Dim connectString As String = "DSN=" + DSNimpots + ";" ' chane de connexion la base

Les cinq paramtres du constructeur de la classe prcdente sont maintenant lus dans le fichier web.config du service. Le code du fichier source impots.asmx est le suivant. Il reprend la majeure partie du code prcdent. Nous nous sommes contents d'encadrer les portions de code propres au service web :
<%@ WebService language="VB" class=impots %> ' cration d'un servie web impots Imports System Imports System.Data Imports Microsoft.Data.Odbc Imports System.Collections Imports System.Configuration Imports System.Web.Services <WebService([Namespace]:="st.istia.univ-angers.fr")> _ Public Class impt Inherits WebService ' les donnes ncessaires au calcul de l'impt ' proviennent d'une source extrieure Private limites(), coeffR(), coeffN() As Decimal Private OK As Boolean = False Private errMessage As String = "" ' constructeur Public Sub New() ' initialise les trois tableaux limites, coeffR, coeffN partir ' du contenu de la table Timpots de la base ODBC DSNimpots ' colLimites, colCoeffR, colCoeffN sont les trois colonnes de cette table ' peut lancer une exception ' on rcupre les paramtres de configuration du service Dim DSNimpots As String = ConfigurationSettings.AppSettings("DSN") Dim Timpots As String = ConfigurationSettings.AppSettings("TABLE") Dim colLimites As String = ConfigurationSettings.AppSettings("COL_LIMITES") Dim colCoeffR As String = ConfigurationSettings.AppSettings("COL_COEFFR") Dim colCoeffN As String = ConfigurationSettings.AppSettings("COL_COEFFN") ' on exploite la base de donnes Dim connectString As String = "DSN=" + DSNimpots + ";" ' chane de connexion la base Dim impotsConn As OdbcConnection = Nothing ' la connexion Dim sqlCommand As OdbcCommand = Nothing ' la commande SQL Dim myReader As OdbcDataReader ' lecteur de donnes Odbc ' la requte SELECT Dim selectCommand As String = "select " + colLimites + "," + colCoeffR + "," + colCoeffN + " from " + Timpots ' tableaux pour rcuprer les donnes Dim tLimites As New ArrayList

Services WEB

276

Dim tCoeffR As New ArrayList Dim tCoeffN As New ArrayList ' on tente d'accder la base de donnes Try impotsConn = New OdbcConnection(connectString) impotsConn.Open() ' on cre un objet command sqlCommand = New OdbcCommand(selectCommand, impotsConn) ' on excute la requte myReader = sqlCommand.ExecuteReader() ' Exploitation de la table rcupre While myReader.Read() ' les donnes de la ligne courante sont mis dans les tableaux tLimites.Add(myReader(colLimites)) tCoeffR.Add(myReader(colCoeffR)) tCoeffN.Add(myReader(colCoeffN)) End While ' libration des ressources myReader.Close() impotsConn.Close() ' les tableaux dynamiques sont mis dans des tableaux statiques Me.limites = New Decimal(tLimites.Count) {} Me.coeffR = New Decimal(tLimites.Count) {} Me.coeffN = New Decimal(tLimites.Count) {} Dim i As Integer For i = 0 To tLimites.Count - 1 limites(i) = Decimal.Parse(tLimites(i).ToString()) coeffR(i) = Decimal.Parse(tCoeffR(i).ToString()) coeffN(i) = Decimal.Parse(tCoeffN(i).ToString()) Next i ' c'est bon OK = True errMessage = "" Catch ex As Exception ' erreur OK = False errMessage += "[" + ex.Message + "]" End Try End Sub ' calcul de l'impt <WebMethod()> _ Function calculer(ByVal mari As Boolean, ByVal nbEnfants As Integer, ByVal salaire As Integer) As Long ' calcul du nombre de parts Dim nbParts As Decimal If mari Then nbParts = CDec(nbEnfants) / 2 + 2 Else nbParts = CDec(nbEnfants) / 2 + 1 End If If nbEnfants >= 3 Then nbParts += 0.5D End If ' calcul revenu imposable & Quotient familial Dim revenu As Decimal = 0.72D * salaire Dim QF As Decimal = revenu / nbParts ' calcul de l'impt limites((limites.Length - 1)) = QF + 1 Dim i As Integer = 0 While QF > limites(i) i += 1 End While ' retour rsultat Return CLng(revenu * coeffR(i) - nbParts * coeffN(i)) End Function ' id <WebMethod()> _ Function id() As String ' pour voir si tout est OK Return "[" + OK + "," + errMessage + "]" End Function End Class

Expliquons les quelques modifications faites la classe impots en-dehors de celles ncessaires pour en faire un service web : Services WEB 277

la lecture de la base de donnes dans le constructeur peut chouer. Aussi avons-nous ajout deux attributs notre classe et une mthode : o le boolen OK est vrai si la base a pu tre lue, faux sinon o la chane errMessage contient un message d'erreur si la base de donnes n'a pu tre lue. o la mthode id sans paramtres permet d'obtenir la valeur ces deux attributs. pour grer l'erreur ventuelle d'accs la base de donnes, la partie du code du constructeur concerne par cet accs a t entoure d'un try-catch.

Le fichier web.config de configuration du service est le suivant :


<configuration> <appSettings> <add key="DSN" value="mysql-impots" /> <add key="TABLE" value="timpots" /> <add key="COL_LIMITES" value="limites" /> <add key="COL_COEFFR" value="coeffr" /> <add key="COL_COEFFN" value="coeffn" /> </appSettings> </configuration>

Lors d'un premier essai de chargement du service impots, le compilateur a dclar qu'il ne trouvait pas l'espace de noms Microsoft.Data.Odbc utilis dans la directive :
Imports Microsoft.Data.Odbc

Aprs consultation de la documentation o une directive de compilation a t ajoute dans web.config pour indiquer qu'il fallait utiliser l'assembly Microsoft.Data.odbc o une copie du fichier microsoft.data.odbc.dll a t place dans le dossier bin du projet. Celui-ci est systmatiquement explor par le compilateur d'un service web lorsqu'il recherche un "assembly". D'autres solutions semblent possibles mais n'ont pas t creuses ici. Le fichier de configuration est donc devenu :
<configuration> <appSettings> <add key="DSN" value="mysql-impots" /> <add key="TABLE" value="timpots" /> <add key="COL_LIMITES" value="limites" /> <add key="COL_COEFFR" value="coeffr" /> <add key="COL_COEFFN" value="coeffn" /> </appSettings> <system.web> <compilation> <assemblies> <add assembly="Microsoft.Data.Odbc" /> </assemblies> </compilation> </system.web> </configuration>

Le contenu du dossier impots\bin :


dos>dir impots\bin 30/01/2002 02:02 327 680 Microsoft.Data.Odbc.dll

Le service et son fichier de configuration ont t placs dans impots :


dos>dir impots 09/03/2004 10:13 09/03/2004 10:19 4 669 impots.asmx 431 web.config

Le dossier physique du service web a t associ au dossier virtuel /impots de IIS. La page du service est alors la suivante :

Services WEB

278

Si on suit le lien id :

Si on utilise le bouton Appeler :

Le rsultat prcdent affiche les valeurs des attributs OK (true) et errMessage (""). Dans cet exemple, la base a t charge correctement. Ca n'a pas toujours t le cas et c'est pourquoi nous avons ajout la mthode id pour avoir accs au message d'erreur. L'erreur tait que le nom DSN de la base avait t dfinie comme DSN utilisateur alors qu'il fallait le dfinir comme DSN systme. Cette distinction se fait dans le gestionnaire de sources ODBC 32 bits :

Revenons la page du service :

Services WEB

279

Suivons le lien calculer :

Nous dfinissons les paramtres de l'appel et nous excutons celui-ci :

Le rsultat est correct.

9.10.2

Gnrer le proxy du service impots

Maintenant que nous avons un service web impots oprationnel, nous pouvons gnrer sa classe proxy. On rappelle que celle-ci sera utilise par des applications clientes pour atteindre le service web impots de faon transparente. On utilise d'abord l'utilitaire wsdl pour gnrer le fichier source de la classe proxy puis celui-ci est compil dans un une dll.
dos>wsdl /language=vb http://localhost/impots/impots.asmx Microsoft (R) Web Services Description Language Utility [Microsoft (R) .NET Framework, Version 1.1.4322.573] Copyright (C) Microsoft Corporation 1998-2002. All rights reserved. criture du fichier 'D:\data\serge\devel\vbnet\poly\chap9\impots\impots.vb'. D:\data\serge\devel\vbnet\poly\chap9\impots>dir 09/03/2004 10:20 <REP> bin 09/03/2004 10:58 4 651 impots.asmx 09/03/2004 11:05 3 364 impots.vb 09/03/2004 10:19 431 web.config

Services WEB

280

dos>vbc /t:library /r:system.dll /r:system.web.services.dll /r:system.xml.dll impots.vb Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573 Copyright (C) Microsoft Corporation 1987-2002. Tous droits rservs. dos>dir 09/03/2004 09/03/2004 09/03/2004 09/03/2004 09/03/2004 10:20 10:58 11:09 11:05 10:19 <REP> bin 4 651 impots.asmx 5 120 impots.dll 3 364 impots.vb 431 web.config

9.10.3
dos>dir 27/02/2004 27/02/2004 27/02/2004 27/02/2004

Utiliser le proxy avec un client


16:56 17:12 17:08 17:18 5 3 6 3 120 586 144 328 impots.dll impots.vb testimpots.exe testimpots.vb

Dans le chapitre sur les bases de donnes nous avions cr une application console permettant le calcul de l'impt :

dos>testimpots pg DSNimpots tabImpots colLimites colCoeffR colCoeffN dos>testimpots odbc-mysql-dbimpots impots limites coeffr coeffn Paramtres du calcul de l'impt au format mari nbEnfants salaire ou rien pour arrter :o 2 200000 impt=22504 F

Le programme testimpots utilisait alors la classe impt classique celle contenue dans le fichier impots.dll. Le code du programme testimpots.vb tait le suivant :
Option Explicit On Option Strict On ' espaces de noms Imports System Imports Microsoft.VisualBasic ' pg de test Module testimpots Sub Main(ByVal arguments() As String) ' programme interactif de calcul d'impt ' l'utilisateur tape trois donnes au clavier : mari nbEnfants salaire ' le programme affiche alors l'impt payer Const syntaxe1 As String = "pg DSNimpots tabImpots colLimites colCoeffR colCoeffN" Const syntaxe2 As String = "syntaxe : mari nbEnfants salaire" + ControlChars.Lf + "mari : o pour mari, n pour non mari" + ControlChars.Lf + "nbEnfants : nombre d'enfants" + ControlChars.Lf + "salaire : salaire annuel en F" ' vrification des paramtres du programme If arguments.Length <> 5 Then ' msg d'erreur Console.Error.WriteLine(syntaxe1) ' fin Environment.Exit(1) End If 'if ' on rcupre les arguments Dim DSNimpots As String = arguments(0) Dim tabImpots As String = arguments(1) Dim colLimites As String = arguments(2) Dim colCoeffR As String = arguments(3) Dim colCoeffN As String = arguments(4) ' cration d'un objet impt Dim objImpt As impt = Nothing Try objImpt = New impt(DSNimpots, tabImpots, colLimites, colCoeffR, colCoeffN) Catch ex As Exception Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message)) Environment.Exit(2) End Try ' boucle infinie While True ' au dpart pas d'erreurs Dim erreur As Boolean = False

Services WEB

281

' on demande les paramtres du calcul de l'impt Console.Out.Write("Paramtres du calcul de l'impt au format mari nbEnfants salaire ou rien pour arrter :") Dim paramtres As String = Console.In.ReadLine().Trim() ' qq chose faire ? If paramtres Is Nothing Or paramtres = "" Then Exit While End If ' vrification du nombre d'arguments dans la ligne saisie Dim args As String() = paramtres.Split(Nothing) Dim nbParamtres As Integer = args.Length If nbParamtres <> 3 Then Console.Error.WriteLine(syntaxe2) erreur = True End If Dim mari As String Dim nbEnfants As Integer Dim salaire As Integer If Not erreur Then ' vrification de la validit des paramtres ' mari mari = args(0).ToLower() If mari <> "o" And mari <> "n" Then Console.Error.WriteLine((syntaxe2 + ControlChars.Lf + "Argument mari incorrect : tapez o ou erreur = True End If ' nbEnfants nbEnfants = 0 Try nbEnfants = Integer.Parse(args(1)) If nbEnfants < 0 Then Throw New Exception End If Catch Console.Error.WriteLine(syntaxe2 + "\nArgument nbEnfants incorrect : tapez un entier positif ou erreur = True End Try ' salaire salaire = 0 Try salaire = Integer.Parse(args(2)) If salaire < 0 Then Throw New Exception End If Catch Console.Error.WriteLine(syntaxe2 + "\nArgument salaire incorrect : tapez un entier positif ou

n"))

nul")

nul")

erreur = True End Try End If If Not erreur Then ' les paramtres sont corrects - on calcule l'impt Console.Out.WriteLine(("impt=" & objImpt.calculer(mari = "o", nbEnfants, salaire).ToString + " F")) End If End While End Sub End Module

Nous reprenons le mme programme pour lui faire utiliser maintenant le service web impots au travers de la classe proxy impots cre prcdemment. Nous sommes obligs de modifier quelque peu le code : o alors que la classe impt d'origine avait un constructeur cinq arguments, la classe proxy impots a un constructeur sans paramtres. Les cinq paramtres, nous l'avons vu, sont maintenant fixs dans le fichier de configuration du service web. o il n'y a donc plus besoin de passer ces cinq paramtres en arguments au programme test Le nouveau code est le suivant :
Imports System Imports Microsoft.VisualBasic ' pg de test Module testimpots Public Sub Main(ByVal arguments() As String) ' programme interactif de calcul d'impt

Services WEB

282

' l'utilisateur tape trois donnes au clavier : mari nbEnfants salaire ' le programme affiche alors l'impt payer Const syntaxe2 As String = "syntaxe : mari nbEnfants salaire" + ControlChars.Lf + "mari : o pour mari, n pour non mari" + ControlChars.Lf + "nbEnfants : nombre d'enfants" + ControlChars.Lf + "salaire : salaire annuel en F" ' cration d'un objet impt Dim objImpt As impt = Nothing Try objImpt = New impt Catch ex As Exception Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message)) Environment.Exit(2) End Try ' boucle infinie Dim erreur As Boolean While True ' au dpart pas d'erreur erreur = False ' on demande les paramtres du calcul de l'impt Console.Out.Write("Paramtres du calcul de l'impt au format mari nbEnfants salaire ou rien pour arrter :") Dim paramtres As String = Console.In.ReadLine().Trim() ' qq chose faire ? If paramtres Is Nothing Or paramtres = "" Then Exit While End If ' vrification du nombre d'arguments dans la ligne saisie Dim args As String() = paramtres.Split(Nothing) Dim nbParamtres As Integer = args.Length If nbParamtres <> 3 Then Console.Error.WriteLine(syntaxe2) erreur = True End If If Not erreur Then ' vrification de la validit des paramtres ' mari Dim mari As String = args(0).ToLower() If mari <> "o" And mari <> "n" Then Console.Error.WriteLine((syntaxe2 + ControlChars.Lf + "Argument mari incorrect : tapez o ou n")) erreur = True End If ' nbEnfants Dim nbEnfants As Integer = 0 Try nbEnfants = Integer.Parse(args(1)) If nbEnfants < 0 Then Throw New Exception End If Catch Console.Error.WriteLine((syntaxe2 + ControlChars.Lf + "Argument nbEnfants incorrect : tapez un entier positif ou nul")) erreur = True End Try ' salaire Dim salaire As Integer = 0 Try salaire = Integer.Parse(args(2)) If salaire < 0 Then Throw New Exception End If Catch Console.Error.WriteLine((syntaxe2 + ControlChars.Lf + "Argument salaire incorrect : tapez un entier positif ou nul")) erreur = True End Try ' si les paramtres sont corrects - on calcule l'impt If Not erreur Then Console.Out.WriteLine(("impt=" + objImpt.calculer(mari = "o", nbEnfants, salaire).ToString + " F")) End If End While End Sub End Module

Nous avons le proxy impots.dll et le source testimpots dans le mme dossier.


dos>dir 09/03/2004 09/03/2004 11:28 11:09 <REP> bin 5 120 impots.dll

Services WEB

283

09/03/2004 09/03/2004

11:34 10:19

3 396 testimpots.vb 431 web.config

Nous compilons le source testimpots.vb :


dos>vbc /r:impots.dll /r:microsoft.visualbasic.dll /r:system.web.services.dll /r:system.dll testimpots.vb dos>dir 09/03/2004 09/03/2004 09/03/2004 09/03/2004 09/03/2004 09/03/2004

11:28 11:09 11:05 11:35 11:34 10:19

<REP> 5 3 5 3 120 364 632 396 431

bin impots.dll impots.vb testimpots.exe testimpots.vb web.config

puis l'excutons :
dos>testimpots Paramtres du calcul de l'impt au format mari nbEnfants salaire ou rien pour arrter :o 2 200000 impt=22504 F

Nous obtenons bien le rsultat attendu.

Services WEB

284

10.
1. 2. 3.

A suivre
les objets DataSet qui permettent de grer une base de donnes en mmoire, de l'exporter ou l'importer au format XML une tude de XML avec les classes .NET permettant de grer les documents XML la programmation Web avec les pages et contrles ASP.NET

Il resterait des thmes importants couvrir. En voici trois :

A lui seul, le point 3 mrite un polycopi. Les points 1 et 2 devraient tre ajouts progressivement ce prsent document.

Services WEB

285

1. LES BASES DU LANGAGE VB.NET....................................................................................................................................7 1.1 INTRODUCTION..............................................................................................................................................................................7 1.2 LES DONNES DE VB.NET........................................................................................................................................................... 7 1.2.1 LES TYPES DE DONNES PRDFINIS................................................................................................................................................ 7 1.2.2 NOTATION DES DONNES LITTRALES..............................................................................................................................................8 1.2.3 DCLARATION DES DONNES..........................................................................................................................................................8 1.2.3.1 Rle des dclarations........................................................................................................................................................... 8 1.2.3.2 Dclaration des constantes...................................................................................................................................................9 1.2.3.3 Dclaration des variables..................................................................................................................................................... 9 1.2.4 LES CONVERSIONS ENTRE NOMBRES ET CHANES DE CARACTRES......................................................................................................... 9 1.2.5 LES TABLEAUX DE DONNES........................................................................................................................................................ 10 1.3 LES INSTRUCTIONS LMENTAIRES DE VB.NET............................................................................................................................13 1.3.1 ECRITURE SUR CRAN.................................................................................................................................................................13 1.3.2 LECTURE DE DONNES TAPES AU CLAVIER.................................................................................................................................... 14 1.3.3 EXEMPLE D'ENTRES-SORTIES...................................................................................................................................................... 14 1.3.4 REDIRECTION DES E/S............................................................................................................................................................... 14 1.3.5 AFFECTATION DE LA VALEUR D'UNE EXPRESSION UNE VARIABLE..................................................................................................... 16 1.3.5.1 Liste des oprateurs............................................................................................................................................................16 1.3.5.2 Expression arithmtique.....................................................................................................................................................16 1.3.5.3 Priorits dans l'valuation des expressions arithmtiques................................................................................................. 17 1.3.5.4 Expressions relationnelles..................................................................................................................................................17 1.3.5.5 Expressions boolennes..................................................................................................................................................... 18 1.3.5.6 Traitement de bits.............................................................................................................................................................. 18 1.3.5.7 Oprateur associ une affectation................................................................................................................................... 19 1.3.5.8 Priorit gnrale des oprateurs......................................................................................................................................... 19 1.3.5.9 Les conversions de type..................................................................................................................................................... 19 1.4 LES INSTRUCTIONS DE CONTRLE DU DROULEMENT DU PROGRAMME............................................................................................... 21 1.4.1 ARRT.....................................................................................................................................................................................21 1.4.2 STRUCTURE DE CHOIX SIMPLE...................................................................................................................................................... 21 1.4.3 STRUCTURE DE CAS....................................................................................................................................................................22 1.4.4 STRUCTURE DE RPTITION..........................................................................................................................................................23 1.4.4.1 Nombre de rptitions connu............................................................................................................................................. 23 1.4.4.2 Nombre de rptitions inconnu..........................................................................................................................................24 1.4.4.3 Instructions de gestion de boucle.......................................................................................................................................24 1.5 LA STRUCTURE D'UN PROGRAMME VB.NET.................................................................................................................................25 1.6 COMPILATION ET EXCUTION D'UN PROGRAMME VB.NET.............................................................................................................26 1.7 L'EXEMPLE IMPOTS................................................................................................................................................................ 27 1.8 ARGUMENTS DU PROGRAMME PRINCIPAL........................................................................................................................................29 1.9 LES NUMRATIONS.....................................................................................................................................................................30 1.10 LA GESTION DES EXCEPTIONS......................................................................................................................................................31 1.11 PASSAGE DE PARAMTRES UNE FONCTION..................................................................................................................................33 1.11.1 PASSAGE PAR VALEUR.............................................................................................................................................................. 33 1.11.2 PASSAGE PAR RFRENCE..........................................................................................................................................................34 2. CLASSES, STUCTURES, INTERFACES............................................................................................................................ 35 2.1 L' OBJET PAR L'EXEMPLE.............................................................................................................................................................35 2.1.1 GNRALITS............................................................................................................................................................................35 2.1.2 DFINITION DE LA CLASSE PERSONNE............................................................................................................................................ 35 2.1.3 LA MTHODE INITIALISE..............................................................................................................................................................36 2.1.4 L'OPRATEUR NEW.....................................................................................................................................................................36 2.1.5 LE MOT CL ME....................................................................................................................................................................... 37 2.1.6 UN PROGRAMME DE TEST............................................................................................................................................................37 2.1.7 UTILISER UN FICHIER DE CLASSES COMPILES (ASSEMBLY)................................................................................................................38 2.1.8 UNE AUTRE MTHODE INITIALISE..................................................................................................................................................39 2.1.9 CONSTRUCTEURS DE LA CLASSE PERSONNE.....................................................................................................................................39 2.1.10 LES RFRENCES D'OBJETS........................................................................................................................................................ 41 2.1.11 LES OBJETS TEMPORAIRES......................................................................................................................................................... 42 2.1.12 MTHODES DE LECTURE ET D'CRITURE DES ATTRIBUTS PRIVS....................................................................................................... 42 Services WEB 286

2.1.13 LES PROPRITS.......................................................................................................................................................................43 2.1.14 LES MTHODES ET ATTRIBUTS DE CLASSE.....................................................................................................................................45 2.1.15 PASSAGE D'UN OBJET UNE FONCTION........................................................................................................................................ 47 2.1.16 UN TABLEAU DE PERSONNES......................................................................................................................................................48 2.2 L'HRITAGE PAR L'EXEMPLE........................................................................................................................................................ 48 2.2.1 GNRALITS............................................................................................................................................................................48 2.2.2 CONSTRUCTION D'UN OBJET ENSEIGNANT....................................................................................................................................... 50 2.2.3 SURCHARGE D'UNE MTHODE OU D'UNE PROPRIT.......................................................................................................................... 52 2.2.4 LE POLYMORPHISME...................................................................................................................................................................53 2.2.5 REDFINITION ET POLYMORPHISME................................................................................................................................................53 2.3 DFINIR UN INDEXEUR POUR UNE CLASSE....................................................................................................................................... 56 2.4 LES STRUCTURES......................................................................................................................................................................... 61 2.5 LES INTERFACES..........................................................................................................................................................................64 2.6 LES ESPACES DE NOMS................................................................................................................................................................. 68 2.7 L'EXEMPLE IMPOTS................................................................................................................................................................ 70 3. CLASSES .NET D'USAGE COURANT................................................................................................................................74 3.1 CHERCHER DE L'AIDE AVEC SDK.NET....................................................................................................................................... 74 3.1.1 WINCV......................................................................................................................................................................................74 3.2 CHERCHER DE L'AIDE SUR LES CLASSES AVEC VS.NET................................................................................................................. 77 3.2.1 OPTION AIDE............................................................................................................................................................................77 3.2.2 AIDE/INDEX............................................................................................................................................................................. 79 3.3 LA CLASSE STRING......................................................................................................................................................................80 3.4 LA CLASSE ARRAY...................................................................................................................................................................... 82 3.5 LA CLASSE ARRAYLIST............................................................................................................................................................... 84 3.6 LA CLASSE HASHTABLE............................................................................................................................................................... 86 3.7 LA CLASSE STREAMREADER.........................................................................................................................................................89 3.8 LA CLASSE STREAMWRITER........................................................................................................................................................ 90 3.9 LA CLASSE REGEX...................................................................................................................................................................... 91 3.9.1 VRIFIER QU'UNE CHANE CORRESPOND UN MODLE DONN........................................................................................................... 93 3.9.2 TROUVER TOUS LES LMENTS D'UNE CHANE CORRESPONDANT UN MODLE..................................................................................... 94 3.9.3 RCUPRER DES PARTIES D'UN MODLE......................................................................................................................................... 95 3.9.4 UN PROGRAMME D'APPRENTISSAGE............................................................................................................................................... 96 3.9.5 LA MTHODE SPLIT....................................................................................................................................................................97 3.10 LES CLASSES BINARYREADER ET BINARYWRITER....................................................................................................................... 98 4. INTERFACES GRAPHIQUES AVEC VB.NET ET VS.NET.......................................................................................... 102 4.1 LES BASES DES INTERFACES GRAPHIQUES..................................................................................................................................... 102 4.1.1 UNE FENTRE SIMPLE............................................................................................................................................................... 102 4.1.2 UN FORMULAIRE AVEC BOUTON................................................................................................................................................. 103 4.2 CONSTRUIRE UNE INTERFACE GRAPHIQUE AVEC VISUAL STUDIO.NET........................................................................................... 106 4.2.1 CRATION INITIALE DU PROJET...................................................................................................................................................106 4.2.2 LES FENTRE DE L'INTERFACE DE VS.NET.................................................................................................................................107 4.2.3 EXCUTION D'UN PROJET...........................................................................................................................................................109 4.2.4 LE CODE GNR PAR VS.NET................................................................................................................................................109 4.2.5 COMPILATION DANS UNE FENTRE DOS........................................................................................................................................ 111 4.2.6 GESTION DES VNEMENTS........................................................................................................................................................112 4.2.7 CONCLUSION...........................................................................................................................................................................112 4.3 FENTRE AVEC CHAMP DE SAISIE, BOUTON ET LIBELL.................................................................................................................. 112 4.3.1 CONCEPTION GRAPHIQUE...........................................................................................................................................................112 4.3.2 GESTION DES VNEMENTS D'UN FORMULAIRE.............................................................................................................................. 115 4.3.3 UNE AUTRE MTHODE POUR GRER LES VNEMENTS.................................................................................................................... 117 4.3.4 CONCLUSION...........................................................................................................................................................................119 4.4 QUELQUES COMPOSANTS UTILES..................................................................................................................................................119 4.4.1 FORMULAIRE FORM.................................................................................................................................................................. 119 4.4.2 TIQUETTES LABEL ET BOTES DE SAISIE TEXTBOX....................................................................................................................... 120 4.4.3 LISTES DROULANTES COMBOBOX..............................................................................................................................................121 4.4.4 COMPOSANT LISTBOX.............................................................................................................................................................. 123 4.4.5 CASES COCHER CHECKBOX, BOUTONS RADIO BUTTONRADIO.......................................................................................................125 Services WEB 287

4.4.6 VARIATEURS SCROLLBAR..........................................................................................................................................................126 4.5 VNEMENTS SOURIS................................................................................................................................................................ 128 4.6 CRER UNE FENTRE AVEC MENU................................................................................................................................................130 4.7 COMPOSANTS NON VISUELS.........................................................................................................................................................134 4.7.1 BOTES DE DIALOGUE OPENFILEDIALOG ET SAVEFILEDIALOG.......................................................................................................134 4.7.2 BOTES DE DIALOGUE FONTCOLOR ET COLORDIALOG...................................................................................................................138 4.7.3 TIMER....................................................................................................................................................................................140 4.8 L'EXEMPLE IMPOTS.............................................................................................................................................................. 142 5. GESTION D'VNEMENTS.............................................................................................................................................. 147 5.1 OBJETS DELEGATE.....................................................................................................................................................................147 5.2 GESTION D'VNEMENTS............................................................................................................................................................ 148 5.2.1 DCLARATION D'UN VNEMENT................................................................................................................................................ 148 5.2.2 DFINIR LES GESTIONNAIRES D'UN VNEMENT............................................................................................................................. 148 5.2.3 DCLENCHER UN VNEMENT.................................................................................................................................................... 148 5.2.4 UN EXEMPLE...........................................................................................................................................................................149 6. ACCS AUX BASES DE DONNES.................................................................................................................................. 153 6.1 GNRALITS........................................................................................................................................................................... 153 6.2 LES DEUX MODES D'EXPLOITATION D'UNE SOURCE DE DONNES...................................................................................................... 154 6.3 ACCS AUX DONNES EN MODE CONNECT................................................................................................................................... 155 6.3.1 LES BASES DE DONNES DE L'EXEMPLE........................................................................................................................................ 155 6.3.2 UTILISATION D'UN PILOTE ODBC............................................................................................................................................. 159 6.3.2.1 La phase de connexion.....................................................................................................................................................160 6.3.2.2 mettre des requtes SQL................................................................................................................................................161 6.3.2.3 Exploitation du rsultat d'une requte SELECT.............................................................................................................. 162 6.3.2.4 Libration des ressources.................................................................................................................................................163 6.3.3 UTILISATION D'UN PILOTE OLE DB.......................................................................................................................................... 163 6.3.4 MISE JOUR D'UNE TABLE........................................................................................................................................................ 164 6.3.5 IMPOTS..............................................................................................................................................................................168 6.4 ACCS AUX DONNES EN MODE DCONNECT................................................................................................................................172 7. LES THREADS D'EXCUTION....................................................................................................................................... 173 7.1 INTRODUCTION..........................................................................................................................................................................173 7.2 CRATION DE THREADS D'EXCUTION..........................................................................................................................................174 7.3 INTRT DES THREADS............................................................................................................................................................... 176 7.4 ACCS DES RESSOURCES PARTAGES.........................................................................................................................................177 7.5 ACCS EXCLUSIF UNE RESSOURCE PARTAGE............................................................................................................................. 178 7.6 SYNCHRONISATION PAR VNEMENTS...........................................................................................................................................181 8. PROGRAMMATION TCP-IP............................................................................................................................................. 184 8.1 GNRALITS........................................................................................................................................................................... 184 8.1.1 LES PROTOCOLES DE L'INTERNET................................................................................................................................................184 8.1.2 LE MODLE OSI..................................................................................................................................................................... 184 8.1.3 LE MODLE TCP/IP................................................................................................................................................................185 8.1.4 FONCTIONNEMENT DES PROTOCOLES DE L'INTERNET...................................................................................................................... 187 8.1.5 L'ADRESSAGE DANS L'INTERNET................................................................................................................................................. 188 8.1.5.1 Les classes d'adresses IP.................................................................................................................................................. 189 8.1.5.2 Les protocoles de conversion Adresse Internet <--> Adresse physique.......................................................................... 190 8.1.6 LA COUCHE RSEAU DITE COUCHE IP DE L'INTERNET.....................................................................................................................190 8.1.6.1 Le routage........................................................................................................................................................................ 191 8.1.6.2 Messages d'erreur et de contrle...................................................................................................................................... 191 8.1.7 LA COUCHE TRANSPORT : LES PROTOCOLES UDP ET TCP.............................................................................................................192 8.1.7.1 Le protocole UDP : User Datagram Protocol.................................................................................................................. 192 8.1.7.2 Le protocole TCP : Transfer Control Protocol................................................................................................................ 192 Services WEB 288

8.1.8 LA COUCHE APPLICATIONS........................................................................................................................................................192 8.1.9 CONCLUSION...........................................................................................................................................................................193 8.2 GESTION DES ADRESSES RSEAU.................................................................................................................................................. 194 8.3 PROGRAMMATION TCP-IP....................................................................................................................................................... 196 8.3.1 GNRALITS..........................................................................................................................................................................196 8.3.2 LES CARACTRISTIQUES DU PROTOCOLE TCP...............................................................................................................................197 8.3.3 LA RELATION CLIENT-SERVEUR.................................................................................................................................................. 197 8.3.4 ARCHITECTURE D'UN CLIENT......................................................................................................................................................197 8.3.5 ARCHITECTURE D'UN SERVEUR................................................................................................................................................... 198 8.3.6 LA CLASSE TCPCLIENT............................................................................................................................................................ 198 8.3.7 LA CLASSE NETWORKSTREAM...................................................................................................................................................198 8.3.8 ARCHITECTURE DE BASE D'UN CLIENT INTERNET............................................................................................................................199 8.3.9 LA CLASSE TCPLISTENER......................................................................................................................................................... 199 8.3.10 ARCHITECTURE DE BASE D'UN SERVEUR INTERNET...................................................................................................................... 200 8.4 EXEMPLES................................................................................................................................................................................ 200 8.4.1 SERVEUR D'CHO..................................................................................................................................................................... 200 8.4.2 UN CLIENT POUR LE SERVEUR D'CHO......................................................................................................................................... 202 8.4.3 UN CLIENT TCP GNRIQUE..................................................................................................................................................... 204 8.4.4 UN SERVEUR TCP GNRIQUE.................................................................................................................................................... 209 8.4.5 UN CLIENT WEB..................................................................................................................................................................... 215 8.4.6 CLIENT WEB GRANT LES REDIRECTIONS.....................................................................................................................................217 8.4.7 SERVEUR DE CALCUL D'IMPTS.................................................................................................................................................. 219 9. SERVICES WEB................................................................................................................................................................... 225 9.1 INTRODUCTION..........................................................................................................................................................................225 9.2 LES NAVIGATEURS ET XML...................................................................................................................................................... 225 9.3 UN PREMIER SERVICE WEB........................................................................................................................................................ 226 9.3.1 VERSION 1............................................................................................................................................................................. 226 9.3.2 VERSION 2............................................................................................................................................................................. 232 9.3.3 VERSION 3............................................................................................................................................................................. 234 9.3.4 VERSION 4............................................................................................................................................................................. 235 9.3.5 CONCLUSION...........................................................................................................................................................................236 9.4 UN SERVICE WEB D'OPRATIONS................................................................................................................................................. 236 9.5 UN CLIENT HTTP-POST........................................................................................................................................................ 241 9.6 UN CLIENT SOAP.................................................................................................................................................................... 250 9.7 ENCAPSULATION DES CHANGES CLIENT-SERVEUR.........................................................................................................................254 9.7.1 LA CLASSE D'ENCAPSULATION....................................................................................................................................................254 9.7.2 UN CLIENT CONSOLE................................................................................................................................................................ 257 9.7.3 UN CLIENT GRAPHIQUE WINDOWS............................................................................................................................................... 260 9.8 UN CLIENT PROXY..................................................................................................................................................................... 264 9.9 CONFIGURER UN SERVICE WEB.................................................................................................................................................. 268 9.10 LE SERVICE WEB IMPOTS................................................................................................................................................... 270 9.10.1 LE SERVICE WEB....................................................................................................................................................................270 9.10.2 GNRER LE PROXY DU SERVICE IMPOTS....................................................................................................................................276 9.10.3 UTILISER LE PROXY AVEC UN CLIENT........................................................................................................................................ 277 10. A SUIVRE.........................................................................................................................................................................281

Services WEB

289