0
et de C 2.0
patrick smacchia
ditions OREILLY
18 rue Sguier
75006 PARIS
http://www.oreilly.fr
Toute reprsentation ou reproduction, intgrale ou partielle, faite sans le consentement de lauteur, de ses ayants droit, ou ayants cause, est illicite (loi du 11 mars 1957, alina 1er de larticle 40).
Cette reprsentation ou reproduction, par quelque procd que ce soit, constituerait une contrefaon sanctionne par les articles 425 et suivants du Code pnal. La loi du 11 mars 1957 autorise
uniquement, aux termes des alinas 2 et 3 de larticle 41, les copies ou reproductions strictement rserves lusage priv du copiste et non destines une utilisation collective dune part et, dautre
part, les analyses et les courtes citations dans un but dexemple et dillustration.
propos de ce livre . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Lorganisation de ce livre . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Support . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Remerciements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Historique . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
10
15
15
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
17
20
25
28
34
Introduction au langage IL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
41
49
49
50
54
Fichiers de configuration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
58
vi
63
66
69
71
73
75
84
85
87
94
102
103
108
. . . . . . . . . . . . . . . . .
111
. . . . . . . . . . . . . . . . . . . . . . . . .
116
Facilits fournies par le CLR pour rendre votre code plus fiable . . . . . . . . . . .
125
CLI et CLS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
129
87
133
Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
133
Les processus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
134
Les threads . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
136
143
145
147
153
158
160
167
Timers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
169
171
176
Contexte dexcution . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
180
La gestion de la scurit
vii
185
185
187
CAS : Accorder des permissions en fonction des preuves avec les stratgies de scurit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
193
198
199
203
205
205
206
Support .NET pour les contrles des accs aux ressources Windows . . . . . . . . .
211
216
219
221
225
230
233
Le mcanisme de rflexion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
233
238
Les attributs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
248
255
265
Le mcanisme P/Invoke . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
265
272
277
278
288
Introduction COM+ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
295
296
299
viii
309
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
309
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
312
Le prprocesseur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
313
Le compilateur csc.exe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
317
Les alias . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
320
323
Les identificateurs
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
326
327
La mthode Main() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
334
10 Le systme de types
Stockage des objets en mmoire
337
. . . . . . . . . . . . . . . . . . . . . . . . . . . .
337
339
342
La classe System.Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
344
345
347
Boxing et UnBoxing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
350
353
358
Les structures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
364
Les numrations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
366
370
378
385
392
395
395
Notions et vocabulaire . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
395
396
Les champs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
397
Les mthodes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
400
Les proprits . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
407
ix
Les indexeurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
409
Les vnements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
411
416
417
Le mot-cl this . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
420
421
423
430
433
443
443
Lhritage dimplmentation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
445
. . . . . . . . . . . . . . . . . . . . . . . .
448
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
452
Les interfaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
456
462
Les oprateurs is et as . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
464
466
Labstraction
13 La gnricit
467
471
474
477
481
484
Lhritage et la gnricit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
486
487
491
493
499
467
501
501
503
509
511
515
517
519
522
522
524
. . . . . . . . . .
536
. . . . . . . . . .
539
. . . . . . . . . .
542
. . . . . . . . . .
Exemples avancs de lutilisation des itrateurs de C 2 . . . . . . . . . . . . . . . .
548
15 Collections
529
552
563
563
Les tableaux
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
565
Les squences . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
575
Les dictionnaires . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
582
587
591
595
16 Bibliothques de classes
Fonctions mathmatiques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
597
597
600
607
613
Le dbogage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
617
Les traces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
620
625
La console . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
629
xi
633
633
636
641
649
651
654
656
657
660
Support des protocoles SSL, NTLM et Kerberos de scurisation des donnes changes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
660
665
665
668
675
679
682
688
695
La bibliothque GDI+ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
695
19 ADO.NET 2.0
705
705
Introduction ADO.NET . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
707
712
720
723
DataSet typs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
731
735
736
738
xii
20 Les transactions
741
741
Le framework System.Transactions . . . . . . . . . . . . . . . . . . . . . . . . . . .
746
752
755
21 XML
759
Introduction XML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
759
762
765
766
769
771
774
775
779
783
22 .NET Remoting
785
Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
785
787
790
La classe ObjectHandle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
792
793
795
799
802
805
808
815
816
Proxys et messages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
817
Canaux (channels) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
830
Contexte .NET . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
842
Rcapitulatif . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
857
xiii
23 ASP.NET 2.0
859
Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
859
861
866
871
873
883
888
Pipeline HTTP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
891
897
902
903
905
908
Contrles utilisateurs
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
912
918
Sources de donnes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
929
935
Master pages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
947
953
954
Scurit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
957
Personnalisation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
966
970
WebParts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
973
987
Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
987
. . . . . . . . . . . . . . . . . . . . . . .
991
993
994
998
998
1000
1003
xiv
1008
1011
1013
1015
1021
1031
D Les outils
1033
Index
1035
Avant-propos
propos de ce livre
La documentation ocielle de Microsoft sur .NET est trs vaste et dcrit en dtail chaque
membre de chaque type du framework .NET. Elle contient aussi de nombreux articles concernant la description ou lutilisation de telle ou telle partie de .NET. En tant que dveloppeur,
je sais combien lutilisation de cette documentation est fondamentale lorsque lon dveloppe
avec les technologies Microsoft. Cependant, de part le volume de cette documentation, il est
assez dicile dacqurir une vision globale des possibilits de .NET. En outre, daprs ma
propre exprience, les nouvelles ides sacquirent mieux partir dun livre. Certes, on pourrait
imprimer les dizaines de milliers de pages des MSDN, mais vous auriez du mal les transporter
pour les lire au calme, dans un jardin ou sur votre canap.
Ce livre a donc t conu dans loptique dtre utilis conjointement avec les MSDN. Il nest pas
question ici dnumrer les dizaines de milliers de membres des milliers de types .NET mais
plutt dexpliquer et dillustrer avec des exemples concrets les multiples facettes du langage C ,
de la plateforme .NET ainsi que de son framework. Jespre quil rpondra aux problmatiques
que vous rencontrerez et quil vous accompagnera hors des sentiers battus dans votre dcouverte
de la technologie .NET.
Lorganisation de ce livre
Partie I : Larchitecture de la plateforme .NET
La premire partie dcrit larchitecture sous-jacente la plateforme .NET. Cest dans cette partie
que vous trouverez les rponses aux questions du type :
Quels sont les liens entre lexcution des applications .NET et le systme dexploitation sousjacent ?
Quelle est la structure des fichiers produits par la compilation de mon programme ?
Comment puis-je tirer parti de tout cela pour amliorer la qualit et les performances de
mes applications ?
Comment puis-je exploiter du code dj dvelopp sous Windows partir de mes applications .NET ?
Les collections.
Les classes de base classiques type calculs mathmatiques, dates et dures, rpertoires et
fichiers, traces et dbogage, expressions rgulires, console.
Remarques
Ce plan de prsentation de la technologie .NET permet davoir une vision globale. Nanmoins il
est bien vident quune technologie aussi vaste comporte de multiples facettes qui transcendent
ce dcoupage. Par exemple, nous avons choisi de placer la gestion de la synchronisation des accs
aux ressources dans larchitecture de la plateforme .NET du fait quelle se base sur les notions
sous-jacentes de threads et de processus. Cependant, en tant quensemble de classes utilisables
dans vos applications, la synchronisation fait aussi partie des classes de base du framework .NET.
En outre le langage C comporte des mots-cls spcialiss pour simplifier lutilisation de ces
classes. Enfin .NET Remoting prsente des techniques qui permettent une approche volue de
la synchronisation.
Cet ouvrage contient donc de nombreuses rfrences internes qui jespre, faciliteront vos recherches sur les dirents sujets exposs.
Support
considre comme la plus ardue mais aussi comme la plus fondamentale (et mon sens comme
la plus passionnante). Il nest pas possible de dvelopper correctement avec la technologie .NET
sans tenir compte des services de la plateforme sous-jacente.
Le lecteur dbutant pourra commencer par lapprentissage du langage C et des technologies
de dveloppement objet, tout en dcouvrant petit petit les possibilits de .NET. .NET est un
sujet trs vaste, qui a plus que doubl en volume avec la version 2.0. Aussi, nous nous sommes
eorc de rester prcis et concis.
Le lecteur expriment dans dautres technologies devrait pouvoir bnficier des explications
concernant les nombreuses possibilits rellement innovantes oertes par .NET.
Le lecteur expriment avec .NET 1.x se servira de lAnnexe B qui rfrence toutes les nouveauts apportes par .NET 2.0 prsentes dans le prsent ouvrage.
Support
Le prsent ouvrage est support sur mon site : http://www.smacchia.com
Vous pouvez y tlcharger les exemples de code fournis dans cet ouvrage. Nous croyons que bien
souvent un exemple vaut mieux quun long discours. Le livre contient 648 exemples dont 523
listings C et 65 pages ASP.NET 2.0.
Vous pouvez galement nous adresser vos remarques sur cet ouvrage en crivant aux ditions
OReilly :
18 rue Sguier, 75006 Paris
Email : info@editions-oreilly.fr
Remerciements
Je souhaite avant tout remercier mon amie Eli Ane pour son soutien qui ma tellement apport
durant la rdaction de cet ouvrage. Jai aussi beaucoup apprci le soutien de Francis, France,
Michel, Christine, Mathieu, Julien, Andre, Patrick, Marie-Laure et Philippe.
Merci Xavier Cazin des Editions OReilly pour son aide et son professionnalisme.
Mes remerciements vont aussi aux relecteurs et amis qui mont apports chacun de prcieux
conseils :
Alain Metge, 18 ans dexprience. Responsable de la cellule architecture logicielle aux autoroutes du Sud de la France
Dr. Bertrand Le Roy, 8 ans dexprience, participe depuis 3 ans la conception et au dveloppement de la technologie ASP.NET Microsoft Corporation.
Bruno Boucard, 18 ans dexprience, architecte/formateur la Socit Gnrale depuis 8 ans.
Microsoft Informed Architect.
Frdric De Lne Mirouze (alias Amthyste), spcialiste en dveloppement web, 20 ans dexprience, collabore notamment avec ELF, Glaxo, Nortel, Usinor. MCAD.NET.
Jean-Baptiste Evain, 3 ans dexprience, spcialiste du Common Language Infrastructure,
contributeur aux projets Mono et AspectDNG.
1
Aborder la plateforme .NET
Un ensemble de spcifications
La plateforme .NET se base sur de nombreuses spcifications, certaines maintenues par dautres
organismes que Microsoft. Ces spcifications dfinissent des nouveaux langages, comme le lan-
gage C et le langage IL ou des protocoles dchange de donnes, comme le format SOAP. Plusieurs autres initiatives dimplmentations de ces spcifications sont en cours de ralisation.
terme, .NET devrait donc tre disponible sur plusieurs systmes dexploitation et ne se cantonnera pas quau monde Microsoft. .NET est donc un nouvelle re dans le monde du dveloppement logiciel, comparable lre C, lre C++ et lre Java. Il est intressant de remarquer que ce
phnomne semble se reproduire priodiquement, approximativement tous les 7 ans. chaque
nouvelle re, la productivit des dveloppeurs augmente grce lintroduction de nouvelles
ides et les applications sont plus conviviales et traitent plus de donnes, notamment grce
la puissance croissante du hardware. La consquence est que lindustrie adopte ces nouvelles
technologies pour dvelopper des logiciels de meilleure qualit tout en rduisant les cots.
Un ensemble de classes de base utilisables partir de programmes dvelopps dans ces langages. On les dsigne parfois sous le terme de BCL (Base Class Library). Cest ce que nous
appellerons framework .NET tout au long de cet ouvrage.
Une couche logicielle respectant une spcification nomme CLI (Common Langage Infrastructure). Elle est responsable de lexcution des applications .NET. Cette couche logicielle
ne connat quun langage nomm langage IL (Intermediate Langage). Cette couche logicielle
est notamment responsable durant lexcution dune application, de la compilation du
code IL en langage machine. En consquence les langages supports par .NET doivent chacun disposer dun compilateur permettant de produire du code IL. Limplmentation Microsoft de la spcification CLI est nomme CLR (Common Langage Runtime).
Paralllement ces trois parties, il existe de nombreux outils facilitant le dveloppement dapplications avec .NET. On peut citer Visual Studio qui est un IDE (Integrated Development Environment, ou environnement de dveloppement intgr en franais) permettant de travailler notamment avec les langages C VB.NET et C++/CLI. La liste de ces outils est disponible dans larticle
.NET Framework Tools des MSDN. La plupart de ces outils sont dcrits dans cet ouvrage et
sont lists dans lAnnexe C.
Le dcoupage du prsent ouvrage se base principalement sur ces trois parties :
Historique
Le pass
Ds 1998, lquipe en charge du dveloppement du produit MTS (Microsoft Transaction Server)
souhaitait dvelopper un nouveau produit pour pallier les problmes de la technologie COM.
Ces problmes concernaient principalement le trop fort couplage entre COM et le systme sousjacent ainsi que la dicult dutilisation de cette technologie, notamment au niveau du dploiement et de la maintenance.
Historique
Outils
Implmentation du CLI
(Common Language Infrastructure)
CLR (Common Language Runtime)
Le prsent
Fin 2005, Microsoft publie la version 2.0 de .NET qui fait lobjet de cet ouvrage. Le nombre de
types de base a plus que doubl couvrant maintenant de nombreux aspects initialement omis
par les versions 1.x. Des amliorations, des volutions et des optimisations sont apparues tant
au niveau de la machine virtuelle en charge dexcuter les applications .NET quau niveau des
langages. Les outils de dveloppement et notamment loutil Visual Studio sont beaucoup plus
complets et conviviaux. Dailleurs, le sentiment gnral est que la qualit et lintgration des
outils est maintenant un lment prpondrant dune plateforme de dveloppement. La liste
des nouveauts couvertes dans le prsent ouvrage fait lobjet de lAnnexe B.
Paralllement, on assiste au dbut de la concrtisation de deux mthodologies dj bien intgrs
dans les autres plateformes de dveloppement : leXtreme Programming (ou XP ne pas confondre
avec Windows XP) et le dveloppement partir de modles.
LXP consiste rationaliser les mthodologies utilises pour dvelopper un systme dinformation en coordonnant au mieux les activits de tous les acteurs impliqus. Lide est de pouvoir
faire face aux direntes volutions et imprvus qui surviennent inexorablement dans le cahier
des charges. Pour cela, on parle aussi parfois de mthode agile. Lagilit dcoule dun certain
nombre de contraintes. Il faut avant tout tre lcoute du client en lui fournissant souvent
et rgulirement une version testable. Il faut aussi faciliter la communication et le partage des
connaissances entre les membres dune quipe grce des outils polyvalents disponibles en plusieurs versions, chacune adapte une fonction prcise. Le facteur humain est central dans lXP.
Dautres principes sont mis en uvre tels que la conception dune batterie de tests automatiques
excute rgulirement afin de signaler au plus tt les rgressions dues aux volutions. Cette
batterie est en gnral excute aprs une compilation complte de lapplication partir des
derniers sources. Le principe de daily build veut quune telle compilation soit eectue quotidiennement, en gnral pendant la nuit. Toutes ces ides sont maintenant faciles mettre en
uvre grce aux nouvelles extensions Team System proposes par Visual Studio 2005.
Le dveloppement partir de modles consiste gnrer automatiquement le code source
dune application directement partir dun modle. Ce modle est exprim en un langage
de haut niveau, spcialement adapt aux besoins fonctionnels de lapplication et donc trs
expressif. On parle de DSL (Domain Specific Language). Lavantage de cette approche est de
permettre lquipe de travailler sur des sources proches des spcifications, rduisant ainsi
les cycles dvolution et la complexit du code. Visual Studio 2005 propose des extensions
spcialises dans la conception et dans lexploitation des DSLs. Ces extensions proposent aussi
la visualisation de votre code source C ou VB.NET sous la forme de diagrammes comparables
ceux fournis par UML.
Le futur
Microsoft publiera Windows Vista durant lanne 2006. Cela marquera un tournant dcisif pour la
plateforme .NET puisque pour la premire fois, lenvironnement dexcution .NET sera fourni
de base avec un systme dexploitation. De nombreux nouveaux types .NET seront prsents
par Windows Vista pour permettre daccder aux fonctionnalits de ce systme dexploitation directement partir de votre code .NET. On peut citer notamment le nouveau framework de dveloppement dapplications graphiques WPF (Windows Presentation Framework) et le nouveau framework de dveloppement dapplication distribue WCF (Windows Communication Framework)
prsent brivement en page 1013.
Plus tard, en 2007 voire 2008, Microsoft publiera .NET 3.0 (nom de code Orcas). Cette version
sera surtout axe sur une intgration pousse des technologies introduites par Windows Vista
dans le framework et Visual Studio. Pour linstant, seule lquipe C commence faire part de ses
travaux de recherches en ce qui concerne la version 3.0 du langage. Ils se focalisent sur un framework dextension du langage et travaillent notamment sur une extension spcialise pour la
rdaction de requtes sur un ensemble de donnes quelconques (objets, relationnelles ou XML).
Des expressions lambda, qui sont un peu dans le mme esprit que les mthodes anonymes de
C 2.0 mais en plus pratiques, viendront sintgrer dans ces requtes. Dautres nouveauts sont
prvues telles que les types anonymes, le typage implicite des variables et des tableaux ainsi
quune syntaxe ecace dinitialisation des objets et des tableaux.
Trois quatre annes plus tard, une version 4.0 de .NET sera publie (nom de code Hawaii) mais
aucune information nest disponible pour linstant.
Le consortium W3C
Le 9 mai 2000, Microsoft et 10 autres entreprises dont IBM, Hewlett Packard, Compaq et SAP ont
propos au consortium W3C de maintenir le standard SOAP. Le standard SOAP dfinit un format de message bas sur XML. Les services web peuvent communiquer au moyen de messages
au format SOAP. Lide de la standardisation de ce format est de rendre les services web compltement indpendants dune entreprise ou dune plateforme. Le format SOAP est dcrit page
1000. Pour plus dinformations veuillez consulter la page http://www.w3.org/TR/SOAP.
Depuis, un certain nombre de spcifications visant tendre les fonctionnalits des services web
ont t soumis au W3C. Certaines sont en cours dimplmentation et certaines sont encore en
cours de validation (voir page 989 et 1011).
Le projet Mono
Le 9 juillet 2001, lentreprise Ximian, fonde par Miguel de Icaza, a annonc quelle dveloppait
une implmentation open source de .NET. La raison est que ses ingnieurs estiment que .NET
reprsente la meilleure technologie de dveloppement logiciel du moment. Le nom de ce projet
est Mono.
la mi 2003, lentreprise Novell rachte Ximian rcuprant ainsi Mono. Le 30 juin 2004, la version 1.0 du projet est publie. Le projet Mono devrait tre rapidement disponible en version
.NET 2.0 et C 2.0.
Le projet Mono comprend entre autres, un compilateur C (distribu sous licence GPL General Public Licence), limplmentation dune bonne partie des bibliothques .NET (distribues
sous licence MIT/X11) ainsi quune machine virtuelle qui implmente le CLI (distribue sous
licence LGPL Lesser GPL). Tout ceci est compilable sur Windows, sur Linux et sur plusieurs autres
systmes dexploitation type UNIX tels que Mac OS X. La home page du projet est http://www.
mono-project.com.
10
Malgr lombre que peut potentiellement faire le projet Mono la version commerciale de
.NET de Microsoft, ce dernier nest pas forcment contre cette initiative. John Montgomery, responsable Microsoft de .NET a dit : ...The fact that Ximian is doing this work is great. Its a validation
of the work weve done, and it validates our standards activities. Also, it has caused a lot of eyeballs in
the open source community to be directed to .Net, which we appreciate... . Le gant du logiciel nest
donc pas mcontent que le monde de lopen source ait une opportunit dutiliser .NET. Cela
reprsente autant de clients potentiels pour les produits dvelopps autour de .NET.
Sites franais
Voici des sites qui en plus de contenir des articles parmi les plus intressants disponible sur le
web, sont entirement en franais :
http://www.dotnetguru.org
http://www.microsoft.com/france/msdn
http://forums.microsoft.com/msdn/
11
http://fr.gotdotnet.com
http://www.sharptoolbox.com/
http://www.dotnet-fr.org
http://www.c2i.fr
http://www.csharpfr.com
http://www.dotnet-news.com/
http://www.labo-dotnet.com/
http://www.techheadbrothers.com
http://www.blabladotnet.com
http://www.programmationworld.com
http://www.essisharp.ht.st
Prcisons que le site dotnetguru (DNG) sort du rang grce ses nombreux articles de qualit et
grce aux entres pertinentes des blogs de ses auteurs.
Newsgroup en franais
Sur le serveur msnews.microsoft.com :
microsoft.public.fr.dotnet
microsoft.public.fr.dotnet.adonet
microsoft.public.fr.dotnet.aspnet
microsoft.public.fr.dotnet.csharp
Sites anglais
Voici dautres sites en anglais :
http://www.msdn.microsoft.com
http://www.gotdotnet.com
http://msdn.microsoft.com/msdnmag/
http://www.theserverside.net
http://www.dotnet247.com
http://www.15seconds.com
http://www.codeproject.com/
http://www.eggheadcafe.com/
http://www.devx.com/
http://channel9.msdn.com/
http://dotnet.sys-con.com/
http://dotnet.oreilly.com http://www.ondotnet.com/
http://www.dotmugs.ch/
12
http://www.asp.net/
http://www.ondotnet.com
http://dotnetjunkies.com/
http://www.codeguru.com
http://www.c-sharpcorner.com http://www.csharp-corner.com
http://www.devhood.com
http://www.developer.com
http://www.4guysfromrolla.com (ASP.NET)
La section download du site http://www.idesign.net
Newsgroup en anglais
Sur le serveur msnews.microsoft.com :
microsoft.public.dotnet.framework
microsoft.public.dotnet.framework.adonet
microsoft.public.dotnet.framework.aspnet
microsoft.public.dotnet.framework.clr
microsoft.public.dotnet.framework.performance
microsoft.public.dotnet.framework.remoting
microsoft.public.dotnet.framework.sdk
microsoft.public.dotnet.framework.webservices
microsoft.public.dotnet.general
microsoft.public.dotnet.languages.csharp
Blogs
Enfin, voici quelques blogs forte valeur ajoute sur le dveloppement logiciel orient sur les
technologies .NET :
BCL Team http://blogs.msdn.com/bclteam/
Bart De Smet (Divers) http://blogs.bartdesmet.net/bart/
Benjamin Mitchell (Web services, eXtreme Programming) http://benjaminm.net/
Brad Abrams (BCL, design) http://blogs.msdn.com/brada/default.aspx
Chris Brumme (CLR) http://blogs.msdn.com/cbrumme/
Chris Sells (Windows Form, divers) http://www.sellsbrothers.com/
Clemens Vaster (SOA, design, divers) http://staff.newtelligence.net/clemensv/
David M. Kean (FxCop, Windows Installer, divers) http://davidkean.net/
Dino Esposito (ASP.NET) http://weblogs.asp.net/despos/
Don Box (WCF, divers) http://www.pluralsight.com/blogs/dbox/default.aspx
13
2
Assemblages, modules, langage IL
Les assemblages sont les quivalents .NET des fichiers .exe et .dll de Windows. Ce sont donc les
composants de la plateforme .NET.
Dans le cas dun assemblage plusieurs modules, cest toujours ce module qui est charg
en premier par le CLR. Le module principal dun assemblage multi modules rfrence les
autres modules. Ainsi, lutilisateur dun assemblage na besoin de connatre que le module
principal.
Le module principal est un fichier dextension .exe ou .dll selon que son assemblage est un
excutable ou une bibliothque de types. Un module qui nest pas le module principal est un
fichier dextension .netmodule.
16
Fichiers de ressource
En plus du code .NET compil , un module peut physiquement contenir dautres types de ressources telles que des images bitmap ou des documents XML. De telles ressources peuvent aussi
tre contenues dans leur fichier dorigine (par exemple dextension .jpg ou .xml) et rfrences
par le module principal. Dans ce cas on dit que ces fichiers rfrencs sont des fichiers de ressources
de lassemblage. Dans ce chapitre, nous verrons que lutilisation de ressources constitue une
technique ecace pour globaliser une application.
1..*
1..*
Fichier
module
1..*
1..*
Assemblage
Type .NET
Ressource
Fichier
ressource
Ladage profr il y a trois dcennies qui disait quun logiciel passe 80% du temps dans 20%
du code est toujours dactualit. Si lon isole la grosse partie du code peu utilise dans des
modules spars, ces modules ne seront peut-tre jamais utiliss tous ensembles. Donc la
plupart du temps on conomisera les ressources ncessaires au chargement total de lassemblage dans le processus. Ces ressources sont la mmoire vive, les accs au disque dur, mais
aussi la bande passante des rseaux si lassemblage est stock sur une machine distante.
Un fichier de ressource ne sera rellement charg que lorsque le programme en aura rellement besoin. Si une application tourne en franais, on fait donc lconomie du chargement
des fichiers de ressources en anglais.
Si un mme assemblage est dvelopp par plusieurs dveloppeurs, il se peut que certains
prfrent le langage VB.NET tandis que dautres prfrent C . Dans ce cas chaque module
peut tre dvelopp dans un langage dirent.
17
Loutil ILMerge
Sachez qu linverse vous pouvez runir plusieurs assemblages au sein dun mme fichier dextension .exe ou .dll. Pour cela, il vous faut avoir recours loutil ILMerge distribu gratuitement
par Microsoft et tlchargeable partir du web. Les fonctionnalits de cet outil sont aussi exploitables programmatiquement grce une API documente. Loutil sait aussi tenir compte des
assemblages signs.
Structure du manifeste
Le manifeste contient les informations dauto description de lassemblage. Il y a quatre types
dinformations dauto description et le manifeste contient une table pour chacun de ces types :
18
Entte
CLR
Manifeste
Mtadonnes
Code
compil
en IL
Ressources
Autre module
Entte
PE
Entte
CLR
Mtadonnes
Code
compil
en IL
Ressources
La table FileDef : Cette table contient une entre pour chaque module et fichier de ressource
de lassemblage mis part le module principal (donc si un assemblage na quun module,
cette table est vide). Chaque entre inclut le nom du fichier (avec lextension), des drapeaux
dcrivant certaines caractristiques du fichier et une valeur de hachage du fichier.
La table ManifestResourceDef : Cette table contient une entre pour chaque type et chaque
ressource de lassemblage. Chaque entre contient un index vers la table FileDef pour indiquer dans quel fichier est le type ou la ressource. Dans le cas dun type, lentre contient aussi
un oset indiquant o se trouve physiquement le type dans le fichier. Une consquence
est que chaque compilation dun module implique la reconstruction du manifeste, donc la
recompilation du module principal.
La table ExportedTypeDef : Cette table contient une entre pour chaque type visible hors de
lassemblage. Chaque entre contient le nom du type, un index vers la table FileDef et un
oset indiquant o se trouve physiquement le type dans le fichier. Par souci dconomie
de place, les types visibles hors de lassemblage dfinis dans le module principal ne sont pas
rpts dans cette table. En eet, nous allons voir que ces types sont dj dcrits dans la
section mtadonnes.
19
La table ModuleDef : Cette table contient une seule entre qui dfinit le prsent module.
Cette entre contient notamment le nom du fichier avec son extension mais sans son chemin.
La table TypeDef : Cette table contient une entre pour chaque type dfini dans le module.
Chaque entre contient le nom du type, le type de base, les drapeaux du type (public,
internal, sealed etc), et des index rfrenant les entres des membres du type dans les
tables MethodDef, FieldDef, PropertyDef, EventDef, ... (une entre pour chaque membre).
La table MethodDef : Cette table contient une entre pour chaque mthode dfinie dans
le module. Chaque entre contient le nom de la mthode, les drapeaux de la mthode
(public, abstract, sealed etc), loset permettant de situer physiquement dans le module
le dbut du code IL de la mthode et une rfrence vers la signature de la mthode, qui est
contenue dans un format binaire dans un tas appel #blob dcrit plus loin.
Il y a aussi une table pour les champs (FieldDef), une pour les proprits (PropertyDef), une
pour les vnements (EventDef) etc. La dfinition de ces tables est standard et chaque table a un
numro cod sur un octet. Par exemple toutes les tables MethodDef de tous les modules .NET
ont le numro de table 6.
La table AssemblyRef : Cette table contient une entre pour chaque assemblage rfrenc
dans le module (i.e chaque assemblage qui contient au moins un lment rfrenc dans
le module). Chaque entre contient les quatre composantes du nom fort savoir : le nom
de lassemblage (sans chemin ni extension), le numro de version, la culture et le jeton de
cl publique (ventuellement la valeur nulle si il ny en a pas).
La table ModuleRef : Cette table contient une entre pour chaque module du mme assemblage rfrenc dans le module (i.e chaque module qui contient au moins un lment rfrenc dans le module). Chaque entre contient le nom du module avec son extension.
La table TypeRef : Cette table contient une entre pour chaque type rfrenc dans le module. Chaque entre contient le nom du type et une rfrence vers l o il est dfini. Si le
type est dfini dans ce module ou dans un autre module du mme assemblage, la rfrence
indique une entre de la table ModuleRef. Si le type est dfini dans un autre assemblage, la
rfrence indique une entre de la table AssemblyRef. Si le type est encapsul dans un autre
type, la rfrence indique une entre de la table TypeRef.
La table MemberRef : Cette table contient une entre pour chaque membre rfrenc dans
le module. Un membre peut tre par exemple une mthode, un champ ou une proprit.
Chaque entre est constitue du nom du membre, de sa signature et dune rfrence vers
la table TypeRef.
La dfinition de ces tables est aussi standard et chaque table a un numro cod sur un octet. Par
exemple toutes les tables MemberRef de tous les modules .NET ont le numro de table 10.
20
Les tas
En plus de ces tables la section des mtadonnes de type contient quatre tas (heap) nomms
#Strings, #Blob, #US et #GUID.
Le tas #Strings contient des chanes de caractres comme le nom des mthodes. Ainsi les
lments des tables MethodDef ou MemberRef ne contiennent pas de chanes de caractres
mais rfrencent les lments du tas #string.
Le tas #Blob contient des informations binaires, comme la signature des mthodes stocke sous une forme binaire. Ainsi les lments des tables MethodDef ou MemberRef ne
contiennent pas les signatures des mthodes mais rfrencent des lments du tas #blob.
Le tas #US (pour User String) contient les chanes de caractres dfinies directement au sein
du code.
Le tas #GUID contient les GUID dfinis et utiliss dans le programme. Un GUID est une
constante de 16 octets, utilise pour nommer une ressource. La particularit des GUID est
que vous pouvez les gnrer partir doutils tels que guidgen.exe, de faon tre quasicertain davoir fabriqu un GUID unique au monde. Les GUID sont particulirement utiliss dans la technologie COM.
Les mtadonnes de type sont trs importantes dans larchitecture .NET. Elles sont rfrences
par les jetons de mtadonnes du code IL qui font lobjet de la section page 46. Cette section
prsente un exemple qui souligne limportance des tables et des tas de la section mtadonnes
de type. Les mtadonnes de type sont aussi utilises par la notion dattributs .NET (dcrite page
248) et le mcanisme de rflexion (dcrit page 233).
Le tas #
Certains documents se rfrent parfois un tas nomm # . Ce tas spcial contient en fait toutes
les tables de mtadonnes, y compris celles du manifeste sil sagit dun module principal.
Un module Foo2.netmodule.
21
Placez dans le mme rpertoire les deux fichiers sources C suivant (Foo1.cs et Foo2.cs) ainsi
quun fichier au format jpeg nomm Image.jpg.
Foo2.cs
Exemple 2-1 :
namespace Foo {
public class UneClasse {
public override string ToString() {
return "Bonjour de Foo2" ;
}
}
}
Foo1.cs
Exemple 2-2 :
using System ;
using System.Reflection ;
[assembly: AssemblyCompany("Lentreprise")]
namespace Foo {
class Program {
public static void Main(string[] argv) {
Console.WriteLine("Bonjour de Foo1") ;
UneClasse a = new UneClasse() ;
Console.WriteLine( a ) ;
}
}
}
Comme nous souhaitons construire un assemblage avec plus dun module, nous navons pas
dautres choix que dutiliser le compilateur csc.exe en ligne de commande (car lenvironnement Visual Studio ne gre pas les assemblages multi modules). Le compilateur csc.exe est dcrit en dtails page 317.
Crer les fichiers Foo2.netmodule puis Foo1.exe en tapant dans lordre les lignes de commande
suivantes (le compilateur csc.exe se trouve dans le rpertoire <rpertoire-d-installation-deWINDOWS>\Microsoft.NET\Framework\v2.*) :
> csc.exe /target:module Foo2.cs
> csc.exe /Addmodule:Foo2.netmodule /LinkResource:Image.jpg
Foo1.cs
22
Vue du manifeste
En double cliquant sur le manifeste le texte suivant apparat dans une nouvelle fentre (certains
commentaires ont t enlevs pour plus de clart) :
.module extern Foo2.netmodule
.assembly extern mscorlib
{
.publickeytoken = (B7 7A 5C 56 19 34 E0 89 )
.ver 2:0:0:0
}
.assembly Foo1
{
.custom instance void
[mscorlib]System.Reflection.AssemblyCompanyAttribute::.ctor(string) =
( 01 00 0E 4C E2 80 99 65 6E 74 72 65 70 72 69 73 65 00 00 ) //
...L...entreprise..
.custom instance void [mscorlib]System.Runtime.CompilerServices.
CompilationRelaxationsAttribute::.ctor(int32) =
( 01 00 08 00 00 00 00 00 )
.hash algorithm 0x00008004
.ver 0:0:0:0
}
.file Foo2.netmodule
.hash = (80 CC 15 14 E2 AB E0 AF D6 BD 55 B9 1B 02 61 10
.file nometadata Image.JPG
.hash = (0D 84 86 DE 03 E0 05 68 9D 38 F4 B0 B6 19 66 BB
.class extern public Foo.UneClasse
{
.file Foo2.netmodule
.class 0x02000002
}
.mresource public Image.jpg
{
.file Image.JPG at 0x00000000
}
.module Foo1.exe
B4 CF AA 94 )
3D 73 76 06 )
23
// MVID: {3C680D21-A6C8-4151-A2A6-9B20B8FDDF27}
.imagebase 0x00400000
.file alignment 0x00000200
.stackreserve 0x00100000
.subsystem 0x0003
// WINDOWS_CUI
.corflags 0x00000001
// ILONLY
// Image base: 0x04110000
On voit clairement que les fichiers Foo2.netmodule et Image.jpg sont rfrencs. On remarque
aussi quun autre assemblage est rfrenc, cest lassemblage mscorlib qui contient entre
autre la classe object. Tous les assemblages .NET rfrencent lassemblage mscorlib car celuici contient les types de base. ce titre, lassemblage mscorlib joue un rle prpondrant et
particulier dans la plateforme .NET. Plus dinformations son sujet sont disponibles en page
94.
24
Options de ildasm.exe
Loutil ildasm.exe prsente des options trs intressantes comme la visualisation du code IL
binaire (option Show Byte) ou la prsentation du code source correspondant en C (option Show
Source Lines).
Avec ildasm.exe 2.0 certaines options sont disponibles par dfaut alors quen version 1.x il fallait
utiliser le commutateur /adv en ligne de commande afin de les obtenir. Ces options sont la
possibilit dobtenir des statistiques quant la taille en octets de chaque section dun assemblage
et quant lachage des informations sur les mtadonnes.
Par lintermdiaire de ildasm.exe vous pouvez donc faire du reverse engineering sur un assemblage. Vous pouvez rcuprer des informations comme le code IL des mthodes ou les noms
des lments dun assemblage (classes, mthodes...). En revanche, les commentaires du code
source dun assemblage ne sont pas conservs dans lassemblage. Ils ne peuvent donc pas tre
retrouvs avec ildasm.exe.
Loutil Reflector
Depuis plusieurs annes, lutilitaire Reflector dvelopp par Lutz Roeder a dtrn ildasm.exe
et est devenu loutil incontournable pour analyser des assemblages .NET. Cet outil est tlchargeable gratuitement http://www.aisto.com/roeder/dotnet/. Voici une copie dcran du traitement de notre exemple par Reflector :
25
De nombreux addins tel que statement graph de Jonathan de Halleux, qui permet de dcompiler une mthode sous la forme dun organigramme, Reflector Di de Sean Hederman qui
permet de mettre en vidence les dirences entre deux versions dun assemblage ou file
disassembler de Denis Bauer qui permet de rcuprer le code source dune application
partir de ses assemblages. Une consquence est que file disassembler permet de migrer une
application, par exemple de VB.NET vers C .
26
Les attributs utiliss pour la constitution du nom fort (voir un peu plus loin pour la dfinition dun assemblage nom fort) :
AssemblyKeyFile : spcifie le fichier dextension .snk (strong name key) gnr par lexcutable sn.exe.
AssemblyFlags : spcifie si lassemblage peut tre excut cte cte ou non. La notion
dutilisation cte cte dun assemblage est expose page 65. Si lexcution cte cte
est autorise, cet attribut spcifie si lutilisation cte cte peut tre faite dans un mme
domaine dapplication, dans un mme processus ou seulement sur la mme machine.
Les attributs relatifs lutilisation de lassemblage au sein dune application COM+. On peut
citer les attributs ApplicationID, Application-Name, ApplicationActivation.
Dans larticle Setting Assembly Attributes des MSDN vous pouvez avoir plus de dtails sur les
attributs standard relatifs aux assemblages.
Le numro majeur.
Le numro mineur.
Le numro de compilation.
27
Le numro de rvision.
Vous pouvez fixer le numro de version de lassemblage avec lattribut AssemblyVersion. Il est
possible dutiliser dans cet attribut un astrisque qui laisse le choix au compilateur de fixer les
numros de compilation et de rvision, par exemple 2.3.* . Dans ce cas le numro de compilation sera le nombre de jours couls depuis le 1er janvier 2000, et le numro de rvision sera
le nombre de secondes (divis par deux car 24*60*60=86400 et 86400 > 65536) coules dans
la journe. Ce processus de datation du numro de version dun assemblage est trs utile pour
obtenir des numros de version croissants mais toujours dirents.
Il existe en fait trois types de numros de version pour un mme assemblage ce qui entrane
une certaine confusion. Seul le numro de version de lassemblage est utilis par le CLR pour
permettre la possibilit de stockage dun assemblage cte cte. Les deux autres numros de
version, le numro de version du produit (fix par lattribut AssemblyInformationalVersion)
et le numro de version du fichier (fix par lattribut AssemblyFileVersion) sont purement
informatifs.
Lorsque vous estimez que plusieurs assemblages doivent constamment avoir le mme numro
de version, vous pouvez faire en sorte que leurs projets rfrencent le mme fichier qui contient
ce numro. Nanmoins, sachez que si vous avez rencontrez ce besoin, il est peut tre judicieux
de rassembler le code de vos assemblages dans un seul assemblage.
La dfinition des rgles dincrmentation des composantes des versions des assemblages est un
sujet sensible qui doit tre mrement rflchit. Il ny a pas de bonnes solutions dans labsolu
car ces rgles sont fonctions du modle commercial de chaque lentreprise. Elles varient selon
quun assemblage est utilis par un ou plusieurs produits, externes ou internes lentreprise,
selon que lentreprise vend des bibliothques de classes ou des produits excutables etc. Voici
quelques recommandations :
Le numro mineur : incrmenter lorsquune fonctionnalit a lgrement volu ou lorsquun membre visible de lextrieur (par exemple une mthode protge dune classe publique) a chang ou lorsquun bug majeur a t corrig.
28
Lutilisation de cet attribut permet de prciser zro, une ou plusieurs composantes du nom fort
dun assemblage amis selon les direntes versions dun assemblage avec lesquelles on souhaite
tre amis :
using System.Runtime.CompilerServices ;
...
[assembly:InternalsVisibleTo("AsmAmi1")]
[assembly:InternalsVisibleTo("AsmAmi2,PublicKeyToken=0123456789abcdef")]
[assembly:InternalsVisibleTo("AsmAmi3,Version=1.2.3.4")]
...
Le nom du fichier ;
le jeton de cl publique de la signature numrique (tous ces termes sont expliqus cidessous) ;
Le nom est fort dans le sens o il permet didentifier lassemblage dune manire unique. Cette
unicit est garantie par la seule composante jeton de cl publique .
Un nom fort est aussi sens rendre un assemblage infalsifiable. Richard Grimes dcrit lURL
http://www.grimes.demon.co.uk/workshops/fusionWSCrackThree.htm une mthode permettant de craquer un nom fort dun assemblage (i.e cette mthode permet de falsifier un assemblage sign). Cette mthode se base sur un bug du CLR version 1.1 corrig en version 2.0. Il ny
a plus notre connaissance de telles failles de scurit en .NET 2.0.
29
Avant de nous pencher sur la cration dun assemblage nom fort, nous vous conseillons de
vous familiariser avec les notions de cls publiques/cls prives et de signature numrique prsentes en page 223. En apposant une signature numrique leurs assemblages, un auteur ou
une entreprise peuvent lauthentifier. Microsoft et lorganisation ECMA ont chacun un couple
de cl publique/cl prive quils utilisent pour authentifier leurs assemblages.
Loutil sn.exe
La premire tape pour un dveloppeur ou une entreprise qui dsire crer des assemblages
noms forts est de crer un couple de cl prive/cl publique. Cette opration utilise des algorithmes mathmatiques complexes. Heureusement le Framework .NET met notre disposition
loutil sn.exe (sn pour strong name) qui eectue cette tche. Cet outil peut fabriquer un fichier
dextension .snk (pour strong name key en anglais) qui contient un couple de cl prive/cl publique en utilisant loption -k. Par exemple :
C:\Test>sn.exe -k Cles.snk
Microsoft (R) .NET Framework Strong Name Utility Version 2.0.XXXXX
Copyright (C) Microsoft Corporation. All rights reserved.
Key pair written to Cles.snk
C:\Test>
sn.exe permet aussi de visualiser la cl publique et la cl prive contenues dans un fichier dextension .snk avec loption -tp :
C:\Test>sn.exe -tp Cles.snk
Microsoft (R) .NET Framework Strong Name Utility Version 2.0.XXXXX
Copyright (C) Microsoft Corporation. All rights reserved.
Public key is
070200000024000052534132000400000100010051a7dadde83cf10e8b7c6cd99e4d062b
1aca430e11db76365ab29d6c31fc93a7bea6def9d7b2e8a7c568b0d5ada5e8e131cb98ea
3e9a876236b33b362e433fdd62bb4c5cc5ea23f1dfa76d35b5412d812f66d03e079009ea
76462392663bc08ab5f937524e794948532c679db5eda50210f8a8b2b8b186fcb342859c
48ea76d609d108b1957d3888f75b270cf85029ede8437c36b4ae59342c5fa7aacdb453c7
465cc7027405930627a5b153e5f48cdd0375840bf6feaa3548aa421ab5138fb095efa5d5
81ae61bd9248ac97293ee69b139ef9ae79d907c5cf2c194adf7c2723e269b5eef55157c4
095fccf436d7db1893aed8c63d57e9d5eba5c1dd88f8bda81b6c74b77899071823c85c86
2254865337d2b70d545d17de9b8471527bbd54d4e1bd6cb6b53fed9135c9c7b1b1af2b27
ab0414b423b61334c9c1adb0700145ba1354848b081e09e8a860d24fb9ea6c48ac2657f1
9ff1fab37a177744c377d9d7d09f34498901f4439bc6754b4ac0efcc4d84d4a6c22a05c2
eecaec3f7fabf8b45555d4788eaeda815cf743001477a8c31c24c04b4016f4ef3401617e
22441b95ca78265a0a6133150ca03c2886d4e3893f9d1dc6a3e2d8770a63b8fbd0db52d8
176bbda6e1f4074d9dfda916cf316294f0499eade4aa47d1b780627ab6fb7beb5aa48412
9062d3152e6b6585c865d319018727c1a34866484018f5f1c0ab0bf2b35e63a8a3bbf7a0
b6aeeb110f4b162426a977dc2034adf08ec41cc5d20f2d6beac92a1619aff0e25030e30a
02570eb9ad74eba0f2aba90b18789ae99f8da72
30
Rduire la taille des noms forts (qui incluent ces jetons la place de la cl publique elle
mme).
Rduire la taille des assemblages qui rfrencent de nombreux assemblages noms forts.
Signer un assemblage
Pour attribuer un nom fort un assemblage vous pouvez soit utiliser les options /keycontainer
et /keyfile du compilateur csc.exe soit utiliser le menu Proprit du projet Signature Signe
lassemblage de Visual Studio 2005.
Vous pouvez aussi utiliser lattribut dassemblage AssemblyKeyFile dans le code source du module principal. Cet attribut accepte un argument qui est le nom dun fichier dextension .snk.
Par exemple :
[AssemblyKeyFile("Cles.snk")]
Lorsque cet attribut est ajout, le compilateur signe lassemblage mais met un avertissement
vous mettant en garde quune des deux premires techniques cite est prfrable.
Lorsquun assemblage doit tre sign par une de ces trois techniques, le compilateur inclut une
signature numrique et la cl publique dans le corps de lassemblage. Lalgorithme utilis pour
hacher le contenu de lassemblage se nomme SHA-1 (Secure Hash Algorithm). Vous pouvez choisir un autre algorithme de hachage de lassemblage, comme le trs connu MD5 (MD pour Message Digest) au moyen de lattribut dassemblage AssemblyAlgorithmId qui prend en argument
une valeur de lnumration AssemblyHashAlgorithm. La taille dune valeur de hachage cre
par lalgorithme SHA-1 est de 20 octets. Cette taille peut varier selon lalgorithme utilis.
31
Le compilateur encrypte la valeur de hachage avec la cl prive et obtient ainsi la signature numrique RSA de lassemblage. La cl publique est insre dans la table AssemblyDef du manifeste
tandis que la signature numrique est insre dans une partie spciale de lentte CLR. Voici le
schma dcrivant le processus de signature dun assemblage :
Module principal
de foo1.exe
avant signature
Module principal
de foo1.exe
aprs signature
Signature
numrique
Entte PE
Entte CLR
Entte PE
Entte CLR
Encryption
avec la cl
prive
Manifeste
Signature
numrique
Manifeste
Fichier
cles.snk
Cl publique
Cl prive
Cl publique
Mtadonnes
Mtadonnes
Code compil
en IL
Code compil
en IL
Valeur de
hachage
Ressources
Ressources
Un exemple
Voici le code dun assemblage Foo1 :
Foo1.cs
Exemple 2-3 :
using System ;
namespace Foo {
class Program {
public static void Main( string[] argv ){
Console.WriteLine( "Bonjour de Foo1" ) ;
}
}
}
Compilons et signons cet assemblage avec la cl Cles.snk :
>csc.exe /keyfile:Cles.snk Foo1.cs
Analysons le manifeste de lassemblage Foo1.exe avec loutil ildasm.exe (pour plus de clart
nous avons enlev quelques commentaires) :
.assembly extern mscorlib
{
.publickeytoken = (B7 7A 5C 56 19 34 E0 89 )
.ver 2:0:0:0
}
.assembly Foo1
{
.custom instance void
// .z\V.4..
32
00 24 00 00
00 24 00 00
51 A7 DA DD
1A CA 43 0E
BE A6 DE F9
31 CB 98 EA
62 BB 4C 5C
2F 66 D0 3E
B5 F9 37 52
10 F8 A8 B2
.hash algorithm 0x00008004
.ver 0:0:0:0
04
52
E8
11
D7
3E
C5
07
4E
B8
80
53
3C
DB
B2
9A
EA
90
79
B1
00
41
F1
76
E8
87
23
09
49
86
00
31
0E
36
A7
62
F1
EA
48
FC
94
00
8B
5A
C5
36
DF
76
53
B3
00
04
7C
B2
68
B3
A7
46
2C
42
00
00
6C
9D
B0
3B
6D
23
67
85
00
00
D9
6C
D5
36
35
92
9D
9C
06
01
9E
31
AD
2E
B5
66
B5
48
02
00
4D
FC
A5
43
41
3B
ED
EA
00
01
06
93
E8
3F
2D
C0
A5
76
00
00
2B
A7
E1
DD
81
8A
02
D6 )
}
.module Foo1.exe
// MVID: {5DD7C72B-D1C1-49BB-AB33-AF7DA5617BD1}
.imagebase 0x00400000
.file alignment 512
.stackreserve 0x00100000
.subsystem 0x00000003
.corflags 0x00000009
// Image base: 0x03240000
La cl publique est bien la mme que celle que lon a surligne plus haut, lors de lanalyse du
fichier Cles.snk. Loutil ildasm.exe ne nous permet pas de visualiser ni le jeton de cl publique
ni la signature numrique. Loutil sn.exe nous permet de visualiser le jeton de cl publique avec
loption -T :
C:\Code\CodeDotNet\ModuleTest>sn.exe -T Foo1.exe
Microsoft (R) .NET Framework Strong Name Utility Version 2.0.XXXXX
Copyright (C) Microsoft Corporation. All rights reserved.
Public key token is c64b742bd612d74a
Lassemblage mscorlib est rfrenc et son jeton de cl publique est connu. Cest normal,
puisque ce jeton fait partie intgrante du nom fort de lassemblage. En fait le jeton de cl
publique a t cr pour le rfrencement dautres assemblages. Nous rappelons que la taille
dune cl publique tant de 128 octets, la taille des assemblages rfrenant beaucoup dautres
assemblages aurait t trop grosse sans la technique du jeton cl publique.
Un assemblage nom fort ne peut rfrencer un autre assemblage qui na pas de nom fort. En
revanche, un assemblage sans nom fort, peut rfrencer un assemblage nom fort.
Dans le cas o un assemblage est constitu de plusieurs modules, les modules autres que le
module principal nont pas de cl publique. En revanche durant la compilation du module
principal, le compilateur calcule une valeur de hachage pour chaque module. Ces valeurs sont
33
intgres dans les entres de la table FileDef du manifeste. Ces valeurs sont donc prises en compte
lors du calcul de la valeur de hachage du module principal. Grce cette astuce les modules dun
assemblage nom fort sont eux aussi nomms dune manire unique et infalsifiables (en .NET
2.0), sans intgrer une signature numrique.
Il faut dabord construire un fichier dextension .snk qui ne contient quune cl publique.
Pour cela il faut utiliser loption -p de sn.exe.
C:\Code>sn -p Cles.snk ClePublique.snk
Microsoft (R) .NET Framework Strong Name Utility Version 2.0.XXXX
Copyright (C) Microsoft Corporation. All rights reserved.
Public key written to ClesPubliques.snk
C:\Code>
Le fichier ClePublique.snk est distribu aux dveloppeurs qui lutilisent durant le dveloppement des assemblages la place du fichier Cles.snk (donc dans lattribut AssemblyKeyFile).
Lattribut dassemblage AssemblyDelaySign initialis avec largument boolen true, doit
tre utilis. Le compilateur comprend quil ne doit pas signer le module principal. Cependant le compilateur prvoit lespace ncessaire la signature, dans le module principal,
insre la cl publique dans le manifeste et calcule la valeur de hachage des modules sans
manifeste.
Lorsque les quipes de dveloppement fournissent des assemblages prts tre packags et
dploys, il sut de signer les assemblages avec loption -R de sn.exe.
C:\Code>sn.exe -R Foo1.exe Cles.snk
Microsoft (R) .NET Framework Strong Name Utility Version 2.0.XXX
Copyright (C) Microsoft Corporation. All rights reserved.
Assembly Foo1.exe successfully re-signed
C:\Code>
34
Vous pouvez aussi retarder la signature dun assemblage avec le compilateur csc.exe en utilisant
conjointement les options /delaysign, /keycontainer et /keyfile.
La technique de la signature retarde peut tre aussi utilise pour signer des assemblages modifis aprs la compilation, par exemple avec un framework de programmation oriente aspect.
Fichiers de ressources
Concrtement, pour exploiter des ressources de type chanes de caractres ou des ressources stockes dans un format binaire (images, animations, sons...) dans un assemblage, il faut procder
selon les quatre tapes suivantes :
Editer le fichier de ressources dans un format exploitable par les humains (extension du fichier .txt ou .resx).
Convertir le fichier de ressources dans un format binaire adapt la lecture par un programme (extension du fichier .resources).
Exploiter les ressources dans votre code source au moyen de la classe System.Resources.
ResourceManager. Bien souvent, on se sert dune classe gnre qui encapsule laccs cette
classe.
Nous commencerons par montrer comment faire ces manipulations la main laide doutils en ligne de commande. Ensuite, nous verrons que Visual Studio 2005 permet dautomatiser
grandement ce processus.
Un fichier de ressources donn cible une seule culture. Il existe trois formats de fichiers de ressources, utiliss lors de direntes tapes du dveloppement dune application :
35
Les fichiers de ressources dextension .txt : Ces fichiers associent des identifiants aux
chanes de caractres dune culture. Par exemple voici un tel fichier dont la culture destination est langlais :
Exemple 2-4 :
MyRes.txt
Bonjour = Hello!
AuRevoir = Bye...
Les fichiers de ressources dextension .resx : Ces fichiers sont au format XML. Ils associent
des identifiants aux chanes de caractres dune culture. la dirence des fichiers de ressources dextension .txt, les fichiers dextension .resx peuvent aussi associer des identifiants des ressources stockes dans un format binaire. Dans un fichier dextension .resx,
linformation binaire est convertie au format UNICODE en utilisant lencodage Base64.
Lutilitaire resxgen.exe sert convertir une telle ressource (par exemple une image au format bmp ou jpg) en un fichier dextension .resx. Lutilisation de Visual Studio 2005 simplifie
considrablement la visualisation, ldition et la maintenance des fichiers dextension .resx
grce un diteur ddi.
Les fichiers de ressources dextension .resources : Ces fichiers sont logiquement quivalents aux fichiers dextension .resx. la dirence de ces derniers, ces fichiers sont dans un
format binaire. Ce sont ces fichiers qui sont intgrs dans les assemblages ltape de la
compilation.
Comme nous lavons expliqu, seul un fichier de ressources au format .resources peut tre
intgr dans un assemblage. Loutil resgen.exe utilisable en ligne de commande permet de
construire un fichier de ressources dans un de ces formats, partir dun autre fichier de ressources dans un autre format. resgen.exe connat le format du fichier en entre et du fichier
en sortie grce aux extensions de leurs noms. Voici un exemple dutilisation :
>resgen.exe MyRes.txt MyRes.resources
36
Exemple :
MyRes.cs
Program.cs
class Program {
static void Main() {
System.Console.WriteLine( MyRes.Bonjour ) ;
}
}
Il est intressant de comparer ce code au code dun programme qui nutilise pas la classe gnre
MyRes :
Exemple 2-6 :
Program.cs
using System.Resources ;
class Program {
static void Main() {
ResourceManager rm = new ResourceManager( "MyRes" ,
typeof(MyRes).Assembly) ;
System.Console.WriteLine( rm.GetString("Bonjour") ) ;
37
}
}
La classe ResourceManager prsente plusieurs versions surcharges de GetString(). De plus, elle
prsente la mthode plus gnrale GetObject() qui peut tre utilise pour charger des chanes
de caractres. Cette mthode peut aussi tre utilise pour charger des ressources stockes dans
un format binaire telles que des images. Par exemple :
...
System.Drawing.Bitmap image = (Bitmap) rm.GetObject("UneImage") ;
string s = (string) rm.GetObject("Bonjour") ;
...
Dans le cas dutilisation de la classe gnre par resgen.exe, une proprit de type System.Drawing.Bitmap reprsente laccs une ressource de type image.
...
System.Drawing.Bitmap image = MyRes.UneImage ;
...
Intressons nous maintenant la compilation de ce programme. Dans le cas o lassemblage
contient du code, il faut prciser les noms des fichiers de ressources dextension .resources
la compilation de lassemblage. Le compilateur csc.exe du langage C prvoit cet eet les
options /resource et /linkresource. Par exemple :
>csc.exe /resource:MyRes.resources,MyRes.resources Program.cs MyRes.cs
La syntaxe avec une virgule, qui spare le nom physique du fichier de ressources ( gauche de la
virgule) du nom logique ( droite de la virgule) qui sera utilis dans le code de lassemblage pour
lidentifier. Les noms logique et physique sont ici identiques et gaux "MyRes.resources". Notez que loption /resource copie physiquement le contenu du fichier ressource dans le corps du
module compil. Vous pouvez aussi utiliser loption /linkresource pour rfrencer le fichier
de ressources partir du module principal.
38
Nous utilisons en entre de al.exe avec loption /embed un fichier nomm MyRes.es-ES.ressources.
Ce fichier a t fabriqu avec loutil resgen.exe. partir du fichier MyRes.es-ES.txt suivant :
MyRes.es-ES.txt
Exemple 2-7 :
Bonjour = Hola!
AuRevoir = Adios...
Loption /embed fait en sorte que le contenu du fichier MyRes.es-ES.ressources soit physiquement inclus dans le fichier Program.Ressources.dll.
Le nom du module principal dun assemblage satellite (celui cr avec loutil al.exe) doit
tre
[nom de lassemblage qui contient le code qui manipule les ressources].
Resources.dll.
Un assemblage satellite correspondant une culture xx-XX doit se trouver dans le sous rpertoire xx-XX (par rapport au rpertoire de lassemblage qui contient le code qui manipule
les ressources). On remarque donc que les assemblages satellites relatifs un mme assemblage ont tous le mme nom. Seul les noms des rpertoires qui les stockent permettent de
dterminer pour chacun la culture laquelle il se rfre.
Ces rgles sont illustres par cette organisation des fichiers de notre exemple :
\Program.exe
\es-ES\Program.Resources.dll
...
// fichier fabriqu
e par csc.exe
// fichier fabriqu
e par al.exe
Program.cs
class Program {
static void Main() {
MyRes.Culture = new System.Globalization.CultureInfo("es-ES");
System.Console.WriteLine( MyRes.Bonjour ) ;
}
}
En analysant le code de la classe MyRes gnre par resgen.exe, on saperoit que la recherche de
ressource ne se fait pas selon la culture prcise par la proprit Thread.CurrentUICulture mais
selon la valeur de la proprit MyRes.Culture (qui correspond au champ MyRes.resourceCulture).
On peut rcrire ce programme sans utiliser la classe MyRes comme ceci :
39
Program.cs
using System.Resources ;
// Pour la classe ResourceManager.
using System.Threading ;
// Pour la classe Thread.
using System.Globalization ; // Pour la classe CultureInfo.
class Program {
static void Main() {
Thread.CurrentThread.CurrentUICulture = new CultureInfo("es-ES");
ResourceManager rm = new ResourceManager( "MyRes" ,
typeof(MyRes).Assembly) ;
System.Console.WriteLine(rm.GetString("Bonjour")) ;
}
}
Les programmes prcdents fonctionnent aussi si lassemblage satellite Program.Resources.dll
de culture es-ES se trouve dans le GAC. Comme tout assemblage situ dans le GAC, un tel assemblage satellite a un nom fort. Il est direnci de ces homologues relatifs dautres cultures
grce la composante culture de ce nom fort.
Lors de la compilation de Program.exe vous navez aucunement besoin de rfrencer un des
assemblages satellites. Il est ainsi possible de rajouter des assemblages satellites aprs la compilation de votre programme. Pour cela, vous devez faire en sorte que votre application rcupre
la culture avec laquelle elle doit sexcuter. Celle-ci peut par exemple tre rcupre partir dun
fichier de configuration ou partir de prfrences utilisateurs.
Program.cs
using System ;
using System.Globalization ; // Pour la classe CultureInfo.
class Program {
static void Main() {
CultureInfo spainCulture = new CultureInfo("es-ES") ;
if( MyRes.ResourceManager.GetResourceSet(
spainCulture , true, false )!= null ) {
MyRes.Culture = spainCulture ;
Console.WriteLine(MyRes.Bonjour) ;
}
else
Console.WriteLine("Assemblage satellite es-ES non trouv
e !") ;
}
}
40
Formatage et culture
Le formatage de certains lments prsents aux utilisateurs tels que les dates ou les nombres
peut dpendre de la culture. Le framework .NET se sert de la valeur de la proprit CultureInfo
Thread.CurrentCulture{get;set} pour dterminer quelle culture utiliser lors dune opration
de formatage. Par exemple, le programme suivant...
Introduction au langage IL
41
Exemple 2-11 :
using System.Threading ;
// Pour la classe Thread.
using System.Globalization ; // Pour la classe CultureInfo.
class Program {
static void Main() {
Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");
System.Console.WriteLine(System.DateTime.Now.ToString()) ;
Thread.CurrentThread.CurrentCulture = new CultureInfo("fr-FR");
System.Console.WriteLine(System.DateTime.Now.ToString()) ;
}
}
...ache ceci :
6/20/2005 10:54:10 PM
20/06/2005 22:54:10
Introduction au langage IL
Nous avons vu que les assemblages contiennent du code crit en langage IL (Intermediate Language). Le langage IL est en fait un langage objet part entire. Il constitue le plus petit dnominateur commun des langages .NET.
Il faut savoir que certaines documentations, notamment celles de Microsoft, utilisent le terme
de langage MSIL pour nommer limplmentation Microsoft du langage IL. Dautres documentations utilisent le terme CIL (Common Intermediate Langage) pour nommer le langage IL. Le langage IL est entirement spcifi par lorganisation ECMA. Vous pouvez trouver les spcifications
du langage IL sur le site de lECMA.
Les outils ildasm.exe et Reflector prsents prcdemment permettent de visualiser le code
en langage IL contenu dans un module. Nous allons voir et commenter du code IL bien quune
prsentation complte du langage IL dpasserait le cadre de cet ouvrage. Nous pensons quil est
bon que les dveloppeurs .NET aient une ide de ce quest le langage IL. Pour les programmeurs
Java cela ne sera pas sans rappeler le bytecode.
Vous pouvez compiler vos propres sources crites en langage IL, en assemblages, laide du
compilateur ilasm.exe fournis avec le framework .NET ( ne pas confondre avec loutil ildasm.
exe).
Comprenez bien que malgr sa ressemblance avec du langage machine, le langage IL nest support par aucun processeur (pour linstant du moins). Le code IL est compil lexcution, en
un langage machine cible. Ce langage machine est fonction du processeur de la machine. Ce mcanisme de compilation du code IL durant lexcution, est dcrit page 111. Cette ide dutiliser
un langage intermdiaire entre les langages de haut niveau et le langage machine nest pas nouvelle. Cette ide est exploite depuis longtemps sous Java et sous dautres langages/compilateurs.
42
Comme beaucoup dautres langages, IL est un langage avec une pile (stack en anglais). Une pile
est un espace mmoire contigu qui a la particularit davoir seulement un point daccs, appel le sommet de la pile. Tout comme une pile dassiette, vous pouvez ajouter une assiette au
sommet de la pile ou enlever une assiette au sommet de la pile. En informatique les piles ne
contiennent pas des assiettes mais des valeurs types.
Une pile appartient un thread. Pour chaque oprande dune opration excute par le thread
(oprations arithmtiques, appel de fonction...), il faut faire une copie de sa valeur au sommet
de la pile.
En IL, la pile est gre par le CLR. Dans ce cas le CLR joue le rle dun processeur virtuel .
Cest pour cela que dans le monde Java, lquivalent du CLR est nomm machine virtuelle .
Le CLR ne fonctionne pas exactement comme un processeur classique. En eet, les processeurs
travaillent avec une pile et des registres alors que le CLR remplit les mmes fonctions seulement
avec la pile.
Le fait qu aucun moment cette mthode ne charge plus de deux valeurs sur la pile, est
sauv dans un attribut appel .maxstack. La taille de la pile est donc borne durant la compilation.
Les variables locales sont types et numrotes.
Chaque instruction prend exactement un octet (IL_XXXX gauche reprsente loset de
linstruction IL correspondante, par rapport au dbut de la mthode).
Introduction au langage IL
43
ldc.i4.5 (load constant 5/charge constante 5) est une instruction IL qui pousse la valeur
constante entire 5 sur la pile, sous la forme dun entier cod sur quatre octets (idem pour
6). Comprenez bien que lentier 5 nest pas un paramtre de cette instruction. En consquence, linstruction ldc.i4.5 ne prend quun octet pour tre stocke. linverse, si lon
avait eu pousser la valeur constante entire 12345678 stocke sur quatre octets sur la pile,
on aurait utilis linstruction IL ldc.i4. Cette instruction IL prend en paramtre un entier
cod sur quatre octets. Cette instruction avec son paramtre aurait alors pris cinq octets
pour tre stock. On saperoit donc, qu linstar de ce qui se fait pour les langages machines, certaines instructions du langage IL ont t spcialement conues pour optimiser
le nombre doctets utiliss pour stocker le code IL. De plus, avec cette pratique, la vitesse
dexcution est aussi optimise, puisquon conomise le temps de lecture du paramtre.
ldloc.N (load local/charge locale) est une instruction IL qui pousse la valeur de la variable
locale numro N au sommet de la pile.
stloc.N (store local/enregistre locale) est une instruction IL qui dpile la valeur au sommet
de la pile et lenregistre dans la variable locale numro N.
add (add/ajoute) est une instruction IL qui dpile les deux valeurs au sommet de la pile
et les ajoute. Le rsultat est alors stock au sommet de la pile. Notez que lautre instruction IL add.ovf teste le dpassement de valeur et lance, le cas chant, une exception
OverflowException. De plus, on ne peut combiner tous les types pour les valeurs dentrs.
Dans le cas o une combinaison de type nest pas prvue, une exception est lance.
ret (return/retourne) est une instruction IL qui provoque le retour au code de la mthode
appelante.
Plus gnralement toutes les instructions IL, dont le nom commence par ld, chargent une valeur au sommet de la pile. chacune de ces instructions IL est associe une instruction IL symtrique, dont le nom commence par st, qui dpile la valeur au sommet et lenregistre.
44
On voit bien que, linstar de lexemple prcdent, les deux valeurs de i1 et i2 sont charges
sur la pile, avant lappel de la mthode Program.f(), grce linstruction IL call qui :
Introduction au langage IL
45
46
Notez quen page 498 nous exposons les modifications principales du langage IL pour le support
la gnricit.
0x70000001 : rfrence lentre dans le tas #userstring (0x70) reprsentant la chane de caractres "Hello world!" (0x000001).
Introduction au langage IL
47
0x0100000F : rfrence lentre dans la table TypeRef (0x01) reprsentant la classe System.
Console (0x00000F).
3
Construction, configuration et
dploiement des applications .NET
Soit avoir recours une tierce technologie telle que loutil open-source Nant, ou mme, utiliser des fichiers batch qui applent le compilateur C csc.exe.
MSBuild vise unifier toutes ces techniques. Ceux qui connaissent Nant ne seront pas dpayss
car MSBuild reprend beaucoup de concepts de cet outil. Latout majeur de MSBuild sur Nant
est dtre exploit par Visual Studio 2005. MSBuild na aucune dpendance par rapport Visual
Studio 2005 puisque, rappelons le, MSBuild fait partie intgrante de la plateforme .NET 2.0. En
revanche, les fichiers dextension .proj , .csproj, .vbproj etc gnrs par Visual Studio 2005
pour construire les projets sont rdigs au format XML MSBuild. la compilation, Visual Studio
2005 utilise les services de MSBuild. En outre, le format XML MSBuild est pleinement support
50
et document. Le support de MSBuild est donc une volution consquente de Visual Studio qui
jusquici, utilisait des scripts de compilation non documents.
Description
Copy
MakeDir
Construit un rpertoire.
Csc
Exec
AL
ResGen
La liste complte de ces types de tches standard est disponible dans larticle MSBuild Task Reference des MSDN. Un aspect particulirement intressant de MSBuild est quun type de tche
est matrialis par une classe .NET. Il est ainsi possible dtendre MSBuild avec de nouveaux
types de tches en fournissant vos propres classes. Nous reviendrons sur ce point un peu plus
loin.
Reprenons notre exemple dassemblage multi modules de la page 20. Rappelons que pour
construire cet assemblage constitu des trois modules Foo1.exe, Foo2.netmodule et Image.jpg
nous avions excuts les deux lignes de commande suivantes :
> csc.exe /target:module Foo2.cs
> csc.exe /Addmodule:Foo2.netmodule /LinkResource:Image.jpg
Foo1.cs
En plus, nous dsirons ici qu lissue de la construction de lassemblage, les trois modules se
trouvent dans un sous rpertoire \bin du rpertoire courant. Voici le script MSBuild Foo.proj
capable de raliser tout ceci :
51
Foo.proj
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="FooCompilation">
<MakeDir Directories= "bin"/>
<Copy SourceFiles="Image.jpg" DestinationFiles=".\bin\Image.jpg"/>
<Csc Sources="Foo2.cs"
TargetType="module"
OutputAssembly=".\bin\Foo2.netmodule" />
<Csc Sources="Foo1.cs"
TargetType="exe"
AddModules=".\bin\Foo2.netmodule" LinkResources="Image.jpg"
OutputAssembly=".\bin\Foo1.exe" />
</Target>
</Project>
On voit que la cible nomme FooCompilation est constitue de quatre tches :
Une tche de type Copy qui copie le fichier Image.jpg dans le rpertoire \bin ;
Les deux tches de type Csc qui invoquent chacune le compilateur csc.exe.
Pour excuter ce script de compilation, il faut constituer un rpertoire ayant le contenu suivant :
.\Foo.proj
.\Foo1.cs
.\Foo2.cs
.\Image.jpg
Allez dans ce rpertoire avec la fentre de commande SDK Command Prompt (Menu Dmarrer
Microsoft .NET Framework SDK v2.0 SDK Command Prompt) puis lancer la commande
>msbuild.exe. Chaque cible est obligatoirement nomme. Par dfaut msbuild.exe excute
seulement la premire cible. Vous pouvez spcifier une liste de noms de cibles spars par des
points virgules en ligne de commande avec loption /target (raccourcis /t). Vous pouvez aussi
spcifier une telle liste avec lattribut DefaultTarget de llment <Project>. Si plusieurs cibles
sont spcifies, lordre dexcution nest pas dfini.
Le comportement par dfaut de MSBuild est de stopper la construction ds quune tche met
une erreur. Vous pouvez souhaiter avoir un script de construction tolrant aux erreurs. Aussi,
chaque lment reprsentant une tche peut contenir un attribut ContinueOnError qui est positionn false par dfaut mais qui peut tre positionn true.
Notion de proprit
Pour vous permettre de paramtrer vos scripts, MSBuild prsente la notion de proprit. Une
proprit est un couple cl/valeur dfini dans un lment <PropertyGroup>. Les proprits
MSBuild fonctionnent comme un systme dalias. Chaque occurrence de $(cl
e) dans le script
est remplace par la valeur associe Typiquement, le nom du rpertoire /bin est utilis cinq
reprises dans notre script Foo.proj. Il constitue un bon candidat pour dfinir une proprit :
52
Exemple 3-2 :
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<OutputPath>.\bin</OutputPath>
</PropertyGroup>
<Target Name="FooCompilation">
<MakeDir Directories= "$(OutputPath)"/>
<Copy SourceFiles="Image.jpg"
DestinationFiles="$(OutputPath)\Image.jpg"/>
<Csc Sources="Foo2.cs"
TargetType="module"
OutputAssembly="$(OutputPath)\Foo2.netmodule" />
<Csc Sources="Foo1.cs"
TargetType="exe"
AddModules="$(OutputPath)\Foo2.netmodule"
LinkResources="Image.jpg"
OutputAssembly="$(OutputPath)\Foo1.exe" />
</Target>
</Project>
Vous pouvez en outre exploiter des proprits dfinies par dfaut par MSBuild telles que :
Type de tche
Description
MSBuildProjectDirectory
MSBuildProjetFile
MSBuildProjectExtension
MSBuildProjectFullPath
MSBuildProjectName
MSBuildPath
Lors de ldition des proprits avec Visual Studio 2005, vous vous apercevrez quun certains
nombre de cls vous sont proposes par lintellisense. OutputPath constitue une telle cl. Vous
pouvez utiliser ces cls mais rien ne vous empche de dfinir vos propres cls. Nous aurions
ainsi pu choisir pour cl RepDeSortie la place de OutputPath.
Notion ditem
La base de la construction dun projet par un script est la manipulation de rpertoires, de fichiers
(sources, ressources, excutables etc) et de rfrences (vers des assemblages, vers des services, vers
des classes COM, vers des fichiers ressources, vers des projets etc). On utilise le terme item pour
dsigner ces entits qui constituent les entres et les sorties de la plupart des tches. Dans notre
exemple, le fichier Image.jpg est un item consomm la fois par la tche Copy et par la seconde
tche Csc. Le fichier Foo2.netmodule est un item produit par la premire tche Csc et consomm
par la seconde tche Csc. Rcrivons notre script avec cette notion ditem :
53
Foo.proj
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup><OutputPath>.\bin</OutputPath></PropertyGroup>
<ItemGroup>
<Fichier_Image Include="$(OutputPath)\Image.jpg"/>
<NetModule_Foo2 Include="$(OutputPath)\Foo2.netmodule"/>
</ItemGroup>
<Target Name="FooCompilation">
<MakeDir Directories= "$(OutputPath)"/>
<Copy SourceFiles="Image.jpg"
DestinationFiles="@(Fichier_Image)"/>
<Csc Sources="Foo2.cs"
TargetType="module"
OutputAssembly="@(NetModule_Foo2)" />
<Csc Sources="Foo1.cs"
TargetType="exe"
AddModules="@(NetModule_Foo2)"
LinkResources="@(Fichier_Image)"
OutputAssembly="$(OutputPath)\Foo1.exe" />
</Target>
</Project>
Nous remarquons que lon utilise la syntaxe @(nom de litem) pour se rfrer un item. En
outre un item peut dfinir un ensemble de fichier grce la syntaxe wildcard. Par exemple, litem
suivant fait rfrence tous les fichiers sources C du rpertoire courant sauf Foo1.cs :
<cs_source Include=".\*.cs" Exclude=".\Foo1.cs" />
Foo.proj
54
Lorsquelles sont dfinies, les proprits standard Optimize et DebugSymbols sont automatiquement prises en comptes par les tches de type Csc.
Avant de lancer ce script, il faut prciser comme argument en ligne de commande la valeur de
la proprit Configuration. Cela peut se faire avec loption /property (raccourcis /p) :
>msbuild /p:Configuration=Release
Avec un peu dastuce, il est possible dutiliser une condition pour dfinir la valeur par dfaut
de la proprit Condition :
Exemple 3-5 :
Foo.proj
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Condition="$(Configuration)==">
<Configuration>Debug</Configuration>
</PropertyGroup>
<PropertyGroup Condition="$(Configuration)==Debug">
...
55
MSBuild, par exemple avec lattribut DefaultTargets, vous ne pouvez prsumer daucun
ordre dexcution. Vous pouvez cependant dfinir un systme de dpendance entre cibles avec
lattribut DependsOnTargets. MSBuild nexcute une cible que lorsque lensemble des cibles sur
lesquelles elle dpend a t excut. Naturellement, le moteur de MSBuild dtecte et sanctionne
dune erreur les dpendances circulaires entre cibles :
Exemple 3-6 :
Foo.proj
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"
DefaultTargets="FooCompilation">
...
<Target Name="CreeOutputPath" Condition="!Exists($(OutputPath))">
<MakeDir Directories= "$(OutputPath)"/>
</Target>
<Target Name="FooCompilation" DependsOnTargets="Cr
eeOutputPath"
Inputs="Foo2.cs;Foo1.cs"
Outputs="@(NetModule_Foo2);$(OutputPath)\Foo1.exe">
...
</Target>
</Project>
Transformations MSBuild
Vous avez la possibilit dtablir une correspondance biunivoque entre lensemble des items en
entre et lensemble des items en sortie dune cible. Pour cela, vous devez utiliser les transformations MSBuild dtailles dans larticle MSBuild Transforms des MSDN. Lavantage dutiliser
des transformations est que MSBuild ne dcide dexcuter la cible que si au moins un item en
entre est plus vieux que litem en sortie qui lui correspond. Logiquement, une telle cible est
moins souvent excute do un gain de temps.
Foo.proj
56
Exemple 3-8 :
Foo.target.proj
Une cible nomme CreateManifestResourceNames qui soccupe de lorganisation des fichiers ressources (transformation des fichiers .resx en .resources etc).
Une cible CoreCompile qui contient une tche Csc qui ralise eectivement la compilation.
Nous vous invitons consulter ces fichiers .targets situs dans le rpertoire $(MSBuildBinPath) qui est le rpertoire dinstallation de .NET 2.0 (i.e [Rep dinstallation de Windows]
\Microsoft.NET\Framework\v2.0.XXXXX).
En plus dimporter un tel fichier .targets, les fichiers gnrs par Visual Studio 2005 contiennent
essentiellement les dfinitions des proprits et items qui servent paramtrer les cibles gnriques.
Elle doit implmenter la mthode bool Execute() de linterface ITask. Cette mthode
contient le corps de la tche et doit retourner true si elle a t excute avec succs.
Elle peut prsenter des proprits dont les valeurs seront positionnes par MSBuild partir
des valeurs des attributs de la tche, avant lappel la mthode Execute(). Seules les proprit marques avec lattribut Required doivent tre obligatoirement initialises.
Voici en exemple le code dune tche nomme MyTouch qui met jour la date des fichiers spcifis par lattribut Files (nous prcisons quune telle tche nomme Touch est prsente par le
framework MSBuild) :
57
MyTask.cs
using System ;
using Microsoft.Build.Framework ;
using Microsoft.Build.Utilities ;
namespace MyTask {
public class MyTouch : Task {
public override bool Execute() {
DateTime now = DateTime.Now ;
Log.LogMessage(now.ToString() +
" est la nouvelle date des fichiers suivants:") ;
try {
foreach(string fileName in m_FilesNames) {
Log.LogMessage("
" + fileName) ;
System.IO.File.SetLastWriteTime(fileName, now) ;
}
}
catch (Exception ex) {
Log.LogErrorFromException(ex, true) ;
return false ;
}
return true ;
}
[Required]
public string[] Files {
get { return (m_FilesNames) ; } set { m_FilesNames = value ; }
}
private string[] m_FilesNames ;
}
}
Notez lutilisation de la proprit TaskLoggingHelper Task.Log{get;} qui permet dacher
des informations concernant le droulement de la tche.
Notre tche MyTouch doit tre enregistre auprs de tous les scripts MSBuild qui sont susceptibles dy avoir recours. Cela se fait au moyen dun lment <UsingTask>. Voici un tel script qui
met jour les dates des fichiers sources C du rpertoire courant (lassemblage MyTask.dll doit
tre situ dans le rpertoire C:\CustomTasks\) :
Exemple 3-10 :
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<UsingTask AssemblyFile="C:\CustomTasks\MyTask.dll"
TaskName="MyTask.MyTouch"/>
<ItemGroup>
<FichierSrcCs Include="*.cs"/>
</ItemGroup>
<Target Name="ToucheLesFichiersSrcCs" >
<MyTouch Files= "@(FichierSrcCs)"/>
</Target>
</Project>
Foo.proj
58
Il est intressant de noter que toutes les tches standard sont dclares par des lments <UsingTask> dans le fichier Microsoft.Common.Tasks. Ce fichier est automatiquement et implicitement import par msbuild.exe chaque excution. En analysant ce fichier, on voit que les
classes correspondantes aux tches standard sont dfinies dans lassemblage Microsoft.Build.
Tasks.dll. Vous pouvez ainsi avoir accs au code de ces tches en utilisant un outil tel que
Reflector.
Fichiers de configuration
Un assemblage excutable .NET a la possibilit davoir un fichier de configuration XML qui peut
tre modifi aprs linstallation. Le nom dun tel fichier de configuration doit obligatoirement
commencer par le nom du module contenant le manifeste (extension .exe comprise) auquel
on ajoute lextension .config (par exemple Foo.exe.config). Il doit tre plac imprativement
dans le mme rpertoire que lassemblage.
Visual Studio prsente des facilits pour ldition et la maintenance du fichier de configuration
dune application. Vous pouvez cliquer droit sur le projet qui dfinit un assemblage excutable
Add New Item... Application Configuration File Gardez le nom App.Config. la compilation, Visual Studio eectuera une copie du contenu de ce fichier dans un fichier nomm
[Nom de lassemblage executable avec extension].config situ dans le mme rpertoire
que lassemblage excutable produit.
Le fichier machine.config
La plupart des lments de configuration dune application .NET peuvent tre dclars dans
un fichier nomm machine.config situ dans le sous rpertoire /CONFIG du rpertoire dinstallation de .NET ([Rep_dinstallation_de_Windows]\Microsoft.NET\Framework\v2.0.XXXXX).
La valeur dun paramtre dfini dans le fichier machine.config nest prise en compte par une application .NET que si ce paramtre nest pas initialis dans son fichier de configuration. En outre,
certains paramtres tels que le modle de processus utilis par ASP.NET nont de sens quau
niveau de la machine. Ils ne peuvent ainsi qutre initialiss dans le fichier machine.config.
Il nest pas conseill de modifier le fichier de configuration de la machine. En eet, vous pouvez
altrer par inadvertance le comportement des applications installes sur votre machine. Cependant, dans le cas dune machine en production (un serveur ou une machine ddie une application) ce fichier peut constituer un moyen ecace pour dfinir une stratgie de configuration
globale la machine.
Fichiers de configuration
59
<connectionStrings> : Stocke les chanes de connexion aux bases de donnes (voir page
717).
<location> : Permet de dfinir les lments de configuration ASP.NET pour chaque page
web (voir page 890).
<protectedData> : Permet de dfinir des sections dinformation confidentielle dans un fichier de configuration (voir page 718).
<system.Data.[Founisseur de donn
ees]> : Permet de paramtrer les fournisseurs de donnes ADO.NET disponibles avec notamment des fichiers XML consomms par le framework
de schma (voir page 718).
<system.Diagnostics> : Contient des informations relatives aux traces (voir page 620).
<system.web> : Dfinit les paramtres utiliss par ASP.NET. La faon de configurer les applications ASP.NET est un vaste sujet auquel nous consacrons la section 23 Configuration
dune application ASP.NET , page 888.
MyApp.exe.config
60
Exemple 3-12 :
using System.Configuration ;
class Program {
static void Main() {
Configuration appCfg = ConfigurationManager.OpenExeConfiguration(
ConfigurationUserLevel.None) ;
AppSettingsSection appSettings = appCfg.AppSettings ;
int myEntier ;
if (int.TryParse( appSettings.Settings["MyEntier"].Value,
out myEntier)) {
System.Console.WriteLine(myEntier) ;
myEntier *= 10 ;
appSettings.Settings["MyEntier"].Value = myEntier.ToString() ;
appCfg.Save() ;
}
}
}
Si vous recompilez lapplication MyApp avec Visual Studio et que vous utilisez le fichier App.
Config, la valeur de MyEntier est rinitialise 1234 puisque le contenu du fichier de configuration MyApp.exe.config est cras par le contenu du fichier App.Config.
Il est clair que cette faon de procder prsente deux dsavantages majeurs :
La valeur dun paramtre nest pas type. Il a fallu explicitement parser une chaine de caractre pour obtenir la valeur du paramtre MyEntier dans une variable entire.
Le nom du paramtre nest pas vrifi par le compilateur puisquil est fourni sous forme
dune chane de caractres. Cela nuit la productivit des dveloppeurs qui ne bnficient
pas non plus de lintellisense.
MyApp.exe.config
Fichiers de configuration
61
PublicKeyToken=b77a5c561934e089" >
name="MySettings"
type="System.Configuration.ClientSettingsSection,
System, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089"
allowExeDefinition="MachineToLocalUser" />
</sectionGroup>
<sectionGroup name="applicationSettings"
type="System.Configuration.ApplicationSettingsGroup,
System, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089" >
<section
name="MySettings"
type="System.Configuration.ClientSettingsSection,
System, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089" />
</sectionGroup>
</configSections>
<userSettings>
<MySettings>
<setting name="MyEntierUsr" serializeAs="String">
<value>1234</value>
</setting>
</MySettings>
</userSettings>
<applicationSettings>
<MySettings>
<setting name="MyEntierApp" serializeAs="String">
<value>4321</value>
</setting>
</MySettings>
</applicationSettings>
</configuration>
<section
Voici le code de lapplication MyApp correspondante. On voit que les deux problmes de
lexemple prcdent sont rsolus grce lintroduction dune classe nomme MySettings drive de la classe System.Configuration.ApplicationSettingsBase. Cette classe prsente une
proprit pour chaque paramtre. Les proprits relatives aux paramtres utilisateurs sont marques avec lattribut UserScopedSettingAttribute tandis que les proprits relatives aux paramtres de lapplication sont marques avec lattribut ApplicationScopedSettingAttribute.
On remarque que la proprit MyEntierApp na pas daccesseur set. En eet, dans le cadre de
cette technique les paramtres de lapplication doivent tre accessible en lecture seule :
Exemple 3-14 :
using System.Configuration ;
class Program {
static void Main() {
MySettings mySettings = new MySettings() ;
int myEntierUsr = mySettings.MyEntierUsr ;
System.Console.WriteLine(myEntierUsr) ;
MyApp.cs
62
Mapper le nom des classes drives de la classe ApplicationSettingsBase avec les noms
des sections de configuration.
Mapper le nom des proprits de ces classes avec les paramtres de configuration.
63
Il est intressant danalyser votre fichier machine.config pour sapercevoir que toutes les
sections standard prsentes un peu plus haut sont dclares par des lments <section>.
chacune de ces sections correspond un type standard tel que ConnectionStringsSection,
AppSettingsSection ou ProtectedConfigurationSection. LExemple 3-12 montre comment
avoir accs une instance de AppSettingsSection afin de modifier le contenu de la section
<appSettings>. Remarquez que certains de ces types tels que SystemDiagnosticsSection ne
doivent tre utiliss que par le CLR. Aussi, ils ne vous sont pas accessibles.
Les valeurs des paramtres utilisateurs ne sont pas stockes dans le fichier de configuration de
lapplication mais dans un fichier nomm [nom de lutilisateur].config. Ce fichier est stock dans le rpertoire spcifi par la proprit statique System.Windows.Forms.Application.
LocalUserAppDataPath.
Dans le cas dune application dploye avec la technologie ClickOnce (prsente dans la suite du
prsent chapitre), les fichiers de configuration (de lapplication et des utilisateurs) sont dploys
dans le rpertoire ClickOnce de lapplication (voir page 83).
Par dfaut, les classes drives de ApplicationSettingBase utilisent en interne la classe
System.Configuration.LocalFileSettingsProvider pour avoir accs en lecture et en criture aux fichiers de configuration. Cette classe drive de la classe System.Configuration.
SettingsProvider. Vous pouvez construire vos propres classes drives de SettingsProvider
afin dimplmenter vos propres mcanisme de persistance des paramtres dune application
(dans une base de donnes, dans la base des registres, par lintermdiaire dun service web
etc). Il sut ensuite dtablir le lien entre vos classes drives de ApplicationSettingBase
et vos classes drives de SettingsProvider en marquant les premires avec des attributs
SettingsProviderAttribute.
Le contrle ToolStrip du framework Windows Form 2.0 prsente des facilits pour stocker directement son tat dans les paramtres de lapplication. En outre, vous pouvez prvoir le mme
genre de facilit pour vos propres types de contrles. Ceci fait lobjet de larticle Application
Settings for Custom Controls des MSDN.
Une autre stratgie doit tre utilise pour dployer les assemblages qui sont partags par
plusieurs applications. Cette stratgie prsente des fonctionnalits trs intressantes tel
quune gestion performante du versionning.
Dploiement XCopy
La stratgie de dploiement dassemblages XCopy est la plus simple quil puisse exister. Elle
consiste copier tous les assemblages concernant lapplication dployer, dans un mme rpertoire. En gnral les dveloppeurs crent une arborescence de rpertoires. Ni la base des registres
ni les Active Directory de Windows ne sont sollicits lors dun dploiement de type XCopy.
64
Pour ne perdre aucun fichier, il est conseill dencapsuler larborescence et ses fichiers dans un
seul fichier (par exemple une archive cab, zip ou rar, comme expliqu plus loin). La dsinstallation de lapplication consiste dtruire larborescence du disque dur. Le changement de version
consiste dtruire larborescence du disque dur et la remplacer par la nouvelle.
Ceux qui on dj eu grer un dploiement sous Windows, que ce soit en tant que dveloppeur,
quadministrateur ou en tant que simple utilisateur, mesurent le gain deort apport par les
dploiements de type XCopy.
Pour accder des fonctionnalits avances, telles que la cration de raccourcis bureaux ou
dicones dans la barre des tches, vous pouvez utiliser le service dinstallation de programmes
Windows nomms MSI, qui fait lobjet dune section un peu plus loin.
Comprenez bien que la grande majorit des assemblages ne sont pas partags entre plusieurs applications. Vous rencontrerez rarement la ncessit de stocker un assemblage dans
le rpertoire GAC.
Un assemblage doit avoir un nom fort pour pouvoir tre stock dans le GAC. La notion de nom
fort est expose page 28. Un assemblage sans nom fort ne peut pas tre stock dans le GAC. En
revanche un assemblage nom fort peut ne pas tre stock dans le GAC (i.e dployer avec la
stratgie de dploiement XCopy).
Chaque fois quun assemblage nom fort est charg dans un processus, la vrification de la
validit de sa signature numrique doit tre faite. Cependant, pour les assemblages partags du
rpertoire GAC, ce nest pas ncessaire. En eet, lorsquun assemblage partag est insr dans
le rpertoire GAC, la validit de sa signature numrique est automatiquement vrifie. Si la
signature numrique nest pas valide, lassemblage ne peut tre insr dans le rpertoire GAC.
Il y a donc un gain de performance lorsquun assemblage est charg partir du rpertoire GAC.
Il est intressant de remarquer que lorsque vous installez la plateforme dexcution .NET
sur une machine, les assemblages contenant les classes du framework .NET (System.dll,
System.Data.dll, System.Security.dll etc) sont installs dans le GAC. Une copie de chacun
de ces assemblages existe dans le rpertoire dinstallation de .NET (i.e [Rep dinstallation
de Windows]\Microsoft.NET\framework\v2.0.XXXXX\). Si vous installez Visual Studio, il aura
recours ces copies lors de lanalyse des rfrences vers les assemblages standard du framework
dun projet. En eet, Visual Studio ne sait pas rfrencer les assemblages stocks dans le GAC.
Seul le CLR sait exploiter les assemblages du GAC grce une couche de code nomme assembly
loader qui fait lobjet de la section page 103.
65
66
Seuls les administrateurs de la machine peuvent modifier le rpertoire GAC. Vous pouvez eectivement visualiser et modifier la structure interne du rpertoire GAC partir
dune fentre de commande. Cependant il est impratif de ne jamais chercher modifier
la structure du rpertoire GAC sans passer par loutil GACUtil.exe ou lextension du shell
ShFusion.dll (bien que cette manipulation soit possible avec un peu dastuce).
Le cache des images natives dcrit page 113, est inclus dans le rpertoire GAC. Ainsi lorsque
lon visualise le rpertoire GAC, que ce soit avec GacUtil.exe ou lextension ShFusion.dll, les
images natives sont aussi visualises.
67
Supposons quun diteur redistribue une nouvelle version de lassemblage partag parce que
des bugs ont ts corrigs. Cette version est compltement compatible avec la prcdente (compatibilit ascendante). Cette nouvelle version va tre installe cte cte avec lancienne version
dans le rpertoire GAC. Cependant les applications ne vont pas utiliser cette nouvelle version
car elles contiennent le numro de lancienne version cod en dur dans leurs manifestes.
Ce problme nexiste pas avec le modle de DLL classique puisque lancienne version de la DLL
est purement et simplement remplace par sa nouvelle version.
La solution
On pourrait rsoudre ce problme en recompilant et en rinstallant toutes les applications
clientes de lassemblage partag. La recompilation permettrait aux manifestes des assemblages
clients de mettre jour le numro de version de lassemblage partag. Clairement cette solution
lourde et fastidieuse est impraticable et il a fallu trouver une autre solution.
La solution propose par Microsoft est de crer un assemblage de stratgie dditeur (publisher policy
en anglais). Cet assemblage, plac dans le rpertoire GAC, nest l que pour crer une redirection
sur le numro de version dun autre assemblage partag, lui aussi stock dans le rpertoire GAC.
Lorsquune application demande la version 3.3.0.0 de lassemblage XXX elle utilisera en fait la
version 3.3.1.0 de lassemblage XXX, car lassemblage de stratgie dditeur associ XXX contient
linformation de redirection de 3.3.0.0 vers 3.3.1.0.
Lassemblage de stratgie dditeur nest quun fichier de configuration. La redirection est eectue par le CLR. En eet, cest le CLR qui est charg de localiser et de charger les assemblages
durant lexcution. Or, le CLR tient compte des assemblages de stratgie dditeur lors de cette
opration.
Un assemblage de stratgie dditeur ne redirige que les appels destins un assemblage. De plus
il peut y avoir plusieurs assemblages de stratgie dditeur pour un mme assemblage. Dans ce
cas, seule la version la plus rcente de lassemblage de stratgie dditeur sera prise en compte
par le CLR.
Le comportement par dfaut du CLR est de tenir compte des assemblages de stratgie dditeur.
Cependant pour une application donne, qui utilise un assemblage partag donn, lutilisateur
de lapplication peut dcider que le CLR ne doit pas tenir compte de lassemblage de stratgie
dditeur. Ceci est bien pratique dans le cas o lutilisateur se rend compte que la nouvelle version cre plus de problmes quelle nen rsout (et nous savons bien que ce genre de situation
arrive !). Nous exposons en page 106 comment raliser cette manipulation.
Comprenez bien que les assemblages de stratgie dditeur sont ncessaires lorsquun diteur a lgrement modifi un assemblage (pour une correction de bug en gnral) et na
pas modifi la compatibilit ascendante. Les assemblages de stratgie dditeur ne doivent
tre utiliss que dans le cadre prcis de cette problmatique.
68
loutil al.exe, qui est prsent en page 37. Le fichier de configuration XML doit ressembler
celui-ci :
Exemple 3-15 :
Foo2.config
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Foo2" culture= "neutral"
publicKeyToken="C64B742BD612D74A" />
<bindingRedirect oldVersion="1.0.0.0" newVersion="2.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>
Remarquez llment <assemblyIdentity> qui reprend les composantes du nom fort de lassemblage sur lequel doit se faire la redirection ( part le numro de version).
Remarquez llment <bindingRedirect> qui redirige le numro de version. Cet lment peut
rediriger un intervalle de numro de version, vers un numro de version, avec la syntaxe suivante : "0.5.0.0-1.2.23.3". Vous pouvez aussi avoir recours plusieurs lments <bindingRedirect>.
La cration du module de lassemblage de stratgie dditeur contenant le manifeste, se fait avec
loutil al.exe utilis avec la syntaxe suivante :
>al.exe /out:policy.1.0.Foo2.dll /version:1.0.0.0 /keyfile:Cles.snk
/linkresource:Foo2.config
Loption /out prcise le nom quaura le module contenant le manifeste. Ce nom est form
par des rgles bien prcises.
1.0 indiquera au CLR que cette stratgie dditeur sapplique aux demandes de lassemblage Foo2.dll avec la version 1.0. Seul les numros majeurs et mineurs sont prciss
ici ;
Foo2 indiquera au CLR que cette stratgie dditeur sapplique aux demandes de lassemblage Foo2.dll.
Loption /keyfile prcise le fichier contenant la paire de cls publique/priv qui signera
lassemblage de stratgie dditeur. Ces cls doivent tre les mmes que celles qui signent
Foo2.dll pour prouver au CLR que cest bien le mme diteur qui fournit Foo2.dll et sa
stratgie dditeur. De plus, en tant quassemblage partag qui doit tre stock dans le GAC,
lassemblage de stratgie dditeur doit avoir un nom fort et doit donc tre sign.
69
Foo2.config devient un module de lassemblage de stratgie dditeur grce cette option. Concrtement, le fichier Foo2.config nest pas inclus physiquement dans le module
policy.1.0.Foo2.dll mais est inclus logiquement dans lassemblage policy.1.0.Foo2.
Cab Projet : cre un fichier cab, qui rassemble les fichiers installer et les compresse dans
un fichier .cab (i.e une archive cab). Une section du prsent chapitre est consacre au dploiement par fichier .cab.
Smart Device Cab Projet : cre un fichier cab spcialement adapt au dploiement sur
Windows CE sur des machines type Pocket PC ou Smart Phone.
Merge Module Project : cre un module de dploiement. Un tel module peut tre intgr
dans dautres projets de dploiement. Ainsi un mme module de dploiement peut tre
commun plusieurs projets de dploiement. La notion de modules de dploiement nest
pas la mme que la notion de modules dassemblages.
Setup Project : Cre un projet de dploiement exploitant la technologie MSI. Cette technologie permet deectuer des actions en plus dinstaller des fichiers. Une section est consacre
la technologie MSI un peu plus loin.
Web Setup Project : Permet de dployer un projet dapplication web en installant les fichiers dans des rpertoires virtuels IIS. Le dploiement dapplication web ASP.NET est un
sujet particulier. Plus dinformation ce sujet sont disponibles en page 871.
Setup Wizard : Ceci est une aide pas pas au moyen dun assistant, pour construire un
projet de dploiement dun de ces types.
Depuis sa premire version la plateforme .NET prsente une technologie nomme No Touch
Deployment (NTD) spcialement conue pour le dploiement dapplication partir dinternet.
Cette technologie est toujours supporte par la version 2.0. Elle na pas volue car Microsoft a
prfr miser sur une technologie nomme ClickOnce qui est beaucoup plus adapte aux fortes
contraintes dun dploiement internet (scurit, bande passante, mises jour etc). Ces technologies font chacune lobjet dune section de ce chapitre.
Il ny a pas de type de projets de dploiement spciaux aux technologies NTD et ClickOnce. NTD
se gre partir de fichier de configuration et de dploiement XCopy sur le serveur permettant
le tlchargement de lapplication. En revanche, nous verrons que Visual Studio 2005 prsente
des facilits pour permettre le dploiement dune application avec la technologie ClickOnce.
Pour cela, il sut que lapplication soit reprsente par un projet de type application fentre
Windows Forms ou application console.
70
Les dploiements lourds qui impactent profondment le systme dexploitation en installant des assemblages dans le GAC, en enregistrant des classes COM dans la base des registres,
qui sont utilisables par plusieurs utilisateurs de la machine, qui requirent des droits levs type administrateurs pour leur excution etc. Clairement, seule la technologie MSI est
adapte au dploiement de ces applications. De part leur taille et pour des raisons de scurit, ce type de dploiement se fait parfois partir dun CD plutt que par lintermdiaire
dun rseau. Lutilisateur doit payer le prix dun tel dploiement : une gestion de scurit
primaire avec la technologie authenticode (si dploy partir dun rseau), dicult de
mises jour, exposition aux consquences de lenfer des DLLs, dlais dus la livraison dun
CD etc.
Les dploiements lgers dapplication purement .NET qui impactent peu le systme dexploitation. Les quatre techniques cab, XCopy, ClickOnce et NTD peuvent tre envisages pour
ce type de dploiement. ClickOnce est en gnral privilgier de part ces fonctionnalits
avances notamment quant la gestion de la scurit, des mises jour et de la bande passante. Mis part la compatibilit ascendante, il ny a pas de cas o la technologie NTD est
prfrable ClickOnce. Les techniques cab et XCopy prsentent lavantage dtre trs simples
tant pour le dveloppeur que pour lutilisateur en gnral habitu au copier/coller de fichiers. Aussi, ces deux techniques ne sont pas dnues dintrt quand il sagit de raliser des
dploiements trs simples. Dans ce cas, on prfrera srement la technologie cab puisquun
seul fichier est toujours plus facile acheminer de lditeur lutilisateur que plusieurs.
ClickOnce
MSI
Installation de fichiers.
X
X
Gestion de ODBC.
71
Self rparation
72
73
</IMPLEMENTATION>
</CODE>
</MSICD::NATIVECODE>
</SOFTPKG>
Les pages de proprits, accessibles par maj F4. Elles permettent essentiellement de grer les
configurations du projet setup (Debug /Release...) et dajouter une signature Authenticode au
projet.
74
La fentre de proprit du projet setup qui vous permet de configurer de nombreux attributs
associs au projet, comme son nom, sa culture ou le nom de lditeur, comme le montre la
Figure 3 -7.
75
comme sur la Figure 3 -8. Comprenez bien que la plupart des applications .NET ne devraient pas
utiliser la base des registres, aussi faites attention vos modifications.
76
Le manifeste de lapplication : Cest un fichier XML dextension .manifest qui rfrence lensemble des fichiers de lapplication et qui dfinit lensemble des permissions CAS ncessaires pour son excution. Son nom est la concatnation du nom de lassemblage contenant
le point dentre de lapplication (extension .exe comprise) avec lextension .manifest.
Ces deux fichiers manifestes doivent tre signs numriquement par la technologie authenticode
pour pouvoir tre utiliss. Cela se traduit par une prsence dun lment <signature> dans
77
chacun deux. La technologie authenticode est dcrit en page 230. Vous pouvez aussi consulter
larticle ClickOnce Deployment and Authenticode des MSDN.
Lorganisation des fichiers dans le rpertoire de dploiement est la suivante :
.\MyApp_1_0_0_0.application
// Manifeste de d
eploiement.
.\MyApp_1_0_0_0\MyApp.exe.manifest
// Manifeste de lapplication.
.\MyApp_1_0_0_0\MyApp.exe.deploy
// Fichiers de lapplication ...
.\MyApp_1_0_0_0\MyAppClassLib.dll.deploy // ... avec lextension .deploy.
.\MyApp_1_0_0_0\en-US\MyApp.resources.dll.deploy
...
On remarque quil existe un sous rpertoire par version de lapplication. Le nom de ce sous rpertoire ne contient pas ncessairement dindication sur le numro de version bien que ceci soit
une bonne pratique. Chacun de ces sous rpertoires contient le manifeste de lapplication relatif
la version ainsi que tous les fichiers dployer pour cette version. Une extension .deploy est
rajoute au nom de chacun de ces fichiers.
Le manifeste de dploiement est stock dans le rpertoire de dploiement. Puisquil rfrence
un manifeste dapplication qui est relatif une version de lapplication, il est lui-mme relatif
une version de lapplication. Aussi, il est judicieux que son nom soit la concatnation du nom
de lassemblage contenant le point dentre de lapplication (extension .exe non comprise) avec
une indication sur le numro de version suivie de lextension .application. Ainsi, plusieurs
manifestes de dploiement relatifs plusieurs versions dune mme application peuvent cohabiter dans le mme rpertoire de dploiement.
Utiliser le menu Properties Publish de Visual Studio 2005 sur un projet de type console ou
application Windows Forms.
Utiliser loutil fentr mageui.exe (mage pour Manifest Generation and Editing Tool) disponible dans le rpertoire SDK de linstallation de Visual Studio 2005.
Utiliser loutil en ligne de commande mage.exe disponible dans le mme rpertoire que
mageui.exe.
Loutil mage.exe est particulirement utile si vous souhaitez intgrer la cration des fichiers manifestes dans un script MSBuild. Loutil mageui.exe est surtout pdagogique car il permet dobtenir une vue prcise et comprhensible du contenu de chacun des fichiers manifestes. Lutilisation de ces outils est dcrite dans larticle Walkthrough : Deploying a ClickOnce Application
Manually des MSDN. Hormis ces deux raisons, nous vous conseillons davoir recours Visual
Studio 2005 qui prsente linterface suivante :
Application Files... : Permet de choisir lensemble des fichiers qui constituent lapplication.
Lassemblage excutable gnr par le projet courant fait automatiquement partie de cet
ensemble. Les assemblages rfrencs par ce projet, les assemblages satellites gnrs par ce
projet ainsi que tous autres fichiers relatifs ce projet font aussi partie de cet ensemble. Mis
78
part lassemblage excutable qui est forcment requis, chaque fichier peut tre marqu
comme prrequis, requis ou optionnel. Dans le premier cas, le fichier doit tre disponible
avant linstallation de lapplication. Dans le second cas le fichier doit tre tlcharg lors de
linstallation de lapplication. Dans le troisime cas le fichier doit faire partie dun groupe
de fichiers optionnels. Ces groupes permettent un tlchargement la demande des parties
fonctionnelles dune application. Nous revenons sur ce point un peu plus loin.
Prerequisites... : Permet de choisir les prrequis de linstallation de lapplication. Vous pouvez choisir notamment le framework .NET 2.0, SQL Server 2005 Express Edition mais aussi
nimporte quel application, fichier ou framework installer. Visual Studio 2005 vous permet
de gnrer un fichier setup.exe que la littrature anglo saxonne nomme bootstrapper. ClickOnce proposera au client de tlcharger et dexcuter le bootstrapper avant linstallation
de lapplication si sa machine na pas les prrequis. Vous pouvez spcifier do le bootstrapper doit tlcharger chaque prrequis (site de dploiement, site ociel du composant
etc). Techniquement lutilisateur na pas besoin dtre administrateur de la machine pour
excuter le bootstrapper. En pratique, il a souvent besoin de ltre puisque le bootstrapper doit
gnralement installer des composants qui requirent une installation avec la technologie
MSI. Plus dinformation concernant le bootstrapper sont disponible dans larticle Use the
Visual Studio 2005 Bootstrapper to Kick-Start Your Installation de Sean Draine consultable en ligne dans le MSDN Magazine doctobre 2004.
Updates... : Permet de positionner une politique de mise jour de lapplication. Une section est consacre aux mises jour un peu plus loin.
79
Options... : Permet de positionner les paramtres de lapplication tels que le nom de lditeur, du produit, la gnration dune page web daide au tlchargement, la culture cible
du dploiement, lutilisation de lextension .deploy etc.
Publish Wizard... et Publish Now : permet de publier lapplication, cest--dire de gnrer les deux fichiers manifestes et de construire larborescence de fichier que nous avons
prsent. Il faut publier votre application dans un rpertoire de dploiement spcifique
pour chaque culture supporte. Vous pouvez choisir si le rpertoire de dploiement est
un rpertoire virtuel IIS, un rpertoire FTP ou un rpertoire Windows classique que vous
pouvez alors dupliquer sur un CD.
Cette interface vous permet aussi de choisir si lapplication est disponible oine. Dans ce cas
un raccourci vers lapplication est install dans le menu des programmes et lutilisateur na pas
besoin dtre connect internet pour la lancer.
80
Contrairement la technologie MSI, vous ne pouvez pas personnaliser le processus de dploiement dune application ClickOnce. Concrtement, vous ne pouvez pas fournir de dialogues demandant lutilisateur des informations comme le rpertoire o installer lapplication. Cette
contrainte est essentielle pour garantir une scurit optimale.
Installation la demande
Nous avons vu quun fichier dune application peut tre marqu comme optionnel. Dans ce cas,
il fait partie dun groupe de fichiers optionnels. Tous les fichiers dun tel groupe sont tlchargs explicitement par le code de lapplication lorsque qu lexcution elle a besoin dun de ces
fichiers pour la premire fois. Cest linstallation la demande.
Le framework reprsent par lespace de nom standard System.Deployment permet au code
de votre application de tlcharger un groupe de fichier de lapplication non encore install. Ce code doit tre excut lors du dclenchement dun vnement type AppDomain.
AssemblyResolve ou AppDomain.ResourceResolve. On parle parfois dinstallation transactionnelle dun tel groupe de fichiers puisque soit ils sont tous installs soit aucun nest install.
Lexemple suivant expose un assemblage excutable MyApp qui rfrence un assemblage bibliothque MyLib. Supposons que dans un projet ClickOnce de dploiement, MyLib fasse partie dun
groupe de fichiers optionnels nomm MyGroup. Le code de la mthode AssemblyResolveHandler() est dclench lors de la premire excution de MyApp, lorsque MyLib nest pas trouv. Ce
code tlcharge les fichiers du groupe MyGroup puis rcupre et retourne lassemblage MyLib.
Notez la ncessit de la prsence de la classe Foo. Si nous ne nous servions pas dune classe
intermdiaire pour invoquer MyClass, le compilateur JIT compilerait la classe Program avant
davoir excut la mthode Main(). Lvnement AssemblyResolve serait donc dclench avant
mme que la mthode AssemblyResolveHandler() ait pu tre abonne :
Exemple 3-16 :
MyLib.cs
MyApp.cs
using System ;
using System.Reflection ;
using System.Deployment.Application ;
class Program {
public static void Main() {
AppDomain.CurrentDomain.AssemblyResolve += AssemblyResolveHandler ;
Console.WriteLine("Bonjour de MyApp.") ;
Foo.FooFct() ;
}
static Assembly AssemblyResolveHandler(object sender,
ResolveEventArgs args) {
if ( ApplicationDeployment.IsNetworkDeployed ) {
// La propriete CurrentDeployement retourne null
81
Aprs le dmarrage de lapplication pour un dmarrage rapide mais une prise en compte
des mises jour qu la prochaine excution.
Ou avant le dmarrage de lapplication pour sassurer que les utilisateurs excutent toujours
la version la plus rcente au prix dun dmarrage parfois ralenti.
Lors de la mise jour dune application la technologie ClickOnce ne tlcharge que les fichiers
qui ont chang. En outre lensemble des permissions accordes lors de linstallation dune application sera automatiquement hrit par les futures mises jour. Enfin, ds lors quune application a t au moins une fois mise jour sur une machine la technologie ClickOnce se met
stocker sur cette machine les donnes ncessaires pour ventuellement revenir la version prcdente. Ainsi, lutilisateur ne prend pas de risques en mettant jour une application puisquil a
la possibilit dans le menu de dsinstallation de programme de revenir la version prcdente.
82
83
de lapplication].exe. Cest cet assemblage excutable VSHost qui est lanc par Visual Studio
2005 lorsque vous demandez de dboguer lapplication. Le code de cet assemblage positionne
lensemble des permissions puis charge et excute lapplication.
Enfin, sachez que lintellisense de Visual Studio 2005 tient compte de lensemble des permissions
dfini dans le menu Security. Concrtement, il grise les noms des classes et mthodes du framework qui requirent des permissions non accordes.
84
Lors de la mise jour dune application, les anciens fichiers de donnes sont copis dans le nouveau rpertoire de donnes. Si ClickOnce tlcharge une nouvelle version dun fichier de donnes, celle-ci crase lancienne version. Cependant, pour viter la perte de donnes lancienne
version est alors copie dans un sous rpertoire inc.
Exemple 3-18 :
using System.Reflection ;
using System.Windows.Forms ;
class Program {
static void Main() {
Assembly asm = Assembly.GetExecutingAssembly() ;
MessageBox.Show("Mon CodeBase est:" + asm.CodeBase) ;
}
}
Louverture dinternet explorer sur cette URL est illustre par la figure suivante :
85
Le cache de tlchargement
Lorsquun assemblage est tlcharg pour la premire fois partir du web, il est automatiquement stock dans le cache de tlchargement (download cache en anglais). Le cache de tlchargement est un rpertoire entirement gr par le CLR. Le cache de tlchargement est indpendant du cache dinternet explorer ainsi que des dirents caches de la technologie ClickOnce. Les
avantages du cache de tlchargement sont les suivant :
Il vite de devoir tlcharger un assemblage accessible partir du web chaque fois quil
doit tre excut, do la notion de cache.
Il permet disoler physiquement les assemblages tlchargs du web des autres assemblages.
Le cache de tlchargement prsente les deux dirences suivantes avec le rpertoire GAC :
Le rpertoire GAC est global une machine. Il ne peut y avoir quun rpertoire GAC par
machine. En revanche, le CLR fait en sorte quil existe un cache de tlchargement par utilisateur dune machine. Ainsi, il peut y avoir plusieurs caches de tlchargement sur une
machine.
Le CLR accorde une plus grande confiance (i.e accorde plus de permissions) aux assemblages
contenus dans le rpertoire GAC quaux assemblages contenus dans le cache de tlchargement.
Enfin, sachez que loption /ldl de loutil gacutil.exe vous permet de visualiser le contenu du
cache de tlchargement :
C:>gacutil.exe /ldl
Microsoft (R) .NET Global Assembly Cache Utility. Version 2.0.XXXXX
Copyright (C) Microsoft Corporation 1998-2002. All rights reserved.
The cache of downloaded files contains the following entries:
Foo.exe, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null,
Custom=null
Number of items = 1
Plus dinformation sur la technologie No-Touch Deployment sont disponibles dans larticle NoTouch Deployment in the .NET Framework des MSDN.
86
Cela signifie que lors de linstallation de lapplication .NET, le framework .NET ne sera pas install sur la machine cible. Toutes les applications .NET ont besoin que le framework .NET soit
install sur la machine pour pouvoir tre excutes.
Lavertissement nous apprend que pour installer le framework .NET sur la machine cible, il faut
excuter le fichier dotnetfx.exe. Ce fichier peut tre redistribu librement.
Ce fichier fait une taille denviron 23 Mo. Il existe un tel fichier par version de .NET. Bien
entendu, il faut que le framework .NET 2.0 soit install sur une machine avant linstallation
dune application .NET, quelle que soit la technologie de dploiement utilise. Dans le cas dun
dploiement avec la technologie ClickOnce nous avons vu que vous pouvez prciser en prrequis
la prsence du framework .NET. Sinon, il faut soit fournir dotnetfx.exe par exemple sur le CD
distribu soit prvoir un lien pour permettre au client de le tlcharger.
4
Le CLR
(le moteur dexcution
des applications .NET)
Le CLR (Common Langage Runtime en anglais et moteur dexcution en franais) est llment central de larchitecture de la plateforme .NET. Le CLR est une couche logicielle qui gre lexcution le code des applications .NET. Le mot grer recouvre en fait une multitude dactions
ncessaires au bon droulement de lapplication. Listons en quelques-unes :
88
Les domaines dapplication hbergs dans un mme processus Windows partagent les ressources du processus telles que le CLR, les types de base de .NET, lespace dadressage ou les
threads.
Lorsquun assemblage excutable est dmarr, le CLR cre automatiquement un domaine dapplication par dfaut pour lexcuter. Chaque domaine dapplication a un nom et le nom du domaine dapplication par dfaut est le nom du module principal de lassemblage lanc (extension
.exe comprise).
Si un mme assemblage est charg par plusieurs domaines dapplication dans un mme processus, il peut y avoir deux comportements :
soit le CLR va charger plusieurs fois lassemblage, une fois pour chaque domaine dapplication du processus.
soit le CLR va charger une seule fois lassemblage hors de tous domaines dapplication du
processus. Lassemblage pourra nanmoins tre utilis par chaque domaine dapplication
du processus. On dit que lassemblage est domain neutral.
On verra un peu plus loin dans ce chapitre que le choix de ce comportement est configurable.
Le comportement par dfaut est de charger plusieurs fois lassemblage.
89
Un domaine dapplication peut tre dcharg indpendamment des autres domaines applications.
Un domaine dapplication na pas daccs direct aux assemblages et aux objets des autres
domaines application.
Un domaine dapplication peut avoir sa propre stratgie de gestion dexception. Du moment quil ne laisse pas sortir une exception hors de ses frontires, les problmes dun
domaine dapplication nont pas dincidence sur les autres domaines dapplication du mme
processus.
Chaque domaine dapplication peut dfinir sa propre stratgie de scurit pour paramtrer
le mcanisme CAS quutilise le CLR pour accorder des permissions au code dun assemblage.
Chaque domaine dapplication peut dfinir ses propres rgles pour paramtrer le mcanisme quutilise le CLR pour localiser ses assemblages avant de les charger.
La classe System.AppDomain
Une instance de la classe System.AppDomain est une rfrence vers un domaine dapplication du
processus courant. La proprit statique CurrentDomain{get;} de cette classe vous permet de
rcuprer une rfrence vers le domaine dapplication courant. Lexemple suivant illustre lutilisation de cette classe pour numrer les assemblages contenus dans le domaine dapplication
courant :
Exemple 4-1 :
using System ;
using System.Reflection ; // Pour la classe Assembly
class Program {
static void Main() {
AppDomain curAppDomain = AppDomain.CurrentDomain;
foreach ( Assembly assembly in curAppDomain.GetAssemblies() )
Console.WriteLine( assembly.FullName ) ;
}
}
90
(optionnel) Les rgles de scurit qui paramtrent le mcanisme CAS sur ce domaine dapplication (par un objet de type System.Security.Policy.Evidence).
(optionnel) Des informations qui paramtrent le mcanisme de localisation des assemblages de ce domaine dapplication utilis par le CLR (par un objet de type System.
AppDomainSetup).
ConfigurationFile : Cette proprit rfrence un ventuel fichier de configuration du domaine dapplication. Ce fichier doit tre au format XML et contient des informations sur
les rgles de versionning et/ou la localisation des assemblages.
Maintenant que vous savez crer un domaine dapplication, nous pouvons prsenter comment
charger et excuter un assemblage excutable dans un domaine en appelant la mthode System.AppDomain.ExecuteAssembly(). Lassemblage doit tre de type excutable et son excution
commence son point dentre. Cest le thread qui appelle ExecuteAssembly() qui excute le
code de lassemblage charg. Cette constatation illustre le fait quun thread peut indiremment
traverser les frontires entre domaines dapplication.
Voici un exemple de code en C . Le premier code est lassemblage qui va tre charg dans un
domaine dapplication par lassemblage produit par la compilation du second code :
Exemple 4-2 :
AssemblyACharger.exe
using System ;
using System.Threading ;
public class Program {
public static void Main() {
Console.WriteLine(
"Thread:{0} Vous avez le bonjour du domaine : {1}",
Thread.CurrentThread.Name,
AppDomain.CurrentDomain.FriendlyName) ;
}
}
Exemple 4-3 :
using System ;
using System.Threading ;
public class Program {
public static void Main() {
AssemblyChargeur.exe
91
92
NouveauDomaine
Soyez conscient que cette possibilit dinjecter du code dans un domaine dapplication peut
provoquer la leve dune exception de scurit si vous navez pas les droits susants.
Description
AssemblyLoad
AssemblyResolve
DomainUnload
ProcessExit
ReflectionOnlyPreBindAssemblyResolve
ResourceResolve
TypeResolve
UnhandledException
Certains de ces vnements peuvent tre utiliss pour remdier au problme qui la dclench.
Lexemple suivant illustre comment exploiter lvnement AssemblyResolve pour faire en sorte
de charger un assemblage partir dun emplacement qui na pas t pris en compte par le mcanisme de localisation des assemblages du CLR :
93
Exemple 4-5 :
using System ;
using System.Reflection ; // pour Assembly
public class Program {
public static void Main() {
AppDomain.CurrentDomain.AssemblyResolve += AssemblyResolve ;
Assembly.Load("AssemblyACharger.dll");
}
public static Assembly AssemblyResolve(object sender,
ResolveEventArgs e) {
Console.WriteLine("Assemblage {0} non trouv
e : ", e.Name) ;
return Assembly.LoadFrom(@"C:\AppDir\CetAssemblyACharger.dll");
}
}
Si la seconde tentative de chargement marche, aucune exception nest lance. Le nom de lassemblage charg la seconde tentative nest pas ncessairement le mme que celui que lon a
essay de charger la premire tentative.
En page 519 nous prsentons un programme exploitant lvnement UnhandledException.
En page 80 nous expliquons que les classes de lespace de nom System.Deployment peuvent avoir
recours au vnements AssemblyResolve et ResourceResolve afin de tlcharger dynamiquement un groupe de fichier lorsquune application dploye avec la technologie ClickOnce en a
besoin pour la premire fois.
94
Cet exemple prsente le cas simple car nous stockons un entier qui est un type valeur connu
de tous les domaines dapplication. La technologie .NET Remoting qui fait lobjet du chapitre 22
permet de partager des objets entre domaines dapplication dune manire plus volue mais
plus complexe.
La DLL mscorsvr.dll contient une version du CLR spcialement optimise pour les machines ayant plusieurs processeurs ( svr est employ pour serveur ).
La DLL mscorwks.dll contient une version du CLR spcialement optimise pour les machines ayant un seul processeur ( wks est employ pour workstation ou station de
travail ).
Ces deux DLLs ne sont pas des assemblages. En consquence, elles ne contiennent pas de code
IL, et elles ne peuvent tre analyses avec loutil ildasm.exe. Chaque processus excutant une
ou plusieurs applications .NET contient une de ces deux DLLs. On dit que le processus hberge
le CLR. Nous allons expliquer ici comment le chargement dans le processus dune de ces DLLs
se droule.
Lassemblage mscorlib.dll
Une autre DLL joue un rle prpondrant dans lexcution des applications .NET. Cest la DLL
mscorlib.dll qui est en fait lunique module de lassemblage du mme nom. Cet assemblage
contient les implmentations des types de base du framework .NET (comme la classe System.
String, la classe System.Object ou la classe System.Int32). Cet assemblage est rfrenc par
tous les assemblages .NET. Cette rfrence est cre automatiquement par tous les compilateurs qui produisent du code IL. Il est intressant danalyser lassemblage mscorlib avec loutil
ildasm.exe. Nous prcisons que lassemblage mscorlib rside lexcution hors de tous domaines dapplication. En outre, il ne peut tre charg/dcharg quune fois durant la vie dun
processus.
95
Une fois le CLR charg dans le processus, lhte du moteur dexcution a dautres responsabilits telles que la dcision prendre lorsquune exception nest pas rattrape. La figure suivante
illustre les direntes couches de cette architecture. On voit que le CLR et lhte schangent
des informations grce une API :
CLR
Host API
Hte du moteur dexcution
Win32 API
Systme dexploitation Windows
Lhte du moteur dexcution des applications Console et Winform : Lassemblage excutable est charg dans le domaine dapplication par dfaut. Lorsquun assemblage est charg
implicitement, il est charg dans le mme domaine dapplication que lassemblage qui le
sollicite. En gnral ce type dapplication na pas utiliser dautres domaines dapplication
que le domaine dapplication par dfaut.
Lhte du moteur dexcution Microsoft Internet Explorer : Par dfaut, il cre un domaine dapplication par site web visit. Ainsi les assemblages de dirents sites peuvent
sexcuter avec dirents niveaux de scurit. Le CLR nest charg que lorsque Internet Explorer a besoin dexcuter un assemblage pour la premire fois. Plus dinformations au sujet
de cet hte sont disponibles en page 84.
Lhte du moteur dexcution de SQL Server 2005 : Les requtes vers la base peuvent tre
crites en langage IL. Le CLR nest charg que la premire fois quune telle requte doit
tre eectue. Un domaine dapplication est cr pour chaque couple utilisateur/base de
donnes. Dans le chapitre courant, nous aurons loccasion de revenir sur les possibilits et
proprits de cet hte trs particulier introduit avec .NET 2.0.
96
.NET sont dans un sous rpertoire portant le numro de version. Comme il existe un rpertoire
par version du framework .NET installe sur la machine, il peut y avoir aussi plusieurs versions
des DLLs mscorsvr.dll et mscorwks.dll sur la mme machine. Cependant une seule version
du CLR peut tre charge et hberge par chaque processus.
Le fait davoir potentiellement plusieurs versions du CLR entrane lexistence dune petite
couche logicielle qui prend en paramtre la version dsire du CLR et la charge. Ce code est
appel cale (shim en anglais) et est stock dans la DLL mscoree.dll (MSCOREE veut dire Microsoft
Component Object Runtime Execution Engine).
Il ne peut y avoir quune seule DLL cale par machine. La cale est directement appele par
lhte du moteur dexcution par la fonction CorBindToRuntimeEx(). La DLL mscoree.dll
contient des interfaces et des classes COM. La fonction CorBindToRuntimeEx() cre un objet
COM, instance de la classe COM CorRuntimeHost. Cest cet objet qui va sinterfacer avec le
CLR. Pour manipuler cet objet la fonction CorBindToRuntimeEx() retourne linterface COM
ICLRRuntimeHost.
Lappel CorBindToRuntimeEx() pour crer lobjet COM sinterfaant avec le CLR, transgresse
des rgles fondamentales de COM : il ne faut pas appeler la fonction CoCreateInstance() pour
crer un objet COM. De plus, les appels aux mthodes AddRef() et Release() sur linterface
ICLRRuntimeHost nont pas deet.
PwszVersion : Le numro de version du CLR sous la forme dune chane de caractres commenant par v (exemple "v2.0.50727"). Si cette chane nest pas prcise (i.e si on passe
un pointeur nul), la version la plus rcente disponible du CLR sera alors choisie.
PwszBuildFlavor : Ce paramtre indique si lon souhaite charger le CLR pour workstation (mscorwks.dll) en prcisant la chane de caractres "wks" ou le CLR pour serveur
(mscorsvr.dll) en prcisant la chane de caractres "svr". Si vous navez quun seul processeur, le CLR pour workstation sera charg quelle que soit la valeur de ce paramtre. Microsoft
prvoit dautres types de CLR, donc dautres valeurs pour ce paramtre.
97
Lassemblage mscorlib a un traitement spcial puisquil est charg dune manire neutre
une seule fois, quelle que soit la valeur de ce paramtre.
rclsid : Le classe ID (CLSID) de la classe COM (coclass) qui implmente linterface que
vous cherchez. Seules les valeurs CLSID_CorRuntimeHost, CLSID_CLRRuntimeHost ou null
sont acceptes. La deuxime valeur est apparue avec la version 2.0 de .NET car de nouvelles
fonctionnalits dues lhbergement du CLR dans le processus de SQL Server 2005 ont ncessit une nouvelle interface et une nouvelle classe COM.
pwszBuildFlavor : Linterface ID (IID) de linterface COM dont vous avez besoin. Seules les
valeurs IID_CorRuntimeHost, IID_CLRRuntimeHost ou null sont acceptes
ppv : Un pointeur vers linterface COM retourne, de type ICorRuntimeHost ou IClrRuntimeHost selon ce qui a t demand.
La cale ne fait que charger le CLR dans le processus. Le cycle de vie du CLR est contrl par la partie non gre du code de lhte du moteur dexcution grce linterface COM ICLRRuntimeHost
98
renvoye par la fonction CorBindToRuntimeEx(). Cette interface prsente entre autres deux mthodes Start() et Stop() dont les noms illustrent leurs actions.
99
MyManagedLib.cs
namespace MyProgramNamespace {
public class MyClass {
public static int MyMethod(string s) {
System.Console.WriteLine(s) ;
return 0 ;
}
}
}
Vous pouvez facilement invoquer la mthode Main() partir de notre hte comme ceci :
Exemple 4-9 :
...
pClrHost->Start() ;
DWORD retVal=0 ;
hr = pClrHost->ExecuteInDefaultAppDomain(
L"C:\\test\\MyManagedLib.dll", // Chemin + Asm.
L"MyProgramNamespace.MyClass", // Nom entier du type.
L"MyMethod",
// Nom de la m
ethode elle doit avoir
//
la signature int XXX(string).
L"Hello from host!",
// Chane de caract`
eres en argument.
&retVal) ;
// Valeur OUT de retour.
pClrHost->Stop() ;
...
100
IValidator permet de valider lentte PE/COFF des assemblages (utilis notamment par
loutil peverify.exe).
IMetaDataConverter permet de convertir les mtadonnes COM (i.e tlb/tlh) en mta donnes .NET (utilis notamment par loutil tlbexp.exe).
Pour savoir quelles sont les mthodes prsentes par ces interfaces, il sut dobserver les fichiers
mscoree.h, ivalidator.h et gchost.h. Pour obtenir une de ces interfaces partir de votre interface IClrRuntimeHost, il sut dutiliser la fameuse mthode QueryInterface() comme ceci :
...
ICorThreadpool * pThreadPool
= NULL ;
hr = pClrHost->QueryInterface( IID_ICorThreadpool,
(void**)&pThreadPool);
...
101
entre threads et de minimiser le nombre de pages stockes en mmoire virtuelle sur le disque
dur pour profiter au mieux de la mmoire vive disponible.
Les context switching sont normalement grs par le mcanisme de multitche premptif du rpartiteur de Windows, dcrit en page 138. Lhte du moteur dexcution de SQL Server 2005 implmente sont propre mcanisme de multitche plutt bas sur un modle de multitche coopratif.
Dans ce modle, ce sont les threads eux mmes qui dcident du moment o le processeur peut
passer un autre thread. Un avantage est que les choix de ces moments sont plus fins, car lis
la smantique des traitements. Il en rsulte une gestion globale plus ecace des threads. Un
autre avantage est que ce modle est adapt lutilisation du mcanisme de fibre de Windows
(fiber en anglais).
Une fibre est un thread logique qualifi aussi de thread lger. Un mme thread physique Windows peut enchaner lexcution de direntes fibres. Lavantage est que le passage dune fibre
une autre est une opration beaucoup moins coteuse que le context switching. En contrepartie,
lorsque le mode fibre est utilis par lhte du moteur dexcution de SQL Server 2005, on perd
la garantie dune relation biunivoque entre les threads physiques Windows et les threads grs
.NET. Un mme thread gr nest plus forcment excut par le mme thread physique durant
son toute son existence. Il faut donc absolument saranchir de tout type danit entre ces
deux entits. Parmi les types danits possibles entre un thread gr et son thread physique
sous-jacent on peut citer les Thread Local Storage, la culture courante et les objets de synchronisation Windows qui drivent de la classe WaitHandle style mutex, smaphore ou vnement.
Sachez que vous avez la possibilit de communiquer lhte du moteur dexcution le commencement et la fin dune rgion de code qui exploite ce type danit avec les mthode BeginThreadAffinity() et EndThreadAffinity() de la classe Thread. Ce dernier saura alors dsactiver temporairement le mode fibre, auquel on attribut un facteur doptimisation de 20% des
performances. (Note : Dans la version actuelle de SQL Server 2005, le mode fibre a t retir car les
ingnieurs de Microsoft ntaient pas certains de la fiabilit de ce mode. Ce mode sera rintroduit dans
les versions ultrieures de ce produit).
Le stockage des pages mmoires est normalement gr par le mcanisme de mmoire virtuelle
de Windows dcrit en page 134. Lhte du moteur dexcution de SQL Server 2005 sintercale
entre les demandes mmoire du CLR et ce mcanisme afin de profiter au mieux de la mmoire
vive disponible. Cela permet aussi dobtenir un comportement prvisible lorsquune demande
dallocation mmoire du CLR choue. Comme nous le verrons plus tard, cette particularit est
essentielle pour assurer la fiabilit de serveurs tels que SQL Server 2005.
Toutes ces nouvelles possibilits sont accessibles grce une API qui permet au CLR et son
hte de dialoguer. Une trentaine de nouvelles interfaces ont t prvues. Elles sont listes un
peu plus bas. Il incombe lhte de fournir un objet qui implmente linterface IHostControl
au moyen de la mthode ICLRRuntimeHost.SetHostControl(IHostControl*). Cette interface
prsente la mthode GetHostManager(IID,[out]obj). Le CLR appelle cette mthode pour obtenir un objet de lhte auquel il va dlguer une responsabilit telles que la gestion des threads
ou le chargement des assemblages. Plus dinformation ce sujet sont disponibles en analysant
les interfaces suivantes dans le fichier mscoree.h.
102
Responsabilit
Chargement
blages
des
Interfaces
par lhte.
assem-
implmentes
IHostAssemblyManager
IHostAssemblyStore
ICLRAssemblyReferenceList
ICLRAssemblyIdentityManager
Scurit
IHostSecurityManager
IHostSecurityContext
ICLRHostProtectionManager
IHostPolicyManager
ICLRPolicyManager
Gestion de la mmoire
IHostMemoryManager
IHostMalloc
ICLRMemoryNotificationCallback
Ramasse-miettes
IHostGCManager
ICLRGCManager
Threading
IHostTaskManager
Task
Pool de threads
IHostThreadPoolManager
Synchronisation
IHostSyncManager IHostCriticalSection
IHostManualEvent IHostAutoEvent
IHostSemaphore
ICLRSyncManager
I/O Completion
IHostIoCompletionManager
ICLRIoCompletionManager
ICLRTaskManager ICLRTask
ICLRDebugManager
Dbogage
vnements du CLR
IHost-
IActionOnCLREvent
ICLROnEventManager
103
Intelligent dans le sens o lorsque le CLR ne trouve pas un assemblage dans un rpertoire,
il applique un algorithme qui lui permet, par exemple, daller chercher dans les sous rpertoires qui ont le mme nom que lassemblage. Intelligent aussi dans le sens o si une
application marchait mais ne marche plus cause dun assemblage qui nest plus localisable,
on puisse revenir trs simplement en arrire.
104
Lutilisation dune des surcharges de la mthode AppDomain.Load() qui charge un assemblage dans le domaine dapplication sur lequel est appele la mthode.
Le chargement implicite par le CLR dun assemblage. Ceci est dcrit dans la prochaine section lorsque nous expliquons comment le CLR rsout les types.
Remarquez que ces deux mthodes ne fonctionnent pas selon la philosophie .NET puisquelles
prennent en argument un chemin vers un fichier et non le nom dun assemblage. Il est prfrable de ne jamais utiliser LoadFrom() qui peut toujours tre remplace par Load(). En revanche
lutilisation trs simple de la mthode AppDomain.ExecuteAssembly() peut savrer tre un raccourci ecace.
Lalgorithme de localisation
Recherche dans le rpertoire GAC
Lalgorithme va dabord aller chercher lassemblage dans le rpertoire GAC, condition que le
nom de lassemblage fourni soit un nom fort. Lors de la recherche dans le rpertoire GAC, lutilisateur de lapplication peut choisir ou non (par lintermdiaire du fichier de configuration de
lapplication) dutiliser les assemblages de stratgies dditeurs relatifs lassemblage localiser.
105
...
<dependentAssembly>
<assemblyIdentity name="Foo3" publicKeyToken="C64B742BD612D74A"
culture= "fr-FR"/>
<codebase version="3.0.0.0"
href = "http://www.smacchia.com/Foo3.dll>"/>
</dependentAssembly>
...
Vous remarquez que lURL contient le nom du module de lassemblage contenant le manifeste (en loccurrence Foo3.dll). Si lassemblage a dautres modules, comprenez bien que tous
ces modules doivent tre aussi tlchargeables cette adresse (en loccurrence http://www.
smacchia.com/).
Lorsquun assemblage est tlcharg partir du web grce llment <codebase>, il est stock
dans le cache de tlchargement. Aussi, avant de tenter de charger un assemblage partir dune
URL, fusion va consulter ce cache pour vrifier sil na pas dj t tlcharg.
Le mcanisme de probing
Si le nom fort nest pas correctement fourni ou sil est fourni mais que lassemblage nest pas
trouv dans le rpertoire GAC et si le fichier de configuration ne contient pas dlment <codebase> relatif lassemblage localiser, alors lalgorithme tente de trouver lassemblage en
sondant certains rpertoires. Cest le mcanisme de probing (qui peut se traduire par sondage en
franais) qui est expos par lexemple suivant :
Supposons que lon veuille localiser lassemblage Foo (notez quon ne fournit pas lextension du fichier).
Supposons que les sous rpertoires indiqus par llment <probing> du fichier de configuration de lapplication pour lassemblage Foo soit "Path1" et "Path2\Bin".
Supposons que le rpertoire de base de lapplication soit "C:\AppDir\".
Supposons enfin quaucune information de culture nait t fournie avec le nom dassemblage, ou quil soit de culture neutre (i.e ce nest pas un assemblage satellite).
La recherche de lassemblage se fait dans cet ordre, dans les rpertoires suivants :
C:\AppDir\Foo.dll
C:\AppDir\Foo\Foo.dll
C:\AppDir\Path1\Foo.dll
C:\AppDir\Path1\Foo\Foo.dll
C:\AppDir\Path2\Bin\Foo.dll
C:\AppDir\Path2\Bin\Foo\Foo.dll
C:\AppDir\Foo.exe
C:\AppDir\Foo\Foo.exe
C:\AppDir\Path1\Foo.exe
C:\AppDir\Path1\Foo\Foo.exe
C:\AppDir\Path2\Bin\Foo.exe
C:\AppDir\Path2\Bin\Foo\Foo.exe
Si lassemblage est un assemblage satellite (i.e il na pas une culture neutre, par exemple il a la
culture "fr-FR") la recherche se fait dans cet ordre, dans les rpertoires suivants :
106
Lvnement AppDomain.AssemblyResolve
Enfin, si lassemblage na pas t trouv aprs toutes ces tapes, le CLR dclenche lvnement
AssemblyResolve de la classe AppDomain. Les mthodes abonnes cet vnement peuvent retourne un objet de type Assembly. Cela vous permet de fournir votre propre mcanisme de
localisation dassemblage. Cette possibilit est notamment exploite par la technologie de dploiement ClickOnce pour tlcharger des groupes de fichiers la demande comme illustr en
page 80.
Llment <codebase>, dcrit dans la section prcdente, dfinit une URL partir de laquelle lassemblage localiser doit tre charg.
Voici quoi peut ressembler un fichier de configuration (notez la ressemblance avec le fichier
de configuration dune stratgie dditeur) :
107
Assembly.Load() est appele par lapplication foo pour localiser lassemblage ASM
Le
nom fourni
est-il un
nom fort ?
foo est-elle
configure pour tenir
compte des stratgies
dditeur pour
ASM ?
N
Y a-t-il
un assemblage de
stratgie dditeur dans
le GAC pour
ASM ?
ASM
avec ce nom
fort, est-il dans
le GAC ?
La version de lassemblage
chercher est modifie
selon la stratgie dditeur
O
ASM est localis dans le GAC
foo.exe.config
a-t-il un lment codeBase
avec une URL
O
pour ASM ?
N
ASM est-il
localis dans un
sous-rpertoire du rpertoire de
lOO (en tenant compte des sous-rpertoires spcifis parllment
N
probing) ?
ASM est-il
localis dans le cadre
de tlchargement ?
ASM
est-il localis
lURL spcifie ?
O
ASM est localis dans le sous-rpertoire
108
Si vous ne souhaitez pas manipuler les documents XML, sachez que ces informations peuvent
tre configures partir de loutil .NET Framework Configuration accessible par : Menu Dmarrer Panneau de configuration Outils dadministration Microsoft .NET Framework 2.0 Configuration menu configured assembly.
Soit vous comptez sur votre compilateur pour crer un lien prcoce avec le type et vous
comptez sur le CLR pour charger lassemblage qui le contient au bon moment. Cest cette
technique nomme chargement implicite que nous dtaillons ici.
Soit vous chargez explicitement lassemblage lexcution et vous crer un lien tardif avec le
type que vous souhaitez exploiter.
109
Dans les deux cas la responsabilit du CLR incombe une partie du CLR nomme chargeur de
classe (class loader). Le chargement implicite dun assemblage A est dclench lors de la premire
compilation JIT dune mthode qui utilise un type de A.
La notion de chargement implicite se rapproche conceptuellement du mcanisme de chargement des DLLs. De mme, la notion de chargement explicite se rapproche conceptuellement du
mcanisme dutilisation dun objet COM par un langage de script (notamment le mcanisme
Automation et sa fameuse interface IDispatch).
Soit vous utilisez lenvironnement Visual Studio et il faut utiliser le menu Reference AddReference. Rappelons que lenvironnement Visual Studio .NET utilise dune manire implicite
le compilateur csc.exe pour produire des assemblages partir de code source C . Cette
manipulation ne fait donc que forcer Visual Studio utiliser les options de compilation
/reference /r et /lib du compilateur csc.exe.
Dans les deux cas il faut prciser lassemblage charger implicitement par son nom (fort ou
non). Les types de lassemblage B ainsi que leurs membres, qui sont rfrencs dans lassemblage A sont rfrencs dans les tables TypeRef et MemberRef de lassemblage A. Lassemblage B est
rfrenc dans la table AssemblyRef de lassemblage A. Un assemblage peut rfrencer plusieurs
autres assemblages mais il faut absolument viter les rfrencements cycliques (A rfrence B qui
rfrence C qui rfrence A). Visual Studio sait dtecter et interdit les rfrencements cycliques.
Vous pouvez aussi utiliser loutil NDepend (dcrit en page 1034) pour dtecter les rfrencements cycliques dassemblages.
Un exemple
Voici un exemple illustrant linfrastructure mise en uvre pour que le CLR puisse charger
implicitement un assemblage. Le premier code dfinit lassemblage rfrenc par lassemblage
dfini par le deuxime code.
Exemple 4-11 :
Code de lassemblage r
ef
erenc
e : AssemblageBibliotheque.cs
using System ;
namespace MesTypes{
public class UneClasse{
public static int Somme(int a,int b){return a+b;}
}
}
110
Exemple 4-12 :
Code de lassemblage r
ef
eren
cant : AssemblageExecutable.cs
using System ;
using MesTypes;
class Program{
static void Main(){
int i = UneClasse.Somme(3,4) ;
}
}
Notez lutilisation de lespace de noms MesTypes dans le code de lassemblage rfrenant. On
aurait pu aussi mettre les classes UneClasse et Program dans un mme espace de noms ou dans
lespace de noms anonyme. Dans ce cas, on aurait illustr le fait quun espace de noms peut
stendre sur plusieurs assemblages.
Il est intressant danalyser le manifeste de lassemblage rfrenant avec lutilitaire ildasm.
exe). On y voit clairement le fait que lassemblage AssemblageBibliotheque est rfrenc.
Cette rfrence est matrialise par une entre de la table AssemblyRef.
...
.assembly extern AssemblageBibliotheque{
.ver 0:0:0:0
}
...
Il est aussi intressant danalyser le code IL de la mthode Main(). On y voit clairement que
la mthode Somme() se trouve dans un autre assemblage appel AssemblageBibliotheque
(physiquement cette information est contenue dans les tables de mtadonnes MemberRef et
TypeRef) :
111
Schma rcapitulatif
Un type inconnu dans ce domaine dapplication est rencontr
par le compilateur JIT dans le code IL dune mthode
Le type est
dfini dans
le module
courant de
lassemblage
Lexception System.IO.FileNotFoundException
est lance
O
Charge le module
Charge lassemblage
112
Aucun de ces scnarios nest utilis pour la compilation du code IL en langage machine. Une
solution intermdiaire et plus performante a t mise en place. Cette solution consiste compiler le corps dune mthode en langage IL en langage machine, juste avant le premier appel
de la mthode. Cest pour cela que le mcanisme sappelle Juste temps (JIT Just In Time). La
compilation se fait juste temps pour que lexcution de la mthode en langage machine puisse
se faire.
Vrification du code IL
Avant de compiler une mthode en langage natif, le compilateur JIT eectue une srie de vrification quant la validit du corps de la mthode. Pour dcider quune mthode est valide ou
pas, le compilateur JIT vrifie lenchanement des instructions IL, value lvolution du contenu
de la pile et dtecte les accs mmoires interdits. Une exception est envoye si cette vrification
choue.
Votre code C crit en mode vrifiable est automatiquement traduit en code IL vrifiable. Cependant, en page 501 nous montrons que sous certaines conditions le compilateur C peut produire des instructions IL spciales qui ont la particularit dtre non vrifiables par le compilateur JIT.
Pour viter le cot du passage des arguments entrants et sortants, le compilateur JIT a la
possibilit dinsrer le corps dune mthode appele dans le corps de la mthode appelante.
Cette optimisation est nomme inlining. Pour que le cot de cette optimisation ne soit pas
suprieur au gain de performance, la mthode appele doit satisfaire un certains nombre
de contraintes simples vrifier. Son corps compil en IL doit avoir une taille infrieure
32 octets, elle ne doit pas rattraper dexceptions, elle ne doit pas contenir de boucles, elle ne
doit pas tre virtuelle etc.
Le compilateur JIT peut positionner null une variable locale de type rfrence aprs sa
dernire utilisation et avant la fin de la mthode. Ainsi, lobjet rfrenc aura une rfrence
de moins vers lui. Cela augmente les chances quil soit collect plus tt par le ramassemiettes. Cette optimisation peut tre localement dsactive en utilisant la mthode System.GC.KeepAlive().
113
Le compilateur JIT a la possibilit de stocker les variables locales les plus frquemment utilises directement dans les registres du processeur plutt que sur la pile. Cela constitue bien
une optimisation car laccs aux registres du processeur est significativement plus rapide.
Cette optimisation est nomme Enregistration.
Notion de pitching
La compilation des mthodes par le JIT consomme de la mmoire puisque le code natif des
mthodes est stock. Si le CLR dtecte que la mmoire devient une ressource critique pour
lexcution de lapplication, il a la possibilit de rcuprer de la mmoire en librant le code
natif de certaines mthodes. Naturellement un stub est regnr pour chacune de ces mthodes.
Cette fonctionnalit est appele pitching.
La mise en uvre du pitching est beaucoup plus complique quil ny parat. Pour tre ecace
le code des mthodes libres doit tre contigu pour viter la fragmentation de la mmoire.
Dautres problmes importants apparaissent dans les piles des threads puisquelles contiennent
des adresses mmoire rfrenant le code natif de certaines mthodes. Heureusement toutes ces
considrations sont totalement masques au dveloppeur.
Le code natif dune mthode produit par le compilateur JIT ne survit pas au dchargement du
domaine dapplication qui le contient.
114
Option de ngen.exe
Description
/show[nom assemblage
|nom repertoire]
/delete[nom assemblage
|nom repertoire]
/debug
De nombreuses autres options existent. Elles sont dcrites dans les MSDN larticle Native
Image Generator (Ngen.exe). Notamment, des nouvelles possibilits ont t ajoutes pour
supporter les assemblages exploitant la rflexion et pour automatiser la mise jour de la version
compile dun assemblage lorsquune de ses dpendances volue. Plus dinformation ce sujet
sont disponibles en ligne dans larticle NGen Revs Up Your Performance with Powerful New
Features de Reid Wilkes du numro dAvril 2005 de MSDN Magazine.
Description
115
Vous avez le choix de visualiser ces compteurs soit pour toutes les applications excutes
en mode gr jusquici depuis le boot de la machine, soit pour le processus courant. Le
choix de cette visualisation se fait avec largument de la mthode PerformanceCounterCategory.GetCounters(). Dans le premier cas il faut fournir la chane de caractres "_Global_"
en argument cette mthode. Dans le second cas il faut fournir la chane de caractres gale au
nom du processus en argument cette mthode.
Ces compteurs sont principalement utiliss pour valuer le cot de la compilation JIT. Si ce cot
vous parat trop lev, il faut prvoir lutilisation de loutil ngen.exe lors du dploiement de
lapplication. Voici un exemple dutilisation de ces compteurs (notez que le nom de lassemblage
est MonAssemblage.exe) :
Exemple 4-13 :
MonAssemblage.cs
using System.Diagnostics ;
class Program {
static void DisplayJITCounters() {
PerformanceCounterCategory perfCategory
= new PerformanceCounterCategory(".NET CLR Jit") ;
PerformanceCounter[] perfCounters ;
perfCounters = perfCategory.GetCounters("MonAssemblage") ;
foreach(PerformanceCounter perfCounter in perfCounters)
System.Console.WriteLine("{0}:{1}",
perfCounter.CounterName,
perfCounter.NextValue()) ;
}
static void f() {
System.Console.WriteLine("--> Appel `
a f().") ;
}
static void Main() {
DisplayJITCounters() ;
f() ;
DisplayJITCounters() ;
}
}
Ce programme ache :
# of Methods Jitted:2
# of IL Bytes Jitted:108
Total # of IL Bytes Jitted:108
IL Bytes Jitted / sec:0
Standard Jit Failures:0
% Time in Jit:0
116
Prcisons que les compteurs de performances sont aussi visualisables avec loutil perfmon.exe
(accessible avec Menu dmarrer Excuter... perfmon.exe).
Les fuites de mmoire subsistent malgr la prsence dun ramasse-miettes. En eet, un dveloppeur peut, par inadvertance, concevoir un programme qui garde des rfrences vers
des objets dont il na plus besoin (par exemple si une collection gonfle indfiniment).
Cependant, la cause la plus courante des fuites de mmoire dune application .NET est la
non dsallocation de ressources non gres.
Pour fixer les ides, le ramasse-miettes est une couche logicielle comprise dans le CLR, prsente
en un seul exemplaire dans chaque processus.
117
il comprendra mieux pourquoi lalgorithme dcrit dans la section suivante a t choisi par Microsoft pour son implmentation du ramasse-miettes.
On pourrait penser quil sut de librer un objet lorsque celui-ci nest plus rfrenc. Cette
approche simpliste peut facilement tre mise en dfaut. Imaginez deux objets A et B ; A maintient une rfrence vers B et B maintient une rfrence vers A ; ces deux objets nadmettent pas
dautres rfrences que leurs rfrences mutuelles. Clairement le programme na plus besoin de
ces objets et le ramasse-miettes doit les dtruire, alors quil existe encore une rfrence valide
sur chacun deux. Le ramasse-miettes utilise donc des algorithmes du type arbres de rfrences
et dtection de boucles dans un graphe. Larbre de rfrences admet pour racines certains objets
considrs comme immuables.
Un autre problme que celui de la destruction des objets incombe au ramasse-miettes. Cest la
fragmentation du tas. Aprs un certain temps, force davoir dsallou et allou des zones mmoires de tailles trs variables, le tas est fragment. Il est parsem despaces mmoire non utiliss
par le programme. Ce fait induit un norme gaspillage de mmoire. Il incombe au ramassemiettes de dfragmenter le tas, afin de limiter les consquences de ce problme. L encore plusieurs algorithmes et approches existent.
Enfin, le bon sens et lapproche empirique nous enseigne la rgle suivante : plus un objet est
ancien plus son esprance de vie est longue, plus il est rcent plus son esprance de vie est courte.
Si cette rgle est prise en compte par un algorithme, cest--dire si lon essaye plus souvent de
dsallouer les objets rcents que les objets anciens, alors le ramasse-miettes sous-jacent gagnera
ncessairement en performance.
Lalgorithme du ramasse-miettes .NET prend en compte ces problmatiques et cette rgle temporelle.
118
Gnration 2
Gnration 1
Gnration 0
C
E
119
collecte partielle de la gnration 0 par seconde, une collecte partielle de la gnration 1 pour
10 collectes partielles de la gnration 0, une collecte complte pour 10 collectes partielles de
la gnration 1.
La Figure 4 -6 montre que lobjet D nest pas marqu. Il est donc inactif et va tre dtruit.
A
Gnration 2
Gnration 1
Gnration 0
B
Gnration 2
Gnration 1
Gnration 0
Bonnes pratiques
Les bonnes pratiques suivantes dcoulent naturellement des proprits de lalgorithme que
nous venons dexposer :
Librez vos objets ds que possible pour viter la promotion dobjets dans les gnrations.
120
Identifiez quels objets sont susceptibles davoir une vie longue, analysez les causes et essayez
de rduire leurs dures de vie. Pour cela, nous vous conseillons davoir recours loutil
CLR Profiler fournit gratuitement par Microsoft ou au profiler fourni avec Visual Studio Team
System .
Dans la mesure du possible, vitez de rfrencer un objet avec une vie courte partir dun
objet avec une vie longue.
viter dimplmenter un finaliseur dans vos classes pour que vos objets ne survivent pas
une collecte.
Assign vos rfrences null ds que possible, surtout avant un long appel.
121
Lobjet peut tre potentiellement utilis plus tard mais ce nest pas certain. Si nous sommes
certains de lutiliser plus tard il faut utiliser une rfrence forte.
Lobjet peut tre intgralement reconstruit lidentique (par exemple partir dune base
de donnes). Si on a potentiellement besoin de lobjet plus tard mais quon ne peut le reconstruire lidentique il ne faut pas que le ramasse-miettes dtruise lobjet.
Lobjet est relativement volumineux en mmoire (plusieurs Ko). Si lobjet est lger on peut
le garder en mmoire. Cependant si les deux conditions prcdentes sappliquent un
grand nombre dobjets lgers il est judicieux dutiliser une rfrence faible pour chacun de
ces objets.
Tout ceci est bien thorique. Dans la pratique on dit quon utilise un cache dobjets. En eet, ces
conditions sont toutes remplies par les objets contenus dans un cache (on parle du concept de
cache en gnral et pas dune implmentation particulire).
Lutilisation de caches peut tre considre comme une rgulation naturelle et automatique
du compromis entre lespace mmoire, la puissance de calcul et la bande passante du rseau.
Lorsque le cache est trop rempli, une partie des objets cachs sont dtruits. Cependant, dans
lhypothse, vraisemblable, o lon doit accder un de ces objets plus tard, il faudra utiliser de
la puissance de calcul (pour fabriquer lobjet) ou/et de la bande passante rseaux (pour obtenir
les donnes contenues dans lobjet, partir dune base de donnes par exemple).
En rsum, si vous avez implmenter un cache nous vous conseillons dutiliser les rfrences
faibles.
122
La classe WeakReference prsente la mthode bool IsAlive() qui retourne true si lobjet rfrenc faiblement na pas encore t dtruit. En outre, il est classique et recommand de mettre
la rfrence forte null ds quune rfrence faible est cre pour sassurer que la rfrence forte
est dtruite.
123
124
2e cas : Si on appelle ReRegisterForFinalize() dans le code de Finalize() lobjet survivra aux collectes. Cela peut servir analyser le comportement du ramasse-miettes.
Cependant si on rpte indfiniment lappel Finalize() le programme ne sarrtera jamais, donc il faut prvoir une condition avant lappel de ReRegisterForFinalize() dans le code de Finalize(). De plus si le code dune telle mthode Finalize()
rfrence dautres objets il faut tre trs prudent car ils peuvent tre dtruits par le
ramasse-miettes sans que lon sen aperoive. En eet, cet objet indestructible nest
pas constamment considr comme actif, donc ses rfrences vers dautres objets ne
sont pas forcment prises en compte lors de la construction de larbre des rfrences
vers les objets actifs.
Facilits fournies par le CLR pour rendre votre code plus fiable
125
Boxing implicite.
Cration des champs statiques dune classe dont lassemblage est partag par les domaines
dapplication du processus.
Une pnurie de ressource se traduit en gnral par la leve dune exception par le CLR de type
OutOfMemoryException, StackOverflowException ou ThreadAbortException sur le thread qui
excute la demande de ressource responsable de la pnurie. On parle dexception asynchrone.
Cette notion soppose la notion dexception applicative. Lorsquune exception applicative est
leve, il incombe au code courant de la rattraper et de la traiter. Typiquement lorsque vous
souhaitez accder un fichier il faut prvoir quune exception de type FileNotFoundException
peut tre leve. En revanche, lorsquune exception asynchrone est leve par le CLR, le code en
cours dexcution ne peut en tre tenu pour responsable. En consquence, il est dconseill de
cder la psychose en mettant des blocs try/catch/finally partout dans votre code pour limiter
les eets de bord des exceptions asynchrones. Lentit responsable du traitement des exceptions
asynchrones est lhte du moteur dexcution que nous avons dj eu loccasion de prsenter
dans ce chapitre.
Dans le cas dune application console ou fentre classique, la leve dune exception asynchrone
est un vnement rare qui traduit en gnral un problme dalgorithme (fuite de mmoire,
appels rcursifs abusifs ou infinis etc). En consquence lhte du moteur dexcution de ces
applications fait en sorte de terminer le processus tout entier lorsquune exception asynchrone
est non rattrape par le code applicatif.
Il en est de mme dans le cas des applications ASP.NET. En eet, on constate que les dirents
mcanismes de dtection dun comportement anormal recyclent le processus en gnral avant
mme que des exceptions asynchrones soient lances. Ces mcanismes sont exposs en page 890.
Ainsi, jusqu lintgration du CLR dans le produit SQL Server 2005, les exceptions asynchrones
ntaient pas si problmatiques. Pour ne pas rgresser, la version 2005 de SQL Server doit fournir
un taux de fiabilit de cinq neufs. En dautres termes le service de persistance des donnes doit
126
tre disponible 99.999% du temps soit, au pire, 5 minutes et 15 secondes dindisponibilit par
an. En outre, pour tre ecace, le processus de SQL Server 2005 doit charger un maximum de
donnes en mmoire et limiter les chargements de pages mmoire partir du disque dur. Il est
donc amen flirter rgulirement avec la limite de 2 ou 3GB du processus si la mmoire vive
disponible le permet (ce qui est maintenant le cas sur la plupart des serveurs). Enfin, les mcanismes de time out sur les excutions des requtes fonctionnent base de leve de lexception
ThreadAbortException. En rsum, lorsque vous avez faire ce type de serveur qui pousse
le systme dans ses retranchements non seulement les exceptions asynchrones deviennent des
vnements banals mais en plus il faut viter absolument quelles ne provoquent le crash du
processus.
Face de tels impratifs les concepteurs du CLR ont du imaginer de nouvelles techniques. Elles
constituent le sujet de la prsente section.
Garder bien lesprit que ces techniques doivent tre utilises avec une grande sagesse,
seulement lorsque vous dveloppez un serveur de grande envergure qui ncessite son
propre moteur dexcution et qui est susceptible de faire face des exceptions asynchrones.
Facilits fournies par le CLR pour rendre votre code plus fiable
127
try/catch/finally. Tout le code atteignable partir des blocs catch et finally reprsente alors
une CER.
La mthode statique ProbeForSufficientStack() de cette classe est ventuellement appele
lors de lappel PrepareConstrainedRegion(). Cela dpend du fait que limplmentation des
CER par votre hte du moteur dexcution doit grer les cas de dpassement de la taille maximale de la pile dappels du thread courant (stack overflow). Sur un processeur x86, cette mthode
tentera de rserver 48Ko.
Malgr cette quantit de mmoire rserve, il se peut quune situation de stack overflow survienne. Aussi vous pouvez vous faire suivre votre appel PrepareConstrainedRegion() par
un appel la mthode statique ExecuteCodeWithGuaranteedCleanup() afin dindiquer une mthode contenant du code de nettoyage invoquer le cas chant. Une telle mthode doit tre
marque avec lattribut PrePrepareMethodAttribute pour prciser loutil ngen.exe sa fonction particulire.
La classe RuntimeHelpers prsente des mthodes permettant aux dveloppeurs dassister le CLR
dans la prparation du terrain avant lexcution de la CER. Vous pouvez par exemple les appeler
dans des constructeurs de classes.
128
Sil savre que la quantit de mmoire indique est indisponible, le constructeur de la classe
MemoryFailPoint lve une exception de type System.InsufficientMemoryException. Pour
cette raison, on parle parfois de portail de mmoire (memory gates en anglais) pour dsigner cette
fonctionnalit.
Description
MayCorruptProcess
La mthode marque peut au pire corrompre ltat du processus tout entier et ainsi, provoquer un crash.
MayCorruptAppDomain
La mthode marque peut au pire corrompre ltat du domaine dapplication courant et ainsi, provoquer son dchargement.
MayCorruptInstance
La mthode marque peut au pire corrompre ltat de linstance sur laquelle elle est appele et ainsi, provoquer sa destruction.
WillNotCorruptState
CLI et CLS
129
CLI et CLS
Sous ces deux acronymes se cache la magie qui permet .NET de supporter plusieurs langages.
Le CLI (Common Langage Infrastructure) est une spcification dcrivant les contraintes respec-
130
tes par le CLR et les assemblages. Une couche logicielle qui supportent les contraintes du
CLI est mme de grer lexcution des applications .NET. Cette spcification est produite par
lECMA. Elle est disponible sur le site de lECMA lURL : http://www.ecma-international.
org/publications/standards/ECMA-335.HTM
Le langage doit prvoir une syntaxe pour rsoudre le cas dune classe qui implmente deux
interfaces qui ont un conflit de dfinitions de mthodes. Il y a conflit de dfinitions de mthodes lorsque deux mthodes, une dans chaque interface implmente par la classe, ont
le mme nom et la mme signature. Le CLS impose que la classe doit implmenter deux
mthodes distinctes.
Seulement certains types primitifs sont compatibles avec le CLS. Par exemple le type ushort
de C nest pas compatible avec le CLS.
Les types des paramtres des mthodes publiques doivent tre CLS compliant. Cette notion
de CLS compliant est prsente quelques lignes plus loin.
Un objet lanc dans une exception doit tre une instance de la classe System.Exception, ou
dune classe drive de celle-ci.
La liste exhaustive de ces contraintes est disponible dans les MSDN larticle Common Language Specification .
La compatibilit dun langage avec le CLS nest pas forcment totale et on observe deux niveaux
de compatibilit :
Un langage supporte la compatibilit extenseur sil peut produire des classes drivant de
classes publiques contenues dans des assemblages compatibles avec le CLS. Cette compatibilit induit la compatibilit consommateur.
CLI et CLS
La dfinition des membres publics et des membres protgs des types publics.
131
Ce qui veut dire que le code des classes et mthodes prives na pas tre compatible avec la
CLS.
Le dveloppeur a tout intrt dvelopper des librairies compatibles avec le CLS. Il lui sera
ainsi plus ais de rutiliser ces classes dans le futur. Heureusement, le dveloppeur na pas
tre spcialiste des contraintes imposes par le CLS pour vrifier si ses classes sont compatibles
avec le CLS. Vous pouvez, grce lattribut System.CLSCompliantAttribute, faire vrifier par
le compilateur si les lments de vos applications (assemblages, classes, mthodes...) sont compatibles avec le CLS. Par exemple :
Exemple 4-15 :
using System ;
[assembly : CLSCompliantAttribute(true)]
namespace CLSCompliantTest {
public class Program {
public static void Fct(ushort i ) { ushort j = i; }
static void Main(){}
}
}
Le compilateur gnre un avertissement car le type ushort, qui nest pas compatible avec le
CLS, est utilis comme type dun paramtre dune mthode publique dune classe publique. En
revanche, lutilisation du type ushort lintrieur du corps de la mthode Fct() ne provoque
pas derreur ni davertissement de compilation.
Avec lattribut CLSCompliantAttribute, on peut aussi indiquer au compilateur de ne pas tester
la compatibilit avec le CLS pour la mthode Fct() tout en gardant la vrification de la compatibilit avec le CLS dans le reste du programme comme ceci :
Exemple 4-16 :
using System ;
[assembly : CLSCompliantAttribute(true)]
namespace CLSCompliantTest {
public class Program {
[CLSCompliantAttribute(false)]
public static void Fct(ushort i ) { ushort j = i ; }
static void Main(){}
}
}
Comprenez bien que la mthode Fct() ne peut pas tre appele partir du code dun langage
ne connaissant pas le type ushort.
5
Processus, threads
et gestion de la synchronisation
Nous exposons ici les notions fondamentales que sont les processus et les threads, dans larchitecture des systmes dexploitation de type Windows NT/2000/XP. Il faut avoir lesprit
que le CLR (ou moteur dexcution) dcrit dans le chapitre prcdent est une couche logicielle charge dans un processus par un hte du moteur dexcution lorsquun assemblage
.NET est lanc.
Introduction
Un processus (process en anglais) est concrtement une zone mmoire contenant des ressources.
Les processus permettent au systme dexploitation de rpartir son travail en plusieurs units
fonctionnelles.
Un processus possde une ou plusieurs units dexcution appele(s) threads. Un processus possde aussi un espace dadressage virtuel priv accessible en lecture et criture, seulement par ses
propres threads.
Dans le cas des programmes .NET, un processus contient aussi dans son espace mmoire la
couche logicielle appele CLR ou moteur dexcution. La description du CLR fait lobjet du
chapitre prcdent. Cette couche logicielle est charge ds la cration du processus par lhte
du moteur dexcution (ceci est dcrit page 94).
Un thread ne peut appartenir qu un processus et ne peut utiliser que les ressources de ce processus. Quand un processus est cr par le systme dexploitation, ce dernier lui alloue automatiquement un thread appel thread principal (main thread ou primary thread en anglais). Cest ce
thread qui excute lhte du moteur dexcution, le chargeur du CLR.
134
Une application est constitue dun ou plusieurs processus cooprants. Par exemple lenvironnement de dveloppement Visual Studio est une application, qui peut utiliser un processus pour
diter les fichiers sources et un processus pour la compilation.
Sous les systmes dexploitation Windows NT/2000/XP, on peut visualiser un instant donn
toutes les applications et tous les processus en lanant le gestionnaire des tches (task manager
en anglais). Il est courant davoir une trentaine de processus en mme temps, mme si vous
avez ouvert un petit nombre dapplications. En fait le systme excute un grand nombre de
processus, un pour la gestion de la session courante, un pour la barre des tches, et bien dautres
encore.
Les processus
Introduction
Dans un systme dexploitation Windows 32 bits, tournant sur un processeur 32 bits, un processus peut tre vu comme un espace linaire mmoire de 4Go (232 octets), de ladresse
0x00000000 0xFFFFFFFF. Cet espace de mmoire est dit priv, car inaccessible par les autres
processus. Cet espace se partage en 2Go pour le systme et 2Go pour lutilisateur. Windows et
certains processeurs soccupent de faire lopration de translation entre cet espace dadressage
virtuel et lespace dadressage rel.
Si N processus tournent sur une machine il nest (heureusement) pas ncessaire davoir Nx4Go
de RAM.
Windows alloue seulement la mmoire ncessaire chaque processus, 4Go tant la limite
suprieure dans un environnement 32 bits.
Un mcanisme de mmoire virtuelle du systme sauve sur le disque dur et charge en RAM
des morceaux de processus appels pages mmoire. Chaque page a une taille de 4Ko. L
encore tout ceci est transparent pour le dveloppeur et lutilisateur.
La classe System.Diagnostics.Process
Une instance de la classe System.Diagnostics.Process rfrence un processus. Les processus
qui peuvent tre rfrencs sont :
Les mthodes et champs de cette classe permettent de crer, dtruire, manipuler ou obtenir des
informations sur ces processus. Nous exposons ici quelques techniques courantes dutilisation
de cette classe.
Les processus
135
processus parent attend une seconde avant de tuer le processus fils. Le programme a donc pour
eet douvrir et de fermer le bloc note :
Exemple 5-1 :
using System.Diagnostics ;
using System.Threading ;
class Program {
static void Main() {
// Cree un processus fils qui lance notepad.exe
// avec le fichier texte hello.txt.
Process process = Process.Start("notepad.exe", "hello.txt") ;
// Endors le thread 1 seconde.
Thread.Sleep(1000) ;
// Tue le processus fils.
process.Kill();
}
}
La mthode statique Start() peut utiliser les associations qui existent sur un systme dexploitation, entre un programme et une extension de fichier. Concrtement ce programme a le mme
comportement si lon crit :
Process process = Process.Start("hello.txt") ;
Par dfaut un processus fils hrite du contexte de scurit de son processus parent. Cependant
une version surcharge de la mthode Process.Start() permet de lancer le processus fils dans
le contexte de scurit de nimporte quel utilisateur pourvu que vous ayez pu fournir le couple
login/mot de passe dans une instance de la classe System.Diagnostics.ProcessStartInfo.
Il y a le risque faible, mais potentiel, que le nom du mutex soit utilis par une autre application, auquel cas cette technique ne marche absolument plus et peut provoquer des bugs
diciles dtecter.
Cette technique ne peut rsoudre le cas gnral o lon nautorise que N instances de lapplication.
136
Les threads
Introduction
Un thread comprend :
Les threads
137
Une pile.
Un ensemble de valeurs pour les registres, dfinissant une partie de ltat du processeur
excutant le thread.
Tous ces lments sont rassembls sous le nom de contexte dexcution du thread. Lespace dadressage et par consquent toutes les ressources qui y sont stockes, sont communs tous les threads
dun mme processus.
Nous ne parlerons pas de lexcution des threads en mode noyau et en mode utilisateur. Ces deux
modes, utiliss par Windows depuis bien avant .NET, existent toujours puisquils se situent dans
une couche en dessous du CLR. Nanmoins ces modes ne sont absolument pas visibles du framework .NET.
Lutilisation en parallle de plusieurs threads constitue souvent une rponse naturelle limplmentation des algorithmes. En eet, les algorithmes utiliss dans les logiciels sont souvent
constitus de tches dont les excutions peuvent se faire en parallle. Attention, utiliser une
grande quantit de threads gnre beaucoup de context switching, et finalement nuit aux performances.
En outre, nous constatons depuis quelques annes que la loi de Moore qui prdisait un doublement de la rapidit dexcution des processeurs nest plus vrifie. Leur frquence semble
stagner autour de 3/4GHz. Cela est du des limites physiques qui prendront quelques temps
tre surmontes. Aussi, pour continuer la course aux performances, les grands fabricants de
processeurs tels que AMD et Intel sorientent vers des solutions types multi processeurs dans un
seul chip. En consquence, on peut sattendre voir prolifrer ce type darchitecture dans les
prochaines annes. La seule solution pour amliorer les performances des applications sera alors
davoir un recours massif au multithreading, do limportance des notions prsentes dans le
prsent chapitre.
Notion de thread gr
Il faut bien comprendre que les threads qui excutent les applications .NET sont bien ceux de
Windows. Cependant on dit quun thread est gr quand le CLR connat ce dernier. Concrtement, un thread est gr sil est cr par du code gr. Si le thread est cr par du code non gr,
alors il nest pas gr. Cependant un tel thread devient gr ds quil excute du code gr.
Un thread gr se distingue dun thread non gr par le fait que le CLR cre une instance de la
classe System.Threading.Thread pour le reprsenter et le manipuler. En interne, le CLR garde
une liste des threads grs nomme ThreadStore.
Le CLR fait en sorte que chaque thread gr soit excut au sein dun domaine dapplication,
un instant donn. Cependant un thread nest absolument pas cantonn un domaine dapplication, et il peut en changer au cours du temps. La notion de domaine dapplication est prsente
page 87.
Dans le domaine de la scurit, lutilisateur principal dun thread gr est indpendant de lutilisateur principal du thread non gr sous-jacent.
138
Le multitche premptif
On peut se poser la question suivante : mon ordinateur a un processeur (voire deux) et pourtant
le gestionnaire des tches indique quune centaine de threads sexcutent simultanment sur ma
machine ! Comment cela est-il possible ?
Cela est possible grce au multitche premptif qui gre lordonnancement des threads. Une partie
du noyau de Windows, appele rpartiteur (scheduler en anglais), segmente le temps en portions
appeles quantum (appeles aussi time slices). Ces intervalles de temps sont de lordre de quelques
millisecondes et ne sont pas de dure constante. Pour chaque processeur, chaque quantum est
allou un seul thread. La succession trs rapide des threads donne lillusion lutilisateur que
les threads sexcutent simultanment. On appelle context switching lintervalle entre deux
quantums conscutifs. Un avantage de cette mthode est que les threads en attente dune ressource nont pas dintervalle de temps allou jusqu la disponibilit de la ressource.
Ladjectif premptif utilis pour qualifier une telle gestion du multitche vient du fait que les
threads sont interrompus dune manire autoritaire par le systme. Pour les curieux sachez que
durant le context switching, le systme dexploitation place une instruction de saut vers le prochain context switching dans le code qui va tre excut par le prochain thread. Cette instruction
est de type interruption software. Si le thread doit sarrter avant de rencontrer cette instruction
(par exemple parce quil est en attente dune ressource) cette instruction est automatiquement
enleve et le context switching a lieu prmaturment.
Linconvnient majeur du multitche premptif est la ncessit de protger les ressources dun
accs anarchique avec des mcanismes de synchronisation. Il existe thoriquement un autre modle de la gestion du multitche, dit multitche coopratif, o la responsabilit de dcider quand
donner la main incombe au threads eux-mmes, mais ce modle est dangereux car les risques de
ne jamais rendre la main sont trop grands. Comme nous lexpliquons en page 100, ce mcanisme
est cependant utilis en interne pour optimiser les performances de certains serveurs tels que
SQL Server2005. En revanche, les systmes dexploitation Windows nimplmentent plus que le
multitche premptif.
Les threads
139
Valeur de ProcessPriorityClass
Low
BelowNormal
Normal
AboveNormal
10
High
13
RealTime
24
Le processus propritaire de la fentre en premier plan voit sa priorit incrmente dune unit
si la proprit PriorityBoostEnabled de la classe System.Diagnostics.Process est positionne
true. Cette proprit est par dfaut positionne true. Cette proprit nest accessible sur une
instance de la classe Process, que si celle-ci rfrence un processus sur la mme machine.
Vous avez la possibilit de changer la priorit dun processus en utilisant le gestionnaire des tches
avec la manipulation : Click droit sur le processus choisi Dfinir la priorit Choisir parmi les
six valeurs proposes savoir Temps rel, Haute ; Suprieure la normale ; Normale ; Infrieure la
normale ; Basse.
Les systmes dexploitation Windows ont un processus dinactivit (idle en anglais) qui a la priorit
0. Cette priorit nest accessible aucun autre processus. Par dfinition lactivit des processeurs,
note en pourcentage, est :
100% moins le pourcentage de temps pass dans le thread du processus dinactivit.
de
Lowest
BelowNormal
Normal
AboveNormal
Highest
140
Dans la plupart de vos applications, vous naurez pas modifier la priorit de vos processus et
threads, qui par dfaut, est assigne Normal.
La classe System.Threading.Thread
Le CLR associe automatiquement une instance de la classe System.Threading.Thread chaque
thread gr. Vous pouvez utiliser cet objet pour manipuler le thread partir dun autre thread
ou partir du thread lui-mme. Vous pouvez obtenir cet objet associ au thread courant avec
la proprit statique CurrentThread de la classe System.Threading.Thread :
using System.Threading ;
...
Thread threadCurrent = Thread.CurrentThread ;
Une fonctionnalit de la classe Thread, bien pratique pour dboguer une application multithread (multitches), est la possibilit de pouvoir nommer ses threads avec une chane de caractres :
threadCurrent.Name = "thread Foo" ;
Les threads
141
Terminer un thread
Un thread gr peut se terminer selon plusieurs scnarios :
Il sort de la mthode sur laquelle il avait commenc sa course (la mthode Main() pour
le thread principal, la mthode rfrence par le dlgu ThreadStart pour les autres
threads).
142
Le premier cas tant trivial, nous ne nous intressons quaux deux autres cas. Dans ces deux cas,
la mthode Abort() peut tre utilise (par le thread courant ou par un thread extrieur). Elle
provoque lenvoi dune exception de type ThreadAbortException. Cette exception a la particularit dtre relance automatiquement lorsquelle est rattrape par un gestionnaire dexception
car le thread est dans un tat spcial nomm AbortRequested. Seul lappel la mthode statique
ResetAbort() (si on dispose de la permission ncessaire) dans le gestionnaire dexception empche cette propagation.
Exemple 5-4 :
using System ;
using System.Threading ;
namespace ThreadTest{
class Program {
static void Main() {
Thread t = Thread.CurrentThread ;
try{
t.Abort() ;
}
catch( ThreadAbortException ) {
Thread.ResetAbort() ;
}
}
}
}
Lorsquun thread A appelle la mthode Abort() sur un autre thread B, il est conseill que A
attende que B soit eectivement termin en appelant la mthode Join() sur B.
Il existe aussi la mthode Interrupt() qui permet de terminer un thread lorsquil est dans un
tat dattente (i.e bloqu sur une des mthodes Wait(), Sleep() ou Join()). Cette mthode a
un comportement dirent selon que le thread terminer est dans un tat dattente ou non.
Si le thread terminer est dans un tat dattente lorsque Interrupt() est appele par un
autre thread, lexception ThreadInterruptedException est lance.
Si le thread terminer nest pas dans un tat dattente lorsque Interrupt() est appele, la
mme exception sera lance ds que ce thread rentrera dans un tat dattente. Le comportement est le mme si le thread terminer appelle Interrupt() sur lui-mme.
143
AbortRequested
Stopped
SuspendRequested
Background
StopRequested
Unstarted
La description de chacun de ces tats se trouve dans larticle ThreadState Enumeration des
MSDN. Cette numration est un indicateur binaire, cest--dire que ses instances peuvent
prendre plusieurs valeurs la fois. Par exemple un thread peut tre la fois dans ltat Running
AbortRequested et Background. La notion dindicateur binaire est prsente page 369.
Daprs ce quon a vu dans la section prcdente, on peut dfinir le diagramme dtat simplifi
suivant :
Unstarted
Start()
Wait()
Sleep()
Join()
WaitSleepJoin
Interrupt()
Suspended()
Running
Suspended
Resume()
Abort()
Stopped
Introduction la synchronisation
des accs aux ressources
En informatique, le mot synchronisation ne peut tre utilis que dans le cas des applications
multithreads (mono ou multi-processus). En eet, la particularit de ces applications est davoir
plusieurs units dexcution, do possibilit de conflits daccs aux ressources. Les objets de
synchronisation sont des objets partageables entre threads excuts sur la mme machine. Le
propre dun objet de synchronisation est de pouvoir bloquer un des threads utilisateur jusqu
la ralisation dune condition par un autre thread.
Comme nous allons le voir, il existe de nombreuses classes et mcanismes de synchronisation.
Chacun rpond un ou plusieurs besoins spcifiques et il est ncessaire davoir assimil tout ce
chapitre avant de concevoir une application professionnelle multithreads utilisant la synchronisation. Nous nous sommes eorc de souligner les dirences, surtout les plus subtiles, qui
existent entre les dirents mcanismes. Quand vous aurez compris les dirences, vous serez
capable dutiliser ces mcanismes.
Synchroniser correctement un programme est une des tches du dveloppement logiciel les plus
subtiles. Le sujet remplit de nombreux ouvrages. Avant de vous plonger dans des spcifications
144
Race conditions
Il sagit dune situation o des actions eectues par des units dexcution direntes senchanent dans un ordre illogique, entranant des tats non prvus.
Par exemple un thread T modifie une ressource R, rend les droits daccs dcriture R, reprend
les droits daccs en lecture sur R et utilise R comme si son tat tait celui dans lequel il lavait
laiss. Pendant lintervalle de temps entre la libration des droits daccs en criture et lacquisition des droits daccs en lecture, il se peut quun autre thread ait modifi ltat de R.
Un autre exemple classique de situation de comptition est le modle producteur consommateur. Le producteur utilise souvent le mme espace physique pour stocker les informations produites. En gnral on noublie pas de protger cet espace physique des accs concurrents entre
producteurs et consommateurs. On oublie plus souvent que le producteur doit sassurer quun
consommateur a eectivement lu une ancienne information avant de produire une nouvelle
information. Si lon ne prend pas cette prcaution, on sexpose au risque de produire des informations qui ne seront jamais consommes.
Les consquences de situations de comptition mal gres peuvent tre des failles dans un systme de scurit. Une autre application peut forcer un enchanement dactions non prvues
par les dveloppeurs. Typiquement il faut absolument protger laccs en criture un boolen
qui confirme ou infirme une authentification. Sinon il se peut que son tat soit modifi entre
linstant ou ce boolen est positionn par le mcanisme dauthentification et linstant ou ce
boolen est lu pour protger des accs des ressources. De clbres cas de failles de scurit
dues une mauvaise gestion des situations de comptition ont exist. Une de celles-ci concernait
notamment le noyau Unix.
Deadlocks
Il sagit dune situation de blocage cause de deux ou plusieurs units dexcution qui sattendent mutuellement. Par exemple :
Un thread T1 acquiert les droits daccs sur la ressource R1.
Un thread T2 acquiert les droits daccs sur la ressource R2.
T1 demande les droits daccs sur R2 et attend, car cest T2 qui les possde.
145
T2 demande les droits daccs sur R1 et attend, car cest T1 qui les possde.
T1 et T2 attendront donc indfiniment, la situation est bloque ! Il existe trois grandes approches
pour viter ce problme qui est plus subtil que la plupart des bugs que lon rencontre.
Nautoriser aucun thread avoir des droits daccs sur plusieurs ressources simultanment.
Dfinir une relation dordre dans lacquisition des droits daccs aux ressources. Cest--dire
quun thread ne peut acqurir les droits daccs sur R2 sil na pas dj acquis les droits daccs
sur R1. Naturellement la libration des droits daccs se fait dans lordre inverse de lacquisition.
Systmatiquement dfinir un temps maximum dattente (timeout) pour toutes les demandes daccs aux ressources et traiter les cas dchec. Pratiquement tous les mcanismes
de synchronisation .NET orent cette possibilit.
Les deux premires techniques sont plus ecaces mais aussi plus dicile implmenter. En effet, elles ncessitent chacune une contrainte trs forte et dicile maintenir durant lvolution
de lapplication. En revanche les situations dchecs sont inexistantes.
Les gros projets utilisent systmatiquement la troisime technique. En eet, si le projet est gros,
le nombre de ressources est en gnral trs grand. Dans ces projets, les conflits daccs simultans une ressource sont donc des situations marginales. La consquence est que les situations
dchec sont, elles aussi, marginales. On dit dune telle approche quelle est optimiste. Dans le
mme esprit, on dcrit en page 727 le modle de gestion optimiste des accs concurrents une
base de donnes.
Un type rfrence.
sbyte, byte, short, ushort, int, uint, char, float, bool (double, long et ulong,
la condition de travailler avec une machine 64 bits).
Une numration dont le type sous-jacent est parmi : byte, sbyte, short, ushort, int,
uint (double, long et ulong condition de travailler avec une machine 64 bits).
Comme vous laurez remarquez, seuls les types dont la valeur ou la rfrence fait au plus le
nombre doctets dun entier natif (quatre ou huit selon le processeur sous-jacent) peuvent tre
146
volatiles. Cela implique que les oprations concurrentes sur une valeur de plus de ce nombre
doctets (une grosse structure par exemple) doivent tre protges en utilisant les mcanismes
de synchronisation prsents ci-aprs.
La classe System.Threading.Interlocked
Lexprience a montr que les ressources protger dans un contexte multithreads sont souvent des variables entires. Les oprations les plus courantes ralises par les threads sur ces
variables entires partages sont lincrmentation et la dcrmentation dune unit et laddition de deux variables. Le framework .NET prvoit donc un mcanisme spcial avec la classe
System.Threading.Interlocked pour ces oprations trs spcifiques, mais aussi trs courantes.
Cette classe les mthodes statiques Increment(), Decrement() et Add() qui respectivement incrmente, dcrmente et additionne des entiers de type int ou long passs par rfrence. On dit
que lutilisation de la classe Interlocked rend ces oprations atomiques (cest--dire indivisibles,
comme ce que lon pensait il y a quelques dcennies pour les atomes de la matire).
Le programme suivant prsente laccs concurrent de deux threads la variable entire
compteur. Un thread lincrmente cinq fois tandis que lautre la dcrmente cinq fois.
Exemple 5-5 :
using System.Threading ;
class Program {
static long compteur = 1 ;
static void Main() {
Thread t1 = new Thread(f1) ;
Thread t2 = new Thread(f2) ;
t1.Start() ; t2.Start() ; t1.Join() ; t2.Join() ;
}
static void f1() {
for (int i = 0 ; i < 5 ; i++){
Interlocked.Increment(ref compteur);
System.Console.WriteLine("compteur++ {0}", compteur) ;
Thread.Sleep(10) ;
}
}
static void f2() {
for (int i = 0 ; i < 5 ; i++){
Interlocked.Decrement(ref compteur);
System.Console.WriteLine("compteur-- {0}", compteur) ;
Thread.Sleep(10) ;
}
}
}
Ce programme ache ceci (dune manire non dterministe, cest--dire que lachage pourrait
varier dune excution une autre) :
compteur++ 2
compteur-- 1
compteur++ 2
147
1
2
1
2
1
0
1
Si on nendormait pas les threads 10 millimes de seconde chaque modification, les threads
auraient le temps de raliser leurs tches en un quantum et il ny aurait pas lentrelacement des
excutions, donc pas daccs concurrent.
Un thread peut appeler Enter() plusieurs fois sur le mme objet la condition quil appelle
Exit() autant de fois sur le mme objet pour se librer des droits exclusifs.
Un thread peut possder des droits exclusifs sur plusieurs objets la fois, mais cela peut
mener une situation de deadlock.
Il ne faut jamais appeler les mthodes Enter() et Exit() sur un objet de type valeur,
comme un entier !
Il faut toujours appeler la mthode Exit() dans un bloc finally afin dtre certain de
librer les droits daccs exclusifs quoi quil arrive.
148
Si dans lExemple 5-5, un thread doit lever la variable compteur au carr tandis que lautre
thread doit la multiplier par deux, il faudrait remplacer lutilisation de la classe Interlocked
par lutilisation de la classe Monitor. Le code de f1() et f2() serait alors :
Exemple 5-6 :
using System.Threading ;
class Program {
static long compteur = 1 ;
static void Main() {
Thread t1 = new Thread(f1) ;
Thread t2 = new Thread(f2) ;
t1.Start() ; t2.Start() ; t1.Join() ; t2.Join() ;
}
static void f1() {
for (int i = 0 ; i < 5 ; i++){
try{
Monitor.Enter( typeof(Program) );
compteur *= compteur ;
}
finally{ Monitor.Exit( typeof(Program) ) ; }
System.Console.WriteLine("compteur^2 {0}", compteur) ;
Thread.Sleep(10) ;
}
}
static void f2() {
for (int i = 0 ; i < 5 ; i++){
try{
Monitor.Enter( typeof(Program) );
compteur *= 2 ;
}
finally{ Monitor.Exit( typeof(Program) ) ; }
System.Console.WriteLine("compteur*2 {0}", compteur) ;
Thread.Sleep(10) ;
}
}
}
Il est tentant dcrire compteur la place de typeof(Program) mais compteur est un membre
statique de type valeur. Remarquez que les oprations lvation au carr et multiplication
par deux ntant pas commutatives, la valeur finale de compteur est ici non dtermine.
Le mot cl lock de C
Le langage C prsente le mot-cl lock qui remplace lgamment lutilisation des mthode Enter() et Exit(). La mthode f1() pourrait donc scrire :
Exemple 5-7 :
using System.Threading ;
class Program {
149
Le pattern SyncRoot
linstar des exemples prcdents, on utilise en gnral la classe Monitor avec une instance de
la clase Type du type courant lintrieur dune mthode statique. De mme, on se synchronise
souvent sur le mot cl this lintrieur dune mthode non statique. Dans les deux cas, on
se synchronise sur un objet visible hors de la classe. Cela peut poser des problmes si dautres
parties du code se synchronisent sur ces objets. Pour viter ces problmes potentiels, nous vous
conseillons dutiliser un membre priv SyncRoot de type object, statique ou non selon vos besoins :
Exemple 5-8 :
class Foo {
private static object staticSyncRoot = new object();
private object instanceSyncRoot = new object();
public static void StaticFct() {
lock (staticSyncRoot) { /*...*/ }
150
151
La mthode Monitor.TryEnter()
public static bool TryEnter(object [,int] )
Cette mthode est similaire Enter() mais elle nest pas bloquante. Si les droits daccs exclusifs
sont dj possds par un autre thread, cette mthode retourne immdiatement et sa valeur de
retour est false. On peut aussi rendre un appel TryEnter() bloquant pour une dure limite
spcifie en millisecondes. Puisque lissue de cette mthode est incertaine, et que dans le cas
o lon acquerrait les droits daccs exclusifs il faudrait les librer dans un bloc finally, il est
conseill de sortir immdiatement de la mthode courante dans le cas o lappel TryEnter()
chouerait :
Exemple 5-11 :
using System.Threading ;
class Program {
static void Main() {
// `A commenter pour tester le cas o`u TryEnter() retourne true.
Monitor.Enter(typeof(Program)) ;
Thread t1 = new Thread(f1) ;
t1.Start() ; t1.Join() ;
}
static void f1() {
bool bOwner = false ;
try {
if( ! Monitor.TryEnter( typeof(Program) ) )
return;
bOwner = true;
// ...
}
finally {
// Ne surtout pas appeler Monitor.Exit() si on a pas lacc`
es.
// Noubliez pas que lon passe n
ecessairement par le bloc
// finally, y compris si lappel `
a TryEnter() retourne false.
if( bOwner )
Monitor.Exit( typeof(Program) );
}
}
}
152
Le thread T1 possdant laccs exclusif lobjet OBJ, appelle la mthode Wait(OBJ) afin de
senregistrer dans une liste dattente passive de OBJ.
Par cet appel T1 perd laccs exclusif OBJ. Ainsi un autre thread T2 prend laccs exclusif
OBJ en appelant la mthode Enter(OBJ).
T2 modifie ventuellement ltat de OBJ puis appelle Pulse(OBJ) pour signaler cette modification. Cet appel provoque le passage du premier thread de la liste dattente passive de OBJ
(en loccurrence T1) en haut de la liste dattente active de OBJ. Le premier thread de la liste
active de OBJ a la garantie quil sera le prochain avoir les droits daccs exclusifs OBJ ds
quils seront librs. Il pourra ainsi sortir de son attente dans la mthode Wait(OBJ).
Dans notre scnario T2 libre les droits daccs exclusifs sur OBJ en appelant Exit(OBJ) et
T1 les rcupre et sort de la mthode Wait(OBJ).
La mthode PulseAll() fait en sorte que les threads de la liste dattente passive, passent
tous dans la liste dattente active. Limportant est que les threads soient dbloqus dans le
mme ordre quils ont appels Wait().
Si Wait(OBJ) est appele par un thread ayant appel plusieurs fois Enter(OBJ), ce thread devra
appeler Exit(OBJ) le mme nombre de fois pour librer les droits daccs OBJ. Mme dans ce
cas, un seul appel Pulse(OBJ) par un autre thread sut dbloquer le premier thread.
Le programme suivant illustre cette fonctionnalit au moyen de deux threads ping et pong qui
se obtiennent tour de rle les droits daccs un objet balle :
Exemple 5-12 :
using System.Threading ;
public class Program {
static object balle = new object();
public static void Main() {
Thread threadPing = new Thread( ThreadPingProc ) ;
Thread threadPong = new Thread( ThreadPongProc ) ;
threadPing.Start() ; threadPong.Start() ;
threadPing.Join() ; threadPong.Join() ;
}
static void ThreadPongProc() {
System.Console.WriteLine("ThreadPong: Hello!") ;
lock (balle)
for (int i = 0 ; i < 5 ; i++){
153
Hello!
Ping
Hello!
Pong
Ping
Pong
Ping
Pong
Ping
Pong
Ping
Pong
Bye!
Le thread pong ne se termine pas et reste bloqu sur la mthode Wait(). Ceci rsulte du fait que
le thread pong a obtenu le droit daccs exclusif sur lobjet balle en deuxime.
La classe Mutex (le mot mutex est la concatnation de mutuelle exclusion. En franais on
parle parfois de mutant)).
154
La classe WaitHandle et ses classes drives, ont la particularit dimplmenter la mthode non
statique WaitOne() et les mthodes statiques WaitAll(), WaitAny() et SignalAndWait(). Elles
permettent respectivement dattendre quun objet soit signal, que tous les objets dans un tableau soient signals, quau moins un objet dans un tableau soit signal, de signaler un objet et
dattendre sur un autre. Contrairement la classe Monitor et Interlocked, ces classes doivent
tre instancies pour tre utilises. Il faut donc raisonner ici en terme dobjets de synchronisation et non dobjets synchroniss. Ceci implique que les objets passs en paramtre des mthodes statiques WaitAll(), WaitAny() et SignalAndWait() sont soit des mutex, soit des vnements soit des smaphores.
Les Mutex
En terme de fonctionnalits les mutex sont proches de lutilisation de la classe Monitor ces
dirences prs :
On peut utiliser un mme mutex dans plusieurs processus dune mme machine ou sexcutant sur des machines direntes.
Les mutex nont pas la fonctionnalit des mthodes Wait(), Pulse() et PulseAll() de la
classe Monitor.
Sachez que lorsque lutilisation dun mutex se cantonne un processus, il vaut mieux synchroniser vos accs avec la classe Monitor qui est plus performante.
Le programme suivant montre lutilisation dun mutex nomm pour protger laccs une ressource partage par plusieurs processus sur la mme machine. La ressource partage est un fichier dans lequel chaque instance du programme crit 10 lignes.
155
Exemple 5-13 :
using System.Threading ;
using System.IO ;
class Program {
static void Main() {
// Le mutex est nomme MutexTest.
Mutex mutexFile = new Mutex(false, "MutexTest");
for (int i = 0 ; i < 10 ; i++){
mutexFile.WaitOne();
// Ouvre le fichier, ecrit Hello i, ferme le fichier.
FileInfo fi = new FileInfo("tmp.txt") ;
StreamWriter sw = fi.AppendText() ;
sw.WriteLine("Hello {0}", i) ;
sw.Flush() ;
sw.Close() ;
// Attend 1 seconde pour rendre
evidente laction du mutex.
System.Console.WriteLine("Hello {0}", i) ;
Thread.Sleep(1000) ;
mutexFile.ReleaseMutex();
}
mutexFile.Close();
}
}
Remarquez lutilisation de la mthode WaitOne() qui bloque le thread courant jusqu lobtention du mutex et lutilisation de la mthode ReleaseMutex() qui libre le mutex.
Dans ce programme, new Mutex ne signifie pas forcment la cration du mutex mais la cration
dune rfrence vers le mutex nomm "MutexTest". Le mutex est eectivement cr par le systme dexploitation seulement sil nexiste pas dj. De mme la mthode Close() ne dtruit
pas forcment le mutex si ce dernier est encore rfrenc par dautres processus.
Pour ceux qui avaient lhabitude dutiliser la technique du mutex nomm en win32 pour viter
de lancer deux instances du mme programme sur la mme machine, sachez quil existe une
faon plus lgante de procder sous .NET, dcrite dans la section page 135.
Les vnements
Contrairement aux mcanismes de synchronisation vus jusquici, les vnements ne dfinissent
pas explicitement de notion dappartenance dune ressource un thread un instant donn.
Les vnements servent passer une notification dun thread un autre, cette notification
tant un vnement sest pass . Lvnement concern est associ une instance dune
des deux classes dvnement, System.Threading.AutoResetEvent et System.Threading.
ManualResetEvent. Ces deux classes drivent de la classe System.Threading.EventWaitHandle.
Une instance de EventWaitHandle peut tre initialise avec une des deux valeurs AutoReset ou
ManualReset de lnumration System.Threading.EventResetMode ce qui fait que vous pouvez
viter de vous servir de ses deux classes filles.
Concrtement, un thread attend quun vnement soit signal en appelant la mthode bloquante WaitOne() sur lobjet vnement associ. Puis un autre thread signale lvnement en
156
appelant la mthode Set() sur lobjet vnement associ et permet ainsi au premier thread de
reprendre son excution.
La dirence entre les vnement repositionnement automatique et les vnement repositionnement manuel est que lon a besoin dappeler la mthode Reset() pour repositionner lvnement
en position non active, sur un vnement de type repositionnement manuel aprs lavoir
signal.
157
Thread.Sleep(60) ;
}
}
}
Ce programme ache :
Thread0: 0
Thread1: 0
Thread1: 1
Thread0: 1
Thread1: 2
Thread1: 3
Thread0: 2
MainThread: Thread0 est arrive `
a 2 et Thread1 est arriv
e `
a 3
Thread1: 4
Thread0: 3
Thread0: 4
Les smaphores
Une instance de la classe System.Threading.Semaphore permet de contraindre le nombre daccs simultans une ressource. Vous pouvez vous limaginez comme la barrire dentre dun
parking automobile qui contient un nombre fix de places. Elle ne souvre plus lorsque le parking est complet. De mme, une tentative dentre dans un smaphore au moyen de la mthode
WaitOne() devient bloquante lorsque le nombre dentres courantes a atteint le nombre dentre maximal. Ce nombre dentre maximal est fix par le deuxime argument des constructeurs
de la classe Semaphore. Le premier argument dfinit le nombre dentres libres initiales. Si le
premier argument a une valeur infrieure celle du deuxime, le thread qui appelle le constructeur dtient automatiquement un nombre dentres gal la dirence des deux valeurs. Cette
dernire remarque montre quun mme thread peut dtenir simultanment plusieurs entres
dun mme smaphore.
Lexemple suivant illustre tout ceci en lanant 3 threads qui tentent rgulirement dentrer dans
un smaphore dont le nombre dentres simultanes maximal est fix cinq. Le thread principal
dtient durant toute la dure de lexcution trois de ces entres, obligeant les 3 threads fils se
partager 2 entres.
Exemple 5-15 :
using System ;
using System.Threading ;
class Program {
static Semaphore semaphore;
static void Main() {
// Nombre dentrees libres initiales
: 2.
// Nombre maximal dentrees simultan
ees : 5.
// Nombre dentrees detenues par le thread principal : 3 (5-2).
semaphore = new Semaphore(2, 5);
for (int i = 0 ; i < 3 ; ++i){
Thread t = new Thread(WorkerProc) ;
158
Voici lachage de ce programme. On voit bien quil ny a jamais plus de deux threads fils qui
travaillent simultanment :
Thread0:
Thread1:
Thread0:
Thread2:
Thread1:
Thread1:
Thread2:
Thread2:
Thread1:
Thread1:
Thread2:
Thread2:
Thread1:
Thread0:
Thread2:
Thread0:
Thread0:
Thread0:
Begin
Begin
End
Begin
End
Begin
End
Begin
End
Begin
End
Begin
End
Begin
End
End
Begin
End
159
t demand mais pas encore obtenu, les nouvelles demandes daccs en lecture sont mises en
attente.
Lorsque ce modle de synchronisation peut tre appliqu, il faut toujours le privilgier par
rapport au modle propos par les classes Monitor ou Mutex. En eet, le modle accs exclusif ne permet en aucun cas des accs simultans et est donc moins performant. De plus,
empiriquement, on se rend compte que la plupart des applications accdent beaucoup plus
souvent aux donnes en lecture quen criture.
linstar des mutex et des vnements et au contraire de la classe Monitor, la classe ReaderWriterLock doit tre instancie pour tre utilise. Il faut donc ici aussi raisonner en terme dobjets
de synchronisation et non dobjets synchroniss.
Voici un exemple de code qui montre lutilisation de cette classe, mais qui nen exploite pas
toutes les possibilits. En eet les mthodes DowngradeFromWriterLock() et UpgradeToWriterLock() permettent de demander un changement de droit daccs sans avoir librer son droit
daccs courant.
Exemple 5-16 :
using System ;
using System.Threading ;
class Program {
static int laRessource = 0 ;
static ReaderWriterLock rwl = new ReaderWriterLock();
static void Main() {
Thread tr0 = new Thread(ThreadReader) ;
Thread tr1 = new Thread(ThreadReader) ;
Thread tw = new Thread(ThreadWriter) ;
tr0.Start() ; tr1.Start() ; tw.Start() ;
tr0.Join() ; tr1.Join() ; tw.Join() ;
}
static void ThreadReader() {
for (int i = 0 ; i < 3 ; i++)
try{
// AcquireReaderLock() lance une
// ApplicationException si timeout.
rwl.AcquireReaderLock(1000);
Console.WriteLine(
"Debut Lecture laRessource = {0}", laRessource) ;
Thread.Sleep(10) ;
Console.WriteLine(
"Fin
Lecture laRessource = {0}", laRessource) ;
rwl.ReleaseReaderLock() ;
}
catch ( ApplicationException ) { /* `
a traiter */ }
}
static void ThreadWriter() {
for (int i = 0 ; i < 3 ; i++)
160
Ce programme ache :
Debut
Debut
Fin
Fin
Debut
Fin
Debut
Debut
Fin
Fin
Debut
Fin
Debut
Debut
Fin
Fin
Debut
Fin
lecture
lecture
lecture
lecture
ecriture
ecriture
lecture
lecture
lecture
lecture
ecriture
ecriture
lecture
lecture
lecture
lecture
ecriture
ecriture
laRessource
laRessource
laRessource
laRessource
laRessource
laRessource
laRessource
laRessource
laRessource
laRessource
laRessource
laRessource
laRessource
laRessource
laRessource
laRessource
laRessource
laRessource
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
0
0
0
0
0
1
1
1
1
1
1
2
2
2
2
2
2
3
Pour que ce comportement sapplique correctement il faut que la classe sur laquelle sapplique lattribut soit context-bound, cest--dire quelle doit driver de la classe System.
ContextBoundObject. La signification du terme context-bound est explique page 842.
161
: ManagedThreadId
ManagedThreadId
: ManagedThreadId
ManagedThreadId
: ManagedThreadId
ManagedThreadId
: ManagedThreadId
ManagedThreadId
=
=
=
=
=
=
=
=
27
27
28
28
27
27
28
28
162
Un domaine de synchronisation est une entit entirement prise en charge par le CLR. Un tel
domaine contient un ou plusieurs contextes .NET et donc, contient les objets de ces contextes.
Un contexte .NET ne peut appartenir qu au plus un seul domaine de synchronisation. En
outre, la notion de domaine de synchronisation est plus fine que la notion de domaine dapplication. La figure suivante illustre les relations dinclusions entre processus, domaines dapplication, domaines de synchronisation, contextes .NET et objets .NET :
Processus
Domaine dapplication
Contexte
Domaine de
synchronisation
Contexte
Objet
Objet
Lattribut System.Runtime.Remoting.Contexts.Synchronization et
les domaines de synchronisation
Vous avez peut-tre dj devin que lattribut System.Runtime.Remoting.Contexts.Synchronization sert en fait indiquer au CLR quand crer un domaine de synchronisation et comment en dlimiter sa frontire. Ces rglages se font en utilisant dans la dclaration dun attribut System.Runtime.Remoting.Contexts.Synchronization une des quatre valeurs suivantes
NOTSUPPORTED, SUPPORTED, REQUIRED ou REQUIRES_NEW. Notez que la valeur REQUIRED est choisie
par dfaut.
Lappartenance un domaine dapplication se communique de proche en proche lorsquun objet en cre un autre. On parle ainsi dobjet crateur, mais prenez en compte quun objet peut tre
aussi cr au sein dune mthode statique. Or, il se peut que lexcution de la mthode statique
soit le fruit dun appel dun objet situ dans un domaine de synchronisation. Il faut alors savoir
163
que dans ce cas, la mthode statique propage lappartenance au domaine dapplication et joue
ainsi le rle dun objet crateur. Voici lexplication des quatre comportements possibles :
NOT_SUPPORTED
SUPPORTED
REQUIRED
Ce paramtre assure quune instance de la classe sur laquelle sapplique lattribut Synchronization sera de toutes faons dans
un domaine de synchronisation. Si lobjet crateur appartient
dj un domaine de synchronisation, alors on se satisfera de
celui l. Sinon, un nouveau domaine de synchronisation est cr
pour accueillir ce nouvel objet.
REQUIRES_NEW
Ce paramtre assure quune instance de la classe sur laquelle sapplique lattribut Synchronization sera dans un nouveau domaine de synchronisation (que son objet crateur appartienne
un domaine de synchronisation ou non).
NOT_SUPPORTED Non
Lobjet cr rsidera...
Oui
SUPPORTED
Non
Oui
164
REQUIRED
REQUIRES_NEW
Non
Oui
Non
Oui
Soit le CLR autorise un autre thread T2 acqurir les droits daccs exclusifs de D. Dans ce cas,
il se peut que T1 doivent attendre au retour de son appel lextrieur de D que T2 libre les
droits daccs exclusifs de D. On dit quil y a rentrance puisque T1 demande rentrer dans
D.
Soit les droits daccs exclusifs de D restent allous T1. Dans ce cas, un autre thread T2
souhaitant invoquer une mthode dun objet de D devra attendre que T1 libre les droits
daccs exclusifs de D.
Un appel une mthode statique nest pas considr comme un appel lextrieur dun domaine de synchronisation.
Temps
Course du thread T1
Domaine de synchronisation D
Objet 1
Objet 2
Objet 3
Mthode
Mthode
On dit quil y a rentrance dans le domaine
dapplication D si un
autre thread T1 peut
ventuellement acqurir
les droits daccs exclusifs de D
Mthode
165
Notez quil sut que lattribut de synchronisation de la classe du premier objet rencontr par
un thread dans un domaine de synchronisation ait positionn la rentrance true pour quil y
ait eectivement rentrance.
Lexemple suivant est lillustration par le code de la figure ci-dessus :
Exemple 5-18 :
using System ;
using System.Runtime.Remoting.Contexts ;
using System.Threading ;
[Synchronization(SynchronizationAttribute.REQUIRES_NEW, true)]
public class Foo1 : ContextBoundObject {
public void AfficheThreadId() {
Console.WriteLine("Foo1 D
ebut : ManagedThreadId = " +
Thread.CurrentThread.ManagedThreadId) ;
Thread.Sleep(1000) ;
Foo2 obj2 = new Foo2() ;
obj2.AfficheThreadId() ;
Console.WriteLine("Foo1 Fin : ManagedThreadId = " +
Thread.CurrentThread.ManagedThreadId) ;
}
}
[Synchronization(SynchronizationAttribute.REQUIRED)]
public class Foo2 : ContextBoundObject {
public void AfficheThreadId() {
Console.WriteLine("Foo2 D
ebut : ManagedThreadId = " +
Thread.CurrentThread.ManagedThreadId) ;
Thread.Sleep(1000) ;
Foo3 obj3 = new Foo3() ;
obj3.AfficheThreadId() ;
Console.WriteLine("Foo2 Fin : ManagedThreadId = " +
Thread.CurrentThread.ManagedThreadId) ;
}
}
// On est certains que les instances de cette classe ne
// seront dans aucun domaine de synchronisation.
[Synchronization(SynchronizationAttribute.NOT_SUPPORTED)]
public class Foo3 : ContextBoundObject {
public void AfficheThreadId() {
Console.WriteLine("Foo3 D
ebut : ManagedThreadId = " +
Thread.CurrentThread.ManagedThreadId) ;
Thread.Sleep(1000) ;
Console.WriteLine("Foo3 Fin : ManagedThreadId = " +
Thread.CurrentThread.ManagedThreadId) ;
}
}
public class Program {
static Foo1 m_Objet = new Foo1() ;
static void Main() {
166
Debut
Debut
Debut
Debut
Debut
Debut
Fin :
Fin :
Fin :
Fin :
Fin :
Fin :
:
:
:
:
:
:
ManagedThreadId
ManagedThreadId
ManagedThreadId
ManagedThreadId
ManagedThreadId
ManagedThreadId
ManagedThreadId
ManagedThreadId
ManagedThreadId
ManagedThreadId
ManagedThreadId
ManagedThreadId
=
=
=
=
=
=
=
=
=
=
=
=
3
3
4
4
4
3
4
4
4
3
3
3
Il est clair que si lon avait dsactiv la rentrance dans Foo1, lachage de ce programme aurait
t le suivant :
Foo1
Foo2
Foo3
Foo3
Foo2
Foo1
Foo1
Foo2
Foo3
Foo3
Foo2
Foo1
Debut
Debut
Debut
Fin :
Fin :
Fin :
Debut
Debut
Debut
Fin :
Fin :
Fin :
: ManagedThreadId = 3
: ManagedThreadId = 3
: ManagedThreadId = 3
ManagedThreadId = 3
ManagedThreadId = 3
ManagedThreadId = 3
: ManagedThreadId = 4
: ManagedThreadId = 4
: ManagedThreadId = 4
ManagedThreadId = 4
ManagedThreadId = 4
ManagedThreadId = 4
La rentrance est utilise pour optimiser la gestion des ressources car elle permet de rduire
globalement les dures daccs exclusifs des threads sur les domaines de synchronisation. Cependant par dfaut, la rentrance est dsactive. En eet, lorsquil y a rentrance, notre comprhension de la notion de domaine de synchronisation est fortement perturbe. Bien pire encore,
activer tort et travers la rentrance peut rapidement amener des situations de deadlock. Pour
ces raisons, il est fortement dconseill dactiver la rentrance.
167
Le dveloppeur est donc dcharg de ces responsabilits. Malgr tous ces avantages, il est souhaitable de ne pas utiliser de pool de threads lorsque :
Vos tches doivent tre traites dans des STA (Single Apartment Thread). En eet les threads
dun pool sont de type MTA (Multiple Apartement Thread). Cette notion de thread apartment,
inhrente la technologie COM, est explique page 285.
168
est atteint dans le pool, ce dernier ne cre plus de nouveaux threads et les tches dans la file du
pool ne seront traites que lorsquun thread du pool se librera. En revanche, le thread responsable de la cration de la tche, na pas attendre quelle soit traite. Vous pouvez utiliser le pool
de threads de deux faons direntes :
En postant vos propres tches et leurs mthodes de traitement avec la mthode statique
ThreadPool.QueueUserWorkItem(). Une fois quune tche a t poste au pool, elle ne peut
plus tre annule.
En crant un timer qui poste priodiquement une tche prdfinie et sa mthode de traitement au pool. Pour cela, il faut utiliser la mthode statique ThreadPool.RegisterWaitForSingleObject().
Chacune de ces deux mthodes existe dans une version non protge (UnsafeQueueUserWorkItem() et UnsafeRegisterWaitForSingleObject()) . Ces versions non protges permettent
aux threads ouvriers du pool de ne pas tre dans le mme contexte de scurit que le thread qui
a dpos la tche. Lutilisation de ces mthodes amliore donc les performances puisque la pile
du thread qui a dpos la tche nest pas vrifie lors de la gestion des contextes de scurit. Les
mthodes de traitement sont rfrences par des dlgus.
Voici un exemple qui montre lutilisation de tches utilisateurs (paramtres par un numro et
traites par la mthode ThreadTache()) et de tches postes priodiquement (sans paramtre
et traites par la mthode ThreadTacheWait()). Les tches utilisateurs sont volontairement
longues pour forcer le pool crer de nouveaux threads.
Exemple 5-19 :
using System.Threading ;
public class Program {
public static void Main() {
// Positionnement initial de l
ev
enement : false.
ThreadPool.RegisterWaitForSingleObject(
new AutoResetEvent(false),
// Methode de traitement de la t
ache p
eriodique.
new WaitOrTimerCallback( ThreadTacheWait ),
null,
// La tache periodique na pas de param`
etres.
2000,
// La periode est de 2 secondes.
false) ; // La tache p
eriodique est ind
efiniment d
eclench
ee.
// Poste 3 taches utilisateur de param`
etres 0,1,2.
for (int count = 0 ; count < 3 ; ++count)
ThreadPool.QueueUserWorkItem(
new WaitCallback(ThreadTache), count) ;
// Attente de 12 secondes avant de finir le processus
// car les threads du pool sont des threads background.
Thread.Sleep(12000) ;
}
static void ThreadTache(object obj) {
System.Console.WriteLine("Thread#{0} T
ache#{1} D
ebut",
Thread.CurrentThread.ManagedThreadId , obj.ToString()) ;
Timers
169
Thread.Sleep(5000) ;
System.Console.WriteLine("Thread#{0} T
ache#{1} Fin",
Thread.CurrentThread.ManagedThreadId , obj.ToString()) ;
}
static void ThreadTacheWait(object obj, bool signaled) {
System.Console.WriteLine("Thread#{0} T
acheWait",
Thread.CurrentThread.ManagedThreadId ) ;
}
}
Ce programme ache ceci (dune manire non dterministe):
Thread#4
Thread#5
Thread#6
Thread#7
Thread#7
Thread#4
Thread#5
Thread#6
Thread#7
Thread#7
Tache#0 Debut
Tache#1 Debut
Tache#2 Debut
TacheWait
TacheWait
Tache#0 Fin
Tache#1 Fin
Tache#2 Fin
TacheWait
TacheWait
Timers
La plupart des applications contiennent des tches qui doivent tre excutes priodiquement.
Par exemple vous pouvez imaginez de vrifier priodiquement la disponibilit dun serveur. La
premire solution qui vient lesprit pour implmenter ce paradigme est de crer un thread ddi une tche qui appelle la mthode Thread.Sleep() entre deux excutions. Cette implmentation prsente linconvnient de consommer inutilement un thread entre deux excutions.
Nous allons prsenter plusieurs implmentations plus performantes fournies par le framework
.NET.
Plus prcisment, le framework prsente les trois classes System.Timers.Timer, System.Threading.Timer et System.Windows.Forms.Timer. La classe de lespace de noms Forms doit tre utilise pour excuter des tches priodiques graphiques, telles que le rafrachissement de donnes
sur lcran ou lachage conscutif des images dune animation. Le choix entre les classes des
espaces de noms Timers et Threading est plus dlicat et dpend de vos besoins.
La classe System.Timers.Timer
Limplmentation System.Timers.Timer utilise les threads du pool de threads pour excuter
une tche. Aussi, vous devez synchroniser les accs aux ressources consommes par une telle
tche.
Cette classe prsente la proprit double Interval{get;set;} qui permet daccder la priode
exprime en millisecondes. Les mthodes Start() et Stop() vous permettent dactiver ou de
dsactiver le timer. Vous pouvez aussi changer lactivation du timer en utilisant la proprit
bool Enabled{get;set;}.
170
Un dlgu de type ElapsedEventHandler rfrence la mthode qui reprsente la tche excuter. Cette rfrence est reprsente par lvnement ElapsedEventHandler Elapsed. La signature
propose par la dlgation ElapsedEventHandler est celle-ci :
void ElapsedEventHandler(object sender, ElapsedEventArgs e)
Le premier argument rfrence le timer qui a dclench la tche. Ainsi, plusieurs timers peuvent
dclencher une mme mthode et vous pouvez les direncier au moyen de cet argument.
De plus, puisquun dlgu peut rfrencer plusieurs mthodes, un mme timer peut appeler
conscutivement plusieurs mthodes chaque dclenchement. Le second argument contient la
date de dclenchement du timer que vous pouvez rcuprer avec la proprit DateTime ElapsedEvenArgs.SignalTime{get;}. Le programme suivant illustre lutilisation de la classe System.
Timers.Timer :
Exemple 5-20 :
using System.Timers ;
class Program {
static Timer Timer1 = new Timer() ;
static Timer Timer2 = new Timer() ;
static void Main() {
Timer1.Interval = 1000 ; // P
eriode = 1 seconde.
Timer1.Elapsed +=
new ElapsedEventHandler(PeriodicTaskHandler) ;
Timer2.Interval = 2000 ; // P
eriode = 2 secondes.
Timer2.Elapsed +=
new ElapsedEventHandler(PeriodicTaskHandler) ;
Timer2.Elapsed +=
new ElapsedEventHandler(PeriodicTaskHandler) ;
Timer1.Start() ; Timer2.Start() ;
System.Threading.Thread.Sleep(5000) ; // Dors 5 secondes.
Timer1.Stop() ; Timer2.Stop() ;
}
static void PeriodicTaskHandler(object sender,
ElapsedEventArgs e) {
string str = (sender == Timer1) ? "Timer1 " : "Timer2 " ;
str += e.SignalTime.ToLongTimeString() ;
System.Console.WriteLine(str) ;
}
}
Ce programme ache ceci :
Timer1
Timer1
Timer2
Timer2
Timer1
Timer1
Timer2
Timer2
Timer1
19:42:49
19:42:50
19:42:50
19:42:50
19:42:51
19:42:52
19:42:52
19:42:52
19:42:53
171
La classe System.Threading.Timer
La classe System.Threading.Timer est assez similaire la classe System.Timers.Timer. Cette
classe utilise aussi les threads du pool de threads pour excuter une tche mais la dirence
de la classe System.Timers.Timer, elle ne vous permet de prciser exactement un thread.
Une autre dirence est que cette classe vous permet de fournir une chance de dmarrage (due
time en anglais). Lchance de dmarrage dfinie linstant o le timer va dmarrer en prcisant une dure. Vous pouvez modifier lchance de dmarrage nimporte quel moment en
appelant la mthode Change(). En prcisant une chance de dmarrage nulle vous pouvez dmarrer le timer immdiatement. En prcisant la constante System.Threading.Timer.Infinite
vous pouvez stopper le timer. Vous pouvez aussi stopper le timer en appelant la mthode Dispose() mais alors vous ne pourrez plus le redmarrer.
172
de lappel. Lappel est automatiquement pris en charge par un thread du pool de threads du
processus. Le programme peut ultrieurement rcuprer les informations retournes par lappel asynchrone. La technique dappel asynchrone est entirement gre par le CLR.
Le mcanisme que nous dcrivons peut tre utilis dans votre propre architecture. Il est aussi
utilis par les classes du framework .NET, notamment pour grer les flots de donnes dune
manire asynchrone ou pour grer des appels asynchrones sur des objets distants, cest--dire
situs dans un autre domaine dapplication.
Dlgation asynchrone
Lors dun appel asynchrone vous navez pas crer ni vous occupez du thread qui excute le
corps de la mthode. Ce thread est gr par le pool de threads dcrit un peu plus haut.
Avant dutiliser eectivement un dlgu asynchrone, il est judicieux de remarquer que toutes les
dlgations prsentent automatiquement les deux mthodes BeginInvoke() et EndInvoke(). La
signature de ces deux mthodes est calque sur la signature de la dlgation qui les prsente. Par
exemple la dlgation suivante...
delegate int Deleg(int a,int b) ;
...expose les deux mthodes suivantes :
IAsyncResult BeginInvoke(int a,int b,AsyncCallback callback,object o) ;
int EndInvoke(IAsyncResult result) ;
Ces deux mthodes sont produites par le compilateur de C . Cependant, le mcanisme dintellisense de Visual Studio est capable de les infrer partir dune dlgation de faon vous les
prsenter.
Pour appeler une mthode dune manire asynchrone, il faut dabord la rfrencer avec un
dlgu ayant la mme signature. Il sut ensuite dappeler la mthode BeginInvoke() sur ce
dlgu. Comme vous lavez remarqu, le compilateur a fait en sorte que les premiers arguments
de BeginInvoke() soient les arguments de la mthode appeler. Les deux derniers arguments
de cette mthode de type AsyncCallback et object sont expliqus un peu plus loin.
La valeur de retour de lappel asynchrone dune mthode, peut tre rcupre en appelant la
mthode EndInvoke(). L aussi, le compilateur a fait en sorte que le type de la valeur de retour
de EndInvoke() soit le mme que le type de la valeur de retour de la dlgation (ce type est int
dans notre exemple). Lappel EndInvoke() est bloquant. Cest--dire que lappel ne retourne
que lorsque lexcution asynchrone est eectivement termine.
Le programme suivant illustre lappel asynchrone dune mthode WriteSomme(). Notez que
pour bien direncier le thread excutant la mthode Main() et le thread excutant la mthode
WriteSomme(), nous achons la valeur de hachage du thread courant (qui est dirente pour
chaque thread).
Exemple 5-21 :
using System.Threading ;
class Program {
public delegate int Deleg(int a, int b) ;
static int WriteSomme(int a, int b) {
int somme = a + b ;
173
Procdure de finalisation
Vous avez la possibilit de spcifier une mthode qui sera automatiquement appele lorsque
lappel asynchrone sera termin. Cette mthode est appele procdure de finalisation. Une procdure de finalisation est appele par le mme thread que celui qui a excut lappel asynchrone.
Pour utiliser une procdure de finalisation, il vous sut de spcifier la mthode dans une dlgation de type System.AsyncCallback comme avant-dernier paramtre de la mthode BeginInvoke(). Cette mthode doit tre conforme cette dlgation, cest--dire quelle doit retourner
le type void et prendre pour seul argument une interface IAsyncResult. Comme le montre
lexemple suivant, cette mthode doit appeler EndInvoke().
Un problme se pose, car les threads du pool utiliss pour traiter les appels asynchrones sont
des threads background. linstar de lexemple ci-dessous, il faut que vous implmentiez un
mcanisme de gestion dvnements pour vous assurer que lapplication ne se termine pas sans
avoir termin lexcution asynchrone. Linterface IAsyncResult prsente un objet de synchronisation dune classe drive de WaitHandle, mais cet objet est signal ds que le traitement
asynchrone est fini et avant que la procdure de finalisation soit appele. Cet objet ne peut donc
pas permettre dattendre la fin de lexcution de la procdure de finalisation.
Exemple 5-22 :
using
using
using
class
System ;
System.Threading ;
System.Runtime.Remoting.Messaging ;
Program {
174
:
:
:
:
175
asynchrone et dans la procdure de finalisation. Une autre rfrence vers cet objet est la proprit AsyncState de linterface IAsyncResult. Vous pouvez vous en servir pour reprsenter un
tat positionn dans la procdure de finalisation. Par exemple, lvnement de lexemple de la
section prcdente peut tre vu comme un tat. Voici lexemple prcdent rcrit pour utiliser
cette fonctionnalit :
Exemple 5-23 :
using System ;
using System.Threading ;
using System.Runtime.Remoting.Messaging ;
class Program {
public delegate int Deleg(int a, int b) ;
static int WriteSomme(int a, int b) {
Console.WriteLine(
"{0} : Somme = {1}", Thread.CurrentThread.ManagedThreadId , a+b) ;
return a + b ;
}
static void SommeFinie(IAsyncResult async) {
// Attend une seconde pour simuler un traitement long.
Thread.Sleep(1000) ;
Deleg proc =
((AsyncResult)async).AsyncDelegate as Deleg ;
int somme = proc.EndInvoke(async) ;
Console.WriteLine("{0} : Proc
edure de finalisation somme = {1}",
Thread.CurrentThread.ManagedThreadId , somme) ;
((AutoResetEvent)async.AsyncState).Set();
}
static void Main() {
Deleg proc = WriteSomme ;
AutoResetEvent ev = new AutoResetEvent(false) ;
IAsyncResult async = proc.BeginInvoke(10, 10,
new AsyncCallback(SommeFinie), ev) ;
Console.WriteLine(
"{0} : BeginInvoke() appelee ! Attend l
ex
ecution de SommeFinie()",
Thread.CurrentThread.ManagedThreadId ) ;
ev.WaitOne();
Console.WriteLine(
"{0} : Bye... ", Thread.CurrentThread.ManagedThreadId ) ;
}
}
176
Une mthode marque avec lattribut OneWay peut tre appele dune manire synchrone ou
asynchrone. Si une exception est lance et non rattrape durant lexcution dune mthode marque avec lattribut OneWay, elle est propage si lappel est synchrone. Dans le cas dun appel
asynchrone sans retour lexception nest pas propage. Dans la plupart des cas, les mthodes
marques sans retour sont appeles de manire asynchrone.
Les appels asynchrones sans retour eectuent en gnral des tches annexes dont la russite ou
lchec nont pas dincidence sur le bon droulement de lapplication. La plupart du temps, on
les utilise pour communiquer des informations sur le droulement de lapplication.
Lattribut System.ThreadStatic
Par dfaut, un champ statique est partag par tous les threads dun processus. Ce comportement
oblige le dveloppeur synchroniser les accs un tel champ. En appliquant lattribut System.
ThreadStaticAttribute sur un champ statique, vous pouvez contraindre le CLR crer une
instance de ce champ pour chaque thread du processus. Ainsi, lutilisation de ce mcanisme est
bien un moyen dimplmenter la notion danit entre threads et ressources.
Il vaut mieux viter dinitialiser directement lors de sa dclaration un champ statique qui est
marqu avec cet attribut. En eet, dans ce cas seul le thread qui charge la classe eectuera linitialisation sur sa propre version du champ. Ce comportement est illustr par le programme
suivant :
Exemple 5-24 :
using System.Threading ;
class Program {
[System.ThreadStatic]
static string str = "Valeur initiale ";
static void DisplayStr() {
System.Console.WriteLine("Thread#{0} Str={1}",
Thread.CurrentThread.ManagedThreadId , str) ;
}
static void ThreadProc() {
DisplayStr() ;
str = "Valeur ThreadProc" ;
DisplayStr() ;
}
static void Main() {
DisplayStr() ;
Thread thread = new Thread(ThreadProc) ;
thread.Start() ;
177
thread.Join() ;
DisplayStr() ;
}
}
Ce programme ache ceci :
Thread#1
Thread#2
Thread#2
Thread#1
Str=Valeur initiale
Str=
Str=Valeur ThreadProc
Str=Valeur initiale
// 3 threads `
a cr
eer.
178
179
bye
Jai appele fServer(), Compteur = 1
bye
Jai appele fServer(), Compteur = 1
bye
Je suis le thread principal, bye.
180
Linterface System.ComponentModel.ISynchronizeInvoke
Linterface System.ComponentModel.ISynchronizeInvoke est dfinie comme ceci :
public
public
public
public
public
}
object System.ComponentModel.ISynchronizeInvoke{
object
Invoke(Delegate method,object[] args) ;
IAsyncResult BeginInvoke(Delegate method,object[] args) ;
object
EndInvoke(IAsyncResult result) ;
bool
InvokeRequired{get;}
Une implmentation de cette interface peut faire en sorte que certaines mthodes soient toujours excutes par le mme thread, dune manire synchrone ou asynchrone :
Dans le scnario synchrone, un thread T1 appelle une mthode M() sur un objet OBJ. En
fait, T1 appelle la mthode ISynchronizeInvoke.Invoke() en spcifiant un dlgu qui
rfrence OBJ.M() et un tableau contenant les arguments. Un autre thread T2 excute la
mthode OBJ.M(). T1 attend la fin de lexcution puis rcupre les informations de retour
de lappel.
Le scnario asynchrone dire du scnario synchrone par le fait que T1 appelle la mthode
ISynchronizeInvoke.BeginInvoke(). T1 ne reste pas bloqu pendant que T2 excute la
mthode OBJ.M(). Lorsque T1 a besoin des informations de retour de lappel il appelle la
mthode ISynchronizeInvoke.EndInvoke() qui les lui fournira si T2 termin lexcution
de OBJ.M().
Linterface ISynchronizeInvoke est notamment utilise par le framework pour forcer la technologie Windows Form excuter les mthodes dun formulaire avec un mme thread. Cette
contrainte vient du fait que la technologie Windows Form est construite autour de la notion de
messages Windows. Le mme genre de problmatique est aussi adresse par la classe System.
ComponentModel.BackgroundWorker dcrite en page .
Vous pouvez dvelopper vos propres implmentations de linterface ISynchronizeInvoke en
vous inspirant de lexemple Implementing ISynchronizeInvoke fourni par Juval Lowy lURL
http://docs.msdnaa.net/ark_new3.0/cd3/content/Tech_System%20Programming.htm.
Contexte dexcution
Le framework .NET 2.0 prsente des nouvelles classes qui permettent de capturer et de propager
le contexte dexcution du thread courant un autre thread :
System.Security.SecurityContext
Contexte dexcution
181
Une instance de cette classe contient lidentit de lutilisateur Windows sous-jacent sous la
forme dune instance de la classe System.Security.Principal.WindowsIdentity ainsi que
ltat de la pile du thread sous la forme dune instance de la classe System.Threading.
CompressedStack. Ltat de la pile est notamment exploit par le mcanisme CAS lors du
parcours de la pile.
System.Threading.SynchronizationContext
Permet de saranchir des contraintes de compatibilit entre dirents modles de synchronisation.
System.Threading.HostExecutionContext
Permet un hte du moteur dexcution dtre pris en compte dans le contexte dexcution
du thread courant.
System.Runtime.Remoting.Messaging.LogicalCallContext
.NET Remoting permet de propager des informations au travers de contexte .NET Remoting au moyen dinstances de cette classe. Plus dinformations ce sujet sont disponibles en
page 856.
System.Threading.ExecutionContext
Une instance de cette classe contient la runion des contextes cits.
Reprenons lexemple en page 209 qui modifie le contexte de scurit en impersonifiant lutilisateur invite sur le thread courant :
Exemple 5-27 :
...
static void Main() {
System.IntPtr pJeton ;
if (LogonUser(
"invite" ,
// login
string.Empty, // domaine Windows
"invitepwd" , // mot de passe
2,
// LOGON32_LOGON_INTERACTIVE
0,
// LOGON32_PROVIDER_DEFAUT
out pJeton)) {
WindowsIdentity.Impersonate(pJeton) ;
DisplayContext("Main");
ThreadPool.QueueUserWorkItem(Callback,null) ;
CloseHandle(pJeton) ;
}
}
static void Callback(object o) {
DisplayContext("Callback");
}
static void DisplayContext(string s) {
System.Console.WriteLine(s+" Thread#{0} Current user is {1}",
Thread.CurrentThread.ManagedThreadId,
WindowsIdentity.GetCurrent().Name) ;
}
...
182
Contexte dexcution
static void Callback(object o) {
DisplayContext("Callback") ;
}
...
Sans surprise, cet exemple ache :
Main Thread#1 Current user is PSMACCHIA\invit
e
Callback Thread#3 Current user is PSMACCHIA\invit
e
183
6
La gestion de la scurit
Nous commencerons par exposer la technologie Code Access Security (CAS). La technologie CAS permet de mesurer le degr de confiance que lon peut avoir en un assemblage en
vrifiant sa provenance et en sassurant sa non falsification.
Nous verrons ensuite comment mesurer le degr de confiance que lon peut avoir en un utilisateur. La notion dutilisateur est implmente plusieurs niveaux (Windows, ASP.NET,
COM+ etc).
Enfin nous exposerons les dirents mcanismes de cryptographie que le framework met
notre disposition.
Dautres informations relatives la scurit sont disponibles dans cet ouvrage. Notamment
en page 660 nous prsentons direntes techniques pour tablir une communication scurise entre deux machines et en page 957 nous prsentons la scurisation dune application web
ASP.NET.
186
de scurit. En eet, un individu mal intentionn peut exploiter les faiblesses des dirents rseaux et les failles des systmes dexploitations pour substituer son propre code du code mobile
ou pour faire parvenir du code mobile sur votre machine. En outre, la simplicit dobtention
du code mobile nous pousse tlcharger des logiciels que lon naurait pas pris la peine de
commander. On est donc moins regardant quant lditeur qui publie le logiciel. Il est donc
ncessaire de limiter lensemble des permissions accordes du code mobile (peut il dtruire
des fichiers sur mon disque dur ? peut il avoir accs au rseau ? etc).
La technologie COM adresse ce problme dune manire grossire. Avant dexcuter un composant COM qui vient dtre tlcharg, lutilisateur est averti par une fentre popup qui lui laisse
le choix dexcuter ou non le composant. Un certain niveau de garantie quant la provenance
du composant peut tre fourni grce un mcanisme de certificat mais le problme majeur
subsiste : une fois que lutilisateur a choisi dexcuter le composant, le code de celui-ci a les
mmes droits que lutilisateur.
Le fait davoir une machine virtuelle telle que le CLR permet la plateforme .NET dadresser
cette problmatique avec un mcanisme beaucoup plus fin que celui de COM. En eet, le CLR
est mme dintercepter et peut empcher une action malicieuse telle que la destruction dun
fichier avant que celle-ci ne se produise. Ce mcanisme est nomm CAS (Code Access Security). Il
fait lobjet de la prsente section.
Le dploiement de code mobile dvelopp avec .NET 2.0 se fait de prfrence avec la technologie
ClickOnce qui fait lobjet dune section page 76. Il est absolument ncessaire de bien comprendre
CAS pour tirer partie de ClickOnce.
Lorsque du code demande deectuer une opration critique, le CLR doit vrifier au pralable que les assemblages contenant ce code en ont tous la permission.
187
finalement pas accorde. On peut aussi signaler que le mcanisme de rsolution des permissions
naccorde aucune permission par dfaut.
Lorsquun ensemble de permissions a t accord lassemblage, le code de lassemblage peut
modifier cet ensemble et le comportement de la gestion de la scurit durant lexcution. Naturellement ces modifications ne peuvent jamais accorder plus de permissions quil nen a
t accord lassemblage.
Assemblage charger
Obtention des preuves que
peut fournir lassemblage
charger
Preuves
Application des
stratgies de scurit
(configurable)
Permissions accordes au
code de lassemblage
188
void Main(...) {
Fct1(...);
}
void Fct1(...) {
Fct2(...);
} void Fct2(...) {
FileStream fs-File.openRead(...);
}
System.IO.File.OpenRead(string path) {
CodeAccessPermission perm=new
FileIOPermission(
FileIOPermissionAccess.Read,
path);
perm.Demand();
...
}
P3
P3
Assemblage
foo1.exe
Assemblage
foo2.dll
P1
P4
Assemblage
mscorlib.dll
P2
P1
P3
Lappel Demand()
provoque le parcours de la pile
On peut prouver quun assemblage est stock dans un certain rpertoire de la machine.
Ce type de preuve peut tre reprsent par une instance de la classe System.Security.Policy.
ApplicationDirectory.
On peut prouver quun assemblage est stock dans le GAC. Ce type de preuve peut tre
reprsent par une instance de la classe System.Security.Policy.Gac.
On peut prouver quun assemblage a t obtenu/tlcharg partir dun certain site (par
exemple www.smacchia.com). Ce type de preuve peut tre reprsent par une instance de la
classe System.Security.Policy.Site.
On peut prouver quun assemblage a t obtenu partir dune certaine URL (par exemple
www.smacchia.com/asm/UnAssemblage.dll). Ce type de preuve peut tre reprsent par une
instance de la classe System.Security.Policy.Url.
On peut prouver quun assemblage a t obtenu partir dune certaine zone. .NET prsente
cinq zones :
Internet.
189
Un site internet que vous avez ajout dans votre zone Sites sensibles (untrusted site) d
Internet Explorer.
Un site internet que vous avez ajout dans votre zone Sites de confiance (trusted site)
dInternet Explorer.
Intranet local.
Le systme de stockage local (My Computer).
Chacune de ces zones correspond une valeur de lnumration System.Security.SecurityZone. Ce type de preuves peut tre reprsent par une instance de la classe System.Security.
Policy.Zone.
Si lassemblage a t sign par son diteur avec la technologie Authenticode, on peut tablir
une preuve partir de ce certificat. Cette technologie est dcrite en page 230. Ce type de
preuves peut tre reprsent par une instance de la classe System.Security.Policy.Publisher.
Si lassemblage a un nom fort, on peut tablir une preuve partir de ce nom fort. Ce type de
preuves peut tre reprsent par une instance de la classe System.Security.Policy.StrongName.
La composante culture du nom fort nest pas prise en compte dans cette preuve.
Voici un programme permettant de construire et dacher les noms forts des assemblages contenus dans le domaine dapplication courant. Notez lutilisation de la classe
System.Security.Permissions.StrongNamePublicKeyBlob pour rcuprer la cl publique.
Notez quen informatique blob signifie Binary Large Objet. On rappelle quune cl publique
contient 128 octets en plus des 32 octets den-tte.
Exemple 6-1 :
using System ;
using System.Security.Permissions ;
using System.Security.Policy ;
using System.Reflection ;
[assembly: AssemblyKeyFile("Cles.snk")]
class Program {
static void DisplayStrongName(Assembly assembly) {
AssemblyName name = assembly.GetName() ;
byte[] publicKey = name.GetPublicKey() ;
StrongNamePublicKeyBlob blob =
new StrongNamePublicKeyBlob(publicKey);
StrongName sn = new StrongName(blob, name.Name, name.Version);
Console.WriteLine(sn.Name) ;
Console.WriteLine(sn.Version) ;
Console.WriteLine(sn.PublicKey) ;
}
static void Main() {
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies() ;
foreach (Assembly assembly in assemblies)
DisplayStrongName(assembly) ;
}
}
On peut tablir une preuve partir de la valeur de hachage dun assemblage. La valeur
de hachage dun assemblage permet didentifier le rsultat dune compilation dun assemblage, un peu comme un numro de version sauf que la valeur de hachage ne contient
190
Vous pouvez ajouter cette liste vos propres preuves. Celles-ci doivent tre imprativement
ajoutes lassemblage avant quil soit sign. Lide est de vous permettre de configurer totalement le mcanisme de scurit. Ce sujet dpasse le cadre de cet ouvrage.
Une instance de la classe System.Security.Policy.Evidence reprsente une collection de preuves.
En fait chaque instance de cette classe contient deux collections de preuves :
Une collection pour stocker les preuves prsentes par le framework .NET (un des huit types
dcrits ci-dessus).
En pratique les dveloppeurs ont peu dintrt manipuler les preuves. Les instances de la
classe Evidence sont manipules en interne par le framework .NET. Notamment, nous rappelons
quune telle collection de preuves est attribue chaque assemblage lors de son chargement.
Voici un programme qui ache les types des preuves fournies par les assemblages du domaine
dapplication courant. Pour ne pas compliquer inutilement cet exemple, nous nachons pas
directement les preuves sur la console. Nous prfrons acher le type des preuves :
Exemple 6-2 :
PreuveTest.cs
using System ;
using System.Reflection ;
[assembly: AssemblyKeyFile("Cles.snk")]
class Program {
static void DisplayEvidence(Assembly assembly) {
Console.WriteLine(assembly.FullName) ;
foreach (object obj in assembly.Evidence)
Console.WriteLine("
" + obj.GetType()) ;
}
static void Main() {
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies() ;
foreach (Assembly assembly in assemblies)
DisplayEvidence(assembly) ;
}
}
Ce programme ache ceci :
mscorlib, Version=2.0.XXXXX.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089
System.Security.Policy.Zone
System.Security.Policy.Url
System.Security.Policy.StrongName
System.Security.Policy.Hash
PreuveTest, Version=0.0.0.0, Culture=neutral, PublicKeyToken=e0a058df80c8a007
191
System.Security.Policy.Zone
System.Security.Policy.Url
System.Security.Policy.StrongName
System.Security.Policy.Hash
Soit par lhte dun domaine dapplication juste avant le chargement du premier assemblage
du domaine dapplication. Dans ce cas lassemblage qui contient lhte du domaine dapplication doit avoir obtenu la mta-permission ControlEvidence. En gnral vous navez
pas vous soucier de cela. En eet, la plupart du temps vous utilisez un hte de domaine
dapplication dvelopp par Microsoft, en qui les stratgies de scurit font entirement
confiance. Pour linstant Microsoft en fournit quatre. Ils sont dcrits en page 95.
Soit par le chargeur de classes juste avant le chargement dun assemblage qui contient un
type demand par un assemblage dj charg dans le domaine dapplication. Puisque le
chargeur de classe fait partie intgrante du CLR, le CLR lui fait entirement confiance et
lui accorde la mta-permission ControlEvidence.
Dans tous les cas le mcanisme dobtention de preuves a imprativement besoin de la mtapermission ControlEvidence (voir SecurityPermission dans la liste des permission ci-dessous).
Les permissions
Comme son nom lindique, une permission permet du code dexcuter un ensemble dactions.
Certains ouvrages qualifient les permissions de privilges, dautorisations ou de droits. Ces termes
sont cependant trs connots par le vocabulaire Windows aussi nous utiliserons le terme de permission dans le prsent ouvrage.
On verra dans la section suivante quel est lalgorithme qui permet dobtenir lensemble des
permissions pour un assemblage en fonction des preuves apportes par lassemblage. En .NET,
il existe quatre catgories de permissions : les permissions standard, les permission didentit,
les mta-permissions et les permissions propritaires.
192
CAS : Accorder des permissions en fonction des preuves avec les stratgies de scurit
193
Les mta-permissions
Les meta-permissions ou permissions de scurit : Ce sont des permissions alloues au
gestionnaire de scurit lui-mme. La liste des meta-permissions est disponible larticle
SecurityPermissionFlag Enumeration des MSDN. On peut citer la mta-permission dexcuter du code non gr (valeur UnmanagedCode), la mta-permission de fournir des preuves
partir dun assemblage (prsente un peu plus haut, valeur ControlEvidence), la mtapermission dexcuter du code non protg (valeur SkipVerification) etc. La classe System.Security.Permissions.SecurityPermission qui drive de la classe CodeAccessPermission
permet de manipuler les mta-permissions partir du code, sans toutefois pouvoir sautoattribuer de telles permissions.
Configure par...
Sapplique...
Entreprise
un administrateur.
194
Machine
un administrateur.
Utilisateur
un administrateur ou
lutilisateur concern.
lhte du domaine
dapplication.
Domaine
tion
dapplica-
Il existe une hirarchie dans les stratgies de scurit. Concrtement elles sont appliques les
unes aprs les autres, dans lordre dans lequel elles sont numres ci-dessus (de entreprise
domaine dapplication ). Pour cette raison on parle de niveaux de stratgie de scurit. Lapplication dune stratgie de scurit peut imposer que les stratgies de scurit des niveaux suivant ne sappliquent pas. Par exemple lapplication de la stratgie de scurit machine peut
empcher lapplication des stratgies de scurit utilisateur et domaine dapplication . En
gnral, on constate que la plupart des rgles de scurits se trouvent dans la stratgie de scurit
machine (dailleurs, par dfaut les stratgies des autres niveaux accordent toutes les permissions).
Des groupes de code (code groups en anglais) stocks dans une arborescence,
Une liste dassemblages auxquels la stratgie de scurit donne son entire confiance (policy
assemblies ou fully trusted assemblies en anglais).
partir de ces lments et des preuves prsentes par un assemblage, on peut calculer un
ensemble de permissions. Cest lapplication de la stratgie de scurit. Avant dexposer lalgorithme utilis pour cela, il faut expliquer ce que sont les groupes de code.
Un groupe de code associe une preuve un ensemble de permissions de la liste densemble
de permissions de la stratgie de scurit. Les groupes de code sont stocks dans une arborescence dans une stratgie de scurit, cest--dire quun groupe de code parent peut avoir zro,
un ou plusieurs groupes de code enfants. Il ny a pas dobligation de relation entre la preuve
dun groupe enfant et la preuve de son groupe parent, il en va de mme pour les permissions
accordes. Cependant afin de faciliter ladministration de la scurit, il est recommand (dans
la mesure du possible) de positionner les relations (parent enfant) avec des liens logiques et de
dfinir les permissions accordes de faon hirarchique.
Pour comprendre pourquoi les groupes de code sont stocks dans une arborescence, il faut se
pencher sur lalgorithme utilis lors de lapplication dune stratgie de scurit.
Les documentations ocielles utilisent ce vocabulaire : si lune des preuves dun assemblage est
identique la preuve dun groupe de code, on dit que lassemblage est membre de ce groupe de
code. Cela justifie lappellation groupe de code. La preuve dun groupe de code permet de dfinir
un groupe dassemblages (de code) : ce groupe est constitu par les assemblages sui vrifient la
preuve.
CAS : Accorder des permissions en fonction des preuves avec les stratgies de scurit
195
Sachez que dans le cas o vous fabriqueriez vos propres preuves, il faudrait fabriquer vos propres
groupes de code pour les exploiter.
Sinon, lalgorithme commence parcourir larborescence des groupes de code selon les
rgles suivantes :
Les groupes de code enfants dun groupe de code sont pris en compte par lalgorithme
seulement si lassemblage est membre du groupe de code parent.
Lensemble des permissions accordes lassemblage par une stratgie de scurit est lunion
des ensembles de permissions des groupes de code dont lassemblage est membre.
Chaque groupe de code peut tre marqu de faon finaliser la stratgie de scurit laquelle il appartient. Dans ce cas, si un assemblage appartient ce groupe de code le mcanisme dvaluation nira pas valuer les niveaux de stratgies de scurit suivantes.
Chaque groupe de code peut tre marqu comme exclusif. Dans ce cas, si un assemblage
appartient ce groupe de code il ne bnficiera que des permissions associes ce groupe.
196
FullTrust
ECMA_Strong_Name
SN.PublicKey=ECMA
FullTrust
MyComputer_Zone
Zone=MyComputer
FullTrust
NetCodeGroup_1
All
LocalIntranet_Zone
Zone=Intranet local
Permission daccder au
site dorigine de lassemblage
LocalIntranet
NetCodeGroup_1
All_Code
Restricted_Zone
All
All
Nothing
Nothing
Permission daccder en
lecture seule aux fichiers
du rpertoire
Internet_Zone
NetCodeGroup_1
Zone=Internet
All
Internet
Permission daccder au
site dorigine de lassemblage
Trusted_Zone
NetCodeGroup_1
Zone=site de confiance
All
Internet
Permission daccder au
site dorigine de lassemblage
Au niveau de la scurit .NET, ces deux outils ont exactement la mme fonctionnalit : la configuration des stratgies de scurit entreprise , machine et utilisateur de la machine. La
stratgie de scurit dun domaine dapplication ne peut se faire que programmatiquement, en
utilisant les classes adquates du framework .NET.
La Figure 6 -4 prsente une vue gnrale de mscorcfg.msc qui permet de retrouver immdiatement les notions prsentes que lon vient dexposer :
Lorsque vous tes un administrateur, pour chaque stratgie de scurit vous pouvez (ou lorsquun utilisateur veut configurer la stratgie de scurit le concernant) :
CAS : Accorder des permissions en fonction des preuves avec les stratgies de scurit
197
Paramtres de configuration ne
concernant pas la scurit
Paramtres de la stratgie de
scurit entreprise
Arborescences
des groupes
de code
Paramtres
de la stratgie
de scurit
machine
Liste des
ensembles de
permissions
Assemblages en
qui la politique
fait confiance
Paramtres de la stratgie de
scurit concernant lutilisateur
logu
Paramtres de configuration ne
concernant pas la scurit
Exporter ou importer la stratgie de scurit dans un (ou partir dun) fichier de dploiement .msi. Ces fonctionnalits sont accessibles dans les menus Stratgies de scurit Crer
un fichier de dploiement... et Stratgies de scurit Ouvrir ...
Pour un assemblage donn, vous pouvez obtenir la liste des groupes de code (dune politique ou de toutes les politiques) dont il est membre, et la liste des permissions qui lui sont
accordes par la ou les stratgies de scurit concernes. Cette fonctionnalit est accessible
dans le menu Stratgies de scurit Evaluer un assemblage...
Sur une machine donne, les paramtres dune stratgie de scurit sont stocks dans des fichiers
de configuration au format XML. Voici leur localisation :
Stratgie de scurit entreprise
Windows XP/2000/NT
198
Windows 98/Me
Windows 98/Me
%USERPROFILE%\Application
config\vXXXX\Security.config
data\Microsoft\CLR
security
Windows 98/Me
Ces paramtres sont stocks pour chaque version du CLR. Ainsi, si plusieurs versions du CLR
cohabitent, chacune ses propres paramtres de scurit.
Le fait que ces paramtres de configuration soient stocks au format XML ore des possibilits
intressantes, comme limport de groupes de code ou densembles de permissions prsentes
au format XML. Les trois articles Importing a Permission Using an XML File, Importing
a Permission Set Using an XML File et Importing a Code Group Using an XML File des
MSDN traitent ce sujet en dtail.
Les directives de loutil caspol.exe accessibles en ligne de commande, sont dcrites dans les
MSDN larticle Code Access Security Policy Tool. travers les nombreuses directives de cet
outil, vous y retrouverez toutes les fonctionnalits prsentes ci-dessus.
199
attribut. Il faut vraiment tre certain que le code distribu ne peut pas tre dtourn. Dailleurs,
seuls certains assemblages standard de Microsoft sont marqus avec cet attribut.
Enfin, soyez conscient que ne pas utiliser cet attribut ne reprsente pas une garantie ultime
contre le dtournement de votre code. En eet, votre code peut toujours tre invoqu partir
dun assemblage qui a la permission FullTrust qui lui mme est invoqu partir dun assemblage qui na pas la permission FullTrust.
La mthode Demand()
La mthode Demand() vrifie que le code courant dispose des permissions reprsentes par le
jeu de permission. Une remonte de la pile des appels est alors dclenche afin de retrouver la
hirarchie de toutes les mthodes responsable de cet appel. Chaque mthode de cette pile est
galement teste pour le jeu de permissions. Si lune dentre elle ne dispose pas de toutes les
permissions requises, alors une exception de type SecurityException est leve. Lexcution du
code sarrte et la suite de la mthode lorigine de la remonte de la pile nest pas excute. Le
programme suivant sassure quil a la permission de lire un fichier avant de le faire :
Exemple 6-3 :
using System.Security ;
using System.Security.Permissions ;
class Program {
static void Main() {
string sFichier = @"C:\data.txt" ;
CodeAccessPermission cap =
new FileIOPermission(FileIOPermissionAccess.Read, sFichier) ;
200
Lintrt de demander explicitement les permissions est danticiper un ventuel refus et dadapter le comportement de lapplication en consquence. La demande explicite permet galement
de mettre en place des stratgies plus sophistiques dans lesquelles le jeu de permissions testes
est ventuellement construit en fonction du contexte comme le rle de lutilisateur.
201
dfaut sont restaurs. Il est aussi possible dannuler les limitations de droits avec le mthode
RevertDeny().
Une alternative existe au couple de mthodes Deny()/RevertDeny(). Le couple de mthode
PermitOnly()/RevertPermitOnly() permet aussi de modifier temporairement lensemble des
permissions accordes la mthode courante. La dirence entre ces deux manires est que
Deny() spcifie les permissions ne pas accorder alors que PermitOnly() spcifie les permissions
accorder.
202
une mme mthode, il faut quentre chaque appel, la mthode statique CodeAccessPermission.RevertAccess() soit appele. Pour supprimer le parcours de la pile dappel pour plusieurs
permissions dans une mme mthode il est donc obligatoire dappeler Assert() sur une instance
de PermissionSet.
Par prudence il vaut mieux ninvoquer la mthode Assert() quau moment o lon en a besoin
et pas, par exemple en dbut de mthode. Il faut dans le mme esprit invoquer la mthode RevertAssert() le plus tt possible. Le mieux est en gnral de servir dune structure try/finally
Notez enfin que contrairement aux mthodes Deny()/RevertDeny(), PermitOnly()/RevertPermitOnly() et Assert()/RevertAssert(), la mthode Demand() est la seule qui ninflue pas sur
le droulement ultrieur des oprations.
Linterface System.Security.IPermission
Linterface System.Security.IPermission permet de faire des oprations ensemblistes sur un ensemble de permission. Cette interface est implmente par la classe PermissionSet, la classe
CodeAccessPermission ainsi que ses classes drives. Voici sa dfinition :
public interface System.Security.IPermission {
IPermission Union(IPermission rhs) ;
IPermission Intersect(IPermission rhs) ;
bool
IsSubsetOf(IPermission rhs) ;
IPermission Copy() ;
void
Demand() ;
}
Avec la mthode IsSubsetOf() vous pouvez calculer des relations dinclusion entre permissions.
Par exemple la permission qui donne tous les accs au dossier "C:\MonRep" inclue la permission
qui donne tous les accs au dossier "C:\MonRep\patrick\".
Exemple 6-6 :
using System.Security ;
using System.Security.Permissions ;
class Program {
static void Main() {
string rep1 = @"C:\Monrep" ;
string rep2 = @"C:\Monrep\Patrick" ;
CodeAccessPermission cap1 = new FileIOPermission(
FileIOPermissionAccess.AllAccess, rep1) ;
CodeAccessPermission cap2 = new FileIOPermission(
FileIOPermissionAccess.AllAccess, rep2) ;
bool b = cap2.IsSubsetOf(cap1);
// Ici b vaut true.
}
}
203
Vous pouvez aussi calculer des nouvelles permissions partir dintersections ou dunions de
permissions avec les mthodes Union() et Intersect(). Vous pouvez ainsi rutiliser des permissions volues. Ces types doprations ensemblistes sur les permissions sont trs utiliss par le
gestionnaire de scurit. En pratique les dveloppeurs nont pas beaucoup doccasions de les
utiliser.
Description de laction
InheritanceDemand
Lorsquun assemblage est charg, permet dimposer que les types drivs du type sur lequel sapplique cette action aient les permissions spcifies.
LinkDemand
204
Description de laction
RequestMinimum
RequestOptional
Spcifie une ou plusieurs permissions requises pour excuter correctement lassemblage. Cependant lassemblage est charg mme si ces permissions ne lui sont pas accordes. On parle de permission optionnelle.
RequestRefuse
Par exemple, un assemblage qui ralise des accs une base de donnes besoin de la permission SqlClientPermission. Il peut avoir besoin de la permission RegistryPermission pour
rcuprer des paramtres mais ceci peut tre optionnel si il prvoit des paramtres par dfaut.
Enfin, il se peut quil nait absolument pas besoin de permissions telles que WebPermission ou
UIPermission. Il faudrait donc quil soit marqu par les attributs suivants :
Exemple 6-8 :
Program.cs
using System.Security.Permissions ;
using System.Data.SqlClient ;
[assembly: SqlClientPermission(SecurityAction.RequestMinimum)]
[assembly: RegistryPermission(SecurityAction.RequestOptional)]
[assembly: UIPermission(SecurityAction.RequestRefuse)]
[assembly: System.Net.WebPermission(SecurityAction.RequestRefuse)]
class Program { public static void Main() { } }
Loutil permview.exe vous permet de visualiser ces attributs. Loutil permcalc.exe dcrit en page
82 va plus loin et permet de calculer lensemble des permissions requis par un assemblage.
Lors de lchec dune demande ou dune assertion de permissions vous ne pouvez pas rattraper dexception lendroit o lerreur a eu lieu.
Les arguments passs aux permissions (comme le nom dun rpertoire pour grer les permissions daccs ce rpertoire) doivent tre connus la compilation. Plus gnralement,
vous ne pouvez pas mettre en place une logique de scurit dynamique (i.e base sur des
informations connues seulement qu lexcution telles que le rle de lutilisateur courant
par exemple).
205
Les avantages dutiliser des attributs pour manipuler des permissions sont :
La possibilit davoir accs ces attributs et aux paramtres de ces attributs au travers des
mtadonnes de lassemblage ou en utilisant loutil permview.exe.
206
thodes statiques telles que GetUserStoreForAssembly(), GetMachineStoreForAssembly(), GetMachineStoreForDomain() ou GetMachineStoreForApplication() permettant dobtenir le rpertoire correspondant la porte dsire.
Voici un exemple montrant comment accder au rpertoire de stockage isol :
Exemple 6-9 :
using System.IO ;
using System.IO.IsolatedStorage ;
class Program {
static void Main() {
// Obtient le repertoire en fonction de lutilisateur
// et de lassemblage courant.
IsolatedStorageFile isf =
IsolatedStorageFile.GetUserStoreForAssembly() ;
IsolatedStorageFileStream isfs = new IsolatedStorageFileStream(
"pref.txt", FileMode.Create, isf) ;
StreamWriter sw = new StreamWriter(isfs) ;
sw.WriteLine("Mettre ici vos pr
ef
erences...") ;
sw.Close() ;
}
}
Le rpertoire utilis par ce programme pour faire du stockage isol sous mon identit est celui
de la Figure 6 -5:
207
chaque client peut tre autoris consulter son compte ( partir dinternet). Une telle application ncessite de sparer les utilisateurs entre les clients, les simples caissiers, les caissiers avec des
responsabilits et les administrateurs. Chacune de ces catgories est appele rle. Pour lapplication chaque utilisateur joue zro, un ou plusieurs rles. En fonction du cahier des charges de
lapplication bancaire, les dveloppeurs doivent pouvoir vrifier directement partir du code
le rle de lutilisateur courant. Dans le code, cette vrification se fait avant toutes les excutions
dune fonctionnalit critique. Dans ce contexte, pour une application, accorder de la confiance
un utilisateur revient dterminer quels sont les rles quil joue.
208
209
210
Support .NET pour les contrles des accs aux ressources Windows
211
mthode nous retourne lidentit de lutilisateur seulement si le thread nest pas en cours demprunt didentit.
Lorsquun thread tente dobtenir des droits daccs une ressource, Windows tablit son verdict
partir du SID du thread et de la DACL de la ressource. Les ACE sont valus dans lordre dans
lequel ils sont stocks dans la DACL. Chaque ACE accorde ou retire des droits daccs lorsque
le SID du thread est inclus dans son SID. Lensemble des droits daccs demands est accord
ds que tous les droits daccs ont t accords durant lvaluation. Les droits daccs sont tous
refuss ds quun seul des droits daccs demands est refus par un ACE. Comprenez bien que
lordre de stockage des ACE dans la DACL importe et que Windows nvalue pas ncessairement
tous les ACE lors dune demande de droits daccs.
Pour certains types de ressources Windows permet lhritage de SD. Cette possibilit se rvle
essentielle par exemple pour un administrateur qui souhaiterait configurer les SD de milliers
de fichiers contenus dans un rpertoire en une seule manipulation.
Chaque SD dune ressource Windows contient une seconde liste dACE nomme System Access
Control List (SACL). Cette seconde liste est exploite par Windows pour auditer les accs une
ressource. linstar des ACE dune DACL les ACE dune SACL associent chacun un SID une
liste de droits daccs. Au contraire des ACE dune DACL les ACE dune SACL contiennent deux
informations binaires qui peuvent sinterprter comme ceci :
212
lvnement un de mes droits daccs a t accord un SID inclus dans mon SID doit-il tre
logu ?
lvnement un de mes droits daccs a t refus un SID inclus dans mon SID doit-il tre logu ?
Clairement, lordre de stockage des ACE dans une SACL nimporte pas.
Le nouvel espace de noms System.Security.AccessControl dfinit des types permettant dexploiter les SD. Aprs avoir prsent les types ddis la manipulation des SD de ressources
spcifiques Windows nous prsenterons des types prvus pour exploiter les SD dune manire
gnrique (i.e indpendante du type de ressource Windows sous jacent).
Support .NET pour les contrles des accs aux ressources Windows
213
System.Security.AccessControl.EventWaitHandleAuditRule
System.Security.AccessControl.FileSystemAuditRule
System.Security.AccessControl.MutexAuditRule
System.Security.AccessControl.ObjectAuditRule
System.DirectoryServices.ActiveDirectoryAuditRule
System.Security.AccessControl.RegistryAuditRule
System.Security.AccessControl.SemaphoreAuditRule
Voici la liste des numrations reprsentant les droits daccs. Par exemple, lnumration
FileSystemRights contient une valeur AppendData tandis que lnumration MutexRights
contient une valeur TakeOwnership.
Microsoft.Iis.Metabase.MetaKeyRights
System.Security.AccessControl.EventWaitHandleRights
System.Security.AccessControl.FileSystemRights
System.Security.AccessControl.MutexRights
System.Security.AccessControl.RegistryRights
System.Security.AccessControl.SemaphoreRights
Enfin les dirents types du framework .NET reprsentant directement les ressources Windows
concernes (System.Threading.Mutex, System.IO.File etc) ont des nouveaux constructeurs acceptant des ACL et des mthodes Set/GetAccessControl permettant de positionner et dobtenir
les ACL dune instance. Voici un exemple illustrant tout ceci lors de la cration dun fichier avec
une DACL :
Exemple 6-13 :
using System.Security.AccessControl ;
using System.Security.Principal ;
using System.IO ;
class Program {
static void Main() {
// Cree la DACL.
FileSecurity dacl = new FileSecurity() ;
// Rempli la DACL avec un ACE.
FileSystemAccessRule ace = new FileSystemAccessRule(
WindowsIdentity.GetCurrent().Name,
FileSystemRights.AppendData | FileSystemRights.ReadData,
AccessControlType.Allow) ;
dacl.AddAccessRule(ace) ;
// Cree un nouveau fichier avec cette DACL.
System.IO.FileStream fileStream = new System.IO.FileStream(
@"fichier.bin" , FileMode.Create , FileSystemRights.Write ,
FileShare.None, 4096 , FileOptions.None, dacl ) ;
fileStream.Write(new byte[] { 0, 1, 2, 3 }, 0, 4) ;
fileStream.Close() ;
}
}
Vous pouvez visualiser les droits daccs aux fichiers fichier.bin comme ceci : Proprit du fichier fichier.bin Scurit Paramtres avancs Autorisations Modifier les autorisations spciales
214
accordes au principal avec lequel vous avez excut le programme lecture de donnes et ajout de
donnes.
Si longlet Scurit ne sache pas il faut eectuer cette manipulation : Panneau de configuration
Options des dossiers Achage Utiliser le partage de fichiers simple (recommand).
Support .NET pour les contrles des accs aux ressources Windows
215
dacl.AddAccess(
AccessControlType.Allow, // Allow OU Deny.
WindowsIdentity.GetCurrent().Owner, // Utilisateur courant.
0x00180000, // Masque : TakeOwnerShip ET Synchronize
//
equivalent `
a
//(int) MutexRights.TakeOwnership | (int) MutexRights.Synchronize
InheritanceFlags.None, // D
esactive...
PropagationFlags.None); // ...lheritage dACE.
string sSDDL = csd.GetSddlForm( AccessControlSections.Owner ) ;
Console.WriteLine("Security Descriptor : " + sSDDL) ;
MutexSecurity mutexSec = new MutexSecurity() ;
mutexSec.SetSecurityDescriptorSddlForm(sSDDL);
AuthorizationRuleCollection aces = mutexSec.GetAccessRules(
true, true, typeof(NTAccount)) ;
foreach (AuthorizationRule ace in aces){
if (ace is MutexAccessRule){
MutexAccessRule mutexAce = (MutexAccessRule)ace ;
Console.WriteLine("-->SID : " +
mutexAce.IdentityReference.Value) ;
Console.WriteLine("
Type de droits dacc`
es : " +
mutexAce.AccessControlType.ToString()) ;
if (0xffffffff == (uint)mutexAce.MutexRights)
Console.WriteLine("
Tous les droits !") ;
else
Console.WriteLine("
Droits : " +
mutexAce.MutexRights.ToString()) ;
}
}
}
}
Cet exemple ache :
On remarque que par dfaut un DACL dun nouveau SD contient lACE qui donne tous
les droits tout le monde. Vous pouvez utiliser la mthode CommonAcl.Purge(SecurityIdentifier) pour supprimer les ACE relatives un SID dans un ACL.
216
Ces trois oprations ncessitent la mta-permission CAS SecurityPermissionFlag.ControlPrincipal pour tre menes bien.
217
Cette dernire alternative constitue la politique de principal prise par dfaut par tous les domaines dapplication.
218
219
S(P(M)) = P(S(M)) = M
On ne peut obtenir M si lon dtient P(M) sans connatre la paire de cls (S,P).
On ne peut obtenir M si lon dtient S(M) sans connatre la paire de cls (S,P).
On voit que les cls S et P jouent un rle symtrique do le nom de ce type dalgorithme. Pour
que Julien envoi le message M Mathieu dune manire confidentielle, il doit lui envoyer une
des deux versions cryptes du message. Mathieu pourra alors obtenir le message original en
appliquant lalgorithme symtrique sur le message crypt avec les deux cls. En pratique, Julien
et Mathieu se seront mis daccord sur la cl utilise pour lencryption. Si un tiers intercepte la
version crypte du message, il ne pourra pas obtenir le message original puisquil ne dtient pas
la paire de cl (S,P). Tout ceci est rsum dans la figure suivante :
Rseau peu sr, seuls les
messages encrypts y circulent
Mathieu
Connat le couple
de cls (S,P)
S(M1)
S(M2)
Julien
Connat le couple
de cls (S,P)
Un tiers
Ne peut retrouver un message partir de sa version
encrypte car il ne possde pas la paire de cls (S,P)
220
puisque pendant des sicles, les algorithmes ont constitu la pierre angulaire de la cryptographie.
221
sDec = utf.GetString(bDec) ;
}
System.Console.WriteLine("Message : " + sMsg) ;
System.Console.WriteLine("Encrypt
e : " + sEnc) ;
System.Console.WriteLine("Decrypt
e : " + sDec) ;
}
}
Cet exemple ache ceci :
Message : Le message a` encrypter !
Encrypte : iNnbHD1R3nci5tGElIvKIBapaTmfqHEV
Decrypte : Le message `a encrypter !
Dans lexemple prcdent, notez que les objets encryptor et decryptor implmentent tous les
deux linterface ICryptoTransform. Ceci est une consquence de laspect symtrique des algorithmes.
La classe DESCryptoServiceProvider peut aussi tre utilise pour construire une cl et un vecteur dinitialisation. Ainsi lexemple prcdent peut tre rcrit comme suit pour exploiter cette
possibilit :
Exemple 6-21 :
...
des.GenerateKey() ;
des.GenerateIV() ;
ICryptoTransform encryptor = des.CreateEncryptor() ;
ICryptoTransform decryptor = des.CreateDecryptor() ;
...
La paire de cls doit tre connue des deux parties souhaitant schanger des messages. un
moment donn, il faut que cette paire de cls circule sur un canal de communication entre
les deux parties.
Une paire de cl nest valable quentre deux parties. Si Julien souhaite changer des messages crypts avec Mathieu et avec Sbastien il doit dtenir deux paires de cls : une pour
crypter les changes avec Mathieu et une pour crypter les changes avec Sbastien. On voit
donc apparatre un problme de gestion de cls.
Les algorithmes asymtriques rsolvent ces deux problmes. Un algorithme asymtrique a les
trois proprits dun algorithme symtrique que nous rappelons en reprenant les notations de
la section prcdente :
222
En plus de ces trois proprits un algorithme asymtrique a les deux proprits suivantes :
Nous avons donc introduit une dissymtrie dans notre paire de cl do le nom de ce type dalgorithme.
On comprend maintenant comment Mathieu et Julien peuvent exploiter ce type dalgorithme
pour schanger des messages sans schanger de cls et sans avoir grer un grand nombre de
cls. Il sut quils calculent chacun une paire de cl que nous nommons (Sj,Pj) et (Sm,Pm).
Mathieu diuse la cl Sm tandis que Julien diuse la cl Sj. Toute personne souhaitant envoyer
un message M Mathieu dune manire confidentielle peut utiliser la cl Sm pour lencrypter. Si
Mathieu a pris le soin de garder prive la cl Pm, il est alors le seul pouvoir dcrypter Sm(M) en
calculant Pm(Sm(M)). Tout ceci est rsum dans la figure ci-dessous
Rseau peu sr, seuls les
messages encrypts y circulent
Mathieu
Connat le couple
de cls (Sm,Pm) ainsi
que la cl Sj
Sm(M1)
Sj(M2)
Julien
Connat le couple
de cls (Sj,Pj) ainsi
que la cl Sm
Un tiers
Connat les cles Sj et Sm. Ne peut retrouver un message partir de
sa version encrypte car il ne possde ni la cl Pj, ni la cl Pm
223
Lalgorithme RSA
Lalgorithme RSA cr en 1977, est lheure actuelle lalgorithme asymtrique le plus utilis. La
protection des cartes bancaires ou des messages militaires lutilise. La plateforme .NET lutilise
aussi. Le nom de RSA provient du nom de ses inventeurs, R.L. Rivest, A. Shamir et L.M Adelman.
Lalgorithme RSA est bas sur une proprit des grands nombres premiers. Soit deux grands
nombres premiers A et B. Il est trs facile de calculer le produit de A et de B. En revanche, lorsque
lon ne connat que le produit AB, il est trs dicile de calculer les nombres A et B. Sans rentrer
dans les dtails de lalgorithme RSA, vous pouvez considrer que la paire de nombre (A,B) dfinit la cl prive tandis que le produit de A et de B dfinit la cl publique.
Tant que lon ne saura obtenir une paire de nombres premiers A et B partir de leur produit
quen temps polynomial, lalgorithme RSA restera extrmement fiable. En fait, on ne sait mme
pas prouver quil existe un algorithme en un temps meilleur que le temps polynomial. La plupart des mathmaticiens contemporains supposent que ce problme restera inaccessible pendant encore de nombreuses dcennies. Nanmoins, lhistoire a montr quil est quasiment impossible destimer quand un problme mathmatique sera rsolu.
Sachez enfin que lon utilise des algorithmes statistiques ecaces pour le calcul de grands
nombres premiers. Ce type dalgorithme permet de dterminer si un grand nombre est premier
un degr de certitude qui peut tre aussi grand que lon veut sans jamais tre gal 100%.
Si par ces deux calculs Julien obtient la mme valeur, il peut tre sr que lauteur de FOO dtient
la cl prive Pm. On dit que Pm(x) constitue une signature numrique du fichier FOO. Si Mathieu
224
a pu convaincre Julien quil est le seul dtenteur de la cl prive Pm, Julien peut tre sr que
lauteur de FOO est Mathieu.
Une faille dans cet algorithme existe nanmoins. Nous avons dit que Le calcul de la valeur de
hachage la particularit de fournir de manire quasiment sre deux nombres dirents pour deux
fichiers dirents . Si un tiers parvient trouver une squence doctets qui produit la mme
valeur de hachage, il peut utiliser la signature numrique prcdente dans un fichier contenant
cette squence doctets. Julien naura aucun moyen de savoir que ce fichier na pas t produit
par un dtenteur de la cl prive Pm. Cependant la taille des valeurs de hachage est de lordre
de 20 octets. Il y a donc moins dune chance sur 10 puissance 48 pour quune squence doctets
prise au hasard fournisse une valeur de hachage donne.
En page 28 nous expliquons comment la plateforme .NET permet de signer numriquement les
assemblages. En page 230 nous verrons que cette technique est utilise dans les environnements
Windows depuis bien avant .NET pour authentifier des fichiers. Nous prsenterons alors une
technologie permettant de pouvoir convaincre quon est le seul dtenteur dune certaine cl
prive.
225
226
Cette API est capable de grer les modifications de mots de passe. Autrement dit, si vous stockez
des donnes en les encryptant pour un utilisateur donn, vous serez capable de les exploiter
mme lorsque le mot de passe de cet utilisateur aura t modifi. Ceci est possible grce un
systme de stockage des cls expires. Plus de dtails ce sujet sont disponibles dans larticle
Windows Data Protection des MSDN.
La classe System.Security.Cryptography.ProtectedData
Lexemple suivant montre comment utiliser la classe System.Security.Cryptography.ProtectedData pour protger des donnes au niveau dun utilisateur. Nous aurions pu utiliser la valeur DataProtectionScope.LocalMachine pour protger ces donnes au niveau de la machine
courante. Dans cet exemple, nous exploitons loption dajouter de lentropie lencryption. Cela
signifie quun processus sexcutant dans le contexte adquat (i.e sous le bon utilisateur ou sur la
bonne machine) naura pas la possibilit de dcrypter les donnes sil ne connat pas lentropie
utilise pour les encrypter. Vous pouvez donc considrer lentropie comme une sorte de cl
secondaire :
Exemple 6-23 :
using System.Security.Cryptography ;
class Program{
static void Main() {
string sMsg = "Le message `
a encrypter !" ;
string sEnc, sDec ;
System.Text.Encoding utf = new System.Text.UTF8Encoding() ;
byte[] entropy = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 };
{
byte[] bMsg = utf.GetBytes(sMsg) ;
byte[] bEnc = ProtectedData.Protect(
bMsg , entropy , DataProtectionScope.CurrentUser);
sEnc = System.Convert.ToBase64String(bEnc) ;
}
{
byte[] bEnc = System.Convert.FromBase64String(sEnc) ;
byte[] bDec = ProtectedData.Unprotect(
bEnc, entropy, DataProtectionScope.CurrentUser);
sDec = utf.GetString(bDec) ;
}
System.Console.WriteLine("Message : " + sMsg) ;
System.Console.WriteLine("Encrypt
e : " + sEnc) ;
System.Console.WriteLine("Decrypt
e : " + sDec) ;
}
}
Cet exemple ache ceci :
Message : Le message `a encrypter!
Encrypte: AQAAANCMnd8BFdERjHoAwE/Cl+sBAAAA3uy2/pifMEGRALpANT44y
QAAAAACAAAAAAADZgAAqAAAABAAAAA4UMUfUFqt5Xrz5U6hAVJ1AAAAAASAAACg
AAAAEAAAAHMxZCILz7K6JJc4Mmd/4P8YAAAAiiE+UBJdtaReGyP9vMsmw6HsqHM
227
LksdXFAAAAMRqfALSa8aaMm1mkestfBudeX91
Decrypte: Le message `a encrypter!
La classe System.Security.Cryptography.ProtectedMemory
La classe System.Security.Cryptography.ProtectedMemory permet de protger des donnes
au niveau de portes plus fines que celles prsentes par la classe ProtectedData. Les options
prsentes par lnumration MemoryProtectionScope sont les suivantes :
SameProcess : Spcifie que seul du code invoqu dans le mme processus que celui qui a
encrypt les donnes sera mme de les dcrypter.
SameLogon : Spcifie que seul du code invoqu dans un processus dans le mme contexte
utilisateur que celui qui a encrypt les donnes sera mme de les dcrypter. Cela implique
notamment quil faut que les oprations dencryptions et de dcryptions dune mme donne aient lieux durant la mme session Windows.
CrossProcess : Spcifie que les donnes peuvent tre dcryptes par nimporte quel code
excut dans nimporte quel processus la condition que le systme dexploitation nait pas
t redmarr entre lopration dencryption et lopration de dcryption.
Lexemple suivant illustre lutilisation de cette classe. Il faut que les donnes encrypter soient
stockes sur un tableau doctets dune taille multiple de 16 :
Exemple 6-24 :
using System.Security.Cryptography ;
class Program {
static void Main() {
string sMsg = "01234567890123456789012345678901" ;
System.Text.Encoding utf = new System.Text.UTF8Encoding() ;
System.Console.WriteLine("Message : " + sMsg) ;
byte[] bMsg = utf.GetBytes(sMsg) ;
ProtectedMemory.Protect(bMsg,
MemoryProtectionScope.SameProcess);
System.Console.WriteLine("Encrypt
e : " + utf.GetString(bMsg)) ;
ProtectedMemory.Unprotect(bMsg,
MemoryProtectionScope.SameProcess);
System.Console.WriteLine("Decrypt
e : " + utf.GetString(bMsg)) ;
}
}
Cet exemple ache ceci :
Message : 01234567890123456789012345678901
Encrypte : m;SH<^"?vfn6b{m.Op%L
Decrypte : 01234567890123456789012345678901
La classe System.Security.SecureString
La manipulation des chanes de caractres avec la classe String prsente plusieurs vulnrabilits
si lon considre quun individu mal intentionn peut avoir accs aux pages mmoires dun
processus Windows :
228
Pour pallier cette vulnrabilit, le framework .NET prsente la classe System.Security.SecureString. Cette classe dont limplmentation se base sur les services DPAPI, permet de stocker en
mmoire une chane de caractre sous une forme encrypte.
Vous pouvez initialiser une instance de SecureString soit partir dun tableau doctets (que
vous pouvez par la suite mettre zro) soit en la construisant caractre aprs caractre. La classe
Marshal prsente plusieurs mthodes pour rcuprer une chane de caractres encrypte dans
une instance de SecureString.
Lexemple suivant montre comment utiliser une instance de SecureString pour stocker un mot
de passe saisi par un utilisateur sur la console. Nous achons ensuite cette chane de caractres
sur la console. Pour les besoins de lexemple, nous convertissons linstance de BSTR contenant
la chane de caractre dcrypte en une instance de String. En pratique, il faut viter cette manipulation. Fatalement, pour exploiter une chane de caractres scurise il faut un moment
ou un autre la dcrypter en mmoire. Il faut alors la stocker dans une zone mmoire que
lon matrise pour pouvoir la dtruire ds que possible et pour viter les problmes poss par
la classe String vus prcdemment :
Exemple 6-25 :
using System ;
using System.Runtime.InteropServices ;
class Program {
static void Main() {
System.Security.SecureString pwd = new
System.Security.SecureString();
ConsoleKeyInfo nextKey = Console.ReadKey(true) ;
while(nextKey.Key != ConsoleKey.Enter){
pwd.AppendChar(nextKey.KeyChar);
Console.Write("*") ;
nextKey = Console.ReadKey(true) ;
}
Console.WriteLine() ;
pwd.MakeReadOnly();
IntPtr bstr = Marshal.SecureStringToBSTR(pwd) ;
// Recup`ere une instance de la classe String
// pour les besoins de lexemple.
try{
Console.WriteLine( Marshal.PtrToStringAuto(bstr) ) ; }
finally{ Marshal.ZeroFreeBSTR(bstr) ; }
}
}
229
230
La technologie Authenticode doit tre utilise pour identifier lauteur ou lentreprise qui
a fabriqu le fichier excutable. Elle permet dexcuter un programme sans crainte dune
ventuelle falsification.
La technologie du nom fort doit tre utilise pour identifier un assemblage (i.e pour nommer dune manire unique chaque assemblage et mme, chaque version dun mme assemblage). Elle permet notamment le stockage dassemblages cte cte.
La technologie Authenticode permet de signer toutes sortes de fichiers excutables, les assemblages .NET comme les fichiers excutable Windows contenant du code natif. La technologie du nom fort ne se cantonne quant elle quaux assemblages.
La vrification de la validit dun fichier excutable par la technologie Authenticode nest
eectue par Windows quune seule fois, lorsque lon tlcharge le fichier ou lorsquon linstalle (par exemple partir dun CD). La vrification du nom fort est eectue par le CLR
chaque chargement dun assemblage sign.
Cependant, la dirence principale entre ces deux technologies rside dans le concept de certificat.
231
232
Le mcanisme CAS est capable dobtenir une preuve partir dun assemblage sign avec un
certificat X.509 (voir page 189). Vous pouvez ainsi dcider que les assemblages signs avec un
certificat peuvent sexcuter avec plus (ou moins) de permissions que les autres.
Les dtails de la cration dun certificat, de la signature dun excutable par un certificat et de
la vrification dun certificat dpassent le cadre du prsent ouvrage. Tout ceci est dcrit dans
larticle Signing and Checking Code with Authenticode des MSDN.
En outre, sachez que le framework .NET prsente les espaces de noms System.Security.
Cryptography.X509Certificates et System.Security.Cryptography.Pkcs qui contiennent
des types spcialiss dans la manipulation de ces standards ainsi que dans la manipulation des
listes de certificats.
7
Rflexion, liens tardifs,
attributs
Nous avons parl page 17 des mtadonnes et de la faon dont elles sont physiquement stockes
dans les assemblages. Nous allons voir dans ce chapitre quelles constituent la base des mcanismes de rflexion et dattributs.
Le mcanisme de rflexion
Le mcanisme de rflexion dnomme lutilisation durant lexcution, des mtadonnes de type
dun assemblage. En gnral cet assemblage est charg explicitement lors de lexcution dun
autre assemblage mais il peut tre aussi construit puis charg dynamiquement.
Le mot rflexion est utilis pour montrer que lon utilise limage dun assemblage (comme une
image dans un miroir). Cette image est constitue par les mtadonnes de type de lassemblage.
Lors de la dcouverte des types dun assemblage lexcution par lanalyse dynamique de
ses mtadonnes de type. Par exemple, les outils ildasm.exe ou Reflector chargent explicitement un module dun assemblage et analyse son contenu (voir page 21).
Lors de lutilisation de liens tardifs. Cette technique consiste utiliser une classe situe dans
un assemblage qui nest pas connu la compilation. La technique du lien tardif est particulirement utilise par les langages interprts comme les langages de script.
Lorsque lon souhaite exploiter les informations contenues dans les attributs.
234
Lorsque lon souhaite accder aux membres non publics dune classe partir de lextrieur
de la classe. Bien videmment cette pratique est viter, mais il est ncessaire parfois dy
avoir recours, par exemple pour faire des tests unitaires qui ne peuvent se satisfaire des seuls
membres non publics.
Lors de la construction dynamique dun assemblage. Pour utiliser les classes dun assemblage construit dynamiquement on utilise la technique du lien tardif explicite.
Le mcanisme de rflexion est utilis par le CLR et le framework dans de multiples cas. Par
exemple, limplmentation par dfaut de la mthode Equals() sur un type valeur utilise la
rflexion pour comparer deux instances champs champs.
La rflexion est galement utilise par le CLR lors de la srialisation dun objet afin de connatre
les champs srialiser, ou encore par le ramasse-miettes, qui sen sert pour construire larbre de
rfrencement lors dune collecte dobjets.
Il est plus abstrait que le format TLB et le langage IDL. Par exemple il nutilise pas dadresses
physiques. Ainsi il peut tre utilis la fois sur des machines 32 et 64 bits.
Contrairement aux mtadonnes du format TLB, les mtadonnes .NET sont toujours physiquement contenues dans le module quelles dcrivent.
Le niveau de dtail de description des donnes est beaucoup plus pouss que dans le format
TLB. Concrtement on peut trs simplement avoir toutes les informations possibles sur
nimporte quel lment dclar dans un assemblage (par exemple le type dun argument
dune mthode dune classe).
Le niveau de dtail sans prcdent de la rflexion .NET est d de nombreuses classes de base
du Framework .NET qui permettent dextraire et dutiliser les mtadonnes de type dun assemblage contenu dans un domaine dapplication. La plupart de ses classes se trouvent dans lespace
de noms System.Reflection. Il y a une classe pour chaque type dlment dun assemblage :
une classe dont les instances reprsentent les mthodes des classes
(System.Reflection.MethodInfo) ;
une classe dont les instances reprsentent les champs des classes
(System.Reflection.FieldInfo) ;
Le mcanisme de rflexion
235
une classe dont les instances reprsentent les paramtres des mthodes
(System.Reflection.ParameterInfo).
etc.
Finalement ces classes ne reprsentent rien dautre quun moyen de visualiser logiquement lensemble des mtadonnes de type. La visualisation nest pas physique dans le sens o certains lments utiliss pour lorganisation interne dun assemblage (comme les jetons de mtadonnes)
ne sont pas reprsents.
Toutes les classes de lespace de noms System.Reflection sutilisent dune manire trs logique. Par exemple, partir dune instance de System.Reflection.Assembly, on peut obtenir un tableau dinstances de System.Type. partir dune instance de System.Type on
peut obtenir un tableau dinstances de System.Reflection.MethodInfo. partir dune instance de System.Reflection.MethodInfo, on peut obtenir un tableau dinstances de System.
Reflection.ParameterInfo. Tout ceci est illustr par la Figure 7-1.
Byte[]
GetILAsByteArray()
GetModules()
MethodBody
GetMethodBody()
Module
ConstructorInfo
GetTypes()
GetTypes()
GetConstructor()
GetParameters()
ParameterInfo
GetParameters()
GetMethodBody()
Assembly
MethodInfo
GetMethods()
Type
GetEvents()
GetProperties()
GetFields()
FieldInfo
EventInfo
PropertyInfo
ReflectedType
236
Exemple 7-1 :
using System ;
using System.Reflection ;
class Program{
public static void Main(){
Assembly assembly = Assembly.GetExecutingAssembly() ;
foreach (Type type in assembly.GetTypes()){
Console.WriteLine("Classe : " + type) ;
foreach (MethodInfo method in type.GetMethods()){
Console.WriteLine(" M
ethode : " + method) ;
foreach (ParameterInfo param in method.GetParameters())
Console.WriteLine("
Param : " +
param.GetType()) ;
}
}
}
}
Voici lachage de ce programme :
Classe : Program
Methode : Void Main()
Methode : System.Type GetType()
Methode : System.String ToString()
Methode : Boolean Equals(System.Object)
Param : System.Reflection.ParameterInfo
Methode : Int32 GetHashCode()
Le mcanisme de rflexion
237
"PublicKeyToken=b77a5c561934e089, Version=" +
typeof(System.Object).Assembly.GetName().Version.ToString() ;
// Charge explicitement lassemblage System.
// Nul besoin de charger lassemblage mscorlib car il est
// automatiquement et implicitement charg
e par le CLR.
Assembly.ReflectionOnlyLoad(systemAsmStrongName) ;
// Pour chaque assemblage du domaine dapplication courant...
foreach (Assembly a in AppDomain.CurrentDomain.GetAssemblies()){
Console.WriteLine("\nAssemblage:" + a.GetName().Name) ;
// Pour chaque type de cet assemblage...
foreach (Type t in a.GetTypes()){
// Seules les classes publiques sont retenues...
if (!t.IsClass || !t.IsPublic) continue ;
bool bDeriveDException = false ;
bool bDeriveDirectement = true ;
// System.Exception est-il un type de base de ce type ?
Type baseType = t.BaseType ;
while (baseType != null && !bDeriveDException){
// Pour trouver les classes dattributs remplacer cette
// ligne par if( baseType == typeof(System.Attribute))
if (baseType == typeof(System.Exception))
bDeriveDException = true ;
else bDeriveDirectement = false ;
baseType = baseType.BaseType ;
}// end while
//
//
//
if
238
classe Assembly prsente la proprit bool ReflectionOnly{get;} qui permet de savoir quun
assemblage a t charg par cette mthode.
239
du corps de la mthode en langage machine. Cette information est physiquement stocke dans
une zone mmoire associe la mthode, appele stub.
Cette constatation est trs importante puisque dans un langage comme C++, lorsquune mthode nest ni virtuelle ni abstraite (i.e ni virtuelle pure en terminologie C++), le compilateur
calcule lemplacement physique du corps de la mthode en langage machine. Ensuite, le compilateur insre un pointeur vers cet emplacement chaque appel de la mthode concerne.
Cette dirence confre un avantage certains .NET, puisque les compilateurs nont plus se
soucier de dtails techniques tels que la reprsentation dune adresse mmoire. Le code IL est
totalement indpendant de la couche physique qui lexcute.
En ce qui concerne les liens dynamiques, presque tout se passe comme pour les liens prcoces. Le
compilateur insre le jeton de mtadonne correspondant la mthode virtuelle (ou abstraite)
appeler, lendroit du code IL o lappel a lieu. On parle ici du jeton de mtadonne de la
mthode dfinie dans le type de la rfrence sur laquelle lappel lieu. Cest ensuite au CLR
de dterminer lexcution vers quelle mthode se brancher en fonction de limplmentation
exacte de lobjet rfrenc. Cest le polymorphisme.
Cette technique dinsertion par le compilateur du jeton de mtadonne pour les liens prcoces
et dynamiques est utilise dans les trois cas suivant :
Lorsque le code contenu dans un module appelle une mthode dfinie dans le mme module.
Lorsque le code contenu dans un module appelle une mthode dfinie dans un autre module du mme assemblage.
Lorsque le code contenu dans un module dun assemblage appelle une mthode dfinie
dans un autre assemblage rfrenc la compilation. Si ce dernier assemblage nest pas dj
charg lors de lappel de la mthode, le CLR le charge implicitement.
Liens tardifs
En .NET, le code dun assemblage A peut instancier et utiliser lexcution un type dfini dans
un assemblage B non rfrenc la compilation de A. On qualifie ce type de lien lien tardif. Dans
la premire dition du prsent ouvrage ainsi que dans dautres documents, les liens tardifs sont
parfois nomms liens tardifs explicites. Le lien est tardif dans le sens o, comme nous allons le
voir, il est cr aprs la compilation, durant lexcution. Le lien est explicite dans le sens o le
nom de la mthode appeler doit tre prcis explicitement dans une chane de caractres.
Lide de lien tardif nest pas nouvelle dans le monde du dveloppement Microsoft. Tout le mcanisme dit dAutomation dans la technologie COM, qui utilise linterface IDispatch, nest rien
dautre quune usine gaz faite pour pouvoir crer des liens tardifs partir de langages de
scripts ou peu typs, comme VB. La notion de liens tardifs existe aussi en Java.
Le concept de lien tardif fait partie des ides que les dveloppeurs habitus au langage C++ ont
du mal assimiler. En eet, en C++ seul les liens prcoces et les liens dynamiques existent. Le
problme de comprhension vient du fait suivant : on comprend bien que les informations ncessaires pour crer un lien (i.e les jetons de mtadonnes) sont dans lassemblage B contenant la
classe appeler, mais on ne comprend pas pourquoi le dveloppeur ne profite pas de la capacit
du compilateur crer des liens prcoces et dynamiques en rfrenant B la compilation de
A. Il existe plusieurs raisons direntes :
240
La raison la plus courante est que pour certains langages il ny a pas de compilateur ! Dans
un langage type script, les instructions sont interprtes une une. Dans ce cas il ne peut y
avoir que des liens tardifs. Les liens tardifs permettent donc dutiliser des classes compiles et
contenues dans des assemblages, partir de langages interprts. Le fait que la technique des
liens tardifs soit trs facile utiliser en .NET, fait que lon peut facilement crer des langages
interprts propritaires (tel que le langage IronPython http://www.ironpython.com/).
Certaines applications ont pour vocation dappeler le code de nimporte quel assemblage
qui lui est propos. Lexemple typique est loutil open-source NUnit qui permet de tester le
code de nimporte quel assemblage en invoquant ses mthodes. Nous approfondissons ce
sujet lors de la construction dun attribut personnalis un peu plus loin dans ce chapitre.
On doit utiliser des liens tardifs entre le code dun assemblage A et les classes dun assemblage B si B nexiste pas lors de la compilation de A. Cette situation est dcrite un peu plus
loin dans ce chapitre o lon y parle de construction dynamique dassemblages.
Les performances des liens tardifs explicites sont beaucoup moins bonnes que celles des
liens prcoces et des liens tardifs implicites (mme si vous utilisez les optimisations prsentes un peu plus loin).
On ne peut crer un lien tardif avec une classe obfusque. En eet, lors de lobfuscation, le
nom de la classe est chang dans lassemblage qui la contient. Or, la technique du lien tardif
retrouve la classe avec laquelle le lien tardif doit tre cr grce son nom. Comme nous
allons le voir, ce nom est stock du cot du consommateur de la classe dans une chane de
caractres. Il y a donc incompatibilit entre obfuscation et lien tardif.
Prciser un type
Intressons-nous aux direntes techniques qui permettent de prciser un type :
241
Certaines mthodes de certaines classes acceptent une chane de caractres qui contient le
nom complet du type (i.e avec son espace de noms).
En C on utilise souvent le mot-cl typeof() qui prend en paramtre un type, et retourne linstance de System.Type adquate.
Vous pouvez aussi utiliser une des surcharges de la mthode statique GetType() de la
classe System.Type.
Si un type est encapsul dans un autre type, vous pouvez utiliser une des surcharges des
mthodes non statiques GetNestedType() ou GetNestedTypes() de la classe System.
Type.
Vous pouvez aussi utiliser les mthodes non statiques GetType() GetTypes() ou GetExportedTypes() de la classe System.Reflection.Assembly.
Vous pouvez aussi utiliser les mthodes non statiques GetType() GetTypes() ou FindTypes() de la classe System.Relection.Module.
Supposons maintenant que le programme suivant soit compil dans un assemblage Foo.dll.
Nous allons exposer plusieurs manires permettant de crer une instance de la classe NMFoo.
Calc partir dassemblages qui ne rfrencent pas Foo.dll.
Exemple 7-3 :
using System ;
namespace NMFoo {
public class Calc {
public Calc() {
Console.WriteLine("Calc.Constructeur appel
e") ;
}
public int Sum(int a, int b) {
Console.WriteLine("Methode Calc.Sum() appel
ee") ;
return a + b ;
}
}
}
242
Chacune de ces mthodes se dcline en de nombreuses versions surcharges (et parfois gnriques) avec les arguments suivants :
Une classe, sous forme dune chane de caractres ou dune instance de System.Type ;
Si lassemblage qui contient la classe nest pas prsent dans le domaine dapplication, lappel CreateInstance() ou CreateInstanceFrom() dclenche le chargement de cet assemblage. Pour raliser ce chargement, une des mthodes System.AppDomain.Load() ou System.AppDomain.LoadFrom() est appele en interne, selon que lon appelle CreateInstance()
ou CreateInstanceFrom(). Un constructeur de la classe est choisi, partir des arguments passs.
Une instance de la classe ObjectHandle, renfermant un objet marshall par valeur est retourne.
Dans le chapitre 22 relatif .NET Remoting, nous prsentons une autre utilisation de ces
mthodes dans le cadre dapplications distribues.
Les surcharges de CreateInstance() o le type est prcis sous la forme dune instance de
System.Type, retournent directement une rfrence vers un objet.
La classe System.Activator prsente aussi la mthode CreateComInstanceFrom() utilise pour
crer des instances dobjets COM et la mthode GetObject() utilise pour crer des objets distants.
243
Cas particuliers
Avec les mthodes prsentes, vous pouvez crer une instance de pratiquement nimporte
quelle classe ou structure. Deux cas particuliers sont noter :
244
La mthode Type.InvokeMember()
Revenons vers la mthode Type.InvokeMember() qui nous a servi prcdemment crer une
instance dun type inconnu la compilation en invoquant un de ces constructeurs. Cette mthode accomplit en interne trois tches :
Elle cherche un membre du type sur lequel elle est appele qui correspond aux informations quon lui passe.
Elle utilise le membre (invocation pour une mthode, cration dobjet puis invocation
pour un constructeur, obtention ou dfinition de la valeur pour un champ...).
Lexemple suivant montre comment invoquer la mthode Sum() sur notre instance de la classe
NMFoo.Calc (notez que lors de la phase de dboguage, le dbogueur est capable de continuer sa
course dans le corps de la mthode invoquer grce un lien tardif) :
Exemple 7-8 :
using System ;
using System.Reflection ;
class Program {
static void Main() {
object obj = AppDomain.CurrentDomain.CreateInstanceAndUnwrap(
"Foo.dll", "NMFoo.Calc") ;
Type type = obj.GetType() ;
object[] parametres = new object[2] ;
parametres[0] = 7 ;
parametres[1] = 8 ;
int result = (int)type.InvokeMember(
"Sum",
// Nom de la m
ethode.
BindingFlags.InvokeMethod,
null,
// Pas besoin de binder.
obj,
// Lobjet cible.
parametres); // Les param`
etres.
// Ici, result vaut 15.
}
}
La surcharge la plus couramment utilise de la mthode Type.InvokeMember() est :
245
//
//
//
//
//
Le nom du membre.
Quel membre doit
etre pris en compte.
Les r`
egles de recherche du membre.
Lobjet sur lequel invoquer le membre.
Les arguments de lappel.
Le paramtre invokeAttr est un indicateur binaire qui signale sur quel type de membre la recherche va se porter. Pour chercher sur les mthodes, nous utilisons lindicateur BindingFlags.
InvokeMethod. Les dirents indicateurs sont dcrits en dtail dans les MSDN, dans larticle
BindingFlags Enumeration.
Le paramtre binder indique un objet de type Binder qui va orienter InvokeMember() sur la
faon dont elle va eectuer ses recherches. La plupart du temps vous positionnerez ce paramtre
null pour indiquer que vous souhaitez utiliser la proprit System.Type.DefaultBinder. Un
objet de type Binder fournit ce genre dinformations :
Il indique quelles conversions de types sont acceptes ou non pour les arguments. Dans
lexemple prcdent, nous aurions pu fournir deux arguments de type double. Grce au
DefaultBinder, lappel de la mthode aurait march car il supporte la conversion du type
double vers le type int.
Il indique si nous utilisons les paramtres optionnels dans notre liste de paramtres.
Tout ceci (notamment la table de conversion de type) est dcrit en dtail dans les MSDN lentre Type.DefaultBinder Property. Vous pouvez crer vos propres objets Binder en drivant
de la classe Binder. Nanmoins une instance de DefaultBinder sut dans la plupart des cas et
cette possibilit est trs rarement utilise.
Si une exception est lance durant lappel du membre, InvokeMember() intercepte lexception
et relance une exception de type System.Reflection.TargetInvocationException. Naturellement lexception lance dans la mthode est rfrence par la proprit InnerException de lexception relance.
Notez enfin que lorsque vous crez un lien tardif, vous ne pouvez pas accder, a priori,
aux membres non publics. Lexception System.Security.SecurityException est alors, en
gnral, lance. Nanmoins si le bit TypeInformation de System.Security.Permissions.
ReflectionPermissionFlags (accessible par une instance de la classe System.Security.
Permissions.ReflectionPermission) est positionn, vous avez accs aux membres non publics.
Si le bit MemberAccess est positionn, vous avez accs aux types non visibles (i.e encapsuls dans
dautres types, dune manire non publique).
246
Exemple 7-9 :
using System ;
using System.Reflection ;
class Program {
static void Main() {
object obj = AppDomain.CurrentDomain.CreateInstanceAndUnwrap(
"Foo.dll", "NMFoo.Calc") ;
Type type = obj.GetType() ;
// Creation du lien tardif.
MethodInfo methodInfo = type.GetMethod("Sum") ;
object[] parametres = new object[2] ;
parametres[0] = 7 ;
parametres[1] = 8 ;
int result ;
// 10 invocations de Sum au travers du lien tardif.
for (int i = 0 ; i < 10 ; i++)
result = (int)methodInfo.Invoke(obj, parametres) ;
}
}
247
namespace NMFoo {
public interface ICalc {
int Sum(int a, int b) ;
}
}
Exemple 7-13 :
using System ;
namespace NMFoo {
public class CalcAvecInterface : ICalc {
public CalcAvecInterface() {
Console.WriteLine("Calc.Constructeur appel
e") ;
}
public int Sum(int a, int b) {
Console.WriteLine("Methode Calc.Sum() appel
ee") ;
return a + b ;
}
}
}
Exemple 7-14 :
Code de lassemblage client de la classe cible, non connue `
a la
compilation (ProgramAsm.cs)
using
using
using
class
System ;
System.Reflection ;
NMFoo ;
Program {
248
Soyez attentif au transtypage explicite en ICalc de lobjet retourn par la mthode CreateInstanceAndUnwrap() qui nous permet dutiliser ensuite un lien dynamique sur la mthode Sum().
On aurait pu viter ce transtypage en utilisant la surcharge gnrique de la mthode Activator.CreateInstance<ICalc>().
La figure suivante reprend lorganisation ainsi que les liens entre nos trois assemblages :
Assemblage contenant la classe
Program
Assemblage contenant
linterface ICalc
Les attributs
Quest ce quun attribut ?
Un attribut est une information qui se rapporte un lment du code tel quune classe ou une
mthode. Par exemple, le framework .NET fournit lattribut System.ObsoleteAttribute qui
peut tre utilis pour marquer une mthode comme ceci (notez la syntaxe avec les crochets []) :
[System.ObsoleteAttribute()]
void Fct() { }
Linformation la mthode Fct() est marque avec lattribut System.ObsoleteAttribute est
insre dans lassemblage lors de la compilation. Cette information peut alors tre exploite
par le compilateur C . Lorsquil rencontre un appel cette mthode, il peut mettre un avertissement indiquant quil vaut mieux viter dinvoquer une mthode obsolte, qui risque de
Les attributs
249
disparatre dans les prochaines versions. Sans attribut, vous seriez oblig de commenter et de
documenter lobsolescence de la mthode Fct(). La faiblesse de cette dmarche est que vous
nauriez aucune garantie que vos clients soient au courant de cette obsolescence.
Un attribut peut tre consomm par le CLR lexcution. Par exemple, le framework .NET
prsente lattribut System.ThreadStaticAttribute. Lorsquun champ statique est marqu
avec cet attribut, le CLR fait en sorte qu lexcution il existe une version de ce champ par
thread.
Un attribut peut tre consomm par un dbuggeur lexcution. Ainsi lattribut System.
Diagnostics.DebuggerDisplayAttribute permet de personnaliser lachage dun lment du code (ltat dun objet par exemple) lors du dboguage.
Un attribut peut tre consomm par un outil. Par exemple, le framework .NET prsente
lattribut System.Runtime.InteropServices.ComVisibleAttribute. Lorsquune classe est
marque avec cet attribut, loutil tlbexp.exe gnre un fichier qui permettra ultrieurement cette classe dtre consomme comme si elle tait dfinie avec la technologie COM.
Un attribut peut tre consomm par votre propre code lexcution, en ayant recours au
mcanisme de rflexion pour accder linformation. Ainsi, il peut tre intressant de dfinir lintgrit des champs de vos classes en marquant leurs champs avec des attributs. Tel
champ entier doit tre dans telle plage de valeur. Tel champ de type rfrence ne doit jamais tre nul. Tel champ doit rfrencer une chane dau plus 100 caractres etc. Grce au
mcanisme de rflexion, il est ais dcrire du code capable de valider ltat de nimporte
quelle classe dont les champs sont marqus. Nous dtaillons un peu plus loin un exemple
de consommation dattributs lexcution par du code propritaire.
Un attribut peut tre consomm par un utilisateur qui analyse un assemblage avec un outil
tel que ildasm.exe. Ainsi, nous pouvons imaginer un attribut qui permettrait dassocier
une chane de caractres un lment du code source. Cette chane de caractres tant insre dans lassemblage, il devient alors possible de consulter des commentaires sans avoir
besoin du code source.
Un attribut est ncessairement dfinit par une classe qui drive de la classe System.
Attribute.
250
Une classe dattribut nest instancie que lorsque le mcanisme de rflexion accde un
de ses reprsentant. Selon lutilisation, une classe dattribut nest donc pas forcment instancie ( linstar de la classe System.ObsoleteAttribute qui na pas tre utilis avec le
mcanisme de rflexion).
Le Framework .NET met votre disposition de nombreux attributs. Certains sont destins
tre consomms par le CLR. Dautres sont consomms par le compilateur ou des outils
fournis par MS.
Vous avez la possibilit de crer vos propres classes dattributs. Ils seront alors ncessairement consomms par vos propres programmes ou outils puisque vous ne pouvez pas modifier ni le compilateur ni le CLR.
Par convention, le nom dune classe dattribut est sux par Attribute. Cependant, un attribut nomm XXXAttribute peut en C tre utilis la fois avec lexpression XXXAttribute
mais aussi avec lexpression XXX lorsquil marque un lment du code.
Dans le chapitre consacr la gnricit, nous prsentons en page 497 les rgles relatives au
recoupements entre les deux notions dattribut et de gnricit.
All
Assembly
Lassemblage lui-mme.
Class
Les classes.
Constructor
Les constructeurs.
Delegate
Les dlgus.
Enum
Les numrations.
Event
Les vnements.
Field
Les champs.
GenericParameter
Interface
Les interfaces.
Method
Les mthodes.
Module
Les modules.
Les attributs
251
Parameter
Property
ReturnValue
Struct
Les structures.
252
Loutil NUnit permet dexcuter et donc de tester, nimporte quelle mthode de nimporte quel
assemblage. Comme il ny a pas de sens excuter toutes les mthodes dun assemblage, NUnit
nexcute que les mthodes marques avec un attribut de type TestAttribute.
Pour implmenter une version simplifie de ce comportement, nous nous imposons les
contraintes suivantes :
Le test dune mthode est considr comme concluant si celle-ci nenvoie aucune exception
non rattrape.
Nous dfinissions un attribut TestAttribute qui peut sappliquer sur les mthodes. Lattribut peut tre paramtr par le nombre de fois que la mthode doit tre excute (proprit
int TestAttribute.nTime). Lattribut peut aussi tre paramtr pour permettre dignorer
une mthode marque (proprit bool TestAttribute.nTime).
Les attributs
foreach(Type type in assembly.GetTypes() ){
foreach(MethodInfo method in type.GetMethods() ){
// Obtient les attributs de type TestAttribute
// qui marquent la m
ethode r
ef
erenc
ee par method.
// Declenche lappel `
a TestAttribute.ctor().
object[] attributes = method.GetCustomAttributes(
typeof(TestAttribute),false) ;
if( attributes.Length == 1 ){
// Obtient une ref
erence de type TestAttribute.
TestAttribute testAttribute =
attributes[0] as TestAttribute ;
// Si la methode nest pas `
a ignorer.
if( ! testAttribute.Ignore ){
object [] parameters = new object[0] ;
object instance = Activator.CreateInstance(type) ;
// Invoque la m
ethode nTime fois.
for(int i=0;i< testAttribute.nTime ; i++){
try{
//Invocation de la m
ethode avec un lien tardif.
method.Invoke(instance,parameters) ;
} catch(TargetInvocationException ex) {
Console.WriteLine(
"La m
ethode {" + type.FullName + "." +
method.Name +
"} a lanc
e une exception de type " +
ex.InnerException.GetType() +
" lors de lex
ecution " + (i+1) + ".") ;
}// end catch(...
}// end for(...
}// end if( ! attribute.Ignore )
}// end if( attributes.Length == 1 )
}// end foreach(MethodInfo...
}// end foreach(Type...
}
}
class Foo {
[Test()]
public void Plante() {
Console.WriteLine("Plante()") ;
throw new ApplicationException() ;
}
int state = 0 ;
[Test(4)]
public void PlanteLaDeuxiemeFois() {
Console.WriteLine("PlanteLaDeuxiemeFois()") ;
state++ ;
if (state == 2) throw new ApplicationException() ;
}
253
254
Ce programme ache :
TestAttribute.ctor() par defaut.
Plante()
La methode {Foo.Plante} a lance une exception de type
System.ApplicationException lors de lex
ecution 1.
TestAttribute.ctor(int).
PlanteLaDeuxiemeFois()
PlanteLaDeuxiemeFois()
La methode {Foo.PlanteLaDeuxiemeFois} a lanc
e une exception de type
System.ApplicationException lors de lex
ecution 2.
PlanteLaDeuxiemeFois()
PlanteLaDeuxiemeFois()
TestAttribute.ctor() par defaut.
NePlantePas()
TestAttribute.ctor() par defaut.
Plusieurs remarques simposent :
255
256
Lexcution dun script dans un navigateur web : lide est quun script dans un page Web
construit dynamiquement un assemblage qui est sauv de faon persistante chez le client.
Lexcution dun script dans une page ASP.NET : lide est quun script dans un page
ASP.NET, construit dynamiquement un assemblage qui est sauv de faon persistante
dans le cache du serveur. Ainsi, seule la premire visite de la page provoque la cration de
lassemblage.
La compilation dune expression rgulire fournie lexcution. Le problme de lvaluation dune expression rgulire est reprsentatif de cette classe de problmes qui admettent
une solution globale lente, et des solutions particulires rapides. Lide est de construire
une solution particulire lexcution, lorsque lexpression rgulire est fournie, plutt que
dimplmenter au dveloppement la solution globale lente. Le framework .NET permet la
compilation des expressions rgulires et tout ceci est expliqu page 625.
Tout ceci peut paratre abstrait aussi allons-nous prsenter un exemple concret qui a la mrite
davoir une utilit potentielle dans lindustrie.
Un problme concret
Prsentation du problme
Lexemple prsent ici est bas sur lvaluation dun polynme P coecients entiers, dfini
sur les entiers. Imaginez une application o ou un tel polynme est fourni lexcution (par
exemple par un utilisateur). Supposons que lapplication doive, par la suite, valuer ce polynme pour un trs grand nombre de valeurs. Nous allons montrer que ce problme concret
admet une solution trs optimise, utilisant la fabrication lexcution dun nouvel assemblage.
Par la suite nous allons supposer que le polynme P saisi par lutilisateur durant lexcution de
lapplication est :
P(x) = 66x3 + 83x2 13 735x + 30 139
Pour la petite histoire, ce polynme dcouvert par les mathmaticiens Dress et Landreau en
1999, a la particularit de ne prendre pour valeurs que des nombres premiers pour x entre 26
et 19 (inclus). Nous aurions pu prendre nimporte quel autre polynme coecients entiers
pour illustrer notre exemple.
257
Pour nos tests de performance, nous valuerons P pour chacune de ces valeurs 10 millions de
fois. On ne travaillera quavec des entiers sur quatre octets signs (le type int en C , qui est aussi
le type System.Int32). Pour ceux qui ont oubli leurs cours de mathmatique nous rappelons
que la manire la plus conomique en terme doprations dvaluer ce polynme est de lcrire
sous cette forme :
P(x) = 30 139 + x(13 735 + x(83 + 66x))
Cette astuce sappelle la mthode de Hrner. Seulement trois multiplications et trois additions
sont ncessaires pour valuer P pour une valeur de x.
258
Problme potentiel 2 : Le compilateur JIT pourrait tre assez intelligent pour sapercevoir que ce nest pas la peine dappeler la mthode Evalue() puisquon nutilise pas ses rsultats. Nous avons pu tablir que ce nest pas le cas en vrifiant que si la mthode Evalue()
incrmente un compteur global, les rsultats sont sensiblement quivalents.
259
ldc.i4
0x75bb // coef 30139 mis sur le haut de la pile
ldarg.0
// valeur de x mise sur le haut de la pile
ldc.i4
0xffffca59// coef -13735 mis sur le haut de la pile
ldarg.0
// valeur de x mise sur le haut de la pile
ldc.i4.s
83
// coef 83 mis sur le haut de la pile
ldarg.0
// valeur de x mise sur le haut de la pile
ldc.i4.s
66
// coef 66 mis sur le haut de la pile
mul
//
add
//
mul
//
Evaluation du polyn
ome en x
add
//
avec trois additions et
mul
//
trois multiplications
add
//
stloc.0
br.s
IL_001a
ldloc.0
ret
method Program::Calc
Il sut maintenant de produire ce code IL pour nimporte quel polynme, avec les classes de
lespace de noms System.Reflection.Emit.
Dans cet exemple, linstruction IL ldarg est utilise avec le paramtre 0 pour charger le
premier argument. Si la mthode navait pas t statique ldarg.0 aurait reprsent le pointeur this et il aurait fallu utiliser ldarg.1 pour accder au premier argument. Par la suite
la mthode ne sera pas statique, aussi nous utiliserons ldarg.1 pour accder au premier
lment.
Voici le code :
Exemple 7-21 :
using
using
using
using
using
System ;
System.Reflection ;
System.Reflection.Emit ;
System.Threading ;
System.Diagnostics ;
260
261
262
Sauver lassemblage avec la ligne de code suivant, juste avant le retour de la mthode BuildCodeInternal() :
TheAsm.Save("MainMod.dll") ;
Nous pouvons comparer le code IL produit par le compilateur C et le code IL produit par notre
propre programme :
Code IL produit par le compilateur C
.method private hidebysig static
int32 Calc(int32 x) cil managed
{
// Code size 28 (0x1c)
.maxstack 7
.locals init ([0]
int32 CS$00000003$00000000)
IL_0000: ldc.i4 0x75bb
IL_0005: ldarg.0
IL_0006: ldc.i4 0xffffca59
IL_000b: ldarg.0
IL_000c: ldc.i4.s 83
IL_000e: ldarg.0
IL_000f: ldc.i4.s 66
IL_0011: mul
IL_0012: add
IL_0013: mul
IL_0014: add
IL_0015: mul
IL_0016: add
IL_0017: stloc.0
IL_0018: br.s IL_001a
IL_001a: ldloc.0
IL_001b: ret
} // end of method CCalc::Calc
Nous nutilisons pas de variables locales, contrairement au code produit par le compilateur
C.
Nous nutilisons pas linstruction optimise ldc.i4.s qui charge sur la pile une valeur dun
octet (i.e entre -128 et 127) dans quatre octets. Nous pourrions optimiser notre programme
en utilisant cette instruction.
263
Conclusion
Vous avez vu ici un exemple reprsentatif des possibilits de la cration dynamique dassemblages. On pourrait ladapter simplement dautres domaines aussi utiles que le calcul vectoriel (composition avec une matrice, valuation dune forme quadratique etc). Il arrive souvent quune mme matrice, inconnue avant lexcution, soit compose des millions de fois (par
exemple pour calculer les dplacements des points dune scne 3D).
La majorit des dveloppeurs nauront jamais construire dassemblages dynamiquement, mais
certains projets ont beaucoup gagner utiliser cette possibilit.
8
Interoprabilit .NET
code natif / COM / COM+
La plateforme .NET prsente plusieurs techniques pour faire interoprer du code gr avec du
code natif et pour faire cooprer des objets grs avec des objets COM. Ce besoin est particulirement prsent lors dun processus de migration dune application C++ ou VB6 vers .NET. En
eet, linteroprabilit permet de migrer vos projets petit petit, composant aprs composant.
Le mcanisme P/Invoke
Le CLR utilis conjointement avec certaines classes de base du framework .NET, permet du
code gr dappeler des fonctions compiles en code natif. Cette possibilit est nomme Platform
Invoke ou P/Invoke. Vous pouvez lutiliser pour appeler les fonctions de vos propres DLLs natives.
Microsoft exploite aussi le mcanisme P/Invoke pour appeler les fonctions de ses propres DLLs
natives.
Notons lexistence du mcanisme internal call qui a la mme finalit que P/Invoke. Ce mcanisme consiste implmenter les artefacts des appels aux fonctions natives directement (en dur)
dans le code du CLR. Il est donc plus performant que P/Invoke mais nest utilisable que par
Microsoft.
Lattribut DllImport
Les classes qui permettent dutiliser P/Invoke se trouvent dans lespace de noms System.
Runtime.InteropServices. Pour appeler une fonction dune DLL native partir dun programme C , il faut dabord dclarer cette fonction dans une classe C :
La dclaration de cette fonction doit tre marque avec lattribut System.Runtime.InteropServices.DllImport qui indique le nom de la DLL.
266
Un exemple
Le programme suivant appelle la fonction Beep() dfinie dans la DLL native et standard
Kernel32.dll.
Exemple 8-1 :
using System.Runtime.InteropServices ;
class Program {
[DllImport("Kernel32.dll")]
public static extern bool Beep(uint iFreq, uint iDuration);
static void Main() {
bool b = Beep(100, 100);
}
}
Dans certains cas rares, vous ne pouvez pas spcifier le nom de la fonction dfinie dans la DLL
car vous avez dj une mthode qui a ce nom. Il est possible de changer le nom dune fonction
implmente dans une DLL native comme ceci :
Exemple 8-2 :
using System.Runtime.InteropServices ;
class Program {
[DllImport("Kernel32.dll", EntryPoint = "Beep")]
public static extern bool MonBeep(uint iFreq, uint iDuration) ;
static void Main() {
bool b = MonBeep(100, 100) ;
}
}
Convention dappel
Les fonctions implmentes dans les DLLs natives supportent plusieurs conventions dappel.
Ces conventions dappel indiquent au compilateur comment doit se comporter le passage dargument. Si vous devez utiliser une fonction qui a une convention dappel particulire, il faut
utiliser lattribut DllImport comme ceci :
[DllImport("MaDLL.dll", CallingConvention=XXX)]
XXX est une valeur de lnumration System.Runtime.InteropServices.CallingConvention.
Les significations des valeurs de cette numration sont exposes dans les MSDN larticle
CallingConvention Enumeration. Par dfaut cette valeur vaut StdCall qui est la convention
dappel standard sur les systmes dexploitation Microsoft (mis part Windows CE qui admet la
convention dappel standard Cdecl).
Le mcanisme P/Invoke
267
Il faut parcourir la pile pour vrifier que tous les appelants ont la permission UnmanagedCode.
Afin damliorer les performances, vous pouvez supprimer cette tape en utilisant lattribut
System.Security.SuppressUnmanagedCodeSecurity sur une mthode P/Invoke ou sur
une classe qui contient des mthodes P/Invoke . Cependant, lutilisation de cet attribut
peut tre compromettante pour la scurit.
Il faut crer une fentre de pile (stack frame en anglais) qui soit compatible avec les fentres
de pile utilises dans le code non gr. Cette tape est en gnral trs lgre.
Puisque la seconde tape est lgre et puisque la premire tape peut tre supprime si la scurit ne fait pas partie de vos considrations, vous pouvez faire en sorte que lutilisation de
P/Invoke ait un faible impact sur les performances.
Type win32
Type .NET
System.StringSystem.
StringBuilder
string
BYTE
System.Byte
Byte
SHORT
System.Int16
Short
WORD
System.UInt16
ushort
DWORD, UINT,ULONG
System.Int32
uint
INT, LONG
System.UInt32
uint
BOOL
System.Bool
bool
CHAR
System.Char
char
FLOAT
System.Single
float
DOUBLE
System.Double
double
268
On dit quun type est blittable si la reprsentation binaire de ses instances est identique en mode
gr et en mode natif. Dans le cadre de linteroprabilit, lutilisation des types blittable est donc
plus performante. Dans le tableau prcdent, seuls les types de chanes de caractres ne sont
pas blittables. Les tableaux monodimensionnels dlments de type blittable ainsi que les types
seulement composs de champs de types blittables sont aussi des types blittable.
Le mcanisme P/Invoke
269
Exemple 8-3 :
using System.Runtime.InteropServices ;
class Program {
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern int MessageBox(System.IntPtr hWnd,
string text, string caption, uint type) ;
static void Main() {
MessageBox(System.IntPtr.Zero, "hello", "caption text", 0) ;
}
}
Soit le code appelant fournit une mmoire tampon et la taille de cette mmoire tampon.
Dans ce cas la fonction appele remplie la mmoire tampon avec la chane de caractres
retourner.
Soit le code appelant sattend recevoir un pointeur partir de la fonction appele. Dans
ce cas, la fonction appele alloue la zone mmoire contenant la chane de caractres.
Il faut absolument tenir compte de ces dirences lorsque lon appelle au moyen de P/Invoke
une fonction dont le code est non gr. La fonction GetCurrentDirectory() de la DLL
Kernel32.dll est une bonne candidate pour illustrer le premier cas.
DWORD GetCurrentDirectory(
DWORD
nBufferSize,
LPTSTR lpBuffer) ;
En eet, cette fonction admet un pointeur vers une zone de mmoire tampon et sa taille en
argument. Voici le code C permettant dappeler cette fonction. On ne peut utiliser la classe
string car les instances de cette classe sont immuables. Il faut donc se servir de la classe System.
Text.StringBuilder.
Exemple 8-4 :
using System.Text ;
using System.Runtime.InteropServices ;
class Program {
[DllImport("Kernel32.dll", CharSet = CharSet.Auto)]
public static extern uint GetCurrentDirectory(
uint Taille,
StringBuilder sTmp);
static void Main() {
uint Taille = 255 ;
StringBuilder sTmp = new StringBuilder( (int) Taille);
uint i = GetCurrentDirectory(Taille, sTmp) ;
System.Console.WriteLine(sTmp) ;
}
}
270
La fonction GetCommandLine() de la DLL Kernel32.dll est une bonne candidate pour illustrer
le second cas.
LPTSTR GetCommandLine() ;
Cette fonction retourne un pointeur vers la chane de caractres fournie en ligne de commande.
Si nous utilisons le type string comme type de retour, le P/Invoke marshaller copiera la chane
de caractres retourne dans une mmoire tampon alloue par ses soins. Ensuite le P/Invoke
marshaller dsallouera la chane de caractres originale. Or, cette chane de caractres originale
ne doit pas tre dsalloue, car elle existait avant lappel de la fonction GetCommandLine() et
sera srement utilise ultrieurement, dans dautres appels de fonctions. Il faut donc utiliser la
classe IntPtr, qui permet de copier la chane de caractres retourne dans une instance de la
classe string, sans dsallouer la chane de caractres originale :
Exemple 8-5 :
using System.Runtime.InteropServices ;
class Program {
[DllImport("Kernel32.dll", CharSet = CharSet.Auto)]
public static extern System.IntPtr GetCommandLine() ;
static void Main() {
System.IntPtr ptr = GetCommandLine() ;
string sTmp = Marshal.PtrToStringAuto(ptr);
System.Console.WriteLine(sTmp) ;
}
}
Le mcanisme P/Invoke
271
Attribut de direction
Lors de la dclaration dune mthode P/Invoke , vous pouvez utiliser les attributs System.
Runtime.InteropServices.In et System.Runtime.InteropServices.Out devant chaque argu-
272
273
Exemple 8-7 :
// compile with: /clr
#include "stdafx.h"
#include "windows.h"
int main(){
bool b = Beep(100, 100) ;
}
Le compilateur C++/CLI peut se passer de lattribut DllImport car il connat la dfinition de la
mthode que lon souhaite appeler grce linclusion du fichier dentte adquat. Dans notre
exemple, la mthode Beep() est dfinie dans le fichier dentte windows.h. La visualisation de
lassemblage produit par le compilateur C++/CLI avec loutil ildasm.exe nous rvle que la mthode statique suivante a t fabrique :
.method public static pinvokeimpl(lasterr stdcall)
int32 modopt([mscorlib]System.Runtime.CompilerServices.CallConvStdcall)
Beep(
uint32 modopt([Microsoft.VisualC]Microsoft.VisualC.IsLongModifier) A_0,
uint32 modopt([Microsoft.VisualC]Microsoft.VisualC.IsLongModifier) A_1)
native unmanaged preservesig {
.custom instance void
[mscorlib]System.Security.SuppressUnmanagedCodeSecurityAttribute::
.ctor() = ( 01 00 00 00 )
// Embedded native code
// Disassembly of native methods is not supported.
// Managed TargetRVA = 0x00001D92
} // end of method Global Functions::Beep
La visualisation de lassemblage produit par le compilateur C pour lExemple 8-7 expose cette
dfinition :
.method public hidebysig static pinvokeimpl("Kernel32.dll" winapi)
bool Beep(uint32 iFreq,uint32 iDuration) cil managed preservesig{}
Du point de vue du CLR, les deux versions de la mthode Beep() sont invoquer avec le mcanisme P/Invoke puisquelles ont toutes les deux le drapeau pinvokeimpl. Nanmoins, on remarque que le compilateur C++/CLI a gnr du code natif pour localiser la mthode Beep()
contenue dans la DLL kernel32.dll tandis que la version C compte sur le CLR pour raliser cet
opration. Dautres dirences sont noter comme lutilisation dans la version C++/CLI de lattribut SuppressUnmanagedCodeSecurity (dcrit page 201) et du modificateur IsLongModifier
qui rsout les problmes dus au fait que les mots-cls C++/CLI long et int se rfrrent tous deux
au type Int32.
274
La dfinition de types non grs avec des mthodes dont le corps contient du code natif.
La dfinition de types non grs avec des mthodes dont le corps contient du code IL.
La dfinition de types grs avec des mthode dont le corps contient du code IL.
Les #pragma managed et unmanaged doivent tre dfinis hors dun type pour prendre eet. Un
type ne peut donc avoir la fois des mthodes natives et gres. En outre, le langage C++/CLI ne
permet pas la dfinition de types grs avec des mthodes dont le corps contient du code natif.
Tout cela est illustr par le programme suivant :
Exemple 8-8 :
// compile with: /clr
#include "stdafx.h"
#using <mscorlib.dll>
#pragma unmanaged
class TypeNatifCodeNatif{
public:
TypeNatifCodeNatif(int state){ m_State = state ; }
int GetEtat(){ return m_State ; }
private:
int m_State ;
} ;
#pragma managed
class TypeNatifCodeIL {
public:
TypeNatifCodeIL(int state){ m_State = state ; }
int GetEtat(){ return m_State ; }
private:
int m_State ;
} ;
ref class TypeGereCodeIL {
public:
TypeGereCodeIL(int state){ m_State = state ; }
int GetEtat(){ return m_State ; }
private:
int m_State ;
} ;
int main(){
TypeNatifCodeNatif* o1 = new TypeNatifCodeNatif(1) ;
int i1 = o1->GetEtat() ;
delete o1 ;
TypeNatifCodeIL* o2 = new TypeNatifCodeIL(2) ;
int i2 = o2->GetEtat() ;
delete o2 ;
TypeGereCodeIL^ o3 = gcnew TypeGereCodeIL(3) ;
int i3 = o3->GetEtat() ;
return 0 ;
}
275
Si vous compilez ce code et que vous analysez lassemblage produit avec loutil ildasm.exe
vous vous apercevrez que le compilateur a prvu deux types valeurs grs TypeNatifCodeIL
et TypeNatifCodeNatif pour permettre dutiliser les types natifs sous-jacent partir du code
gr. Ces deux types grs ne prsentent aucun membre. Leurs tats sont stocks par les types
natifs. Les types natifs sont stocks dans des sections binaires de lassemblage qui ne sont pas
visualisables partir des outils ildasm.exe et Reflector.
On remarque la prsence des quatre mthodes gres publiques statiques TypeNatifCodeIL.GetEtat(...), TypeNatifCodeIL.{ctor}(...), TypeNatifCodeNatif.GetEtat(...) et
TypeNatifCodeNatifL.{ctor}(...). Il ny a pas despace de noms TypeNatifCodeNatif ou
TypeNatifCodeIL. Le langage IL accepte les noms de mthodes contenant un point. Il est intressant de remarquer que les deux mthodes .GetEtat() prennent un paramtre reprsentant
la rfrence this. En outre, les deux mthodes relatives TypeNatifCodeIL contiennent du code
IL alors que les deux mthodes relatives TypeNatifCodeNatif ont le drapeaux pinvokeimpl
indiquant quelle appelle une mthode native au moyen de P/Invoke.
Enfin, si vous visualisez le code IL de la mthode main(), vous verrez que le compilateur
C++/CLI utilise les types de lespace de noms System.Runtime.CompilerServices pour rendre
possible la magie du code IL qui manipule des types natifs.
Un champ _handle de type void*. Ce champ ne peut tre de type GCHandle. En eet, a
structure gcroot<T> est native et ne peut avoir de champs de type gr tels que GCHandle.
Les mthodes de gcroot<T> peuvent utiliser des instances de GCHandle de puisquelles sont
compiles en IL. La passerelle entre une instance de GCHandle et le champ _handle est assure par des oprateurs statiques de conversion de la structure GCHandle. Pour simplifier
ces oprations, le fichier gcroot<T> utilise les deux macros suivantes :
__GCHANDLE_TO_VOIDPTR(x)
((GCHandle::operator System::IntPtr(x)).ToPointer())
__VOIDPTR_TO_GCHANDLE(x)
(GCHandle::operator GCHandle(System::IntPtr(x)))
276
Dans lexemple suivant, la classe TypeNatifCodeNatif garde une rfrence vers une chane de
caractre gre. Comme vous pouvez le voir, le recours gcroot est rduit au strict ncessaire
et vous ne voyez pas apparatre la structure gre GCHandle :
Exemple 8-9 :
// compile with: /clr
#include "stdafx.h"
#include <vcclr.h>
using namespace System ;
#pragma unmanaged
class TypeNatifCodeNatif {
public:
TypeNatifCodeNatif( gcroot<String^> s ) {m_s = s;}
gcroot<String^> m_s ;
} ;
#pragma managed
int main() {
TypeNatifCodeNatif* obj = new TypeNatifCodeNatif("Bonjour") ;
Console::WriteLine( obj->m_s ) ;
delete obj ;
}
Pour exploiter un objet gr partir du code natif vous devez dvelopper vous-mme un type
natif avec du code gr qui fait la passerelle entre les deux mondes. Lexemple suivant montre
comment du code natif qui dtient une rfrence (en fait un handle) sur une instance gre de
type string peut invoquer la mthode get_Length() sur cette instance :
Exemple 8-10 :
// compile with: /clr
#include "stdafx.h"
#include <vcclr.h>
using namespace System ;
#pragma managed
class TypeNatifCodeIL {
public:
static int Lentgh(gcroot<String^> s){
return s->Length;
}
};
#pragma unmanaged
class TypeNatifCodeNatif {
public:
TypeNatifCodeNatif(gcroot<String^> s) {
m_Length = TypeNatifCodeIL::Lentgh(s) ;
}
277
int m_Length ;
} ;
#pragma managed
int main() {
TypeNatifCodeNatif* obj = new TypeNatifCodeNatif("Bonjour") ;
Console::WriteLine( obj->m_Length ) ;
delete obj ;
}
Introduction
Une application excute sous un systmes Windows a la possibilit dexploiter des ressources
systmes telles que les fichiers, le registre, les pilotes, les processus, les threads, les mutex, les
pipes nomms, les sokets, les fentres etc. Une mme ressource systme peut tre exploite
simultanment par plusieurs processus (on peut citer par exemple les mutex nomms). Une
ressource systme ne peut donc pas tre rfrence par un pointeur puisquelle nappartient
pas lespace dadressage dun processus. En consquence, les processus Windows accdent aux
ressources systmes par lintermdiaire de pointeurs logiques nomms handles.
Toutes les fonctions win32 permettant la manipulation dune ressource systme acceptent un
paramtre en entr de type HANDLE. Un handle est un numro cod sur quatre octets. Chaque
processus Windows maintient en interne une table dassociation entre les handles et les ressources systmes utilises. Deux ressources systmes ne peuvent donc pas tre rfrence par
le mme handle au sein dun processus. Deux handles rfrenant une mme ressource partir
de deux processus distincts peuvent tre deux entiers dirents.
Une ressource systme est cre lors de lappel de certaines fonctions win32 telles que CreateFile(), CreateMutex() ou CreateThread(). Ces fonctions ont la particularit de retourner un
handle. Lappel une de ces fonctions ne cre pas ncessairement une ressource. Par exemple
vous pouvez rcuprer un handle vers un mutex existant en appelant la fonction CreateMutex() paramtre avec le nom du mutex. La fonction win32 CloseHandle() permet de signifier
Windows que le processus appelant na plus besoin de la ressource systme rfrence. Windows
dtruit une ressource systme lorsque plus aucun processus ne maintient de handle vers elle.
Le compteur de performance Windows Processus/Nombre de handles vous permet de connatre
le nombre de handles couramment dtenus par un processus. La colonne Handles du gestionnaire des tches vous permet aussi de visualiser ce nombre en temps rel.
La classe HandleCollector
Une instance de la classe System.Runtime.InteropServices.HandleCollector permet de fournir au CLR une estimation du nombre de handles actuellement dtenu par le processus. Vous
278
pouvez prciser un seuil initial et un seuil maximum partir duquel le ramasse-miettes lancera
une collecte. En eet, le ramasse-miettes na aucune connaissance de la quantit de mmoire
non gre maintenue par des ressources systmes et cette classe permet de pallier cette faiblesse.
Une instance de HandleCollector peut tre nomme de faon ntre concerne que par les
handles vers un certain type de ressource.
Il ny avait pas de vrification de type la compilation. Par exemple, rien ne vous empchez
de communiquer un handle sur une fentre (de type win32 HWND) une fonction win32 qui
a besoin dun handle sur un fichier (de type win32 HFILE).
Vous naviez pas de garanties quant la libration dun handle. Une exception de type
ThreadAbortException ou OutOfMemory pouvait compromettre cette opration.
Vous tiez expos une situation de comptition (race condition) quant la fermeture du
handle. Rien nempchait un thread dutiliser un handle pendant quun autre thread tait
en train de le fermer. Cela pouvait mme mener un problme de scurit connu sous
le nom de handle-recycling attack o du code malveillant exploitait une ressource en train
dtre ferme.
Le framework .NET 2.0 ore un moyen de pallier ces problmes avec les classes abstraites
System.InteropServices.CriticalHandle et System.InteropServices.SafeHandle. Lide
est de prvoir une classe non abstraite drive dune de ces classes pour grer le cycle de
vie dun type de handle. Cette classe doit tre drive de SafeHandle pour implmenter
un type de handle qui supporte un compteur de rfrences. Sinon cette classe doit driver
de CriticalHandle. Les deux classes SafeHandle et CriticalHandle drivent de la classe
System.Runtime.ConstrainedExecution.CriticalFinalizer. Et ont donc un finaliseur critique. Les finaliseurs critiques sont dcrits en page 129. De ce fait, on obtient certaine garantie
de fiabilit sur lopration de fermeture dun handle.
Vous pouvez aussi driver des classes abstraites CriticalHandleMinusOneIsInvalid, SafeHandleMinusOneIsInvalid et SafeHandleZeroOrMinusOneIsInvalid dont les noms sont loquents
quant aux services de dure de vie oerts. Ces classes sont dans lespace de noms Microsoft.
Win32.SafeHandles.
279
Les composants COM (i.e les quivalents dans la technologie COM des assemblages, des DLLs en
gnral mais aussi des excutables) prsentent optionnellement des mtadonnes, contenues
dans ce que lon appelle une bibliothque de types (type library en anglais). Une bibliothque de
types peut tre contenue directement dans le composant COM ou dans un fichier part, dextension tlb. En plus du fait quun composant COM na pas obligatoirement une bibliothque
de types, le formatage binaire des mtadonnes au sein des bibliothques de types est totalement
dirent du formatage binaire des mtadonnes dans les assemblages .NET.
COMComposant.idl
[
object,
uuid(947469B1-61EB-4010-AE29-8380C2D577E9),
dual,
helpstring("IClasseCOM Interface"),
pointer_default(unique)
]
interface IClasseCOM : IDispatch{
HRESULT CalcSomme([in]int a, [in]int b, [out,retval] int *pResult) ;
} ;
280
DotNETClient.cs
281
Assemblage inter-oprable
CLR
IInterface1
Client
.NET
IInterface2
IInterface1
RCW
IInterface2
Objet
COM
IUnknown
282
Figure 8 -2 : Ajout dune rfrence vers un composant COM partir de Visual Studio .NET
Exemple 8-12 :
using System ;
using System.Runtime.InteropServices ;
[
ComImport,
Guid("947469B1-61EB-4010-AE29-8380C2D577E9"),
InterfaceType(ComInterfaceType.InterfaceIsDual)
]
public interface IClasseCOM {
[return : MarshalAs(UnmanagedType.I4)]
int CalcSomme(
[In,MarshalAs(UnmanagedType.I4)] int a,
[In,MarshalAs(UnmanagedType.I4)] int b,
[Out,MarshalAs(UnmanagedType.I4)]out int c) ;
}
[
ComImport,
Guid("1E3B6413-7E63-42B5-874D-E0A27A42190C")
]
public class CClasseCOM {}
class Program {
static void Main() {
IClasseCOM foo = new CClasseCOM () as IClasseCOM ;
int result ;
foo.CalcSomme(2, 3, out result) ;
System.Console.WriteLine("R
esultat :{0}", result) ;
}
}
283
284
Les classes RCW convertissent les Basic String (BSTR) de COM en des instances de la classe
System.String.
285
Les classes RCW convertissent les VARIANT de COM en des instances dune classe drive de
la classe object. La classe .NET de lobjet sous-jacent dpend naturellement du type sousjacent du VARIANT et vous pouvez transtyper cet objet .NET avec loprateur as de C .
Les classes RCW convertissent les SAFEARRAY de COM en des tableaux grs du type adquat. Par exemple un argument de type SAFEARRAY(BSTR) dans une mthode COM devient un argument de type System.String[] dans la mthode de la classe RCW.
COM gre les erreurs au moyen de la valeur de retour de chaque mthode de chaque interface COM. Cette valeur de retour est toujours de type HRESULT. Un HRESULT est une valeur
code sur quatre octets qui prcise si une erreur sest produite lors de lappel dune mthode
sur un objet COM. Le cas chant, le HRESULT contient le type de lerreur et ventuellement
des informations sur la couche logicielle qui a gnr lerreur.
Si lappel une mthode dun objet COM renvoie un HRESULT derreur, une exception de type
COMException est automatiquement leve par le CLR. La proprit ErrorCode de cette exception
contient la valeur du HRESULT.
286
Vous pouvez aussi spcifier lapartment COM dun thread en utilisant lun des attributs System.
STAThread ou System.MTAThread sur la mthode qui constitue le point dentre du thread. Par
exemple :
...
[MTAThread]
public static void Main(){
}
...
287
MyAsm.exe.manifest
Visual Studio 2005 permet dexploiter simplement cette technique. Pour cela, il faut que
vous positionniez lattribut Isolated dune rfrence COM true. Cela fonctionne aussi si
la rfrence COM est un OCX. La compilation du projet fait alors en sorte de crer le fichier
dextension .manifest dans le rpertoire de sortie. Elle copie aussi les DLLs composants
COM dans ce rpertoire. Reg free COM est particulirement utile si vous dsirez avoir recours des classes COM dans un projet dploy la XCopy, par exemple avec la technologie
ClickOnce.
Prcisons que pour exploiter cet attribut il faut que les classes COM soient enregistres dans
la base des registres de la machine qui ralise la compilation. En outre, il vaut mieux tester
ce genre dapplication sur une machine vierge. En eet, une utilisation dfectueuse de reg
free COM ne serait pas dtecte sur une machine sur laquelle les composants COM utiliss
sont enregistrs.
288
Sachez que vous pouvez vous passer de reg free COM en eectuant le travail de localisation, de
chargement et dinstanciation de la classe COM vous-mme. Cette technique est utile si votre
dploiement seectue potentiellement sur des versions antrieures Windows XP. Pour chaque
classe et composant COM il faut :
CLR
IInterface
IDispatch
Objet
.NET
CCW
IUnknown
Client .NET
289
La classe .NET doit avoir un constructeur sans arguments, appel aussi constructeur par
dfaut. En eet, COM ne supporte pas la notion de constructeur avec arguments. Parmi les
constructeurs de la classe .NET, seul ce constructeur sans argument pourra tre appel par
lintermdiaire du CCW.
Seules les classes publiques dun assemblage peuvent tre encapsules dans un CCW.
La classe .NET peut avoir des membres statiques, mais ils ne seront pas utilisables par lintermdiaire dun CCW puisque COM ne supporte pas cette notion de membres statiques.
Les surcharges dune mthode de la classe .NET seront renommes, car COM ne supporte
pas cette notion. Concrtement, un blanc soulign est mis avant le nom de chaque mthode
surcharge, et un numro est mis la fin du nom de la mthode.
dotNET2COM.cs
namespace Test {
public interface ICalc {
int CalcSomme(int a, int b) ;
}
public class CCalc : ICalc {
public int CalcSomme(int a, int b) {
return a + b ;
}
}
}
Pour produire lassemblage dotNET2COM.dll partir de ce fichier C , il sut de taper la ligne
de commande suivante :
>csc.exe -t:library dotNET2COM.cs
Pour produire une bibliothque de types COM dcrivant le CCW qui encapsule la classe CCalc
et linterface ICalc, il sut de taper la ligne de commande suivante :
>tlbexp.exe dotNET2COM.dll /out:dotNET2COM.tlb
Comprenez bien que le CCW est produit lexcution par le CLR. Durant cette opration
le CLR na pas besoin dune bibliothque de types. La bibliothque de type nest utile que
pour les clients qui souhaitent eectuer un lien prcoce COM avec le CCW.
Nous pouvons visualiser la bibliothque de types dotNET2COM.tlb au moyen de loutil OLE
Viewer (oleview.exe). Cet outil est accessible par le menu Outils de Visual Studio .NET. Il est
aussi accessible en ligne de commande. OLE Viewer comporte le menu Fichier Visualiser une
bibliothque de types... . Rappelons que les bibliothques de types sont dcrites dans un format
binaire, et quil est donc ncessaire de disposer dun tel outil pour les visualiser :
290
{
interface _CCalc;
_Object;
ICalc;
[
odl,
uuid(84181003-CCB9-3219-B373-629AF4E3B246),
hidden,
dual,
291
oleautomation,
custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9, Test.CCalc)
]
interface _CCalc : IDispatch {
} ;
} ;
Plusieurs remarques peuvent tre faites :
_Object : Cette interface reprsente les mthodes de la classe System.Object dont drive toutes les classes .NET. Linterface _Object est dfinie dans la bibliothque de types
mscorlib.tlb.
_CCalc : Cette interface COM est linterface de classe (class interface en anglais) de la
classe .NET CCalc. Une interface de classe est sense prsenter tous les membres
publiques non statiques dune classe .NET (y compris les membres des types dont
la classe est drive). Cependant, dans notre exemple cette interface de classe ne
contient aucun membre. Lutilisation dinterfaces de classes tant dconseille, le
comportement par dfaut de tlbexp.exe est de ne pas insrer de membres dans
une interface de classe quil produit. Lutilisation dinterfaces de classes est dconseille car elle couple les clients non grs avec des classes gres et non avec des
interfaces. Vous pouvez nanmoins prciser tlbexp.exe que vous souhaitez que
linterface de classe soit correctement construite en utilisant dans votre code C lattribut System.Runtime.InteropServices.ClassInterface avec la valeur AutoDual. Par
exemple :
...
[ClassInterface(ClassInterfaceType.AutoDual)]
public class CCalc : ICalc{
...
}
...
Les champs publics dune classe admettant une interface de classe, sont matrialiss
dans linterface de classe sous forme de deux accesseurs. De mme, les accesseurs des
proprits des interfaces publiques, sont prsents comme des mthodes dans les interfaces COM gnres par tlbexp.exe.
Une classe ID a t produite automatiquement pour la classe COM CCalc. On aurait pu utiliser lattribut System.RunTime.InteropServices.Guid pour spcifier notre propre attribut
dans le code C :
...
[Guid("A51C81A3-4892-39EC-981A-AF77FB4CFD36")]
public class CCalc : ICalc{
...
}
...
292
Linterface COM ICalc tend linterface COM IDispatch qui permet de crer des liens tardifs explicites sur les classes COM.
Vous avez la possibilit dutiliser dans votre code .NET lattribut .NET System.RunTime.
InteropServices.COMVisible sur une interface, une classe, une mthode ou un vnement
afin dindiquer tlbexp.exe que lentit concerne ne soit pas visible dans la bibliothque de
types construite. Par exemple :
...
[ComVisible(false)]
public class CCalc : ICalc{
...
}
...
Les structures publiques sont prises en compte par tlbexp.exe. Elles pourront ainsi tre passes
comme arguments des mthodes des classes et des interfaces.
293
dotNET2COM.reg
[HKEY_CLASSES_ROOT\Test.CCalc]
@="Test.CCalc"
[HKEY_CLASSES_ROOT\Test.CCalc\CLSID]
@="{A51C81A3-4892-39EC-981A-AF77FB4CFD36}"
[HKEY_CLASSES_ROOT\CLSID\{A51C81A3-4892-39EC-981A-AF77FB4CFD36}]
@="Test.CCalc"
[HKEY_CLASSES_ROOT\CLSID\{A51C81A3-4892-39EC-981A-AF77FB4CFD36}
\InprocServer32]
@="C:\WINDOWS\System32\mscoree.dll"
"ThreadingModel"="Both"
"Class"="Test.CCalc"
"Assembly"="dotNET2COM, Version=0.0.0.0, Culture=neutral,
PublicKeyToken=null" "RuntimeVersion"="v1.0.3705"
[HKEY_CLASSES_ROOT\CLSID\{A51C81A3-4892-39EC-981A-AF77FB4CFD36}\ProgId]
@="Test.CCalc"
[HKEY_CLASSES_ROOT\CLSID\{A51C81A3-4892-39EC-981A-AF77FB4CFD36}
\Implemented Categories\{62C8FE65-4EBB-45E7-B440-6E39B2CDBF29}]
La cl InprocServer32, qui est sense contenir le chemin et le nom du fichier qui contient limplmentation de la classe COM, est gale mscoree.dll. Cette DLL, appele DLL cale, permet
le chargement du CLR dans un processus. Les quatre composantes du nom fort de lassemblage
qui contient limplmentation de la classe .NET sont spcifies dans la sous-cl Assembly. Lorsquune application aura besoin de la classe COM qui a pour ProgID Test.Calc, elle chargera
dabord le CLR (si ce nest dj fait pour ce processus) puis elle utilisera le mcanisme de localisation dassemblage, pour localiser lassemblage dotNET2COM.
La sous cl ThreadingModel vaut both, cest--dire que les instances de la classe COM CCW supportent indiremment les modes STA et MTA. Ceci est la consquence du fait que les modles
STA et MTA ne sont pas pris en compte dans .NET. Les objets .NET nont pas danit avec les
threads.
Par dfaut le ProgID de la classe COM produite est {espace de nom}.{nom de la classe}. Vous
pouvez toutefois spcifier un autre ProgID en utilisant lattribut .NET System.RunTime.
InteropServices.ProgId dans votre code .NET. Par exemple :
...
[ProgID("dotNET.CCalc")]
public class CCalc : ICalc{
...
Loutil regasm.exe prsente loption /tlb qui permet de fabriquer une bibliothque de type,
exactement comme avec loutil tlbexp.exe.
>regasm.exe dotNET2COM.dll /tlb:dotNET2COM.tlb
294
Les outils regasm.exe et tlbexp.exe peuvent tre utiliss indpendamment sur le mme assemblage. On pourrait craindre que les identifiants uniques des classes et des interfaces ne soient pas
les mmes dans les fichiers produits par regasm.exe et dans les fichiers produits par tlbexp.exe.
Heureusement il nen est rien, ce qui nous amne penser que les valeurs de ces identifiants
uniques sont calcules dune manire dterministe partir de lassemblage. Empiriquement, les
identifiants uniques ne changent pas mme si les membres des classes ou des interfaces concernes varient. En revanche, les identifiants uniques changent pour les classes ou les interfaces
dont le nom change.
Introduction COM+
295
de conversion trs prcises pour convertir les direntes classes dexception et les dirents
HRESULT. Par exemple HRESULT COR_E_DIVIDEBYZERO est converti en une exception de type
DivideByZeroException. Tout ceci fait lobjet de larticle HRESULTs and Exceptions des
MSDN.
Introduction COM+
Quest ce que COM+ ?
COM+ est le terme dsignant les services dentreprise dans le contexte des applications destines
tre excutes sous les systmes dexploitation Microsoft. COM+ est donc la technologie Microsoft
pour construire des serveurs dapplications. Un service dentreprise de COM+ est une fonctionnalit volue qui peut tre ajoute une classe COM. Ces fonctionnalits sont axes autour du
dveloppement dapplications distribues transactionnelles. On verra la liste de ces fonctionnalits dans la prochaine section mais on peut dj citer le pooling dobjets ou la passerelle avec
des milieux transactionnels non Microsoft.
COM+ 1.0 est apparu avec Windows 2000. COM+ ntait pas une nouvelle version de COM mais
une nouvelle version de la technologie MTS (Microsoft Transaction Server) qui permettait, entre
autres, de raliser des transactions distribues. Par rapport la technologie MTS, on obtenait de
bien meilleures performances avec COM+ 1.0. COM+ 1.5 est apparu avec Windows XP et ajoute
quelques services dentreprise COM+ 1.0.
296
Pooling dobjets ;
Lide du pooling dobjet est de recycler les objets pour quils puissent chacun servir plusieurs clients conscutivement. Les cots de la construction et de la destruction dun objet
sont alors diviss par le nombre de clients servis par lobjet. Le pool reprsente le conteneur
des objets qui ne sont pas en train de servir un client.
Transactions distribues ;
COM+ permet dexploiter le serveur transactionnel de Windows nomm MS DTC (Distributed
Transaction Coordinator) afin de raliser des transactions distribues.
297
Interoprabilit XA
Ce mcanisme permet dencapsuler, au sein dune transaction gre par le modle de transaction Microsoft (OLE Transaction), les accs une base de donnes qui supporte le modle
transactionnel XA (X/Open Distributed Transaction Processing (DTP).
Composants privs
Tous les composants servis dune COM+ application ne sont pas ncessairement accds par
les clients de la COM+ application. Il existe souvent des composants servis qui ne doivent
tre utiliss que par dautres composants servis, existant dans le mme processus et appartenant la mme COM+ application. Pour empcher un client dutiliser un tel composant
servi, il faut le dclarer comme un composant servi priv.
298
Scurit Role-Based
COM+ prsente son propre mcanisme de scurit, bas sur le rle que joue lutilisateur
appelant. Ce mcanisme est dirent du mcanisme de scurit .NET bas sur les rles, et
du mcanisme Windows bas sur les rles.
Services SOAP
Ce mcanisme permet de publier un composant servi comme un service web. On peut toujours continuer y accder comme un composant COM+.
299
300
301
Processus
CLR
Composant servi
1 (contexte A)
Composant servi
2 (contexte A)
Composant servi
3 (contexte B)
ContextUtil
COM : Contexte A
COM : Contexte B
servis dun mme assemblage appartiennent la mme COM+ application. Les COM+ applications reprsentent ce que lon appelle communment les serveurs dentreprises. Les clients utilisent les instances des composants servis dune COM+ application, et les composants servis utilisent les services dentreprise COM+ pour eectuer leurs tches.
Plusieurs attributs dassemblage ont t conus pour que vous puissiez configurer les paramtres
de la COM+ application qui contiendra lassemblage que vous dveloppez. Voici les principaux :
302
Le catalogue COM+
Le catalogue COM+ est une base de donnes prsente dans tous les systmes dexploitation Microsoft depuis Windows 2000. Le catalogue COM+ contient les informations de configuration des
COM+ applications qui rsident sur la machine. Le catalogue COM+ est physiquement stock
sur deux supports :
Une partie des informations du catalogue COM+ est stocke dans la base des registres, directement avec les informations des classes COM.
Lautre partie des informations du catalogue COM+ est stocke dans une srie de fichiers
bibliothques de composants (extension .clb). Ces fichiers sont stocks dans le rpertoire
<< %systemroot%\Registration >>.
Le format des fichiers .clb nest pas document. Il est fortement dconseill dessayer de modifier ou de visualiser les donnes du catalogue COM+ par lintermdiaire dun diteur tel que
regedt32.exe ou en modifiant les fichiers .clb. Nous prsenterons un peu plus loin, un outil
spcialement conu pour visualiser et modifier les donnes du catalogue COM+, de faon
pouvoir crer et paramtrer des applications COM+ sur une machine. La Figure 8 -7 expose les
direntes relations existant entre le catalogue COM+, les COM+ applications et les composants
servis, installs sur une machine.
OS Microsoft (Windows 2000 ou XP)
Systme de fichiers
Fichiers .clb
Base des
registres
COM+ Catalogue
Assemblage C
Assemblage
B
Composant
servi
Composant servi
Assemblage
A
Composant
servi
Composant servi
Composant servi
Composant servi
Composant servi
COM+ Application
Nom
ID
Paramters pour les
services dentreprise
utiliss
Liste des composants
303
Processus client
Client non gr
CLR
Client .NET
Proxy
Instance dun
composant servi
Proxy
Client .NET
304
Enregistrer les composants servis dans la base des registres, pour quils puissent tre accds
comme des objets COM. La description de cette tape fait lobjet de la section prcdente.
Construire une bibliothque de types dcrivant les composants servis contenus dans lassemblage. Cette bibliothque de types contient les informations spcifies par les attributs
COM+ contenus dans lassemblage.
Trouver la COM+ application qui doit contenir ces composants servis. Si la COM+ application nest pas trouve alors crer une nouvelle COM+ application.
Configurer la COM+ application partir des informations de la bibliothque de types, issues des attributs COM+ de lassemblage. Les paramtres non positionns par ces valeurs
prennent des valeurs par dfaut. De plus, si la COM+ application existait dj, il se peut
que les valeurs de ses paramtres changent avec linstallation de ces nouveaux composants
servis.
La faon manuelle :
Il vous sut dutiliser loutil Services Installation Utility (regsvcs.exe) en ligne de commande, avec pour argument lassemblage qui contient les composants servis. Par exemple :
>regsvcs.exe SystemeBancaire.dll
Les options de loutil regsvcs.exe sont dcrites dans les MSDN larticle .NET Services
Installation Tool (Regsvcs.exe). Si le nom de la COM+ application nest pas spcifi dans
lassemblage ou partir de loption /appname de regsvcs.exe, le nom de la COM+ application sera gal au nom de lassemblage (sans extension de fichier).
La faon programme :
Il vous sut dutiliser la classe RegistrationHelper au sein dune mthode dune classe
dun assemblage. La classe RegistrationHelper prsente la mthode InstallAssembly()
prvue cet eet. Par exemple :
Exemple 8-17 :
using System.EnterpriseServices ;
public class Program {
static void Main() {
RegistrationHelper rh = new RegistrationHelper() ;
string sCOMPlusAppName = "Serveur bancaire." ;
string sTypeLibName = "SystemeBancaire.tlb" ;
rh.InstallAssembly(
305
La faon automatique :
Quand un client a besoin quun composant servi soit instanci, le CLR vrifie si le composant servi est eectivement prsent dans une COM+ application. Si tel nest pas le cas,
le CLR installe automatiquement lassemblage dans une nouvelle COM+ application. Bien
que sduisante, cette faon de faire doit tre vite car vous pouvez dicilement tre certain
que le premier client a les droits dadministrateur qui sont ncessaires pour raliser cette
opration.
306
Si une COM+ application est en cours dutilisation, loutil services de composants vous lindique avec une petite animation. Il vous permet aussi de stopper une COM+ application utilise.
Si vous changez les paramtres dune COM+ application, il faudra stopper la COM+ application
pour que les changements soient pris en compte lors de la prochaine utilisation.
Le langage C 2.0 et la
comparaison C 2.0/C++
9
Les concepts fondamentaux
du langage
310
Enfin les possibilits de dfinir des alias de noms despace de noms, et dimbriquer des espaces
de noms, sont prsentes en C .
311
Type de ressource
Mot-cl C
espace de noms
namespace
classe
class
interface
interface
structure
struct
numration
enum
dlgu
delegate
Dans une moindre mesure on peut considrer que les commentaires constituent une sorte de
ressources dont la caractristique principale est de ne pas tre pris en compte par le compilateur.
Pour utiliser une ressource dfinie dans lespace de noms de nom B, il faut que celui-ci soit
dclar en tte du fichier source ou dans lespace de noms o lon souhaite utiliser la ressource,
avec le mot-cl using. Par exemple :
Exemple 9-1 :
using B;
namespace A{
class Program{
static void Main() {}
ClasseFoo f ; // la classe ClasseFoo peut
etre utilis
ee.
}
}
namespace B{
using A;
class ClasseFoo{Program p;} // la classe Program peut
etre utilis
ee.
}
Toutes les ressources dclares dans un mme espace de noms sont accessibles partir du code
contenu dans cet espace de noms. Y compris celles qui sont dclares dans le mme espace de
noms dans dautres fichiers sources, dautres modules ou assemblages rfrencs la compilation.
Comprenez bien que le mot-cl using nest quune commodit lusage du compilateur C 2
pour lui permettre de faire le lien entre les ressources et les noms des ressources qualifis sans
leurs espaces de noms. Lquivalent en Java et en VB.NET est le mot cl Imports/imports. Dans
aucun de ces trois langages ce mot-cl nimporte quoi que ce soit au sens o quelque chose est
dplac.
On peut utiliser le mot-cl using pour dfinir un alias despace de noms. En gnral, cela permet
dviter davoir nommer les espaces de noms imbriqus. Par exemple :
using SysWinForm = System.Windows.Forms ;
Il faut tre conscient que dans ce cas on est oblig dutiliser lalias devant les identificateurs de cet
espace. Ce nest pas le cas lorsquil ny a pas dalias et quil ny a pas de collisions didentificateurs.
Enfin, il nexiste pas en C la syntaxe Java suivante :
312
Namespace 1
Namespace 2
fichier1.cs
...
Namespace 3
Ressource D
Ressource E
fichier2.cs
Ressource C
...
Ressource A
fichier3.cs
Ressource B
Les ressources hors de tout espace de noms sont dans lespace de noms anonyme.
Comme expos par la ressource E, depuis la version 2.0 de C une classe une structure ou
une interface a la possibilit dtre dclare sur plusieurs fichiers sources dun mme projet,
mais dans le mme espace de noms.
Le prprocesseur
313
compilation est prise en charge par le compilateur csc.exe dcrit un peu plus loin. Ce compilateur peut tre appel soit directement en ligne de commande, soit par lenvironnement
de dveloppement Visual Studio. Les options de compilation sont dfinies soit dans la ligne de
commande, soit par lintermdiaire de lenvironnement de dveloppement.
Il y a principalement deux tapes la compilation :
Prprocessing des fichiers sources : cette tape gnre de nouveaux fichiers sources, modifis
selon des directives prprocessing qui peuvent tre dfinies soit dans les fichiers sources eux
mme, soit directement en ligne de commande du compilateur. Ces directives sont dcrites
un peu plus loin.
Compilation des fichiers sources rsultant du prprocessing : le produit de cette tape est soit :
un module contenu dans un fichier dextension .netmodule. Rappelons que lenvironnement Visual Studio ne sait pas grer les modules.
Le prprocesseur
C++ C En C la directive #define ne peut plus dfinir une constante remplacer. On ne
lutilise plus que pour changer simplement le code source, avec laide des directives #if #elif
#else et #endif.
Les constantes prdfinies ( __LINE__ ,__FILE__, __DATE__...) nexistent plus.
Les macros avec paramtres nexistent plus.
Lenvironnement de dveloppement Visual Studio est capable de mettre en gris les lignes qui ne
seront pas prises en compte lors de la compilation.
La trs clbre directive #include nexiste plus, puisque lorganisation des fichiers sources est
trs dirente en C .
Les oprateurs # et ## de manipulation de chanes de caractres par le prprocesseur nexistent
plus.
La directive #warning a t ajoute en C et fonctionne sur le mme principe que la directive
#error.
Les directives #line #region et #endregion apparaissent et sont spcifiques C .
C Toute compilation dun fichier source est prcde dune phase de mise en forme du
fichier. Celle-ci est eectue par un prprocesseur. Il neectue que des traitements simples de
manipulation textuelle. En aucun cas le prprocesseur nest charg de la compilation. Toutes les
directives prprocesseur sont prcdes par le caractre #.
Le prprocesseur reconnat les directives suivantes :
#define
#undef
#if
#elif
#else
#endif
#error
#warning #line
#region #endregion
#pragma warning disable
#pragma warning restore
314
Le prprocesseur
315
fonction de la dfinition dune constante symbolique. Lavantage par rapport lutilisation des
directives prprocesseur #if #elif #else et #endif, est quil nest pas ncessaire daller commenter les appels la mthode lorsque celle-ci nest plus dfinie. Linconvnient est quun certain nombre de contraintes doivent sappliquer la mthode. Par exemple la mthode doit retourner le type void. La liste exhaustive de ces contraintes se trouve larticle The Conditional
Attribute dans les MSDN. Voici un exemple dutilisation de lattribut ConditionalAttribute :
Exemple 9-4 :
//#define __TRACE__
class Program {
[System.Diagnostics.Conditional("__TRACE__")]
public static void Trace(string s) {
System.Console.WriteLine(s) ;
}
static void Main() {
Trace("Hello") ;
System.Console.WriteLine("Bye") ;
}
}
Ce code ache ceci si la constante symbolique __TRACE__ nest pas dfinie.
Bye
Ce code ache cela si la constante symbolique __TRACE__ est dfinie.
Hello
Bye
316
La directive #warning fonctionne sur le mme principe, mis part quelle narrte pas la compilation mais gnre un avertissement.
La directive #line
La directive #line permet au dveloppeur de modifier la ligne et ventuellement le fichier o
une erreur est dclare par le compilateur. Lexemple suivant ache que les erreurs de compilations trouves sont la ligne 1 dans le fichier M
ethode Main() :
Exemple 9-7 :
class Program {
public static void Main() {
#line 1 "Methode Main()"
Le compilateur csc.exe
int i == 0 ;
317
// <- erreur ici : pas le droit dutiliser ==
}
}
Attention lutilisation de cette directive car elle peut mettre en dfaut certaines directives de
lenvironnement de dveloppement. Notamment, lenvironnement nest plus capable de retrouver lerreur puisquil ne dispose plus ni du fichier, ni de la ligne valide.
Le compilateur csc.exe
Le compilateur csc.exe peut tre appel soit directement en ligne de commande, soit par lenvironnement de dveloppement Visual Studio, soit par des scripts de compilation type MSBuild
ou NAnt. Les options de compilation sont dfinies soit dans la ligne de commande, soit par
lintermdiaire de lenvironnement soit au sein des scripts.
La Figure 9 -3 illustre les entres possibles et les types de fichiers de sortie possibles, de csc.exe.
Un seul fichier est produit par compilation :
Prsentons les options de compilation les plus utilises laide de quelques exemples :
Compile fichier.cs et produit fichier.exe (notez que pour produire un excutable il faut
quau moins une mthode statique Main() existe dans au moins une classe).
>csc.exe fichier.cs
Compile fichier.cs et produit fichier.dll (une mthode statique Main() nest pas requise ici).
>csc.exe /target:library fichier.cs
318
xx
Fichier1.cs
Modules rfrencs avec loption
/addmodule :
Module.netmodule
xx
Module1.netmodule
Produit un assemblage
librairie si loption
/target:library est utilise
Librairie.dll
xx
Librairie1.dll
Excutable rfrenc avec loption
/reference : ou /r :. Au plus un
excutable rfrenc par
compilation. Si la sortie est un
xecutable, ne pas rfrencer
dexcutable
et
Executable.exe
csc.exe
ou
Produit un assemblage
excutable si loption
/target:exe est utilise
Executable.exe
Compile fichier.cs et produit prog.exe. La mthode Main() qui sert de point dentre est
celle de la classe Prog qui est dans lespace de noms Namespace001.
>csc.exe /out:prog.exe /main:Namespace001.Prog fichier.cs
Compile avec optimisation tous les fichiers dextension .cs dans le rpertoire courant, dfinit la constante symbolique MACRO1 pour chacun de ces fichiers et produit prog.exe.
>csc.exe /out:prog.exe /define:MACRO1 /optimize *.cs
Compile en mode debug tous les fichiers dextension .cs dans le rpertoire courant, ne gnre pas davertissement et produit le fichier mod.netmodule.
>csc.exe /target:module /out:mod.netmodule /warn:0 /debug *.cs
Compile en tenant compte des rfrences aux assemblages dont les modules avec le manifeste sont les fichiers lib1.dll et lib2.dll.
Le module mod.netmodule sera un module de lassemblage dont le module contenant le
manifeste est prog.exe.
Pour la compilation, les fichiers lib1.dll et lib2.dll peuvent se trouver dans le rpertoire
courant ou dans le rpertoire de chemin C:\.
>csc.exe /lib:c:\ /r:lib1.dll;lib2.dll /addmodule:mod.netmodule
/out:prog.exe fichier.cs
Le compilateur csc.exe
319
Option
Description
/target /t
/out
/main /m
Spcifie la classe qui contient la fonction Main() qui servira de point dentre. Attention, le nom de la classe doit comprendre les espaces de nom
et doit respecter la casse. Cette option ne peut tre utilise que si lon fabrique un excutable.
/define /d
/optimise /o
/warn /w
Rgle le niveau davertissement. Ce niveau varie entre 0 (pas davertissement) et 4 (tous les avertissements sont achs).
/debug
/addmodule
Rfrence les modules utiliss par le produit de la compilation (un assemblage ou un module). Rappelons qu lexcution, tous les modules dun
assemblage doivent se trouver dans le mme rpertoire que lassemblage.
/reference/r
/lib
Spcifie les rpertoires o le compilateur peut chercher les fichiers rfrencs par loption /reference ou /r. Ces fichiers sont cherchs dans
lordre suivant :
Dans le rpertoire courant ;
dans le rpertoire du Common Langage Runtime ;
dans les rpertoires spcifis par /lib ;
dans les rpertoires spcifis par la variable denvironnement LIB.
320
/resource/
linkresource
Ajoute des ressources lassemblage. Lutilisation de ces options est dtaille page 37.
/unsafe
Indique que votre code peut contenir des zones de code non vrifiables
par le CLR (voir page 501).
/doc
Cette option permet de produire un fichier XML contenant les informations prsentes dans les commentaires /// du code source. Un exemple de
lutilisation de cette option est prsent un peu plus loin dans ce chapitre.
/help /?
Ache laide.
Une trentaine doptions sont disponibles. Nous vous avons prsent ici les plus courantes. Larticle C Compiler Options Listed by Category des MSDN fournit la liste exhaustive des options.
Toutes ces options sont aussi disponibles dans les proprits dun projet dans lenvironnement
de dveloppement Visual Studio.
Les alias
Alias sur les espaces de noms et sur les types
Le mot cl using peut tre utilis pour dfinir un alias vers un espace de nom ou vers un type.
La porte dun tel alias est le fichier courant si il est dfini hors de tout espace de noms. Dans le
cas contraire, la porte dun alias est le fichier courant intersection lespace de nom dans lequel
il est dfinit.
Exemple 9-8 :
//Definition de lalias C vers le type System.Console.
using C = System.Console;
class Program{
static void Main(){
C.WriteLine("Hello 1");
}
}
Code de Asm1.dll
Code de Asm2.sll
Les alias
Exemple 9-11 :
321
Code de Program.exe qui r
ef
erence Asm1.dll et Asm2.dll
Exemple 9-12 :
namespace Custom.IO{ public class Stream{} }
namespace FooIO{ public class Stream{} }
Le qualificateur global
Dans certains projets volumineux il se peut que vous ayez un conflit entre le nom dun espace
de noms et le nom dune ressource. Le programme suivant ne compile pas :
Exemple 9-14 :
using System ;
class Program {
class System { }
322
}
C 2 introduit le qualificateur global qui, plac devant un qualificateur dalias despaces de
noms, indique au compilateur que lon souhaite utiliser un espace de noms. Lexemple prcdent doit donc tre rcrit comme suit :
Exemple 9-15 :
using System ;
class Program{
class System { }
const int Console = 691 ;
static void Main(){
global::System.Console.WriteLine("Hello 1");
global::System.Console.WriteLine("Hello 2");
}
}
Exemple 9-16 :
namespace FooIO{ public class Stream{} }
Code de Asm2.sll
Exemple 9-17 :
namespace FooIO{ public class Stream{} }
Exemple 9-18 :
323
Les commentaires
C
Le texte plac entre les balises /* suivie de */ est comment. Ces balises peuvent ventuellement se trouver sur deux lignes direntes.
Si une ligne contient la balise // alors le texte de cette ligne qui suit cette balise est comment.
Si une ligne contient la balise /// alors le texte de cette ligne qui suit cette balise est comment. De plus ce texte fera partie de la documentation automatique du code source, prsente plus loin.
Un commentaire de type /*...*/ ne peut tre imbriqu dans un autre commentaire /*...*/.
En revanche, un commentaire de type // ou /// peut tre imbriqu dans un commentaire de
type /*...*/.
En consquence, une bonne ligne de conduite est dutiliser les commentaires de type // ou
/// pour commenter le code, et dutiliser les commentaires de type /*...*/ pour dsactiver
temporairement une rgion du code.
324
La documentation automatique
C ore la possibilit de produire un document partir des commentaires dun code source
marqus avec la balise ///. Cette possibilit est trs intressante car :
Ds quun projet atteint une certaine taille, les seuls commentaires dans le code ne susent
pas donner une vue densemble du projet. On est oblig davoir de la documentation
associe au projet.
Documenter du code est une tche longue et fastidieuse. Lexprience montre quavec le
temps les dveloppeurs ngligent la maintenance de la documentation technique dun projet. Seules les entreprises qui peuvent se permettre davoir un dpartement ddi la documentation technique parviennent maintenir correctement la documentation dun projet.
Les dveloppeurs sont bien souvent les personnes les mieux places pour commenter leur
code.
Le fait que la documentation technique dun projet soit un processus parallle au dveloppement du projet, implique la dsynchronisation inluctable de la documentation. La possibilit
de gnrer automatiquement la documentation partir du code source rsout ce problme
puisque la documentation technique est intgre au dveloppement du projet. Concrtement,
ds que le dveloppeur dclare une classe ou une mthode, il cre la documentation technique
associe au mme endroit (et au mme moment).
La production automatique de la documentation technique se fait en deux tapes :
Il faut dabord extraire et hirarchiser les informations prcises par les commentaires ///
du code source. Ces informations sont alors stockes dans un document XML. Ces commentaires contiennent eux-mmes des balises XML qui se retrouveront directement dans
le document XML gnr. Notez que Visual Studio 2005 vous aide dans la production de
ces balises avec lintellisense. Prcisons que les commentaires issus de la documentation
automatique se retrouvent aussi dans les tooltips de Visual Studio concernant les entits commentes.
Appliquer une feuille de style au fichier XML afin dobtenir une prsentation adapte la
lecture. Cette feuille de style est en gnral une transformation XSLT. La prsentation finale
est en gnral une arborescence de fichiers HTML.
document
XML
Arborescence
HTML
reprsentant la
documentation
automatique
Fichier2.c
Projet 2
Fichier3.c
Extraction et
Transformation
hirarchisation des
XSLT
commentaires ///
(durant la compilation
avec loption /doc)
325
Exemple 9-19 :
namespace MonEspaceDeNoms {
/// <summary>
/// MaClass illustre la production automatique de
/// la documentation technique
/// </summary>
class MaClass {
/// <summary>
/// Le point dentree de lapplication
/// </summary>
static void Main() {}
/// <summary>
/// La fonction f(int)
/// </summary>
/// <param name="i">Un entier</param>
static void f(int i){}
}
}
...produit le fichier XML suivant :
<?xml version="1.0"?>
<doc>
<assembly>
<name>AutomaticDocTest</name>
</assembly>
<members>
<member name="T:MonEspaceDeNoms.MaClass">
<summary>
MaClass illustre la production_automatique de
la documentation technique
</summary>
</member>
<member name="M:MonEspaceDeNoms.MaClass.Main(System.String[])">
<summary>
Le point dentree de lapplication
</summary>
</member>
<member name="M:MonEspaceDeNoms.MaClass.f">
<summary>
La fonction f(int)
</summary>
<param name="i">Un entier</param>
</member>
</members>
</doc>
Notez la prsence des balises <summary> et <param> la fois dans le code source C et dans le
fichier XML. Vous avez deux manires de produire ce fichier XML :
326
Soit vous prcisez dans les proprits du projet dans Visual Studio que vous souhaitez produire un fichier XML de documentation lors de la compilation. Pour cela il faut prciser
le nom du fichier XML dans loption Fichier de documentation XML de la fentre Gnrer des
proprits du projet.
partir du fichier XML, vous pouvez appliquer une feuille de style pour produire la documentation technique au format que vous souhaitez. Visual Studio 2003 prsentait un outil de
cration de pages HTML partir de tels documents XML Avec Visual Studio 2005, nous vous recommandons dutiliser des outils spcialiss eectuer cette tche. Citons notamment lexcellent
outil NDoc qui est open source et tlchargeable gratuitement.
Les identificateurs
Les identificateurs sont des noms choisis par le dveloppeur qui nomment des ressources telles
que des espaces de noms, des classes, des mthodes de classes, des champs de classes et en fait,
tout ce qui peut tre nomm dans le code source.
Un identificateur doit obir aux rgles suivantes :
Le premier caractre est soit une lettre (A Z ou a z ou une lettre accentue UNICODE)
soit le caractre de soulignement _ soit le caractre @. Le premier caractre ne peut tre un
chire.
Les autres caractres sont dans lensemble des caractres cits ( part @), unis avec lensemble des chires (0 9).
Un identificateur ne peut contenir plus de 255 caractres.
Un identificateur ne peut tre un mot-cl C .
327
Il est aussi conseill de faire commencer le nom dun champ priv par m_ et de faire commencer
le nom dune interface par un I majuscule.
Les conditions, qui excutent (ou pas) un bloc de code qu une certaine condition, portant
gnralement sur les tats de variables et dobjets.
Les branchements ou sauts, qui permettent de rediriger directement vers une instruction
particulire lunit dexcution. Cependant ce type de structures de contrle est proscrire
car il complexifie grandement la maintenance du code. De plus il est dmontr quon peut
toujours se passer de branchements dans du code source C .
Les appels de mthodes modifient eux aussi le comportement par dfaut de lunit dexcution,
dexcuter les instructions les unes la suite des autres. Cela peut sassimiler un saut, la
dirence fondamentale qu la fin de la mthode, lunit dexcution est capable de retourner
linstruction situe juste aprs lappel de mthode. Ce comportement fait quen gnral on ne
classe pas un appel de mthode dans les structures de contrles.
Utilisation de if/else
C
328
Lensemble else et son bloc dinstructions est optionnel. Un bloc dinstructions peut tre une
seule instruction ou plusieurs instructions, auquel cas il faut placer les accolades qui dfinissent
le bloc dinstructions :
if ( expression retournant un bool
een )
i = j*5 ;
else{
// Commencement du bloc dinstructions `
a ex
ecuter
// si la condition est fausse.
i = j*2 ;
j++ ;
} // Fin du bloc dinstructions `
a ex
ecuter si la condition est fausse.
Pour les lecteurs non habitus, il faudra faire attention lors de la lecture du code, au cas o il
ny aurait quune instruction. Vous pouvez utiliser les accolades, mme dans le cas o il ny a
quune instruction. En fait, il est conseill de toujours utiliser les accolades pour amliorer la
lisibilit du code.
Une condition est considre comme une instruction. Il est donc tout fait possible dimbriquer
les conditions :
if( expression1 retournant un bool
een )
if( expression2 retournant un bool
een )
Bloc `a executer si expression1 et expression2 sont true
else // se rapportant `a lexpression2
Bloc `a executer si expression1 true et expression2 false
else // se rapportant `a lexpression1
if( expression3 retournant un bool
een ) // pas de bloc else pour ce if
Bloc dinstructions `a executer si expression1 false et expression3 true
Ceci nuit gravement la qualit du code. De plus les conditions imbriques sont, en gnral,
issues dune mauvaise conception.
i = 5 ; int j = 8 ;
// si b vaut true alors...
// si b vaut false alors...
// si b vaut true alors...
// si b vaut false alors...
// si i diff
erent de 0 alors...
// si i egal `
a 0 alors...
// si i egal 4 alors...
// si i diff
erent de 4 alors...
// si i strictement inf
erieur `
a 4 alors...
// si i inf
erieur ou
egal `
a 4 alors...
// si i strictement inf
erieur `
a 4 et j strictement
// superieur `
a 6 alors...
if( i >= 4 && i<= 6)
// si i dans lintervalle ferm
e [4,6] alors...
if( i < 4 || j > 6)
// si i strictement inf
erieur `
a 4 ou j strictement
329
` 6 alors...
// superieur a
// si i hors de lintervalle ferm
e [4,6] alors...
// si i diff
erent de 4 ou b est true alors...
La facilit dcriture ?:
Une facilit dcriture est propose :
condition ? Val retournee si condition true : Val retourn
ee si condition false ;
On parle doprateur ternaire ?:. En eet, ces le seul oprateur pour lequel trois oprandes sont
prises en compte.
Voici quelques exemples dutilisation :
bool b = true ;
int i = 5 ;
int j = 8 ;
// k1 = i si b true, sinon k = j
int k1 = b ? i : j ;
// k2 = 6 si i different de j, sinon k2 = 7
int k2 = (i!=j) ? 6 : 7 ;
// s="bonjour" si i strictement inf
erieur `
a j, sinon s="hello"
string s = i<j ? "bonjour" : "hello" ;
// k3 = i*2 si i superieur ou egal `
a j, sinon k3 = i*3
int k3 = i>=j ? i*2 : i*3 ;
Linstruction switch
C++ C Programmeur C/C++ ATTENTION ! Il y a de subtiles modifications en ce qui
concerne lutilisation du mot-cl switch :
Vous pouvez toujours switcher sur une variable de type entier, boolen, numration.
La nouveaut C est que vous pouvez switcher sur une chane de caractres.
La continuation vers le mot-cl case suivant, ne se fait pas automatiquement, donc le motcl break est obligatoire. La continuation vers le mot-cl case suivant se fait automatiquement lorsquil ny a pas dinstructions pour le cas prsent.
Vous pouvez dclarer des variables lintrieur dun bloc dinstructions, dans un bloc dinstructions case, mme si celui-ci nest pas entre accolades.
C Tout comme les mots-cls if/else, le mot-cl switch permet de modifier le cours du
programme en fonction de la valeur dune variable. Cependant, lutilisation de switch est particulirement adapte aux types valeurs discrtes (entiers, numrations, string) et sa syntaxe
permet de traiter plusieurs cas de valeurs, plus facilement que les mots-cls if/else. Voici un
exemple :
330
Exemple 9-20 :
class Program {
static void Main() {
int i = 6 ;
switch (i){
case 1:
System.Console.WriteLine("i vaut 1") ;
break ;
case 6:
System.Console.WriteLine("i vaut 6") ;
break ;
default:
System.Console.WriteLine("i ne vaut ni 1 ni 6") ;
break ;
}
}
}
Deux mots-cls apparaissent dans cet exemple, en plus des mots-cls switch et case :
break : lorsque lunit dexcution rencontre linstruction break, elle continue son cours
directement la fin du switch. Notez que si un bloc de code switch contient au moins une
instruction, il doit se terminer soit par linstruction break soit par une instruction goto soit
par une instruction return. Dans le cas contraire, le compilateur signale une erreur.
Vous avez la possibilit dexcuter le mme bloc dinstructions pour plusieurs valeurs. Par
exemple :
Exemple 9-21 :
class Program {
static void Main() {
int i = 6 ;
switch (i){
case 1:
case 3:
case 6:
System.Console.WriteLine("i vaut 1 ou 3 ou 6") ;
break ;
default:
System.Console.WriteLine("i ne vaut ni 1 ni 3 ni 6") ;
break ;
}
}
}
331
Dans ce cas, il est impratif quaucune instruction napparaisse aprs case 1: ou case 3:.
Enfin on peut utiliser aussi linstruction de branchement goto (dcrite un peu plus loin) mais
ceci est compltement proscrire. Le cas suivant montre que lon peut faire du code dicilement comprhensible en quelques lignes :
Exemple 9-22 :
class Program {
static void Main() {
int i = 6 ; int j = 7 ;
switch (i){
case 1:
System.Console.WriteLine("passage par case 1") ;
goto default ;
case 6:
System.Console.WriteLine("passage par case 6") ;
if (j > 2) goto case 1 ;
break ;
default:
System.Console.WriteLine("passage par default") ;
break ;
}
}
}
Ce programme ache :
passage par case 6
passage par case 1
passage par default
Enfin, sachez que les types possibles de la variable sur laquelle agit un switch sont :
Les types entiers sbyte byte short ushort int uint long ulong.
Les chanes de caractres, le type string. Notez que dans ce cas, si une instruction switch
a plus de 6 blocs case le compilateur C 2 utilise une table de hachage pour viter de trop
nombreuses comparaisons de chanes de caractres.
332
Boucle while
Boucle do/while
La seule dirence entre ces types de boucle est mise en vidence dans cet exemple. Les boucles
do/while excutent au moins une fois le bloc dinstructions. Si la condition est fausse en entrant
dans le while, les boucles de type while nexcutent pas le bloc dinstructions.
333
Linstruction break fait quitter la boucle. Cette instruction fait aussi quitter un bloc switch
comme on la vue prcdemment.
Dans le cas de boucles imbriques, ces deux instructions sappliquent la boucle la plus proche
deux.
Exemple 9-23 :
class Program {
static void Main() {
for (int i = 0 ; i < 10 ; i++){
System.Console.Write(i) ;
if (i == 2) continue ;
System.Console.Write("C") ;
if (i == 3) break ;
System.Console.Write("B") ;
}
}
}
Ce programme ache :
0CB1CB23C
Les instructions break et continue ont tendance compliquer la lisibilit du code. Le rsultat
de lexemple prcdent, nest vident pour personne. Il faut donc les utiliser le moins souvent
possible.
Linstruction break peut aussi tre utilise pour interrompre ce quon appelle des boucles infinies, cest--dire des boucles de type for, do/while et while, dont la condition de sortie est
toujours vraie. Voici des exemples de boucles infinies :
for(;;) {...}
for(;true;) {...}
while(true) {...}
do{...}
while(true) {...}
Boucles et optimisations
Du fait que le code contenu dans une boucle est potentiellement sujet un grand nombre
dexcutions, il peut tre ecace dessayer de loptimiser. Voici quelques conseils :
Si vous dtectez des appels des mthodes qui ncessitent beaucoup de passages darguments, il peut tre ecace de copier le corps de la mthode dans la boucle (on parle
dinlining). Remarquez quen page 112, nous expliquons que parfois le compilateur JIT du
CLR est capable deectuer une telle optimisation.
Si vous accdez une proprit dun objet dont vous savez que la valeur retourne restera
constante durant toute la boucle, il est ecace de stocker au pralable ces valeurs constantes
dans des variables locales.
Penser utiliser la classe StringBuilder plutt que la classe String pour fabriquer une
chane de caractres dans une boucle.
334
Si vous avez tester plusieurs conditions de sortie dune boucle, il est ecace de tester en
premier la condition de sortie la plus probable.
Bien quen gnral moins pratique dutilisation, les boucles for sont lgrement plus ecaces que les boucles foreach.
Lorsque vous ralisez des optimisations, essayez surtout de quantifier le gain de performance apport. En eet, dans un environnement gr par le CLR, certaines de vos optimisations peuvent
gner le CLR et son compilateur JIT pour finalement savrer contre productives.
La mthode Main()
C++ C Comme en C/C++, en C le point dentre dun assemblage excutable est une
mthode appele Main().
La mthode Main()
335
Comme en C/C++, en C , la mthode Main() peut retourner int ou void et accepte ventuellement un tableau de chanes de caractres reprsentant les arguments en ligne de commande de
lexcutable. En C , il nest plus besoin de prciser la taille du tableau.
la dirence de C/C++ le tableau de chanes de caractres ne contient pas le nom de lexcutable en premire occurrence, mais directement le premier argument.
la dirence de C/C++ le m de Main est une majuscule.
la dirence de C/C++ Main est une mthode statique dune classe et non une fonction globale.
C Chaque assemblage directement excutable (i.e dont le module principal a une extension
.exe) possde au moins une mthode statique Main() dans une de ses classes. Cette mthode
reprsente le point dentre du programme, cest--dire que juste aprs le lancement et linitialisation dune application .NET, le thread principal va commencer par excuter le code de cette
mthode. Lorsque cette mthode retourne, le processus est dtruit la condition quil ny ait pas
de threads de premier plan (thread foreground) qui soient toujours en cours dexcution. Si ces
notions de thread ou de thread foreground vous sont trangres, sachez quelles sont prsentes
au dbut du chapitre 5.
Un mme assemblage peut avoir ventuellement plusieurs mthodes Main() (chacune dans une
classe dirente). Le cas chant, il faut prciser au compilateur quelle mthode Main() constitue le point dentre du programme. Ce qui peut se faire soit avec loption /main en ligne de
commande du compilateur csc.exe, soit dans les proprits du projet sous Visual Studio, Application startup object. Cette facilit est extrmement utile pour dboguer une classe particulire.
Une mthode Main() est statique et sa signature suit les rgles suivantes :
Par exemple le programme suivant ajoute les nombres passs en arguments en ligne de commande, et ache le rsultat. Sil ny a pas dargument, le programme le signale :
Exemple 9-25 :
class Program {
static void Main(string[] args) {
if (args.Length == 0)
System.Console.WriteLine("Entrez des nombres `
a ajouter.") ;
else{
long result = 0 ;
foreach (string s in args)
result += System.Int64.Parse(s) ;
336
Les informations communiques en ligne de commande (ainsi que les valeurs des variables
denvironnement) peuvent tre aussi rcupres grce aux mthodes string[] GetCommandLineArgs() et IDictionary GetEnvironmentVariables() de la classe System.Environment.
10
Le systme de types
C++ C
C C est un langage typ, cest--dire que chaque objet a un type et un seul. Ce type est
compltement dfini au moment de la cration de lobjet, lexcution. En C chaque variable
doit tre initialise, sinon le compilateur produira une erreur lors de son utilisation.
Allocation/dsallocation
Lors de lexcution dun programme, le fait de rserver une zone mmoire pour quelle
contienne les donnes relatives un objet est nomm allocation. Lopration inverse de restitution de la zone mmoire est appele dsallocation. La taille de cette zone mmoire, spcifie
en octets, doit tre au moins gale au nombre doctets ncessaires pour stocker ltat de lobjet.
Ce nombre doctets est fonction de limplmentation de lobjet.
Un processus peut un instant donn contenir un ou plusieurs threads. En tant quespace
dadressage, le processus contient toutes les zones mmoires alloues pour tous les objets du
programme. En tant quunits dexcution, seuls les threads peuvent utiliser les objets.
La pile
Chaque thread Windows a une zone mmoire prive que lon nomme la pile (stack en anglais).
Cette zone de mmoire est prive dans le sens o elle ne devrait pas tre accessible par les autres
threads (bien que ceci soit possible sous certaines conditions spciales). Cette zone mmoire est
338
contenue dans lespace dadressage du processus du thread. Le thread se sert de sa pile principalement pour :
Chaque thread a un accs privilgi sa pile. En eet, les jeux dinstructions machine contiennent
des instructions optimises pour accder la pile. En outre le langage IL contient de nombreuses
instructions ddies la gestion de la pile. Notez quune pile est de taille variable et borne en
gnral par une grandeur de lordre du Mo. Cette limite peut tre dfinie lors de la construction
du thread.
Le tas
Un processus a gnralement un seul (mais parfois plusieurs) tas (heap en anglais). Cest une
zone mmoire contenue dans lespace dadressage du processus. Cette zone mmoire est accessible par tous les threads du processus. Donc, contrairement aux piles des threads dun processus, le tas nest pas spcifique un thread particulier. Le tas est principalement utilis pour
stocker des objets et, linstar des piles, sa taille peut varier au cours du temps. Cependant la
taille maximale du tas est beaucoup plus grosse que le Mo. En fait, il est assez singulier que le
bon droulement dune application soit limit par la taille maximale du tas.
Comparaison pile/tas
Un objet peut donc tre stock soit dans une pile dun thread soit dans un tas dun processus.
Les notions de pile et de tas doivent coexister car chacune a ses avantages :
Lavantage du tas est quil peut tre beaucoup plus gros quune pile. De plus il est accessible
par tous les threads du processus mais ceci nest pas toujours un avantage.
Lavantage de la pile est que laccs aux donnes est plus rapide quavec le tas. Ce gain est d
des instructions IL spciales. Ce gain est aussi d au fait que laccs la pile na pas tre
synchronis.
Il serait donc judicieux dutiliser le tas pour stocker les objets volumineux et dutiliser la pile
pour stocker les objets de petite taille. Nous allons voir que cest exactement ce choix qui a t
fait par les concepteurs de .NET.
En C++ le choix du mode dallocation dune variable (dun objet) est laiss au dveloppeur.
Lallocation statique est utilise lorsque lobjet est directement dclare dans le code (par
exemple int i=0;) Lallocation dynamique est utilise lorsque lobjet est alloue avec loprateur new (par exemple int * pi = new int(0);).
339
Une autre dirence importante entre C++ et C est la responsabilit de la dsallocation des
variables dynamiques. En C++ cette responsabilit incombe au dveloppeur alors quen C elle
incombe une couche logicielle fournie par lenvironnement dexcution .NET. Cette couche
est nomme ramasse-miettes et elle fait lobjet de la section 116. Dans tous les cas, cette responsabilit est lourde car si les variables alloues dynamiquement, devenues inutiles, ne sont pas
rgulirement dsalloues, la taille du tas crot en permanence et finira srement par causer
des problmes. Ce type de problme est connu sous le nom de fuite de mmoire (memory leak en
anglais).
La dsallocation des variables alloues statiquement est dans tous les cas sous la responsabilit
du thread qui possde la pile concerne. Retenez surtout quen C la responsabilit du dveloppeur est allge par rapport au C++ :
340
Exemple 10-1 :
// TypeVal est un type valeur, car cest une structure.
struct TypeVal {
public int m_i ;
public TypeVal( int i ) { m_i = i ; }
}
// TypeRef est un type reference, car cest une classe.
class TypeRef {
public int m_i ;
public TypeRef( int i ) { m_i = i ; }
}
class Program {
static void Main() {
TypeVal v1 = new TypeVal(6);
TypeVal v2 = v1; // Une nouvelle instance du type TypeVal est
// creee et le champ v2.i est aussi
egal `
a 6.
// Neanmoins v1 et v2 sont deux instances diff
erentes
// de type TypeVal.
v2.m_i = 9;
// Ici v1.i vaut 6, il y a bien deux instances du type TypeVal.
System.Diagnostics.Debug.Assert( v1.m_i == 6 && v2.m_i == 9 ) ;
TypeRef r1 = new TypeRef(6);
TypeRef r2 = r1; // Il ny a pas de nouvelle instance de la
// classe TypeRef. r2 et r1 sont deux r
ef
erences de la
// meme instance de la classe TypeRef.
r2.m_i = 9;
// Ici r1.i vaut 9, il ny a quune seule instance de la
// classe TypeRef.
System.Diagnostics.Debug.Assert( r1.m_i == 9 && r2.m_i == 9 );
}
}
On saperoit dans lexemple prcdent que loprateur new peut tre ventuellement utilis
pour les allocations dobjets de type valeur mais ne modifie en rien le caractre statique de lallocation. Dans ce cas, cet oprateur sert cependant communiquer des paramtres au constructeur.
Contrairement au C++, lors dune allocation statique (i.e dun type valeur) en C on ne
peut fournir des arguments au constructeur sans utiliser loprateur new. En C++ ceci tait
accept par le compilateur :
MyType v1(6) ;
En C il faut crire :
MyType v1 = new MyType(6) ;
Dans le cas dune allocation dynamique, donc de lallocation dun objet de type rfrence, lutilisation de loprateur new est obligatoire (y compris si le constructeur ne prend pas de paramtres). Nous allons dtailler par la suite quels sont les types valeur et quels sont les types
341
rfrence, mais nous pouvons dj prciser que les types valeur sont les types primitifs de C
(dclars avec les mots-cls int,double...) les structures (dclares avec le mot-cl struct) et
les numrations (dclares avec le mot-cl enum) alors que les types rfrence sont les classes
(dclares avec le mot-cl class) et les dlgations (qui sont des classes particulires dclares
avec le mot-cl delegate).
Les instances des types valeur ne sont pas toujours stockes sur la pile. En eet, lorsquun champ
dune instance de classe est de type valeur, il est stock au mme endroit que linstance de la
classe, cest--dire sur le tas. En revanche, les objets de types rfrence sont toujours stocks sur
le tas. Lorsquun champ dune instance de structure est de type rfrence, seule la rfrence est
stocke au mme endroit que linstance de la structure (sur la pile ou sur le tas selon les cas).
342
Une classe est dclare avec le mot-cl class. Avant tout, comprenez bien que ref1 ref2 et ref3
sont trois rfrences vers des objets, instances de la classe Personne. Au dpart ref1 est initialise
avec le mot-cl null. Cela signifie quaucun objet nest rfrenc par ref1. ce stade, aucun
membre de Personne ne peut tre utilis sur ref1.
Deux objets Personne sont alors allous (donc sont allous dynamiquement sur le tas du processus puisquils sont de type rfrence). Appelons-les Raymond et Josiane. Contrairement ce
que lon a vu pour les types valeur, dans le cas de type rfrence, il est obligatoire dutiliser loprateur new pour crer Raymond et Josiane. ce stade nous disposons des deux objets Raymond et
Josiane qui instancient la classe Personne. Nous disposons aussi de trois rfrences, ref1 qui est
nulle, ref2 qui rfrence Raymond et ref3 qui rfrence Josiane. ref1 rfrence alors Raymond.
Raymond est vieilli de 10 ans. ref1 rfrence alors Josiane. Josiane est vieillie de 10 ans.
Au final, les objets Raymond et Josiane ont tous les deux t modifis sans passer par les rfrences ref2 et ref3. De plus les objets Raymond et Josiane ne sont jamais manipuls directement. La syntaxe de C ne permet de manipuler des instances de types rfrence que par lintermdiaire de rfrences.
Laccolade de fin de la mthode Main() implique que les objets Raymond et Josiane ne sont
plus rfrencs (en eet les rfrences ref1, ref2 et ref3 nexistent plus). Raymond et Josiane ne
seront plus jamais utiliss puisquils ne sont plus rfrencs. Ainsi ces deux objets seront marqus comme non actifs par le prochain dclenchement du ramasse-miettes. Ils seront dsallous
ultrieurement.
343
Types rfrences
System.Object
Interfaces
Classes
System.ValueType
System.String
System.SByte
System.Byte
System.Int16
System.UInt16
System.Int32
System.UInt32
System.Int64
System.UInt64
System.Char
System.Boolean
System.Double
System.Single
System.MultiCastDelegate
System.Enum
System.Decimal
Dlgations
numrations
Structures
System.Array
Tableaux
System.Delegate
Pointeurs
Lgende
A drive de B
Les types primitifs (appels aussi types lmentaires) : Ces types reprsentent les entiers, les
nombres virgules, les caractres et les boolens. Ce sont des types valeur et en gnral
les langages dfinissent des alias pour faciliter leur utilisation. Ainsi le type System.Int16
correspond lalias short en C et Short en VB.NET.
Les numrations : Ces types sont de type valeur et sont utiliss pour typer des ensembles de
valeurs.
Les structures : Ces types sont de type valeur. Les structures et les classes ont des similitudes
et des dirences.
Les classes : Ces types sont de type rfrence. Notez que le type reprsentant les chanes de
caractres et le type reprsentant les tableaux sont respectivement les classes System.String
344
Les dlgations : Ces types sont des classes particulires dont les instances sont utilises pour
rfrencer des mthodes.
Les pointeurs : Ces types sont trs spciaux et utilisables seulement sous certaines conditions.
Nous dtaillons ce sujet dans la section en page 503.
La classe System.Object
C++ C En C toutes les classes et toutes les structures drivent de la classe Object. Les
mthodes de la classe Object, utilisables par toutes les classes et structures, ajoutent des fonctionnalits de hachage dobjet et de RTTI (Run Time Type Information). La fonctionnalit la plus
utilise est assurment la possibilit de redfinir la mthode Object.ToString() qui est cense
retourner une chane de caractres dcrivant lobjet, un peu comme loprateur C++ dans les
flots de donnes.
C Le mot cl object du langage C est un alias vers System.Object. La vue densemble du
CTS montre que mis part les interfaces et les types pointeurs, tous les types drivent automatiquement et implicitement, directement ou indirectement de la classe Object. En ce qui
concerne les interfaces on peut toujours convertir un objet rfrenc par une interface en une
rfrence de type Object car une interface est toujours implmente par un type rfrence ou
valeur. Ainsi, la classe Object joue un rle prpondrant dans larchitecture de la plateforme
.NET. De nombreuses mthodes standard acceptent leurs arguments sous forme de rfrences
types par la classe Object.
La classe Object prsente plusieurs mthodes. Chacune de ces mthodes peut donc sappliquer
tous les objets. Nanmoins il est logique de redfinir certaines des mthodes virtuelles pour
pouvoir les utiliser. Ces mthodes virtuelles peuvent tre redfinies dans le cadre dune classe,
mais aussi dans le cadre dune structure. Dans le cas dune numration, seule la mthode virtuelle ToString() est automatiquement redfinie par le compilateur. Les mthodes de la classe
Object sont :
345
class Program {
static void Main() {
Personne raymond = new Personne("Raymond", 50) ;
// WriteLine() appelle automatiquement Raymond.ToString()
System.Console.WriteLine(raymond) ;
}
}
Ce programme ache :
Nom:Raymond Age:50
Console.WriteLine() appelle automatiquement la mthode virtuelle ToString() des objets dont elle doit acher ltat sur la console. Si cette mthode nest pas redfinie, cest limplmentation par dfaut de la mthode ToString() de la classe Object qui est invoque.
346
Nous allons maintenant nous intresser aux possibilits oertes par le framework .NET pour
personnaliser la comparaison des instances de vos types.
347
348
349
Exemple 10-7 :
...
class Article : System.ICloneable {
...
public object Clone() {
// Copie superficielle = Copie en profondeur.
return this.MemberwiseClone() ;
}
}
class Commande : System.ICloneable {
...
public object Clone() {
// Copie en profondeur.
Commande clone = new Commande() ;
clone.Quantite = this.Quantite ;
clone.Article = this.Article.Clone() as Article;
return clone ;
}
}
...
Lachage de ce programme est maintenant ceci :
Commande : 2 x Chaussure
Commande : 2 x Chaussure
Remarquez que dans lexemple prcdent nous prcisons quen ce qui concerne la classe Article
la copie superficielle est quivalente la copie en profondeur. On pourrait tre tent darmer
que pour une classe donne, la copie superficielle est quivalente la copie en profondeur si
et seulement si tous ses membres sont de type valeur. Or la classe Article admet un champ de
type string qui est un type rfrence. Nous expliquons un peu plus loin dans ce chapitre (en
page 370) que la classe String prsente certaines proprits dont limmuabilit de ses instances,
qui font que souvent, les instances de cette classe peuvent tre considres comme des instances
dun type valeur.
Linterface ICloneable est souvent critique principalement parce quelle ne permet pas ses
implmentations de communiquer clairement leurs clients sil sagit dune copie en profondeur ou dune copie superficielle, voire dune copie en profondeur incomplte. Les concepteurs
du framework ont failli rendre cette interface obsolte lors du passage .NET 2.0. La raison principale qui a sauve cette interface de lobsolescence est quelle est implmente par de nombreuses classes standard ou propritaires. Aussi, il reste dconseill de limplmenter moins
de fournir une documentation rigoureuse. Une alternative est par exemple un constructeur de
copie qui prend en paramtre un boolen prcisant quelle genre de copie lon souhaite :
Exemple 10-8 :
...
class Commande {
public int Quantite ;
public Article Article ;
public override string ToString() {
350
Un autre argument qui plaide en faveur de linterface ICloneable est quelle permet dimplmenter naturellement le design pattern prototype qui permet de crer de nouveaux objets en
clonant un objet prototype rfrenc par une rfrence de type ICloneable.
Boxing et UnBoxing
C++ C Rien de semblable nexiste en C/C++. Tout ceci dcoule directement du fait que
tous les types drivent de la classe Object. Il nexiste pas de telle classe en C/C++.
C Les instances de type valeur, locales une mthode, sont directement stockes dans la
pile du thread. Le thread na donc pas besoin de pointeurs ni de rfrence vers les instances de
types valeur.
Beaucoup de mthodes ont besoin darguments sous forme dune rfrence de type Object.
Comme tous les types, les types valeur drivent de la classe Object. Cependant les instances de
types valeur nayant pas de rfrences, il a fallu trouver une solution pour pouvoir obtenir une
rfrence vers une instance dun type valeur lorsque lon en a besoin. Cette solution fait lobjet
de la prsente section : cest le boxing (appel compartimentation en franais. Ce terme est peu
usit, aussi nous conserverons le terme boxing dans cet ouvrage).
Opration de Boxing
Voici un exemple concret pour exposer la problmatique. La mthode f() accepte une rfrence
de type object. A priori, on ne peut donc pas lappeler avec un argument qui nadmet pas de
rfrence, par exemple un entier de type int :
Boxing et UnBoxing
351
Exemple 10-9 :
class Program {
static void f( object o ) { }
public static void Main() {
int i = 9 ;
f( i ) ;
}
}
Cependant le petit programme prcdent compile et fonctionne. La magie du boxing a permis
dobtenir une rfrence vers une instance qui nen avait pas ! Lopration de boxing seectue en
interne en trois tapes :
Une nouvelle instance du type valeur est cre et alloue sur le tas.
Cette instance est initialise avec ltat de linstance alloue sur le tas. Dans le cas de notre
entier, une copie de quatre octets est eectue. On peut dire que notre instance initiale a
t clone.
Une rfrence vers la nouvelle instance est utilise la place de linstance alloue sur le tas.
On profite de cet exemple pour souligner que le mot cl C int est un alias vers le type
System.Int32.
352
Exemple 10-10 :
class Program {
static void f(object o1, object o2) {
if (o1 == o2)
System.Console.WriteLine("M
emes r
ef
erences") ;
else
System.Console.WriteLine("R
ef
erences diff
erentes") ;
}
public static void Main() {
int i = 9 ;
f(i, i) ;
}
}
Exemple 10-11 :
class Program {
static void f(object o1, object o2) {
if (o1 == o2)
System.Console.WriteLine("M
emes r
ef
erences") ;
else
System.Console.WriteLine("R
ef
erences diff
erentes") ;
}
public static void Main() {
int i = 9 ;
object o = i;
f(o, o) ;
}
}
Opration de UnBoxing
Lopration inverse du boxing existe et sappelle le UnBoxing (le terme dcompartimentation existe
en franais mais nous continuerons utiliser le terme unboxing). Voici un exemple o le unboxing est mis en uvre :
Exemple 10-12 :
class Program {
public static void Main() {
int i = 9 ;
object o = i;
// i est box
e.
int j = (int)o; // o est unbox
e.
}
}
Le code IL de cette mthode Main() est le suivant :
.locals init ([0] int32 i,
[1] object o,
IL_0000:
IL_0002:
IL_0003:
IL_0004:
IL_0009:
IL_000a:
IL_000b:
IL_0010:
IL_0011:
IL_0012:
353
[2] int32 j)
ldc.i4.s
9
stloc.0
ldloc.0
box
[mscorlib]System.Int32
stloc.1
ldloc.1
unbox
[mscorlib]System.Int32
ldind.i4
stloc.2
ret
On voit que linstruction unbox du langage IL est prvue spcialement pour lopration de unboxing. Sans entrer dans les dtails internes, sachez que linstruction unbox place un pointeur
sur la pile vers lobjet box qui se trouve sur le tas. Cest linstruction IL ldind qui charge sur la
pile, la valeur de lobjet rfrence.
Linstance ne peut tre unboxe vers le type spcifi si elle nest pas exactement de ce type. Lexception InvalidCastException peut donc tre lance par une opration de unboxing. De plus
si la rfrence unboxer est nulle lexception NullReferenceException est lance.
En C , les oprations de boxing et dunboxing sont implicites. Cest--dire que cest le compilateur qui va gnrer les appels aux instructions IL box et unbox lorsque cest ncessaire. Il nen va
pas ncessairement de mme pour tous les langages .NET.
Les types prsents ici sont assez similaires aux types C++.
Le mot-cl unsigned nexiste pas en C . On utilise les types byte, ushort, uint et ulong pour
typer les entiers non signs.
La taille dun long/ulong est de huit octets en C .
En C , le type decimal (sur 16 octets), reprsente les rels dune manire exacte, dans la limite
de 28 chires significatifs.
En C , le type bool naccepte que les valeurs true et false. Aucune conversion avec des types
entiers nest permise.
En C le type char est maintenant cod sur deux octets et respecte la norme UNICODE. Rappelons quen C++ ce type est cod sur un octet et respecte la norme ASCII.
C Le langage C possde plusieurs types primitifs, qui sont tous de type valeur. Chaque type
primitif de C correspond exactement un type du CTS. Les types primitifs sont dfinis par
les mots-cls : bool, byte, sbyte char, short, ushort, int, uint, long, ulong, float,
double, decimal. Les correspondances entre ces mots-cls et les types du Framework .NET sont
dfinies ci-aprs.
Chaque type primitif permet lcriture de constantes. En eet le dveloppeur a souvent besoin
dinitialiser ses variables de type primitif avec des valeurs de ce type.
354
Taille
bits
en
Intervalle de valeur
byte
System.Byte
[ 0 ; 255 ]
sbyte
System.SByte
[ -128 ; 127 ]
short
System.Int16
16
[ -32768 ; 32767 ]
ushort
System.UInt16
16
[ 0 ; 65635 ]
int
System.Int32
32
[-2,1x109 ; 2.1x109 ]
uint
System.UInt32
32
[ 0 ; 4.2x109 ]
long
System.Int64
64
[-9,2x1018 ; 9,2x1018]
ulong
System.UInt64
64
[ 0 ; 1,8x1019]
Les types non signs ushort, uint et ulong ainsi que le type sign sbyte ne sont pas CLS
compliant (cest--dire quils ne sont pas conformes au CLS dfini page 129). Concrtement
les autres langages utilisant .NET ne sont pas tenus de les implmenter. Il ne faut donc pas
utiliser ces types dans les arguments de mthodes susceptibles dtre appeles par un autre
langage.
// i = 1 milliard
// j nest pas
egal `
a 10 milliards
Quelle est la valeur de j ? Elle nest pas de 10 milliards. En eet le calcul stant eectu avec des
int, (limit deux milliards et quelque en valeur absolue) le rsultat est de type int. Ensuite
le rsultat est finalement copi dans une variable de type long. Le problme principal est que
rien nest signal au dveloppeur par le compilateur (sauf sil utilise le mot-cl checked). Pour
rsoudre ces problmes, on doit signaler au compilateur que lon veut utiliser des valeurs sur
huit octets en ajoutant L la constante entire :
355
Types du CTS
correspondants
Taille
en
bits
Intervalle de valeur
Plus petite
valeur possible
Prcision
float
System.Single
32
[-3,4x1038 ;
3,4x1038 ]
1,4x10-45
7 dcimales
double
System.Double
64
[-1,8x10308 ;
1,8x10308]
4,9x10324
15 dcimales
decimal
System.
Decimal
128
[ -8x1028 ; 8x1028
]
1x10-28
28 dcimales
Description
Les types single et double prsentent aussi les champs statiques constants suivants :
356
Champs
Description
Epsilon
Plus petite valeur positive non nulle reprsentable avec le type primitif concern.
MaxValue
MinValue
Le type decimal
Avec ses 28 dcimales, le type dcimal est trs utile dans les applications financires ou chaque
dcimale compte. Il vaut mieux lviter pour des applications de calcul intensif qui ne ncessitent pas une telle prcision car il est coteux de manipuler des instances de decimal.
Dfinition de constantes
Par dfaut les constantes relles sont de type double. Voici quelques exemples :
double
double
double
double
double
d1
d2
d3
d4
d4
=
=
=
=
=
10 ;
10.0 ;
10E3 ;
10.1E3;
1.1E-1;
//
//
//
//
//
OK,
OK,
OK,
OK,
OK,
d1 vaut dix.
d2 vaut dix.
notation scientifique d3 vaut dix milles.
notation scientifique d4 vaut dix milles cent.
notation scientifique d5 vaut z
ero virgule onze.
Le type boolen
Le mot-cl C pour ce type est bool. Une variable de ce type ne peut prendre que deux valeurs :
true ou false. Le fait que la valeur true soit reprsente par un 1 ou un 0 par le CLR lexcution ne concerne en aucun cas le dveloppeur. Si vous avez un tableau de boolens, il est
prfrable dutiliser un des types prvus cet eet dcrit en page 573, qui optimise le stockage
des valeurs en mmoire en stockant 8 boolens dans un octet.
357
Le mot-cl bool est un alias pour le type du CTS System.Boolean. Cette structure prsente les
deux mthodes public static bool Parse(string s) et public string ToString()
dcrite un peu plus haut. Notons que Sytem.Boolean prsente aussi les deux champs statiques
constants de type string, FalseString (gal "False") et TrueString (gal "True"). Ces deux
chanes de caractres sont les images (respectivement les antcdents) des valeurs false et true
par la mthode ToString() (respectivement Parse()).
c1
c2
c3
c4
=
=
=
=
A ;
//
\x41 ; //
(char)65 ; //
\u0041 ; //
Lettre A
Le 65`eme
Le 65`eme
Le 65`eme
en UNICODE.
caract`
ere UNICODE A(41 est en Hexa).
caract`
ere UNICODE A.
caract`
ere UNICODE A(41 est en Hexa).
Le mot-cl char reprsente le type du CTS System.Char. Cette structure prsente notamment
des mthodes statiques pour dterminer si un caractre est une lettre majuscule, une lettre minuscule, un chire etc. En voici quelques-unes :
Mthodes statiques de la classe
System.Char
Description
bool IsLower(char ch )
bool IsUpper(char ch )
bool IsDigit(char ch )
bool IsPunctuation(char ch )
bool IsWhiteSpace(char ch )
char ToUpper(char ch )
char ToLower(char ch )
358
Oprateur
Opration
Laddition.
La soustraction.
La multiplication.
La division.
359
Ces oprations sappliquent tous les types primitifs, entiers ou rels. Chacune de ces oprations
accepte deux oprandes. Une facilit dcriture est propose avec les cinq oprateurs suivants :
Facilit dcriture
Equivalence
i += j;
i = i+j;
i -= j;
i = i-j;
i *= j;
i = i*j;
i /= j;
i = i/j;
i %= j;
i = i%j;
Sur les variables de type double et float, la variable prend la valeur particulire NaN (Not
a Number).
Sur des variables de types entiers les bits significatifs qui dpassent ne sont pas pris en
compte. On obtient donc une valeur errone. Pour pallier ce comportement par dfaut
pour le moins dangereux, vous pouvez utiliser le mot-cl checked :
Exemple 10-14 :
class Program {
static void Main() {
byte i = 255 ;
checked {
i += 1 ; // Une exception de type
// System.OverflowException est lanc
ee.
}
}
}
Le mot-cl checked peut aussi sappliquer directement une expression.
360
...
byte i = 255 ;
i = checked( (byte)(i+1) ) ; // Une exception de type
// System.OverflowException est lanc
ee.
Le mot-cl checked ne peut sappliquer une classe ou une mthode. Dans un bloc de
code vrifi par le mot-cl checked, vous pouvez utiliser le mot-cl unchecked pour ponctuellement revenir au comportement par dfaut.
Lorsquil y a mme priorit loprateur le plus gauche dans lexpression est prioritaire.
Mais cela na pas dimportance puisque les oprations de base avec la mme priorit sont
commutatives.
Par exemple :
Exemple 10-16 :
...
int
int
int
int
int
int
int
a = 3 ;
b = 10 ;
c = 4 ;
r1 = b+c %a ;
r2 = (b+c)%a ;
r3 = a*b+c ;
r4 = a*b%c ;
//
//
//
//
r1
r2
r3
r4
=
=
=
=
11
2
34
2 (30 modulo 4)
...
361
Toute la dicult vient du fait que ces deux oprateurs peuvent se placer soit avant soit aprs
la variable, ce qui change lordre dvaluation des expressions. Par exemple :
Exemple 10-17 :
...
int
int
int
int
i
j
k
l
=
=
=
=
3 ;
i++ ; // j vaut 3 et i vaut 4
3 ;
++k ; // l vaut 4 et i vaut 4
...
Lorsque lon utilise ces oprateurs avec des types entiers, il se peut que lon provoque un dpassement de capacit. Comme pour tout dpassement de capacit sur des variables de type entier
une exception nest pas lance...
Exemple 10-18 :
...
byte i = 255 ;
i++ ; // i vaut 0
...
... moins que lon utilise le mot-cl checked :
Exemple 10-19 :
...
...
byte i = 255 ;
checked{ i++ ; } // Une exception de type
// System.OverflowException est lanc
ee.
Type
retourn
int
int
int
uint
long
362
int
long
ulong
int
ERREUR de
compilation
long
long
long
ulong
ulong
ulong
ulong
long
ERREUR de
compilation
float
float
double
double
decimal
decimal
float double
decimal
ERREUR de
compilation
double
float
double
float
float
float
double
double
double
decimal
decimal
decimal
De plus les transtypages (cast en anglais) sont autoriss de nimporte quel type entier ou rel vers
nimporte quel type entier ou rel. Bien entendu ceci reste dangereux puisque les intervalles de
valeurs de tous ces types sont tous dirents. Le dveloppeur peut toujours utiliser le mot-cl
checked. Ainsi si une valeur doit tre convertie dans un type o elle nest pas reprsente, une
exception de type System.OverflowException sera lance.
Exemple 10-20 :
...
int i = -5 ;
checked {
byte b = (byte)i ; // Exception lanc
ee. Le type byte ne
}
// repr
esente pas de nombres n
egatifs.
...
363
Notez cependant, quil est possible darrondir un type rel en un type entier. Par exemple, on
peut convertir le double 3.1415 en un byte de 3. Cest la valeur entire se trouvant avant la
virgule lors de lcriture du rel qui est alors choisie.
Lorsque plusieurs oprateurs sont utiliss dans une expression, ils sont excuts selon leur ordre
de priorit (dcrit un peu plus haut). Si les types primitifs des variables sont dirents, les rsultats intermdiaires suivent les rgles nonces.
Aussi nomme
Oprateur
AND
Et logique
&
OR
Ou logique
XOR
Ou exclusif logique
NOT
Seul loprateur agit sur un seul oprande, contrairement aux autres qui agissent sur deux
oprandes. Rappelons ces oprations :
Bit A
Bit B
A AND B
A OR B
A XOR B
Des oprations de dcalages peuvent tre eectues sur des variables de type int, long, uint et
ulong grce aux oprateurs :
Rappelons quun dcalage gauche dune position en notation dcimale provoque une multiplication par 10. De mme, un dcalage gauche dune position en notation binaire provoque
364
une multiplication par deux. Un dcalage droite dune position en notation binaire provoque
une division entire par deux.
Exemple 10-21 :
...
uint a = 11 ;
uint b = a << 2 ; // a est inchang
e, b vaut 44
uint c = a >> 2 ; // a est inchang
e, c vaut 2
...
Prcisons que :
Si le dcalage se fait sur une variable dun type sign (i.e int ou long) le dcalage est arithmtique, cest--dire que le bit de signe est inchang et nest pas dcal.
Si le dcalage se fait sur une variable dun type non sign (i.e uint ou ulong) le dcalage est
binaire, et le rsultat reste positif.
Les structures
C++ C En C++, les structures et les classes sont des notions similaires. En C la dirence
de base est que les structures dfinissent des types valeur et les classes dfinissent des types rfrence.
En C toutes les structures drivent de la classe Object.
Contrairement au C++, les champs dune structure sont privs par dfaut. De plus tous les
champs que lon veut dclarer publics doivent tre prcds du mot-cl public.
Contrairement au C++, les structures ne peuvent driver daucune classe ou structure, et ne
peuvent servir de base pour aucune classe ou structure drive.
En C , une structure peut avoir un ou plusieurs constructeurs, mais le dveloppeur na pas le
droit de dfinir un constructeur par dfaut (i.e sans argument). De plus la syntaxe dinitialisation des champs dans le constructeur ( : nom_dechamp(valeur)...) nexiste plus. Enfin le constructeur par dfaut initialise 0 les champs.
Une structure peut avoir des mthodes publiques et prives, mais les mthodes doivent tre
compltement dfinies lintrieur de la dclaration de la structure (idem pour les classes). Il
ny a donc plus doprateur de rsolution de porte :: .
En C , la possibilit de mettre des champs de bits dans une structure nexiste plus.
En C , il nest pas obligatoire de mettre un ; la fin de la dclaration dune structure (idem
pour les classes).
Enfin, mais ceci est une dirence gnrale, en C , au sein dun mme espace de nom, il nest
pas ncessaire davoir dj dfini une structure pour lutiliser.
C En C , une structure est dclare avec le mot-cl struct. La notion de structure est assez
proche de celle dune classe. Les points communs sont :
Une structure peut avoir des champs, des proprits, des mthodes, des dlgus, des vnements et des types. Si lun de ces membres nest pas dclar avec le mot-cl public, il nest
pas accessible hors de la classe.
Les structures
...
struct Employee{
int salaire ;
public string nom ;
}
...
365
Les membres dune structure sont accessibles hors de la dfinition dune structure avec
loprateur point . .
...
Employee raymond ;
raymond.nom = "Raymond" ;
raymond.salaire = 3000 ;
...
lintrieur dune autre structure. Elle peut cependant tre instancie hors de la structure encapsulante, si son niveau de visibilit le permet.
lintrieur dune classe. Elle peut cependant tre instancie hors de la classe encapsulante, si son niveau de visibilit le permet.
lextrieur de toute classe et de toute structure. On peut donc instancier cette structure partout dans lespace de noms courant, et dans les zones du code utilisant cet espace de nom.
Une structure peut avoir un ou plusieurs constructeurs mais le dveloppeur na pas le droit
de dfinir un constructeur par dfaut (i.e sans arguments). De plus le compilateur sattend
ce que tous les champs de la structure soient initialiss dans tous les constructeurs.
...
struct Employee {
int salaire ;
public string nom ;
Employee (int _salaire,string _nom){
salaire = _salaire ;
nom = _nom ;
}
}
...
Le constructeur par dfaut initialise 0 les champs de type valeur et nul les champs de
type rfrence.
Une structure peut avoir des mthodes autres que les constructeurs :
...
struct Employee {
int salaire ;
366
Une structure est un type valeur alors quune classe est un type rfrence, avec toutes les
dirences que cela induit.
Les structures ne peuvent driver daucune classe ou structure, et ne peuvent servir de base
pour aucune classe ou structure drive, bien que toutes les structures drivent implicitement de la classe Object.
Contrairement aux champs dune classe, les champs dune structure ne peuvent tre explicitement initialiss dans la dclaration mme du champ.
Les instances des structures tant souvent stockes sur la pile, il vaut mieux quelles ne soient
pas trop volumineuses. Dans le cas de trs grosses structures, il vaut mieux opter pour des
classes.
Les numrations
C++ C Les numrations du C restent assez proches de celles du C++. Cependant
quelques dirences sont remarquer.
La classe System.Enum prsente des fonctions daide la manipulation des numrations.
Les numrations en C sont idales pour remplacer le mcanisme de drapeaux binaires en C++.
Rappelons quen C++ le mcanisme de drapeaux binaires utilise les macros. Une telle approche
empche le bnfice de la vrification du type par le compilateur.
C Un type numration dfinit un ensemble de valeurs. En C , un tel type est dfini avec
le mot-cl enum. Les variables dun type numration prennent leurs valeurs dans cet ensemble.
Par exemple :
Exemple 10-22 :
class Program {
enum Marque { Renault, Peugeot, Lancia }
static void Main() {
Marque marque = Marque.Renault ;
switch (marque){
case Marque.Renault: System.Console.WriteLine("Renault") ; break ;
case Marque.Peugeot: System.Console.WriteLine("Peugeot") ; break ;
case Marque.Lancia: System.Console.WriteLine("Lancia") ; break ;
}
}
}
Une numration peut tre dfinie :
Les numrations
367
lintrieur dune structure ou dune classe. Elle peut cependant tre instancie hors de la
structure ou de la classe encapsulante, si son niveau de visibilit le permet.
lextrieur de toute classe et de toute structure. On peut donc instancier cette numration partout dans lespace de noms courant, et dans les zones du code utilisant cet espace
de noms.
Associer nos propres valeurs entires dans lensemble des valeurs de lnumration :
...
enum Marque{ Renault = 100 , Peugeot= Renault+1 , Lancia = 200 }
...
Par dfaut la premire valeur est 0, puis il y a un incrment de 1 pour les suivantes. Il vaut
mieux dfinir la valeur 0 comme la plus courante, car les constructeurs initialisent 0 par
dfaut les champs de type numration.
Choisir nimporte quel autre type entier que int pour dfinir les valeurs du type numration :
...
enum MarqueFR : byte { Renault , Peugeot } ;
enum MarqueIT : long { Fiat , Ferrari } ;
...
Toute numration drive de la classe Object. Par consquent les mthodes de cette classe sont
accessibles sur une numration. Notons que la mthode Object.ToString() est redfinie pour
chaque numration, de faon que la chane de caractres retourne corresponde la chane de
caractres dsignant ltat courant dans le code. Par exemple :
368
Exemple 10-23 :
class Program {
enum Marque { Renault, Peugeot, Lancia }
static void Main() {
Marque marque = Marque.Renault ;
string s = marque.ToString();
System.Console.WriteLine(s) ;
}
}
Ce programme ache :
Renault
La classe System.Enum
La classe System.Enum drive de la classe System.ValueType. Elle prsente des mthodes statiques daide la manipulation dun type numration. Parmi ces mthodes remarquons :
Les numrations
369
Exemple 10-26 :
...
foreach( string s in System.Enum.GetNames( typeof(Marque) ) )
System.Console.WriteLine(s) ;
...
Indicateurs binaires
On peut faire en sorte quune instance dune numration puisse contenir plusieurs valeurs
de lensemble des valeurs de lnumration. Cette notion est connue sous le nom dindicateurs
binaires. Les indicateurs binaires sont notamment utiliss pour manipuler les drapeaux (flag en
anglais) pour stocker plusieurs informations binaires dans une mme variable. Par exemple :
Exemple 10-28 :
// Dans cet exemple, pas besoin de plus quun octet.
[System.Flags()]
enum Drapeaux : byte {
Drapeau1 = 0x01, // Le bit 1 est `
a 1, les autres `
a 0.
Drapeau2 = 0x04, // Le bit 3 est `
a 1, les autres `
a 0.
Drapeau3 = 0x10 // Le bit 5 est `
a 1, les autres `
a 0.
}
class Program {
public static void Main() {
// En binaire, Drap vaut 10001000.
Drapeaux drapeau = Drapeaux.Drapeau1 | Drapeaux.Drapeau3 ;
// (Si le bit 1 est positionn
e)
equivalent `
a
// (Si Drapeau1 positionne).
if ((drapeau & Drapeaux.Drapeau1) > 0) {/* */}
// (Si les bits 3 et 5 sont positionn
es)
equivalent `
a
// (Si Drapeau2 et Drapeau3 positionn
es).
if ( (drapeau & Drapeaux.Drapeau2) > 0 &&
(drapeau & Drapeaux.Drapeau3) > 0 )
{ /* */ }
System.Console.WriteLine( drapeau.ToString() ) ;
}
}
370
Remarquez que les indicateurs binaires doivent tre marqus avec lattribut System.Flags. Cet
attribut permet de prciser au CLR et aux clients quil sagit dun type dindicateur binaire et
non pas dune numration classique. Notamment, la prsence de cet attribut aecte le rsultat
de la mthode ToString() et cet exemple ache ceci :
Drapeau1, Drapeau3
Il acherait ceci si lattribut Flags tait absent :
17
Un exemple dindicateur binaire prsent par le framework .NET est lnumration System.
Threading.ThreadState dcrite page 143.
371
Les chanes de caractres constantes rgulires, similaires aux chanes de caractres constantes
de C++.
C
s = "ABCDEFG" ;
On peut introduire des caractres spciaux dans une constante chane de caractres. Comprenez
bien que ces caractres sont interprts par le compilateur C . En voici la liste :
Terme franais
Terme anglais
Reprsentation
Valeur
Nouvelle ligne
New line
\n
0x000A
Tabulation horizontale
Horizontal tab
\t
0x0009
Tabulation verticale
Vertical tab
\v
0x000B
Retour arrire
Backspace
\b
0x0008
Retour chariot
Carriage return
\r
0x000D
Saut de page
Form feed
\f
0x000C
Antislash
Backslash
\\
0x005C
Apostrophe simple
Single quote
0x0027
Apostrophe doubles
Double quote
\"
0x0022
Caractre nul
Null
\0
0x0000
372
Elle accepte tous les caractres tels quils sont, y compris le caractre antislash \ mais mis
part le caractre double apostrophes " . En eet, ce dernier dfinit la fin de la chane.
Elle accepte et prend en compte le passage la ligne, dans lcriture de la chane de caractres. Cette fonctionnalit est particulirement utile lorsque lon gnre du code.
Voici un exemple :
Exemple 10-30 :
class Program {
public static void Main() {
string sReguliere ="\\bonjour\n\\comment ca va\n\\ce matin ?" ;
string sVerbatim = @"\bonjour
\comment ca va
\ce matin ?" ;
System.Console.WriteLine("Chane de caract`
eres r
eguli`
ere :") ;
System.Console.WriteLine(sReguliere) ;
System.Console.WriteLine("Chane de caract`
eres verbatim :") ;
System.Console.WriteLine(sVerbatim) ;
}
}
Voici lachage obtenu :
Chane de caract`eres reguli`ere :
\bonjour
\comment ca va
\ce matin ?
Chane de caract`eres verbatim :
\bonjour
\comment ca va
\ce matin ?
373
Description
int Length()
int CompareTo(string s)
Semblable Compare() mais cette mthode est non statique. Retourne Poids(de linstance)-Poids(de largument).
bool EndWith(string s)
Retourne true si la chane reprsente par linstance courante se termine par la chane s.
Retourne une nouvelle chane qui est la chane reprsente par linstance courante dans laquelle on a insr la
chane s partir du pos-ime caractre.
Retourne une nouvelle chane qui est la chane reprsente par linstance courante dans laquelle on a insr n espaces au dbut.
374
Retourne une nouvelle chane qui est la chane reprsente par linstance courante dans laquelle on a enlev n caractres partir du pos-ime caractre.
string Replace(string
oldstr, string newstr)
Retourne une nouvelle chane qui est la chane reprsente par linstance courante sur laquelle on a remplac chacune des sous chanes gale oldstring par newstring.
Retourne une nouvelle chane qui est la chane reprsente par linstance courante dans laquelle on a remplac
chaque caractre oldchar par newchar.
Retourne un tableau de nouvelles chanes obtenues partir de la chane reprsente par linstance courante. Un tableau de caractres sparateurs est pass en argument.
Retourne true si la chane reprsente par linstance courante commence par la chane s.
string ToLower()
Retourne une nouvelle chane qui est la chane reprsente par linstance courante avec toutes les majuscules transformes en minuscules.
string ToUpper()
Retourne une nouvelle chane qui est la chane reprsente par linstance courante avec toutes les minuscules transformes en majuscules.
string Trim()
Retourne une nouvelle chane qui est la chane reprsente par linstance courante avec les espaces, en dbut et
en fin de chane, supprims. TrimStart() (respectivement
TrimEnd()) est identique mais ne traite que le dbut (respectivement la fin).
Retourne une nouvelle chane qui est la chane reprsente par linstance courante, avec les caractres spcifis dans le tableau en argument en dbut et en fin
de chane supprime. TrimStart(char[]) (respectivement
TrimEnd(char[])) est identique mais ne traite que le dbut
(respectivement la fin).
375
En eet cette mthode est trs utilise et prsente un trs grand nombre doptions. Le tableau
ci-dessous illustre plusieurs exemples de mise en forme avec les variables suivantes :
int i = 123 ;
double d = 3.1415 ;
string sOut = System.String.Format( Signature de Format() ) ;
Signature de
Format()
Valeur de sOut
Remarque
"abcd"
"abcd"
"ab{0}cd",i
"ab123cd"
"ab{0}cd",d
"ab3,1415cd"
"ab{0}cd",i,d
"ab123cd"
Largument d nest pas utilis, mais aucune erreur nest dclare ni provoque.
"ab{0}cd{1}ef",i,d
"ab123cd3.1415ef"
"ab{1}cd{0}ef",i,d
"ab3.1415cd123ef"
"ab{0}{0}cd ",i
"ab123123cd"
"ab{0}cd{1}ef",i
ERREUR `
a lex
ecution
"ab{0,6}cd",i
"ab 123cd"
i est reprsent par six caractres avec cadrage droite. Trois caractres despace sont
ajouts. Si le nombre de caractres tait infrieur au nombre de caractres requis pour
acher i, i aurait t ach dans sa totalit
quand mme.
"ab{0,-6}cd",i
"ab123 cd"
"ab{0:0000}cd",i
"ab0123cd"
376
"ab{0,6:0000}cd",i
"ab 0123cd"
"ab{0:####}cd",i
"ab123cd"
"ab{0:##}cd",i
"ab123cd"
"ab{0:##.##}cd",d
"ab3,14cd"
"ab{0:##.#}cd",3.14
"ab3,1cd"
Arrondi infrieur.
"ab{0:##.#}cd",3.18
"ab3,2cd"
Arrondi suprieur.
"ab{0:##%}cd",0.143
"ab14%cd"
0.143 est multipli par 10 puis arrondi infrieur. 0.147 aurait produit 15%.
"ab{0:E}cd",d
"ab3,141500E+000cd"
Reprsentation scientifique.
"ab{0:X}cd",i
"ab7Bcd"
"ab{0:x}cd",I
"ab7bcd"
La classe System.Text.StringBuilder
Comme nous lavons vu au dbut de la prsente section, les instances de la classe String sont
immuables. Ce fait nuit aux performances, car chaque modification dune chane de caractres
il faut allouer une nouvelle instance de String. Ceci est dautant plus coteux si vous manipulez
des chanes de caractres longues.
Si une de vos applications modifie souvent des chanes de caractres ou manipule des chanes
de caractres volumineuses, il est plus ecace dutiliser la classe System.Text.StringBuilder.
En gnral une instance de StringBuilder se construit partir dune instance de la classe String
avec le constructeur StringBuilder(string).
Pour bien comprendre la classe StringBuilder, il faut assimiler la notion de capacit dune instance de StringBuilder. Pour cela nous prsentons les proprits suivantes de StringBuilder
(qui sont toutes publiques et non statiques) :
int Length{get;}
Cette proprit donne le nombre de caractres de la chane.
int Capacity{get;set;}
Cette proprit reprsente le nombre de caractres allous physiquement pour stocker la
chane de caractres. Ce nombre est la capacit. La valeur de ce champ est toujours suprieure ou gale la valeur du champ Length. Il est particulirement utile de positionner
ce champ une valeur suprieure la taille relle de la chane. Vous pouvez ainsi prvenir
des oprations dallocation mmoire du ramasse-miettes (parfois coteuses), en vous crant
une marge pour manipuler la chane.
377
Notez que la valeur de ce champ peut tre automatiquement incrmente lorsquune manipulation sur la chane de caractres produit un rsultat dune taille plus grande que la capacit. La valeur de cette incrmentation dpend de limplmentation du framework. Dans
cette situation, limplmentation courante de Microsoft double la capacit.
Si vous positionnez ce champ une valeur infrieure la valeur du champ Length lexception ArgumentOutOfRangeException est lance.
int MaxCapacity{get ;}
Cette proprit donne la capacit maximale que peut avoir la chane de caractres. Dans
limplmentation courante de Microsoft ce champ vaut Int32.MaxValue soit 2.147.483.647
caractres.
StringBuilder Append(...)
Ajoute des caractres la fin de la chane de caractres. Cette mthode existe en de nombreuses versions surcharges.
StringBuilder Replace(...old,...new,...)
Remplace le ou les caractres spcifis par old, par le ou les caractres spcifis par new. La
recherche de old peut ventuellement se faire sur une sous chane de caractres en utilisant
dautres versions surcharges de cette mthode.
Voici un petit programme qui illustre tout ceci (lachage de chaque appel Program.Display()
est insr en commentaire en blanc sur fond noir) :
Exemple 10-31 :
class Program{
static void Display(System.Text.StringBuilder s) {
System.Console.WriteLine("La chane : \"{0}\"",s) ;
System.Console.WriteLine(" Length
: {0}", s.Length) ;
System.Console.WriteLine(" Capacity : {0}", s.Capacity) ;
}
public static void Main(){
System.Text.StringBuilder s = new
378
"--salut--" ) ;
"hell--salut--o"
14
16
s.Capacity = 18 ;
Display(s) ;
//La chane : "hell--salut--o"
// Length
: 14
// Capacity : 18
s.Replace("salut","SALUT `
A TOUS") ;
Display(s) ;
//La chane : "hell--SALUT `
A TOUS--o"
// Length
: 21
// Capacity : 36
s.EnsureCapacity(42) ;
Display(s) ;
//La chane : "hell--SALUT `
A TOUS--o"
// Length
: 21
// Capacity : 72
}
}
Avant davoir recours aux services de la classe StringBuilder, vous devez tre conscient quil
est possible de violer la condition dimmutabilit des instances de la classe String si vous vous
autorisez utiliser du code non vrifiable. Cette possibilit est expose en page 508.
Cependant cette notion est conceptuellement proche de celle des pointeurs sur fonction et des
pointeurs sur mthode du C++. Au mme titre que ces derniers, un dlgu est une rfrence
vers une ou plusieurs mthodes (statiques ou non).
Les dlgus sont nanmoins plus puissants que les pointeurs sur fonctions, puisquun mme
dlgu peut rfrencer plusieurs mthodes. En outre la syntaxe est beaucoup plus claire et
conviviale.
379
Certains ouvrages utilisent les termes classe dlgue ou type dlgu pour nommer
les dlgations. Dautres publications utilisent le terme dlgu pour nommer les dlgations. Dans ce cas il faut utiliser les termes instance de dlgu ou objet dlgu
pour nommer ce que nous appelons ici dlgu . Soyez vigilants, et reprez chaque
fois la dirence entre les types et les instances.
380
Ce programme ache :
Appel de f1.
Appel de f2 avec largument "hello"
Nous utilisons deux dlgations, pour bien souligner le fait quun dlgu ne peut rfrencer
seulement une mthode dont la signature (mme ordre, nombre et type des arguments et mme
type de retour) correspond celle fournie lors de la dclaration de la dlgation. Par exemple
la ligne suivante aurait provoqu une erreur de compilation car la mthode, f1() na pas la
signature adquate :
Deleg2 d2
= new Deleg2(f1) ;
381
382
La classe System.Delegate
Il faut savoir quen interne, lorsque vous placez plusieurs mthodes dans le mme dlgu,
une instance de la classe System.Deletage est cre pour chaque mthode. En fait, la classe
MulticastDelegate est une liste dinstances de la classe System.Delegate. Lexemple suivant
montre comment utiliser la classe Delegate pour invoquer une une les mthodes contenues
dans un dlgu. Notez le recours la mthode GetInvocationList() pour obtenir la liste des
dlgus.
383
Exemple 10-36 :
using System ;
public class Article {
public int m_Prix = 0 ;
public Article(int Prix) { m_Prix = Prix ; }
public int IncPrix(int i) {
m_Prix += i ;
return m_Prix ;
}
}
public class Program {
public delegate int Deleg(int i) ;
public static void Main() {
Article a = new Article(100) ;
Article b = new Article(103) ;
Article c = new Article(107) ;
Deleg delegs = a.IncPrix ;
delegs += b.IncPrix ;
delegs += c.IncPrix ;
int somme = 0 ;
// Obtient la liste des del
egu
es.
Delegate[] delegArr = delegs.GetInvocationList();
// Invoque une `a une chaque m
ethode r
ef
erenc
ee.
foreach (Deleg deleg in delegArr)
somme += deleg(20);
Console.WriteLine(
"Prix apr`es increment de 20 : a:{0} b:{1} c:{2}",
a.m_Prix , b.m_Prix , c.m_Prix) ;
Console.WriteLine("Somme des prix:{0}", somme ) ;
}
}
Cet exemple ache :
Prix apr`es increment de 20 : a:120 b:123 c:127
Somme des prix:370
384
385
Dans le premier essai de suppression, le dlgu deleg nest pas modifi. Pour infirmer ou
confirmer le bon droulement dune suppression, vous pouvez inspecter la taille de la liste des
instances de la classe Delegate obtenue avec la mthode GetInvocationList().
Une mthode qui doit retourner une rfrence vers un objet retourne une rfrence nulle
pour signifier que lobjet demand ne peut tre fabriqu ou trouv. Cela vite dimplmenter un code derreur de retour binaire.
Lorsque vous rencontrez une mthode qui accepte un argument de type rfrence qui peut
tre nulle, cela signifie en gnral que largument est optionnel.
Un champ de type rfrence nulle peut signifier que lobjet qui le prsente est en cours
dinitialisation, de mise jour ou de destruction et na donc pas un tat valide.
La notion de nullit est aussi largement exploite dans les bases de donnes relationnelles pour
signifier quune valeur dans un enregistrement na pas t assigne. La notion de nullit peut aussi
servir pour dsigner un attribut optionnel dans un lment XML.
Parmi les nombreuses dirences entre les types valeurs et les types rfrences, nous pouvons
nous pencher sur le fait quune instance dun type valeur ne peut avoir de valeur nulle. Cela
pose en gnral de nombreux problmes. Par exemple, comment interprter une valeur entire
nulle (non encore assigne) rcupre dune base de donne ? De multiples solutions existent
mais aucune dentre elles nest pleinement satisfaisante :
Si tout lintervalle de valeurs entires nest pas utilisable, on cre une convention. Par
exemple, une valeur entire nulle est reprsente par un entier gal 0 ou -1. Les nombreux
dsavantages de cette solution sont vidents : contrainte maintenir partout dans le code,
possibilit dvolution de lintervalle de valeurs pris par ce champ particulier etc.
On cre une structure wrapper contenant deux champs, un entier et un boolen qui, si
positionn false, signifie que nous avons une valeur nulle. Ici, on doit grer une structure
en plus pour chaque type valeur, et un tat en plus pour chaque valeur.
On cre une classe wrapper contenant un champ entier. Ici, le dsavantage est quen plus du
maintient dune nouvelle classe, on surcharge le ramasse-miettes en crant de nombreux
objet sur le tas.
On utilise le boxing, par exemple en transtypant notre valeur entire en une rfrence de
type object. En plus de ne pas tre type-safe, cette solution a aussi le dsavantage de surcharger le ramasse-miettes.
386
Pour pallier ce problme rcurrent, les concepteurs de C 2 ont dcid dajouter au langage la
notion de types nullables.
La structure System.Nullable<T>
Le framework .NET 2005 prsente la structure gnrique System.Nullable<T> dfinie comme
ceci :
public struct System.Nullable<T> {
public Nullable(T value) ;
public static explicit operator T(T? value) ;
public static implicit operator T?(T value) ;
public bool HasValue { get ; }
public T Value { get ; }
public
public
public
public
public
}
Cette structure rpond bien la problmatique des valeurs nulles lorsque le type paramtre T
prend la forme dun type valeur tel que int. Voici un petit exemple qui illustre lutilisation de
cette structure. La premire version de Fct() exploite la nullit dune rfrence de type string
tandis que la seconde version exploite la nullit dune instance de la structure Nullable<int> :
Exemple 10-38 :
class Foo {
static string Fct(string s) {
if (s == null)
return null ;
return s + s ;
}
static System.Nullable<int> Fct(System.Nullable<int> ni){
if (!ni.HasValue)
return ni ;
return (System.Nullable<int>) (ni.Value + ni.Value) ;
}
}
387
388
...est quivalente :
Nullable<int> i = null ;
Par exemple, les deux mthodes suivantes sont rigoureusement quivalentes :
Exemple 10-41 :
class Foo {
static System.Nullable<int> Fct1( System.Nullable<int> ni ) {
if ( !ni.HasValue )
return ni ;
return (System.Nullable<int>) ( ni.Value + ni.Value ) ;
}
static int? Fct2(int? ni){
if (ni == null)
return ni;
return ni + ni ;
}
}
En gnral, les instances de types nullables quivalentes se mlangent bien :
Exemple 10-42 :
class Program{
static void
int? ni1
int? ni2
int? ni3
int? ni4
int? ni5
ni1++ ;
ni2++ ;
}
}
Main(){
= null ;
= 9 ;
= ni1 + ni2 ; // OK,
= ni1 + 3 ; // OK,
= ni2 + 3 ; // OK,
// OK,
// OK,
ni3
ni4
ni5
ni1
ni2
vaut null.
vaut null.
vaut 12.
reste `
a null.
passe `
a 1.
Main(){
= null ;
= 9 ;
ni1 ;
int i2 = ni2 ;
389
//
type int? to int.
// KO: Cannot implicitly convert
//
type int? to int.
int i4 = ni1 + 6 ;
// KO: Cannot implicitly convert
//
type int? to int.
// OK `a la compilation mais une exception de type
// InvalidCastException est lanc
ee `
a lex
ecution,
// car ni1 est toujours nulle.
int i5 = (int)ni1 ;
int i3 = ni1 + ni2 ;
}
}
// os est une r
ef
erence nulle
// oi n
etait pas une r
ef
erence nulle
Sous la pression de la communaut, les ingnieurs de Microsoft ont dcid de remdier ceci.
Ainsi, lassertion du programme suivant est vrifie :
Exemple 10-45 :
class Program{
static void Main(){
int? ni = null ;
object o = ni ;
// boxing
System.Diagnostics.Debug.Assert( o == null ) ;
390
Du point de vue du CLR, une instance dun type valeur T boxe peut tre nulle. Si elle nest pas
nulle, le CLR ne stocke pas dinformation quant savoir si elle tait originalement issue du type
T ou Nullable<T>. Vous pouvez ainsi unboxer un tel objet dans lun de ces deux types comme
le montre lexemple suivant. Soyez nanmoins conscient que vous ne pouvez pas unboxer une
valeur nulle :
Exemple 10-46 :
class Program {
static void Main() {
int i1 = 76 ;
object o1 = i1 ;
// boxing dun int
int? ni1 = (int?)o1 ; // unboxing en un int?
System.Diagnostics.Debug.Assert( ni1 == 76 ) ;
int? ni2 = 98 ;
object o2 = ni2 ;
// boxing dun int?
int i2 = (int)o2 ;
// unboxing en un int
System.Diagnostics.Debug.Assert( i2 == 98 ) ;
int? ni3 = null ;
object o3 = ni3 ;
int i3 = (int)o3 ;
}
}
391
// OK Struct.ctor() par d
efaut
// est appel
e.
KO: System.Nullable<Struct> does not
contain a definition for m_i.
KO: System.Nullable<Struct> does not
contain a definition for Fct.
}
}
En revanche, si besoin est, le compilateur saura utiliser vos redfinitions doprateurs :
Exemple 10-48 :
struct Struct {
public Struct(int i) { m_i = i ; }
public int m_i ;
public static Struct operator +( Struct a, Struct b) {
return new Struct( a.m_i + b.m_i ) ; }
}
class Program {
static void Main() {
Struct? ns1 = new Struct(3) ;
Struct? ns2 = new Struct(2) ;
Struct? ns3 = null ;
Struct? ns4 = ns1 + ns2 ; // OK, ns4.m_i vaut 5.
Struct? ns5 = ns1 + ns3 ; // OK, ns5 vaut null.
}
}
En ce qui concerne une instance dune numration nullable, ayez conscience que vous devez
toujours obtenir la valeur sous-jacente pour lutiliser. Par exemple :
Exemple 10-49 :
class Program{
enum MyEnum { VAL1, VAL2 }
static void Main(){
MyEnum? e = null;
if( e == null )
System.Console.WriteLine("e est null") ;
else
switch(e.Value){ // Ici on est sur que e nest pas null.
case MyEnum.VAL1: System.Console.WriteLine("e vaut VAL1") ;
break ;
case MyEnum.VAL2: System.Console.WriteLine("e vaut VAL2") ;
break ;
}
}
}
392
393
Les mots-cls abstract et sealed. Prcisons quun mme type ne peut tre la fois abstract
et sealed .
Les membres. Lensemble des membres dun type dfini sur plusieurs fichiers sources est
lunion des membres de chaque dfinition partielle.
Les attributs. Lensemble des attributs sappliquant sur un type dfini sur plusieurs fichiers
sources est lunion des attributs appliqus sur chaque dfinition partielle. En particulier, le
type concern sera marqu plusieurs fois par un mme attribut si celui-ci marque plusieurs
dfinitions partielles.
Les interfaces implmentes. Lensemble des interfaces implmentes par un type dfini sur
plusieurs fichiers sources est lunion des interfaces dclares dans chaque dfinition partielle.
11
Notions de classe et dobjet
Notions et vocabulaire
C++ C En C , en franais, le mot attribut est rserv pour dautres notions que celle de
champ dune classe. Cette notion dattribut est prsente page 248. De plus on verra que C
distingue la notion de champs et de proprits.
Enfin puisque la plateforme .NET dispose dun ramasse-miettes, un destructeur C est trs diffrent dun destructeur C++.
C Une classe est un type d