Vous êtes sur la page 1sur 1068

Pratique de .NET 2.

0
et de C  2.0

patrick smacchia

Pratique de .NET 2.0



et de C 2.0

ditions OREILLY
18 rue Sguier
75006 PARIS
http://www.oreilly.fr

Cambridge Cologne Farnham Paris Pkin Sbastopol Tape Tokyo

Couverture conue et ralise par Hanna Dyer & Marcia Friedman.

dition : Xavier Cazin.


Les programmes figurant dans ce livre ont pour but dillustrer les sujets traits. Il nest donn
aucune garantie quant leur fonctionnement une fois compils, assembls ou interprts dans
le cadre dune utilisation professionnelle ou commerciale.

c ditions OReilly, Paris, 2005



ISBN 2-84177-339-6

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.

Table des matires

propos de ce livre . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Lorganisation de ce livre . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

qui sadresse ce livre et comment lexploiter . . . . . . . . . . . . . . . . . . . . .

Support . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Remerciements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Aborder la plateforme .NET

Quest ce que .NET ? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Historique . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

.NET hors du monde Microsoft / Windows . . . . . . . . . . . . . . . . . . . . . .

Liens sur .NET . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

10

Assemblages, modules, langage IL

15

Assemblages, modules et fichiers de ressource . . . . . . . . . . . . . . . . . . . . .

15

Anatomie des modules

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

17

Analyse dun assemblage avec ildasm.exe et Reflector . . . . . . . . . . . . . . . . .

20

Attributs dassemblage et versionning . . . . . . . . . . . . . . . . . . . . . . . . .

25

Assemblage nom fort (strong naming) . . . . . . . . . . . . . . . . . . . . . . . .

28

Internationalisation et assemblages satellites . . . . . . . . . . . . . . . . . . . . .

34

Introduction au langage IL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

41

Construction, configuration et dploiement des applications .NET

49

Construire vos applications avec MSBuild . . . . . . . . . . . . . . . . . . . . . . .

49

MSBuild : Cibles, tches, proprits, items et conditions . . . . . . . . . . . . . . .

50

Concepts avancs de MSBuild . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

54

Fichiers de configuration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

58

vi

Table des matires


Dploiement des assemblages : XCopy vs. Rpertoire GAC . . . . . . . . . . . . . .

63

Assemblage de stratgie dditeur (Publisher policy) . . . . . . . . . . . . . . . . .

66

Introduction au dploiement dapplications .NET . . . . . . . . . . . . . . . . . .

69

Dployer une application avec un fichier cab . . . . . . . . . . . . . . . . . . . . .

71

Dployer une application avec la technologie MSI . . . . . . . . . . . . . . . . . .

73

Dployer une application avec la technologie ClickOnce . . . . . . . . . . . . . . .

75

Dployer une application avec la technologie No Touch Deployment . . . . . . .

84

Et si .NET nest pas install sur la machine cible ? . . . . . . . . . . . . . . . . . . .

85

Le CLR (le moteur dexcution des applications .NET)


Les domaines dapplication . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

87

Chargement du CLR dans un processus grce lhte du moteur dexcution . . .

94

Profiler vos applications . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

102

Localisation et chargement des assemblages lexcution . . . . . . . . . . . . . .

103

Rsolution des types lexcution . . . . . . . . . . . . . . . . . . . . . . . . . . . .

108

La compilation Juste temps (JIT Just In Time)

. . . . . . . . . . . . . . . . .

111

. . . . . . . . . . . . . . . . . . . . . . . . .

116

Facilits fournies par le CLR pour rendre votre code plus fiable . . . . . . . . . . .

125

CLI et CLS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

129

Gestion du tas par le ramasse-miettes

87

Processus, threads et gestion de la synchronisation

133

Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

133

Les processus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

134

Les threads . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

136

Introduction la synchronisation des accs aux ressources . . . . . . . . . . . . . .

143

Synchronisation avec les champs volatiles et la classe Interlocked . . . . . . . . . .

145

Synchronisation avec la classe System.Threading.Monitor et le mot-cl lock . . . .

147

Synchronisation avec des mutex, des vnements et des smaphores . . . . . . . .

153

Synchronisation avec la classe System.Threading.ReaderWriterLock . . . . . . . .

158

Synchronisation avec lattribut System...SynchronizationAttribute . . . . . . . . .

160

Le pool de threads du CLR . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

167

Timers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

169

Appel asynchrone dune mthode . . . . . . . . . . . . . . . . . . . . . . . . . . .

171

Anit entre threads et ressources . . . . . . . . . . . . . . . . . . . . . . . . . . .

176

Contexte dexcution . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

180

Table des matires

La gestion de la scurit

vii

185

Introduction Code Access Security (CAS) . . . . . . . . . . . . . . . . . . . . . .

185

CAS : Preuves et permissions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

187

CAS : Accorder des permissions en fonction des preuves avec les stratgies de scurit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

193

CAS : La permission FullTrust . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

198

CAS : Vrifier les permissions imprativement partir du code source . . . . . . .

199

CAS : Vrifier les permissions dclarativement partir du code source . . . . . . .

203

CAS : Facilits pour tester et dboguer votre code mobile . . . . . . . . . . . . . .

205

CAS : La permission de faire du stockage isol . . . . . . . . . . . . . . . . . . . . .

205

Support .NET pour les utilisateurs et rles Windows . . . . . . . . . . . . . . . . .

206

Support .NET pour les contrles des accs aux ressources Windows . . . . . . . . .

211

.NET et la notion de rle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

216

.NET et les algorithmes symtriques de cryptographie . . . . . . . . . . . . . . . .

219

.NET et les algorithmes asymtriques de cryptographie (cl publique/cl prive) .

221

LAPI de protection des donnes (Data Protection API) . . . . . . . . . . . . . . . .

225

Authentifier vos assemblages avec la technologie Authenticode . . . . . . . . . . .

230

Rflexion, liens tardifs, attributs

233

Le mcanisme de rflexion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

233

Les liens tardifs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

238

Les attributs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

248

Construction et utilisation dynamique dun assemblage . . . . . . . . . . . . . . .

255

Interoprabilit .NET code natif / COM / COM+

265

Le mcanisme P/Invoke . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

265

Introduction linteroprabilit avec C++/CLI . . . . . . . . . . . . . . . . . . . . .

272

.NET et les Handles win32 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

277

Utilisation de COM partir de .NET . . . . . . . . . . . . . . . . . . . . . . . . . .

278

Encapsuler une classe .NET dans une classe COM . . . . . . . . . . . . . . . . . . .

288

Introduction COM+ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

295

Prsentation des services dentreprise COM+ . . . . . . . . . . . . . . . . . . . . . .

296

Utiliser les services COM+ dans des classes .NET . . . . . . . . . . . . . . . . . . .

299

viii

Table des matires

Les concepts fondamentaux du langage

309

Organisation du code source

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

309

Les tapes de la compilation

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

312

Le prprocesseur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

313

Le compilateur csc.exe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

317

Les alias . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

320

Commentaires et documentation automatique . . . . . . . . . . . . . . . . . . . .

323

Les identificateurs

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

326

Les structures de contrle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

327

La mthode Main() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

334

10 Le systme de types
Stockage des objets en mmoire

337
. . . . . . . . . . . . . . . . . . . . . . . . . . . .

337

Type valeur et type rfrence . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

339

Le CTS (Common Type System) . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

342

La classe System.Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

344

Comparer des objets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

345

Cloner des objets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

347

Boxing et UnBoxing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

350

Les types primitifs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

353

Oprations sur les types primitifs . . . . . . . . . . . . . . . . . . . . . . . . . . . .

358

Les structures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

364

Les numrations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

366

Les chanes de caractres . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

370

Les dlgations et les dlgus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

378

Les types nullables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

385

Dfinir un type sur plusieurs fichiers sources . . . . . . . . . . . . . . . . . . . . .

392

11 Notions de classe et dobjet

395

Remarques sur la programmation objet . . . . . . . . . . . . . . . . . . . . . . . .

395

Notions et vocabulaire . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

395

Dfinition dune classe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

396

Les champs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

397

Les mthodes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

400

Les proprits . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

407

Table des matires

ix

Les indexeurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

409

Les vnements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

411

Les types encapsuls . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

416

Encapsulation et niveaux de visibilit . . . . . . . . . . . . . . . . . . . . . . . . .

417

Le mot-cl this . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

420

Construction des objets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

421

Destruction des objets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

423

Les membres statiques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

430

Surcharge des oprateurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

433

12 Hritage/drivation polymorphisme et abstraction

443

Objectif : rutilisation de code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

443

Lhritage dimplmentation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

445

Mthodes virtuelles et polymorphisme

. . . . . . . . . . . . . . . . . . . . . . . .

448

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

452

Les interfaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

456

Proprits, vnements et indexeurs virtuels et abstraits . . . . . . . . . . . . . . .

462

Les oprateurs is et as . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

464

Techniques de rutilisation de code . . . . . . . . . . . . . . . . . . . . . . . . . . .

466

Labstraction

13 La gnricit

467

Un problme de C  1 et sa rsolution par les types gnriques de C  2 . . . . . . . .


Vue densemble de la gnricit de C  2 . . . . . . . . . . . . . . . . . . . . . . . .

471

Possibilit de contraindre un type paramtre . . . . . . . . . . . . . . . . . . . . .

474

Les membres dun type gnrique . . . . . . . . . . . . . . . . . . . . . . . . . . .

477

Les oprateurs et les types gnriques . . . . . . . . . . . . . . . . . . . . . . . . . .

481

Le transtypage (casting) et la gnricit . . . . . . . . . . . . . . . . . . . . . . . . .

484

Lhritage et la gnricit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

486

Les mthodes gnriques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

487

Les dlgus, les vnements et la gnricit . . . . . . . . . . . . . . . . . . . . . .

491

Rflexion, attribut, IL et gnricit . . . . . . . . . . . . . . . . . . . . . . . . . . .

493

La gnricit et le framework .NET . . . . . . . . . . . . . . . . . . . . . . . . . . .

499

467

Table des matires

14 Les mcanismes utilisables dans C 

501

Les pointeurs et les zones de code non vrifiable . . . . . . . . . . . . . . . . . . .


Manipulation des pointeurs en C  . . . . . . . . . . . . . . . . . . . . . . . . . . .

501
503

Les exceptions et le traitement des erreurs . . . . . . . . . . . . . . . . . . . . . . .

509

Objet associ une exception et lancement de vos propres exceptions . . . . . . .

511

Le gestionnaire dexceptions et la clause finally . . . . . . . . . . . . . . . . . . . .

515

Exceptions lances dans un constructeur ou dans la mthode Finalize() . . . . . .

517

Le CLR et la gestion des exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . .

519

Les exceptions et lenvironnement Visual Studio . . . . . . . . . . . . . . . . . . .

522

Conseils dutilisation des exceptions . . . . . . . . . . . . . . . . . . . . . . . . . .

522

Les mthodes anonymes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .


Le compilateur C  2 et les mthodes anonymes . . . . . . . . . . . . . . . . . . . .

524

Exemples avancs dutilisation des mthodes anonymes . . . . .


Les itrateurs avec C  1 . . . . . . . . . . . . . . . . . . . . . . . .
Les itrateurs avec C  2 . . . . . . . . . . . . . . . . . . . . . . . .
Interprtation des itrateurs par le compilateur de C  2 . . . . . .

. . . . . . . . . .

536

. . . . . . . . . .

539

. . . . . . . . . .

542

. . . . . . . . . .

Exemples avancs de lutilisation des itrateurs de C 2 . . . . . . . . . . . . . . . .

548

15 Collections

529

552

563

Parcours des lments dune collection avec foreach et in . . . . . . . . . . .

563

Les tableaux

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

565

Les squences . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

575

Les dictionnaires . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

582

Trier les lments dune collection . . . . . . . . . . . . . . . . . . . . . . . . . . .

587

Foncteurs et manipulation des collections . . . . . . . . . . . . . . . . . . . . . . .

591

Correspondance entre System.Collections.Generic et System.Collections . . . . . .

595

16 Bibliothques de classes
Fonctions mathmatiques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

597
597

Donnes temporelles (dates, dures...) . . . . . . . . . . . . . . . . . . . . . . . . .

600

Volumes, rpertoires et fichiers . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

607

Base des registres . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

613

Le dbogage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

617

Les traces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

620

Les expressions rgulires . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

625

La console . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

629

Table des matires

17 Les mcanismes dentre/sortie

xi

633

Introduction aux flots de donnes . . . . . . . . . . . . . . . . . . . . . . . . . . .

633

Lecture/criture des donnes dun fichier . . . . . . . . . . . . . . . . . . . . . . .

636

Support du protocole TCP/IP et des sockets . . . . . . . . . . . . . . . . . . . . . .

641

Obtenir des informations concernant le rseau . . . . . . . . . . . . . . . . . . . .

649

Clients HTTP et FTP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

651

Serveur HTTP avec HttpListener et HTTP.SYS . . . . . . . . . . . . . . . . . . . . .

654

Support des protocoles denvoi de mails SMTP et MIME . . . . . . . . . . . . . . .

656

Burisation et compression dun flot de donnes . . . . . . . . . . . . . . . . . .

657

Lecture/criture des donnes sur un port srie . . . . . . . . . . . . . . . . . . . . .

660

Support des protocoles SSL, NTLM et Kerberos de scurisation des donnes changes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

660

18 Les applications fentres (Windows Forms)

665

Les applications fentres sous les systmes dexploitation Windows . . . . . . . .

665

Introduction aux formulaires Windows Forms . . . . . . . . . . . . . . . . . . . .

668

Facilits pour dvelopper des formulaires . . . . . . . . . . . . . . . . . . . . . . .

675

Les contrles standards . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

679

Crer vos propres contrles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

682

Prsentation et dition des donnes . . . . . . . . . . . . . . . . . . . . . . . . . .

688

Internationaliser les fentres . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

695

La bibliothque GDI+ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

695

19 ADO.NET 2.0

705

Introduction aux bases de donnes . . . . . . . . . . . . . . . . . . . . . . . . . . .

705

Introduction ADO.NET . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

707

Connexion et fournisseurs de donnes . . . . . . . . . . . . . . . . . . . . . . . . .

712

Travailler en mode connect avec des DataReader . . . . . . . . . . . . . . . . . . .

720

Travailler en mode dconnect avec des DataSet . . . . . . . . . . . . . . . . . . .

723

DataSet typs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

731

Pont entre le mode connect et le mode dconnect . . . . . . . . . . . . . . . . .

735

Ponts entre lobjet et le relationnel . . . . . . . . . . . . . . . . . . . . . . . . . . .

736

Fonctionalits spcifiques au fournisseur de donnes de SQL Server . . . . . . . .

738

xii

Table des matires

20 Les transactions

741

Introduction la notion de transaction . . . . . . . . . . . . . . . . . . . . . . . .

741

Le framework System.Transactions . . . . . . . . . . . . . . . . . . . . . . . . . . .

746

Utilisation avances de System.Transactions . . . . . . . . . . . . . . . . . . . . . .

752

Introduction la cration dun RM transactionnel . . . . . . . . . . . . . . . . . .

755

21 XML

759

Introduction XML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

759

Introduction XSD, XPath, XSLT et XQuery . . . . . . . . . . . . . . . . . . . . . .

762

Les approches pour parcourir et diter un document XML . . . . . . . . . . . . . .

765

Parcours et dition dun document XML avec un curseur (XmlReader et XmlWriter)

766

Parcours et dition dun document XML avec DOM (XmlDocument) . . . . . . .

769

Parcours et dition dun document XML avec XPath . . . . . . . . . . . . . . . . .

771

Transformer un document XML avec XSLT . . . . . . . . . . . . . . . . . . . . . .

774

Ponts entre le relationnel et XML . . . . . . . . . . . . . . . . . . . . . . . . . . . .

775

Ponts entre lobjet et XML (srialisation XML) . . . . . . . . . . . . . . . . . . . .

779

Visual Studio .NET et XML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

783

22 .NET Remoting

785

Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

785

Marshaling By Reference (MBR) . . . . . . . . . . . . . . . . . . . . . . . . . . . .

787

Marshalling By Value (MBV) et serialisation binaire . . . . . . . . . . . . . . . . .

790

La classe ObjectHandle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

792

Introduction lactivation des objets . . . . . . . . . . . . . . . . . . . . . . . . . .

793

Service dactivation par le serveur (WKO) . . . . . . . . . . . . . . . . . . . . . . .

795

Activation par le client (CAO) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

799

Le design pattern factory et loutil soapsuds.exe . . . . . . . . . . . . . . . . . . . .

802

Service de dure de vie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

805

Configurer la partie Remoting dune application . . . . . . . . . . . . . . . . . . .

808

Dploiement dune application distribue .NET . . . . . . . . . . . . . . . . . . .

815

Scuriser une conversation .NET Remoting . . . . . . . . . . . . . . . . . . . . . .

816

Proxys et messages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

817

Canaux (channels) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

830

Contexte .NET . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

842

Rcapitulatif . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

857

Table des matires

xiii

23 ASP.NET 2.0

859

Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

859

Architecture gnrale dASP.NET . . . . . . . . . . . . . . . . . . . . . . . . . . . .

861

Stockage du code source . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

866

Modles de compilation et de dploiement . . . . . . . . . . . . . . . . . . . . . .

871

Web forms et contrles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

873

Cycle de vie dune page . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

883

Configuration dune application ASP.NET . . . . . . . . . . . . . . . . . . . . . . .

888

Pipeline HTTP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

891

Gestion des sessions et des tats . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

897

Le design pattern provider . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

902

Gestion des erreurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

903

Traces, diagnostics et gestion des vnements . . . . . . . . . . . . . . . . . . . . .

905

Validation des donnes saisies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

908

Contrles utilisateurs

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

912

Amliorer les performances avec la mise en cache . . . . . . . . . . . . . . . . . . .

918

Sources de donnes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

929

Prsentation et dition des donnes . . . . . . . . . . . . . . . . . . . . . . . . . .

935

Master pages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

947

Internationaliser une application ASP.NET 2.0 . . . . . . . . . . . . . . . . . . . .

953

Aides la navigation dans un site . . . . . . . . . . . . . . . . . . . . . . . . . . . .

954

Scurit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

957

Personnalisation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

966

Styles, Thmes et Skins . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

970

WebParts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

973

24 Introduction au dveloppement de Services Web avec .NET

987

Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

987

Dveloppement dun service web simple

. . . . . . . . . . . . . . . . . . . . . . .

991

Tester et dboguer un service web . . . . . . . . . . . . . . . . . . . . . . . . . . .

993

Crer un client .NET dun service web . . . . . . . . . . . . . . . . . . . . . . . . .

994

Appels asynchrones et modle dchange de message . . . . . . . . . . . . . . . . .

998

Utiliser un service web partir dun client .NET Remoting . . . . . . . . . . . . .

998

Encoder les messages au format SOAP . . . . . . . . . . . . . . . . . . . . . . . . .

1000

Dfinir des contrats avec le langage WSDL . . . . . . . . . . . . . . . . . . . . . . .

1003

xiv

Table des matires


Introduction WSE et aux spcifications WS-* . . . . . . . . . . . . . . . . . . . .

1008

Les spcifications WS-* non encore supportes par WSE . . . . . . . . . . . . . . .

1011

Introduction WCF (Windows Communication Framework) . . . . . . . . . . . .

1013

A Les mots-cls du langage C  2.0

1015

B Nouveauts .NET 2.0

1021

C Introduction aux design patterns

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 sont gres la scurit et la synchronisation des accs aux ressources ?

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 ?

Table des matires

Partie II : Le langage C  2.0 et la comparaison C  2.0/C++


La deuxime partie dcrit compltement le langage C  2.0. Ce langage est beaucoup plus proche
du langage Java que du langage C++. Je me suis donc eorc de dcrire les similitudes et les dirences entre C  et C++ pour chaque facette du langage C  . Jespre que cette approche rpondra
rapidement aux questions des dveloppeurs C++ qui migrent vers C  .

Partie III : Le framework .NET


La troisime partie dcrit les classes de base du framework .NET. Les fonctionnalits de ces classes
se rpartissent principalement dans les catgories suivantes :

Les collections.

Les classes de base classiques type calculs mathmatiques, dates et dures, rpertoires et
fichiers, traces et dbogage, expressions rgulires, console.

La gestion des entres/sorties au moyen de flots de donnes.

Le dveloppement dapplications graphiques fentres.

La gestion des bases de donnes avec ADO.NET 2.0.

La gestion des transactions.

La cration et lexploitation de documents XML.

Les applications objets distribus avec .NET Remoting.

Le dveloppement dapplication web avec ASP.NET 2.0.

Les services web.

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.

qui sadresse ce livre et comment lexploiter


Ce livre sadresse vous ds lors que vous tes intress par le dveloppement sous .NET, que
vous soyez tudiant, dveloppeur professionnel ou amateur, enseignant ou formateur, architecte ou chef de projet.
Chaque chapitre a t conu pour tre lu dune manire linaire mais il nen est pas de mme
pour louvrage dans sa globalit. La premire partie, larchitecture de la plateforme .NET, est

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.

Table des matires

Laurent Desmons, 10 ans dexprience, architecte et consultant .NET, collabore notamment


avec Pchiney, Arcelor, Sollac. MCSD.NET.
Matthieu Guyonnet-Duluc, 4 ans dexprience, dveloppeur chez France Tlcom dans le domaine commercial.
Dr Michel Futtersack, Matre de Confrences en Informatique, Universit Ren Descartes, enseigne depuis 10 ans la conception et la programmation OO.
Nicolas Frelat, consultant .NET, 4 ans dexprience. Early adopter de la plateforme .NET2.
Olivier Girard, 6 ans dexprience, spcialiste en EAI, architecte la Banque de France.
Patrick Philippot, freelance, 30 ans dexprience (dont 19 IBM), MVP .NET www.mainsoft.
fr.
Sami Jaber, 8 ans dexprience, consultant senior et formateur pour Valtech, collabore notamment avec Airbus, webmaster de www.dotnetguru.org.
Sbastien Ros, 7 ans dexprience, spcialiste en mapping O/R, auteur de loutil DTM, CTO
dEvaluant www.evaluant.com.
Sbastien Vaucouleur, 8 ans dexprience, spcialiste en langages, collabore notamment avec
Bull, Fujitsu, assistant de recherche luniversit ETH (Zurich).
Thibaut Barrre, freelance, spcialiste des plateformes J2EE/.NET/C++, programme depuis
1984 et a rcemment collabor avec Calyon, PPR/Redoute et MCS. Contributeur open-source
sur les projets CruiseControl.Net, NAnt et TestDriven.Net
Thomas Gil, 8 ans dexprience, spcialiste en Programmation Oriente Aspect, chef de projet
de AspectDNG, consultant et formateur indpendant, co-webmaster de www.dotnetguru.org.
Vincent Canestrier, ancien enseignant au Conservatoire National des Arts et Mtiers, ancien
directeur technique de division Cap Gemini Ernst & Young.
Aussi, je souhaiterais remercier Brian Grunkemeyer, Florin Lazar, Krzysztof Cwalina et Michael Marucheck tous ingnieurs Microsoft Corp, pour leurs rponses zles mes interrogations.
Enfin je souhaiterais vous remercier pour avoir choisi mon ouvrage. Jespre sincrement quil
vous aidera dans votre tche.

1
Aborder la plateforme .NET

Quest ce que .NET ?


La technologie de dveloppement logiciel de Microsoft
Sous lappellation .NET on dsigne la plateforme de dveloppement logiciel principale de Microsoft. Le sujet est trs vaste et englobe aussi bien larchitecture interne, le format des composants, les langages de programmation, les classes standard et les outils. Sous lappellation .NET,
on dsigne donc une nouvelle re dans le monde Microsoft, qui supplante petit petit lre
COM/win32/C++/VB/ASP.
Le nom .NET a t choisi du fait que linternet et plus gnralement les rseaux sont de plus
en plus exploits par les logiciels. Les applications sont de plus en plus interconnectes. De ce
fait, la technologie .NET prsente des facilits pour faire communiquer les applications qui font
lobjet des chapitres 22 et 24. Pour faciliter linteroprabilit des applications dans un milieu
htrogne, la plateforme .NET a aussi pour caractristique forte dexploiter XML tous les
niveaux.
Peu peu, tous les produits Microsoft prsentent partiellement ou compltement leurs APIs
avec des types .NET. Par exemple, la version 2005 de SQL Server permet dinjecter du code .NET
excut au sein du processus du SGBD qui ralise des traitements sur les donnes. LAPI de
programmation de Windows Vista, la prochaine version de Windows, est accessible sous forme
de types .NET. La technologie de construction de pages web de .NET, nomme ASP.NET est
maintenant privilgie par le serveur web IIS 7.0. La suite Oce prsente un modle de programmation bas sur .NET qui supplante peu peu le modle VBA.

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-

Chapitre 1 : Aborder la plateforme .NET

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.

Prsentation de la technologie .NET


La technologie .NET se compose principalement de trois parties :

Un ensemble extensible de langages de dveloppement dont le langage C  et le langage


VB.NET. Ces langages doivent respecter une spcification nomme CLS (Common Langage
Spcification). Les types de base utiliss par ces langages doivent eux aussi respecter une spcification, nomme CTS (Common Type System).

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

CLS (Common Language Specification)


C, VB.NET, C++ gr, JScript, etc.

Outils

Bibliothque de classes de base


ADO.NET, Forms, XML, ASP.NET, etc.

Implmentation du CLI
(Common Language Infrastructure)
CLR (Common Language Runtime)

Figure 1 -1 : Vue gnrale de .NET


Paralllement, la communaut Java gagnait du terrain sur la scne du dveloppement logiciel.
De plus en plus dentreprises taient sduites par le concept de machine virtuelle permettant
dexcuter une application sur la plupart des systmes sans eorts supplmentaires. De plus, les
classes Java taient bien plus faciles utiliser que les MFC (Microsoft Fundation Classes) principalement grce labsence de pointeurs et cela augmentait significativement la productivit des
dveloppeurs. Ds juin 2000 Microsoft annona quil tait en train de dvelopper une nouvelle
technologie qui comprenait notamment un nouveau langage, le langage C  . Le 13 fvrier 2002
tait publie la premire version exploitable de .NET. Cet vnement est dcisif dans lhistoire
de lentreprise Microsoft et plus gnralement, dans la scne du dveloppement logiciel.
Parmi les ingnieurs en charge de ce projet, on peut citer Anders Hejlsberg, un des co-fondateurs
de la socit Borland. Cet ingnieur danois, concepteur du langage Turbo Pascal et du langage
Delphi, fut dbauch de Borland par Microsoft en 1996 pour travailler sur les WFC (Windows Fundation Classes), qui sont les classes utilises par la machine virtuelle Java de Microsoft. Rapidement, il est plac dans lquipe qui allait produire ce que lon nomme aujourdhui le CLR et le
langage C  .
En mars 2003, la version 1.1 de .NET est disponible. Elle contient notamment plus de classes
sur le thme des fournisseurs de base de donnes (Oracle et ODBC), de la scurit (cryptographie), de la technologie IP version6 et des technologies XML/XSLT. .NET 1.1 contient des outils
pour dvelopper des applications excutables sous Windows CE (Pocket PC, smart phone...). La
version 1.1 du framework .NET contient aussi le langage J#, destin aider les dveloppeurs Java
migrer ver .NET.

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.

Chapitre 1 : Aborder la plateforme .NET

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.

.NET hors du monde Microsoft / Windows

.NET hors du monde Microsoft / Windows


Lorganisation ECMA et .NET
En Octobre 2000 Microsoft, Intel et Hewlett-Packard ont propos lECMA (European Computer
Manufacturers Association) de standardiser un sous ensemble de .NET. Cette partie comprend
principalement le langage C  , et le CLI. LECMA a accept la demande et a cr une commission
technique pour eectuer cette normalisation.
Microsoft nest donc pas totalement propritaire du langage C  et du CLI et le gant du logiciel a
tolr jusquici le fait que dautres organisations implmentent ces spcifications. Pour plus dinformation et pour obtenir les publications ocielles de ces spcifications, vous pouvez consulter
les URLs suivantes :
http://www.ecma-international.org/
http://www.msdn.microsoft.com/net/ecma/
http://www.ecma-international.org/publications/standards/Ecma-334.htm (spcification
C  2.0)
http://www.ecma-international.org/publications/standards/Ecma-335.htm (spcification
CLI)

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

Chapitre 1 : Aborder la plateforme .NET

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.

Le projet SSCLI (Rotor) de Microsoft


Le projet Shared Source CLI (aussi nomm SSCLI ou Rotor) de Microsoft consiste distribuer du
code source implmentant le CLI et certaines parties du framework .NET. Le projet SSCLI est
surtout distribu des fins acadmiques pour permettre aux tudiants et aux chercheurs de
sinitier et de travailler sur les internes dune machine virtuelle moderne (GC, JIT compilation
etc). Vous pouvez nanmoins vous en servir pour comprendre le fonctionnement interne de
.NET et pour dboguer vos applications. En revanche, vous navez pas le droit de vous en servir
des fins commerciales.
Concrtement, SSCLI cest plusieurs millions de lignes de code, un compilateur C  , un compilateur JScript et de nombreux outils. Une version 2.0 devrait tre disponible rapidement aprs la
sortie de .NET 2.0. Les parties centrales du framework sont supportes telles que XML ou .NET
Remoting. En revanche, dautres domaines tout aussi importants mais plutt orients fonctionnel tels que ADO.NET, ASP.NET et Windows Form ne sont pas implments par SSCLI.
Contrairement limplmentation Microsoft commerciale de .NET, SSCLI peut fonctionner sur
dautres systmes dexploitation que Windows. lheure actuelle vous pouvez vous servir de
SSCLI sur les systmes dexploitation FreeBSD, Mac OS X et Windows. Tout ceci est possible parce
quen interne SSCLI nappelle pas directement lAPI win32. Le projet utilise une API proche
de win32 nomme PAL (Platform Abstraction Layer). Or, cette API est supporte par ces trois
systmes dexploitation.
La page ocielle du projet est lURL : http://msdn.microsoft.com/net/sscli
Vous pouvez consulter en ligne les fichiers sources lURL : http://dotnet.di.unipi.it/
SharedSourceCli.aspx

Liens sur .NET


Nous vous proposons des liens vers les principaux sites consacrs au dveloppement sous .NET.
Notez quavec le temps, je compte mettre de plus en plus de ressources sur mon site http://
www.smacchia.com propos du dveloppement sous .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/

Liens sur .NET

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

Chapitre 1 : Aborder la plateforme .NET

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

Liens sur .NET

13

Dotnetguru blog agrg (Divers) http://www.dotnetguru.org/blogs/index.php


Eric Gunnerson (C  , divers) http://blogs.msdn.com/ericgu/
Frantz Bouma (Divers) http://weblogs.asp.net/fbouma
Fritz Onion (ASP.NET) http://pluralsight.com/blogs/fritz/default.aspx
Florin Lazar (Transactions) http://blogs.msdn.com/florinlazar/
Fredrik Normn (ASP.NET) http://fredrik.nsquared2.com/default.aspx
Jim Johnson (Transactions) http://pluralsight.com/blogs/jimjohn
Junfeng Zhang (GAC, versionning, CLR) http://blogs.msdn.com/junfeng/
Keith Brown (Scurit) http://pluralsight.com/blogs/keith/default.aspx
Krzysztof Cwalina (Design) http://blogs.msdn.com/kcwalina/
Martin Fowler (Architecture, pattern) http://martinfowler.com/bliki/
Matt Pietrek (Win32, Windows) http://blogs.msdn.com/matt_pietrek/
Michele Leroux Bustamente (Divers) http://www.dasblonde.com/
Miguel de Icaza (Mono) http://tirania.org/blog/
Mike Stall (Debug) http://blogs.msdn.com/jmstall/
Mike Taulty (Divers) http://mtaulty.com/blog
Pluarlsight blog agrg (Divers) http://pluralsight.com/blogs/default.aspx
Rico Mariani (Performances, GC) http://blogs.msdn.com/ricom/
Rockford Lhotka (Design, divers) http://www.lhotka.net/WeBlog/
Sahid Malik (ADO.NET, transactions) http://codebetter.com/blogs/sahil.malik/
Sam Gentile (Divers) http://samgentile.com/blog/
Scott Guthrie (ASP.NET) http://weblogs.asp.net/scottgu/
TheServerSide blog agrg (Divers) http://www.theserverside.net/blogs/index.tss
Valery Pryamikov (Scurit) http://www.harper.no/valery/
Wesner Moise (Divers, sujets pointus habituellement peu documents) http://wesnerm.
blogs.com/net_undocumented/

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.

Assemblages, modules et fichiers de ressource


Assemblages et modules
Un assemblage est une unit logique qui peut tre dfinie sur plusieurs fichiers appels modules.
Tous les fichiers constituant un assemblage doivent se trouver dans le mme rpertoire.
On a tendance a considrer quun assemblage est une unit physique (i.e un assemblage = un
fichier) car la plupart des assemblages nont quun seul module. Cela est en grande partie
du au fait que lenvironnement Visual Studio na pas la possibilit de gnrer des assemblage
multi modules. Comme nous allons lexposer, pour obtenir des assemblages multi modules, il
faut faire leort de travailler avec des outils en ligne de commande tels que le compilateur C 
csc.exe dcrit page 317, ou loutil al.exe dcrit page 37).
Parmi les modules dun assemblage, il y a un module principal qui joue un rle particulier car :

Tout assemblage en comporte un et un seul.

En consquence, un assemblage mono module se confond avec son module principal.

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

Chapitre 2 : Assemblages, modules, langage IL

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.

Assemblages, modules, types et ressources


La figure suivante utilise la notation UML pour rsumer les relations entre assemblages,
modules, types et ressources. On y voit quun mme module peut tre contenu dans plusieurs assemblages et quun mme type peut tre contenu dans plusieurs modules. Nous vous
conseillons vivement dviter ces deux techniques de rutilisation de code et de prfrer avoir
recours des assemblages bibliothques de types.

1..*

1..*

Fichier
module

1..*

1..*

Assemblage

Type .NET

Ressource
Fichier
ressource

Figure 2 -1 : Diagramme UML : Assemblages, Modules, Ressources

Pourquoi morceler un assemblage en plusieurs modules/fichiers de


ressources ?
On peut se demander quel est lintrt de morceler un assemblage en plusieurs modules. En
eet, cette possibilit est rarement exploite. Nous avons identifi les trois cas de figure suivants
o cette fonctionnalit apporte un rel avantage :

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.

Anatomie des modules

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.

Anatomie des modules


Notion de fichier Portable Executable (PE)
Un fichier PE (PE pour Portable Executable) est un fichier excutable par Windows. Un fichier
PE est en gnral dextension .exe ou .dll. Les premiers octets dun fichier PE constituent
un en-tte format interprt par Windows au moment o lexcutable est dmarr. Ces octets
contiennent des informations telles que le plus petit numro de version de Windows sur lequel
lexcutable peut tre utilis ou le fait que lexcutable est une application fentre ou une application console.
Le formatage des fichiers PE est optimis pour ne pas dgrader les performances. quelques
octets prs, limage en mmoire dun fichier PE qui est exploite par Windows lors de lexcution est identique au fichier PE. Cest pour cette raison que les fichiers PE sont parfois nomms
fichiers image.
Les modules sont des fichiers PE car la plateforme .NET utilise les services de Windows en ce qui
concerne le dmarrage des applications. Nous expliquons en page comment le CLR est charg
par Windows lors du dmarrage dune application .NET.
Vous entendrez aussi parler de format PE/COFF (pour Common Object File Format). Le format
COFF est utilis par le compilateur C++ lorsquil lie les fichiers objets. Lextension COFF du format PE/COFF est ignore par .NET.

Structure physique dun module


Chaque module comprend une section dentte CLR qui contient la version du CLR pour laquelle lassemblage a t compile. Cette section contient ventuellement une rfrence vers le
point dentre gr si le module en contient un.
Le module principal dun assemblage contient une section appele manifeste qui contient entre
autres les rfrences vers les autres modules et vers les fichiers de ressources. Les donnes du
manifeste sont parfois nommes mtadonnes de lassemblage.
Chaque module contient une section qui dcrit compltement son contenu (les types, les
membres des types, les dpendances entre types, les ressources...). Les informations dauto
description contenues dans cette section sont nommes mtadonnes (metadata en anglais).
Enfin, le module contient le code .NET compil en IL ainsi que les ressources. Le schma suivant
reprsente la structure physique dun module.

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

Chapitre 2 : Assemblages, modules, langage IL


Module principal
Entte
PE

Entte
CLR

Manifeste

Mtadonnes

Code
compil
en IL

Ressources

Autre module
Entte
PE

Entte
CLR

Mtadonnes

Code
compil
en IL

Ressources

Figure 2 -2 : Structure physique dun module

La table AssemblyDef : Cette table a une seule entre qui contient :

Le nom de lassemblage (sans extension, sans chemin).


La version de lassemblage.
La culture de lassemblage.
Des drapeaux dcrivant certaines caractristiques de lassemblage.
Une rfrence vers un algorithme de hachage.
La cl publique de lditeur (qui peut tre nulle ventuellement).
Toutes ces notions sont prsentes dans les pages qui suivent.

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.

Structure de la section mtadonnes de type


Les mtadonnes de type sont stockes dans des tables. Il existe trois sortes de tables de mtadonnes de type : les tables de dfinitions, les tables de rfrences et les tables de pointeurs.

Les tables de dfinition


Chaque table de dfinition renferme des informations propos dune catgorie dlment de
module (i.e une table rfrenant les mthodes de toutes les classes, une table rfrenant toutes
les classes etc). Nous ne prsentons pas toutes ces tables mais voici les plus importantes :

Anatomie des modules

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.

Les tables de rfrences


Les tables de rfrences contiennent les informations sur les lments rfrencs par le module.
Les lments rfrencs peuvent tre dfinis dans dautres modules du mme assemblage ou
dfinis dans dautres assemblages. Voici quelques tables de rfrences couramment utilises :

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

Chapitre 2 : Assemblages, modules, langage IL

Les tables de pointeurs


Les tables de pointeurs permettent la compilation de rfrencer des lments du code encore
inconnus (un peu comme les dclaration en avant dans C++). En changeant lordre de dfinition
des lments de votre code, vous pouvez rduire le contenu de ces tables. On peut citer les tables
MethodPtr, ParamPtr ou FieldPtr.

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.

Analyse dun assemblage avec ildasm.exe et Reflector


Construction de lassemblage analyser
Nous allons crer un assemblage avec :

Un module principal Foo1.exe.

Un module Foo2.netmodule.

Un fichier de ressource Image.jpg.

Analyse dun assemblage avec ildasm.exe et Reflector

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

Lancez lexcutable Foo1.exe, le programme ache sur la console :


Bonjour de Foo1
Bonjour de Foo2

Analyse dun module avec loutil ildasm.exe


On peut analyser le contenu dun assemblage ou dun module avec loutil ildasm.exe fourni
avec lenvironnement de dveloppement .NET. ildasm signifie dsassembleur de code IL. Cet outil se trouve dans le rpertoire <repertoie-d-installation-de-VisualStudio>\SDK\v2.0\Bin.
Chargez le fichier Foo1.exe avec ildasm.exe :

22

Chapitre 2 : Assemblages, modules, langage IL

Figure 2 -3 : Vue gnrale de ildasm

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 )

Analyse dun assemblage avec ildasm.exe et Reflector

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.

Analyse de la classe Foo.Program


En double cliquant sur la classe Foo.Program, on obtient la description de cette classe dans une
nouvelle fentre :
.class private auto ansi beforefieldinit Foo.Program
extends [mscorlib]System.Object
{
} // end of class Foo.Program
On voit clairement les drapeaux associs la classe Program (private...) et que cette classe drive
de la classe System.Object.

Analyse du code de la mthode Main()


En double cliquant par exemple la mthode Main(), on obtient le code en langage IL de cette
mthode dans une nouvelle fentre. Nous prsenterons brivement le langage IL la fin de ce
chapitre :
.method public hidebysig static void Main(string[] argv) cil managed{
.entrypoint
// Code size
31 (0x1f)
.maxstack 1
.locals init (class [.module Foo2.netmodule]Foo.UneClasse V_0)
IL_0000: ldstr
"Bonjour de Foo1"
IL_0005: call
void [mscorlib]System.Console::WriteLine(string)
IL_000a: nop
IL_000b: newobj ...
... instance void [.module Foo2.netmodule]Foo.UneClasse::.ctor()
IL_0010: stloc.0
IL_0011: ldloc.0
IL_0012: call
void [mscorlib]System.Console::WriteLine(object)
IL_0017: nop
IL_0018: call
int32 [mscorlib]System.Console::Read()
IL_001d: pop
IL_001e: ret
} // end of method Program::Main

24

Chapitre 2 : Assemblages, modules, langage IL

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 :

Figure 2 -4 : Vue gnrale de Reflector


En plus dun interface conviviale, Reflector prsente un panel de fonctionnalits trs intressantes et absentes de ildasm.exe telles que :

Attributs dassemblage et versionning

25

La dcompilation en C  ou en VB.NET du code en IL dun assemblage ; par exemple la


dcompilation en VB.NET de la mthode Program.Main() est :
Public Shared Sub Main(ByVal argv As String())
Console.WriteLine("Bonjour de Foo1")
Dim classe1 As New UneClasse
Console.WriteLine(classe1)
Console.Read
End Sub

La possibilit de construire localement pour un lment du code le graphe des appelants


et des appels.

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  .

Attributs dassemblage et versionning


Attributs standard associables aux assemblages
Pour aborder cette section il faut tre familier avec la notion dattribut .NET. Si vous ne savez
pas ce quest un attribut .NET nous vous conseillons de lire la section page 248.
Vous avez la possibilit dutiliser certains attributs du framework pour ajouter des informations
telle que son numro de version lassemblage courant. Ces attributs doivent tre dclars
dans un fichier source de lassemblage aprs les dclaratives using et avant les lments du
programme. Si vous ditez un projet sous lenvironnement de dveloppement Visual Studio, les
attributs relatifs lassemblage se trouvent par dfaut dans le fichier AssemblyInfo.cs.
Pour illustrer ceci, le code de l utilise lattribut AssemblyCompanyAttribute qui est un attribut
dassemblage permettant de prciser le nom de lditeur de logiciel qui a fabriqu lassemblage.
Si vous regardez les proprits du module principal Foo1.exe, linformation Lentreprise
est visible, comme le montre la figure 2-5 ci-dessous :
Voici la liste des attributs standard relatifs aux assemblages les plus utiliss :

Les attributs dinformation :

AssemblyCompany : associe lassemblage le nom de lditeur de logiciel qui la dvelopp.

AssemblyProduct : associe lassemblage le nom du produit (de la solution) auquel il


appartient.

AssemblyTitle : associe un titre lassemblage.

AssemblyDescription : associe une description lassemblage.

AssemblyFileVersion : associe un numro de version du fichier produit par la compilation.

AssemblyInformationalVersion : associe le numro de version du produit.

26

Chapitre 2 : Assemblages, modules, langage IL

Figure 2 -5 : Proprits dun fichier dun assemblage

Les attributs utiliss pour la constitution du nom fort (voir un peu plus loin pour la dfinition dun assemblage nom fort) :

AssemblyVersion : spcifie le numro de version de lassemblage utilis pour constituer


son nom fort.

AssemblyCulture : spcifie la culture de lassemblage.

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.

Numro de version dun assemblage


Un numro de version est compos de quatre numros :

Le numro majeur.

Le numro mineur.

Le numro de compilation.

Attributs dassemblage et versionning

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 majeur : incrmenter lorsqu un ensemble significatif de fonctionnalit ont


t ajout.

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.

Le numro de compilation : incrmenter chaque recompilation du produit destine


tre utilise lextrieur de lentreprise (en gnral pour correction de bug mineur).

Le numro de rvision : incrmenter chaque compilation.

Notion dassemblages amis


Bien souvent, lorsque vous dveloppez un framework contenant plusieurs assemblages vous rencontrez le problme suivant : vous souhaitez que les types dfinis dans un assemblage du framework, soit accessibles partir du code des autres assemblages du framework sans toutefois tre
accessibles partir du code des assemblages qui utilisent le framework. Aucun des niveaux de visibilit des types du CLR public et internal sont mme de rsoudre ce problme. Dans cette situation, il faut avoir recours lattribut dassemblage System.Runtime.CompilerServices.InternalsVisibleToAttribute. Cet attribut permet de spcifier des assemblages qui ont accs aux types
non publics de lassemblage sur lequel il sapplique. On dit que se sont des assemblages amis de
lassemblage qui contient les types non publics.

28

Chapitre 2 : Assemblages, modules, langage IL

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")]
...

Assemblage nom fort (strong naming)


Introduction
Depuis quelques annes, les systmes dexploitation Windows prsentent une technologie nomme Authenticode permettant dauthentifier un fichier excutable. Cest--dire que lorsque vous
excutez un programme sur votre machine, vous pouvez tre certains que ce programme est
bien celui qui a t dvelopp par lentreprise dans laquelle vous avez confiance. Cette technologie fait lobjet dune section en page 230.
La technologie du nom fort (strong name en anglais) que nous allons dcrire dans la prsente
section peut potentiellement servir authentifier les assemblages .NET dans le cas de grosses
organisations telles que Microsoft ou lECMA. Cependant, elle est surtout destine nommer les
assemblages dune manire unique. Cette possibilit est indite puisque jusqu prsent, on utilisait exclusivement les noms des fichiers pour identifier les units de dploiement. Avec .NET,
lorsquun assemblage volue vers de nouvelles versions, il y a plusieurs fichiers avec le mme
nom mais pas la mme version. Il existe un rpertoire spcial prvu pour stocker plusieurs
versions dun mme assemblage. Ce rpertoire, appel le rpertoire GAC, est prsent page 64.
Concrtement un assemblage acquiert un nom fort lorsquil est sign numriquement. Lorsquun assemblage a un nom fort, ce nom peut tre format en une chane de caractres contenant ces quatre informations :

Le nom du fichier ;

le jeton de cl publique de la signature numrique (tous ces termes sont expliqus cidessous) ;

la version de lassemblage, celle spcifie avec lattribut AssemblyVersion, prsent dans la


section prcdente ;

la culture de lassemblage, que nous dtaillons dans la prochaine section.

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.

Assemblage nom fort (strong naming)

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

Chapitre 2 : Assemblages, modules, langage IL

Public key token is f2bf46103b24f5f0


En fait cet outil ache la cl publique (crite en gras) puis la cl prive. La phrase public key
is : est donc errone. De mme, ce qui est prsent comme tant le jeton de cl publique est
une valeur de hachage du couple cl publique/cl prive. Cependant si Microsoft a fait le choix de
ces termes, cest parce quil suppose que la plupart du temps le mcanisme de signature retarde
(expos un peu plus loin) est utilis. Dans ce cas ces termes prennent toute leur signification.
Lors de la fabrication dune couple cl publique/cl prive par loutil sn.exe, la cl publique fait
128 octets (gris) + 32 octets den-tte (non gris) et la cl prive fait 436 octets (non gris).

Les jetons de cls publiques


La cl publique tant volumineuse et dicile manipuler, larchitecture .NET associe un jeton
de cl publique (public key token en anglais) chaque cl publique. Un jeton de cl publique est
un numro cod sur huit octets. Ce numro est calcul partir de la cl publique en utilisant un
algorithme de hachage. Il identifie dune manire quasi unique la cl publique, tout en tant
beaucoup plus maniable de par sa taille rduite. Les jetons de cls publiques ont t crs pour :

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.

Un assemblage sign avec la cl prive dont le jeton de la cl publique correspondante est


b03f5f7f11d50a3a, est garanti avoir t produit par Microsoft. Un assemblage sign avec la cl
prive dont le jeton de la cl publique correspondante est b77a5c561934e089, est garanti avoir
t produit par lorganisme ECMA. Ces armations ne seraient plus valables, si un jour une de
ces cls prives tait pirate.

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.

Assemblage nom fort (strong naming)

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

Figure 2 -6 : Signer un assemblage

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

Chapitre 2 : Assemblages, modules, langage IL


[mscorlib]System.Reflection.AssemblyKeyFileAttribute::.ctor(string) =
( 01 00 08 43 6C 65 73 2E 73 6E 6B 00 00 ) // ...Cles.snk..
[mscorlib]System.Diagnostics.DebuggableAttribute::.ctor
(bool, bool) = ( 01 00 00 01 00 00 )
.publickey = (

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

Assemblage nom fort (strong naming)

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.

Le mcanisme de signature retarde


Utiliser un systme de signature numrique pour rendre ses assemblages infalsifiables est une
formidable fonctionnalit quore .NET. Cependant la mise en uvre de cette technique oblige
fournir un accs en lecture aux fichiers contenants les couples cl publique/cl prive tous
ceux qui sont susceptibles de compiler un assemblage nom fort dans lentreprise. Dans une
entreprise comme Microsoft, ils sont des milliers. Nul doute quaprs un certain temps, un employ finirait par divulguer la cl prive sur internet. La seule manire de limiter ce risque est
de limiter le nombre demploys qui ont accs la cl prive. .NET fournit le mcanisme de
signature retarde cet eet. Concrtement un assemblage est sign avec la cl prive aprs avoir
t compil et test et seulement avant dtre packag et dploy chez les clients. Dans une
grande entreprise, seul un ou quelques employs sont habilits faire cette manipulation. Le
mcanisme de signature retarde ncessite trois tapes :

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

Chapitre 2 : Assemblages, modules, langage IL

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.

Internationalisation et assemblages satellites


Notion de culture et de globalisation dune application
Certaines applications doivent pouvoir tre utilises par des utilisateurs de direntes nationalits parlant direntes langues. Tous ce qui peut tre prsent lutilisateur par lapplication
(chanes de caractres, images, animations, sons, dates, nombres virgules etc) doit alors tre
fournis pour chaque culture supporte par lapplication. On appelle ce processus la globalisation
ou parfois linternationalisation voire la localisation de lapplication.
Une culture est une association pays-langue/dialecte telle que fr-FR pour le franais en
France, fr-CA pour le franais au Canada ou en-US pour langlais des Etats Unis. La liste
des cultures standard du framework .NET est prsente dans larticle CultureInfo Class des
MSDN.
Nous avons vu quune culture peut tre prcise dans le nom fort dun assemblage. Nanmoins,
vous tes oblig dutiliser la culture neutre pour tout assemblage contenant du code. Concrtement, la chane de caractres spcifie pour lattribut CultureInfo doit toujours tre vide
lorsque votre assemblage contient du code. Pour indexer les ressources partir dune culture,
il faut crer un assemblage pour chaque culture, qui ne contient que des ressources. Ces assemblages particuliers qui font lobjet de la prsente section sont appels les assemblages satellites.

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).

Inclure le fichier dextension .resources dans un assemblage (satellite ou non);

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 :

Internationalisation et assemblages satellites

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

Ne convertissez pas un fichier de ressources au format resx ou resources qui contient au


moins une ressource qui nest pas une chane de caractres, en un fichier de ressources au
format txt.

Utiliser des ressources dans un assemblage


La classe System.Resources.ResourceManager doit tre utilise pour exploiter les ressources
selon la culture courante. Cependant, on prfre en gnral se servir dune classe gnre par
loutil resgen.exe pour encapsuler les accs cette classe. Le fichier source dune telle classe peut
tre gnr partir dun fichier de ressources au format .txt, .resx ou .ressources avec loption
/str :
>resgen.exe MyRes.resources /str:cs
Loption /str:cs indique que nous souhaitons gnrer notre fichier source en C  . Nous aurions
pu aussi spcifier /str:vb pour lobtenir en VB.NET. Voici un extrait pertinent :

36

Chapitre 2 : Assemblages, modules, langage IL

Exemple :

MyRes.cs

internal class MyRes {


private static System.Resources.ResourceManager resourceMan ;
private static System.Globalization.CultureInfo resourceCulture ;
...
internal static System.Resources.ResourceManager ResourceManager {
get {
if ((resourceMan == null)) {
System.Resources.ResourceManager temp = new
System.Resources.ResourceManager("MyRes",
typeof(MyRes).Assembly) ;
resourceMan = temp ;
}
return resourceMan ;
}
}
internal static global::System.Globalization.CultureInfo Culture {
get { return resourceCulture ; } set{ resourceCulture = value ; } }
...
internal static string Bonjour {
get{return ResourceManager.GetString("Bonjour", resourceCulture);}}
...
internal static string AuRevoir {
get {return ResourceManager.GetString(
"AuRevoir", resourceCulture) ; } }
}
On comprend alors quen plus dencapsuler laccs la classe ResourceManager, la classe MyRes
prsente des proprits qui permettent daccder aux ressources dune manire type. Il ne nous
reste plus qu utiliser ces proprits pour pouvoir exploiter nos ressources. Le programme suivant ache hello :
Exemple 2-5 :

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") ) ;

Internationalisation et assemblages satellites

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.

Fabriquer un assemblage satellite


Nous avons vu comment encapsuler des ressources dans un assemblage et comment les exploiter. Jusquici, nous navons pas utilis une culture en particulier. Les ressources que nous avons
exploites taient relatives ce que lon nomme la culture invariante. Cest la culture prise par
dfaut lorsque aucune culture nest spcifie. Intressons nous maintenant la notion dassemblage satellite qui permet de diversifier les cultures pour une mme application.
Loutil al.exe (al pour assembly linker), utilisable en ligne de commande, permet de produire
un assemblage bibliothque (i.e un assemblage dont le module principal est dans un fichier
dextension .dll) partir dun ou plusieurs modules. al.exe permet donc de construire des
assemblages tels que les assemblages satellites qui ne contiennent que des ressources. En page 67
nous expliquons que loutil al.exe permet aussi la cration dassemblages de stratgie dditeur.
Un assemblage satellite donn contient des ressources relatives une seule culture. Voici un
exemple dutilisation de al.exe pour construire un assemblage satellite. Notez lutilisation de
loption /c pour prciser la culture (en loccurrence, espagnole) :
>al.exe /out:es-ES\Program.Resources.dll /c:es-ES
/embed:es-ES\MyRes.es-ES.resources

38

Chapitre 2 : Assemblages, modules, langage IL

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.

Dployer et utiliser un assemblage satellite


La classe System.Resources.ResourceManager sait grer la notion dassemblage satellite.
Elle est notamment capable de charger un assemblage satellite lexcution. La culture
qui est prise en compte est celle qui est prcise par la valeur de la proprit CultureInfo
Thread.CurrentUICulture{get;set} du thread courant. Pour bnficier de ce service, il est
ncessaire de se plier une certaine discipline :

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

Si le programme suivant sexcute dans le contexte ci-dessus, il ache Hola! :


Exemple 2-8 :

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 :

Internationalisation et assemblages satellites


Exemple 2-9 :

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.

viter les exceptions dues lchec de la recherche dune ressource


Il est fortement conseill dinclure un fichier de ressource correspondant la culture invariante
dans lassemblage contenant le code. Ainsi, lexcution, si lassemblage satellite correspondant
la culture en cours nest pas trouv ou si celui-ci ne contient pas la traduction de certaines
ressources, la classe ResourceManager fera en sorte de fournir la valeur de la culture invariante.
Si cette valeur ntait pas connue, une exception serait lance.
Pour vous prmunir contre labsence dun assemblages satellites, vous pouvez vrifier son existence avec la mthode ResourceManager.GetResourceSet(). Par exemple :
Exemple 2-10 :

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

Chapitre 2 : Assemblages, modules, langage IL

Visual Studio et les assemblages satellites


Maintenant que nous avons compris le rle des assemblages satellites et des outils resgen.exe et
al.exe nous pouvons montrer comment exploiter Visual Studio 2005 pour simplifier le processus
de globalisation dun assemblage.
Par dfaut, un projet Visual Studio 2005 na pas de fichier de ressources. Vous pouvez en ajouter
avec le menu Project  Add  New Item...  Resources file. Un fichier ressource dextension .resx
est alors ajout au projet. la compilation du projet, ce fichier sera automatiquement compil
en un fichier dextension .resources. Ce fichier .resource sera intgr dans lassemblage compil
ou dans un assemblage satellite selon que Visual Studio peut dterminer la culture laquelle
il se rfre en analysant son nom. Par exemple, un projet nomm MyProject qui contient les
fichiers ressources suivants ...
Res1.resx
Res1.en-US.resx
Res1.fr-FR.resx
Res2.resx
Res2.es-ES.resx
...se compile en ceci :
~/MyProject.exe
contient Res1.resources et Res2.resources
~/en-US/MyProject.Resources.dll contient Res1.en-US.resources
~/fr-FR/MyProject.Resources.dll contient Res1.fr-FR.resources
~/es-ES/MyProject.Resources.dll contient Res2.es-ES.resources
Par dfaut, un fichier source nomm XXX.designer.cs est associ chaque fichier ressources
XXX.resx ajout un projet. Ce fichier source contient une classe nomme [Nom du projet].XXX similaire celle qui est gnre par loption /str de loutil resgen.exe. En gnral, seuls
les fichiers de ressources relatifs la culture invariante (i.e Res1.resx et Res2.resx dans notre
exemple) ont besoin davoir une classe associe. Aussi, sachez que pour dsactiver la gnration
de ce fichier source, il sut de faire : proprit du fichier XXX.resx  Custom Tool  Mettre une
chane de caractres vide la place de la valeur ResXFileCodeGenerator
Visual Studio 2005 contient un diteur de fichier ressources dextension .resx. Vous pouvez trs
facilement ajouter des couples identifiant/chanes de caractres ou identifiant/fichier. Dans ce second cas le fichier peut tre un fichier image, icone, son, texte ou autre.
En page 695 nous dcrivons les facilits de Visual Studio 2005 pour localiser les fentres dune
application Windows Forms 2.0. En page 953 nous dcrivons les facilits de Visual Studio 2005
pour localiser les pages dune application web ASP.NET 2.

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.

Prsentation de la pile et des instructions IL correspondantes


Pour comprendre cette section, il est ncessaire davoir assimil la notion dunit dexcution
(i.e la notion de thread) prsente page 136.

42

Chapitre 2 : Assemblages, modules, langage IL

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.

Exemple 1 : les variables locales et la pile


Le code dune mthode C  suivant...
...
{
int i1 =5 ;
int i2 =6 ;
int i3 = i1+i2 ;
}
...
...produit le code IL suivant :
.maxstack 2
.locals ([0] int32 i1,
[1] int32 i2,
[2] int32 i3)
IL_0000: ldc.i4.5
IL_0001: stloc.0
IL_0002: ldc.i4.6
IL_0003: stloc.1
IL_0004: ldloc.0
IL_0005: ldloc.1
IL_0006: add
IL_0007: stloc.2
IL_0008: ret
Plusieurs remarques peuvent tre faites :

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.

Exemple 2 : les appels de mthodes et la pile


La pile peut tre vue comme un empilage de fentres de pile (stack frames en anglais). Chaque
appel de mthode correspond la cration dune fentre de pile sur le haut de la pile du thead
courant. Chaque retour de mthode correspond la destruction de la fentre de pile situe sur
le haut de la pile.
Le code C  de la mthode Main() suivante...
Exemple 2-12 :
class Program{
static int f(int i1, int i2){
return i1+i2 ;
}
public static void Main(){
int i1 =5 ;
int i2 =6 ;
int i3 = f(i1,i2) ;
}
}
...produit le code IL suivant pour la mthode Main() :

44

Chapitre 2 : Assemblages, modules, langage IL


.maxstack 2
.locals ([0] int32 i1,
[1] int32 i2,
[2] int32 i3)
IL_0000: ldc.i4.5
IL_0001: stloc.0
IL_0002: ldc.i4.6
IL_0003: stloc.1
IL_0004: ldloc.0
IL_0005: ldloc.1
IL_0006: call
int32 Program::f(int32,int32)
IL_000b: stloc.2
IL_000c: ret

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 :

dpile les arguments de lappel et les stocke en mmoire ;

cre une nouvelle fentre de pile en haut de la pile du thread ;

eectue lappel la mthode f().

Le code suivant est produit par le compilateur C  pour la mthode f() :


.maxstack 2
.locals init ([0] int32 CS$00000003$00000000)
IL_0000: ldarg.0
IL_0001: ldarg.1
IL_0002: add
IL_0003: stloc.0
IL_0004: br.s
IL_0006
IL_0006: ldloc.0
IL_0007: ret
Linstruction IL ldarg sert charger la valeur dun argument sur la pile. Rappelez-vous que les
valeurs des arguments ont t sauves en mmoire par linstruction call. Attention, les arguments sont indexs partir de zro dans une mthode statique (comme f()) et partir de un
dans une mthode non statique. En eet lors de lappel dune mthode non statique, largument
index par zro est rserv implicitement pour la rfrence this.
La valeur de retour de la mthode doit tre la seule valeur prsente dans la fentre de pile de
la mthode, juste avant lappel linstruction ret. Linstruction ret indique un retour de la
mthode (donc la destruction de la fentre de pile qui lui est associe) mais garde la valeur de
retour en haut de la pile de faon ce que la mthode appelante puisse la rcuprer.
Notez que le code suivant, qui ne fait pas intervenir de variables locales, aurait t valide aussi :
.maxstack 2
IL_0000: ldarg.0
IL_0001: ldarg.1
IL_0002: add
IL_0003: ret

Introduction au langage IL

45

Instructions IL de comparaison et de branchement


Il existe beaucoup dinstructions IL de comparaison des deux valeurs au sommet de la pile. Leurs
noms est, une fois de plus, emprunt aux langages machines existants : ceq (compare val1 equal
val2), cgt (compare val1 greater than val2), clt(compare val1 less than val2).
Nous prcisons que val2 est au sommet de la pile, donc val1 est juste en dessous. De plus val1
et val2 sont dpiles et une valeur entire est place au sommet, 1 si la comparaison est vraie
sinon 0.
Il existe une famille dinstructions IL pour le branchement. La plupart commencent par b
ou br et prennent en paramtre loset localisant linstruction IL, sur laquelle, lunit dexcution doit (ventuellement) se brancher. Linstruction de branchement inconditionnel sappelle br. Toutes les autres instructions sont des branchements conditionnels, cest--dire que
le branchement ne se fait qu une condition. Par exemple linstruction brtrue neectue le
branchement qu la condition que la valeur au sommet de la pile soit non nulle. Linstruction
beq neectue le branchement qu la condition que les deux valeurs au sommet de la pile soient
gales. Les significations des instructions brfalse (Branch if False), blt (Branch if Lower Than),
ble (Branch if Lower or Equal), bgt (Branch if Greater Than), bge (Branch if Greater or Equal),
bne (Branch if Not Equal) ... dcoulent naturellement de tout ceci.

IL et la programmation oriente objet


Analysons le code IL gnr partir dun petit programme C  objet.
Exemple 2-13 :
class Program {
public override string ToString() { return "Program m_i=" + m_i ; }
int m_i = 9 ;
Program(int i) { m_i = i ; }
public static void Main() {
object obj = new Program(12) ;
obj.ToString() ;
}
}
Voici le code IL de la mthode Main() :
.maxstack 2
.locals init ([0] object obj)
IL_0000: ldc.i4.s
12
IL_0002: newobj
instance void Program::.ctor(int32)
IL_0007: stloc.0
IL_0008: ldloc.0
IL_0009: callvirt
instance string [mscorlib]System.Object::ToString()
IL_000e: ret
On voit quune instruction IL spciale nomme newobj, cre un objet, appelle le constructeur
de la classe et place la rfrence de lobjet sur la pile. On voit que lappel la mthode virtuelle
Program.ToString() (qui redfinie la mthode object.ToString()) se fait avec linstruction IL
callvirt. Avant cet appel, la rfrence de lobjet sur lequel la mthode est appele, est place
sur la pile. Le polymorphisme est donc support par linstruction IL callvirt.

46

Chapitre 2 : Assemblages, modules, langage IL

Notez quen page 498 nous exposons les modifications principales du langage IL pour le support
la gnricit.

Les jetons de mtadonnes


Les tables des mtadonnes de type sont souvent rfrences par le code IL. Lorsquil dsassemble le code IL, ildasm.exe met les noms et les signatures des mthodes directement dans
le code IL dsassembl. En fait, au niveau binaire, le code IL na pas de chanes de caractres
dfinissant le nom des mthodes. Pour rfrencer une mthode, le code IL contient des valeurs
de quatre octets qui pointent vers les tables de la section mtadonnes de type. On appelle ces
valeurs de quatre octets les jetons de mtadonnes (metadata token en anglais).
Le premier octet dun jeton de mtadonnes rfrence la table de mtadonnes. Les trois autres
octets rfrencent un lment dans cette table. Par exemple la table rfrenant les membres
utiliss par un module (la table MethodRef) ayant le numro 10, un jeton de mtadonnes vers
le membre 5 de cette table est : 0x0A000005.
Les jetons de mtadonnes peuvent tre vus en choisissant loption Show token values de ildasm.
exe. Par exemple le code de la mthode Main() du programme suivant...
Exemple 2-14 :
using System ;
class Program{
public static void Main(){
Console.WriteLine( "Hello world!" ) ;
}
}
...contient le code IL suivant : (vu avec la visualisation des jetons de mtadonnes sous ildasm.
exe) :
.method /*06000001*/ public hidebysig static
void Main() cil managed
{
.entrypoint
// Code size
11 (0xb)
.maxstack 1
IL_0000: ldstr
"Hello world!" /* 70000001 */
IL_0005: call
void [mscorlib/* 23000001 */]
System.Console/* 0100000F */::WriteLine(string) /* 0A00000E */
IL_000a: ret
} // end of method Program::Main
Les jetons de mtadonnes vus dans cet exemple sont :

0x06000001 : rfrence lentre dans la table MethodDef (0x06) reprsentant la mthode


Main(0x000001).

0x70000001 : rfrence lentre dans le tas #userstring (0x70) reprsentant la chane de caractres "Hello world!" (0x000001).

0x23000001 : rfrence lentre dans la table AssemblyRef (0x23) reprsentant lassemblage


mscorlib (0x000001).

Introduction au langage IL

47

0x0100000F : rfrence lentre dans la table TypeRef (0x01) reprsentant la classe System.
Console (0x00000F).

0x0A00000E : rfrence lentre dans la table MemberRef (0x0A) reprsentant la mthode


WriteLine (0x00000F). Notez quici, le jeton de mtadonnes de la mthode WriteLine
nest pas dans la table MethodDef car cette mthode est dfinie dans un autre module (et
mme, un autre assemblage).

3
Construction, configuration et
dploiement des applications .NET

Construire vos applications avec MSBuild


La plateforme .NET 2.0 est livre avec un nouvel outil nomm msbuild.exe. Cet outil sert
construire les applications .NET. Il accepte en entre des fichiers XML qui dcrivent lenchanement des tches du processus de construction, un peu dans le mme esprit que des fichiers
makefile. Dailleurs, au dbut du dveloppement de ce projet, Microsoft lavait baptis XMake.
Lexcutable msbuild.exe est situ dans le rpertoire dinstallation de .NET savoir [Rep_d
installation_de_Windows]\Microsoft.NET\Framework\v2.0.XXXX\. Il est prvu que MSBuild
fasse partie du systme dexploitation Windows Vista. Son rayon daction augmentera alors et il
pourra tre utilis pour construire tous types dapplication.
Jusquici, pour construire vos applications .NET, vous deviez :

Soit utiliser la commande Build de Visual Studio.

Soit utiliser lexcutable de Visual Studio devenv.exe en ligne de commande.

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

Chapitre 3 : Construction, configuration et dploiement des applications .NET

et document. Le support de MSBuild est donc une volution consquente de Visual Studio qui
jusquici, utilisait des scripts de compilation non documents.

MSBuild : Cibles, tches, proprits, items et conditions


Fichier .proj, cibles et tches
Llment racine de tous documents XML MSBuild est <Project>. Cet lment contient des lments <Target>. Ces lments <Target> constituent les units de construction nommes cibles.
Un script MSBuild peut contenir plusieurs cibles et msbuild.exe est capable denchaner les
excutions de plusieurs cibles. Lorsque vous lancer msbuild.exe en ligne de commande, il prend
en entre le seul fichier dextension .proj du rpertoire courant. Si plusieurs fichiers .proj sont
prsents, il faut prciser en argument de ligne de commande le fichier .proj que msbuild.exe
doit utiliser. Un seul fichier peut tre prcis.
Une cible MSBuild est un ensemble de tches MSBuild. Chaque lment enfant de llment
<Target> constitue la dfinition dune tche. Les tches dune cible sont excutes en srie dans
leur ordre de dclaration. Une quarantaine de types de tches sont fournis par MSBuild comme
par exemple :
Type de tche

Description

Copy

Copie des fichiers dun rpertoire source vers un rpertoire destination.

MakeDir

Construit un rpertoire.

Csc

Exploite le compilateur C  csc.exe.

Exec

Excute une commande systme.

AL

Exploite loutil al.exe (Assembly Linker)

ResGen

Exploite loutil resgen.exe (Resources Generator).

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 :

MSBuild : Cibles, tches, proprits, items et conditions


Exemple 3-1 :

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 MakeDir qui construit le rpertoire \bin.

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

Chapitre 3 : Construction, configuration et dploiement des applications .NET


Foo.proj

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

Rpertoire qui stocke le script MSBuild courant.

MSBuildProjetFile

Nom du fichier script MSBuild courant.

MSBuildProjectExtension

Extension du fichier script MSBuild courant.

MSBuildProjectFullPath

Chemin complet du fichier script MSBuild courant.

MSBuildProjectName

Nom du fichier script MSBuild courant sans lextension.

MSBuildPath

Rpertoire qui stocke le fichier msbuild.exe.

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 :

MSBuild : Cibles, tches, proprits, items et conditions


Exemple 3-3 :

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" />

Poser des conditions


On peut souhaiter quun mme script MSBuild se dcline sous plusieurs versions. Par exemple,
il serait dommage de devoir crire et maintenir deux scripts pour chaque projet, un pour la
construction en mode Debug et un pour la construction en mode Release. Aussi, MSBuild introduit la notion de condition. Un attribut Condition peut tre appliqu pratiquement nimporte
quel lment dun script MSBuild (proprits, item, cible, tche, groupe de proprits, groupe
ditems etc). Si lexcution la condition dun lment nest pas ralise, le moteur MSBuild
lignorera. Larticle MSBuild Conditions des MSDN dcrit la liste des expressions de condition
qui peuvent tre prcises.
Dans lexemple suivant, nous exploitons les conditions de type test de lgalit de deux chanes
de caractres pour faire en sorte que notre script exemple supporte le mode Debug et Release.
Nous utilisons aussi une condition de type test de lexistence dun fichier ou dune rpertoire pour
nexcuter la tche MakeDir que si le rpertoire crer nexiste pas. Cette condition nest l que
pour des besoins pdagogiques car la tche MakeDir ne sexcute pas si le rpertoire crer existe
dj :
Exemple 3-4 :
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Condition="$(Configuration)==Debug">
<Optimize>false</Optimize>
<DebugSymbols>true</DebugSymbols>

Foo.proj

54

Chapitre 3 : Construction, configuration et dploiement des applications .NET


<OutputPath>.\bin\Debug</OutputPath>
</PropertyGroup>
<PropertyGroup Condition="$(Configuration)==Release">
<Optimize>true</Optimize>
<DebugSymbols>false</DebugSymbols>
<OutputPath>.\bin\Release</OutputPath>
</PropertyGroup>
<ItemGroup>
...
<Target Name="FooCompilation">
<MakeDir Directories= "$(OutputPath)"
Condition="!Exists($(OutputPath))"/>
...

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">
...

Concepts avancs de MSBuild


Construction incrmentale et dpendances entre cibles
Dans un environnement rel, lexcution dun script MSBuild peut prendre plusieurs minutes
voire plusieurs heures pour sexcuter. Pour lanecdote, sachez que depuis ses dbuts le systme
dexploitation Windows met environs 12 heures se compiler. Cela signifie que le volume grandissant de code compiler vient compenser la monte en puissance des machines.
Il nest pas souhaitable de relancer compltement une construction pour un changement mineur eectu dans un fichier source sur lequel aucun autre composant ne dpend. Aussi, vous
pouvez utiliser la notion de construction incrmentale. Pour cela vous devez prciser la liste des
items en entre et des items en sortie dune cible au moyen des attributs Inputs et Outputs. Si
MSBuild dtecte quau moins un item en entre est plus vieux quau moins un item en sortie, il
prend la dcision dexcuter la cible.
Cette technique de construction incrmentale vous oblige partitionner lensemble de vos
tches en plusieurs cibles. Nous avons vu que si vous prcisez plusieurs cibles excuter

Concepts avancs de MSBuild

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.

Fractionner un script MSBuild sur plusieurs fichiers


Nous avons vu que loutil msbuild.exe ne peut traiter plus dun fichier script chaque excution. Cependant, un fichier script MSBuild peut importer un autre fichier script MSBuild
au moyen de llment <Import>. Dans ce cas, tous les lments enfants de llment racine
<Project> du fichier import sont copis en lieu et place de llment <Import> responsable
de limportation. Notre script exemple peut ainsi tre fractionn sur deux fichiers Foo.proj et
Foo.target.proj comme ceci :
Exemple 3-7 :
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"
DefaultTargets="FooCompilation">
<PropertyGroup Condition="$(Configuration)=="> ...
<PropertyGroup Condition="$(Configuration)==Debug"> ...
<PropertyGroup Condition="$(Configuration)==Release"> ...
<ItemGroup> ...
<Import Project="Foo.target.proj"/>
</Project>

Foo.proj

56

Chapitre 3 : Construction, configuration et dploiement des applications .NET

Exemple 3-8 :

Foo.target.proj

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" >


<Target Name="CreeOutputPath"
...
<Target Name="FooCompilation"
...
</Project>

Intgration de MSBuild avec Visual Studio


Nous avons dj prcis que les fichiers dextension .proj, .csproj, .vbproj etc gnrs par
Visual Studio 2005 pour construire les projets sont rdigs au format XML MSBuild. Si vous analysez un tel fichier, vous vous apercevrez quaucune cible ny est explicitement dfinie. En fait,
les fichiers scripts gnrs par Visual Studio 2005 importent des fichiers dextensions .targets
qui contiennent des cibles gnriques. Par exemple, un fichier dextension .csproj contient
llment suivant :
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
Ce fichier Microsoft.CSharp.targets contient deux cibles gnriques :

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.

Crer vos propres tches MSBuild


Un aspect particulirement intressant de MSBuild est que chaque 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. Une telle classe doit supporter les contraintes suivantes :

Elle doit implmenter linterface ITask dfinie dans lassemblage Microsoft.Build.


Framework.dll ou driver de la classe helper Task dfinie dans lassemblage Microsoft.
Build.Utilities.dll.

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) :

Concepts avancs de MSBuild


Exemple 3-9 :

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

Chapitre 3 : Construction, configuration et dploiement des applications .NET

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.

Les paramtres de configuration standard


Tout fichier de configuration .NET admet un lment racine <configuration> :
<configuration
xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">
Voici les principaux sous lments standard de llment <configuration> :

<appSettings> : Contient les lments de configuration propres lapplication.

<configSections> : Permet de dfinir les sections de configuration que nous prsentons un


peu plus loin.

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).

<mscorlib> : (seulement machine.config) Contient un lment <cryptographySettings>


qui contient les paramtres des mcanismes de cryptographie (voir page 219).

<protectedData> : Permet de dfinir des sections dinformation confidentielle dans un fichier de configuration (voir page 718).

<runtime> : Contient une section <gcConcurrent> qui permet de dterminer si le ramasse


miettes sexcute dune manire concurrente (voir page 120). Contient aussi une section
<assemblyBinding> qui positionne les paramtres de lalgorithme de localisation des assemblages (voir page 103).

<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.runtime.remoting> : Permet de configurer les services et canaux .NET Remoting


(voir page 808).

<system.transactions> : Permet de paramtrer les transactions (voir page 751).

<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.

Dfinir vos propres paramtres de configuration avec llment


<appSettings>
Vous pouvez vous servir de llment <appSettings> du fichier de configuration dune application pour stocker les valeurs des paramtres qui lui sont propres. Par exemple, supposons que
lapplication MyApp ait un paramtre entier nomm MyEntier :
Exemple 3-11 :

MyApp.exe.config

<?xml version="1.0" encoding="utf-8" ?>


<configuration
xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">
<appSettings>
<add key="MyEntier" value="1234"/>
</appSettings>
</configuration>
Voici le code de lapplication MyApp. chaque excution, ce code obtient la valeur du paramtre
MyEntier, la multiplie par 10, puis sauvegarde la nouvelle valeur :

60

Chapitre 3 : Construction, configuration et dploiement des applications .NET


MyApp.cs

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.

Dfinir vos propres paramtres avec des sections de configuration


Pour viter ces deux problmes, vous pouvez avoir recours la notion de section de configuration.
Une telle section est un ensemble de paramtres de configuration. Chaque paramtre est dfini
soit au niveau de lapplication, soit au niveau de lutilisateur Windows qui excute lapplication.
Ainsi, vous pouvez vous servir simplement de ce mcanisme pour stocker les prfrences de
chaque utilisateur.
Le fichier de configuration suivant dfini une section de configuration nomme MySettings qui
contient un paramtre de configuration utilisateur nomm MyEntierUsr et un paramtre de
configuration de lapplication nomm MyEntierApp :
Exemple 3-13 :

MyApp.exe.config

<?xml version="1.0" encoding="utf-8" ?>


<configuration
xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">
<configSections>
<sectionGroup name="userSettings"
type="System.Configuration.UserSettingsGroup,
System, Version=2.0.0.0, Culture=neutral,

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

Chapitre 3 : Construction, configuration et dploiement des applications .NET


myEntierUsr *= 10 ;
mySettings.MyEntierUsr = myEntierUsr ;
mySettings.Save() ;
System.Console.WriteLine(mySettings.MyEntierApp) ;
}
}
class MySettings : ApplicationSettingsBase {
[UserScopedSetting()]
public int MyEntierUsr {
get { return (int)(this["MyEntierUsr"]) ; }
set { this["MyEntierUsr"] = value ; }
}
[ApplicationScopedSetting()]
public int MyEntierApp {
get { return (int)(this["MyEntierApp"]) ; }
}
}

lexcution, le CLR se sert de la rflexion pour :

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.

Crer vos sections de configuration avec Visual Studio 2005


Malgr les avantages utiliser les sections de configuration, vous pouvez tre rebut par la fait de
crer une classe ddie et par le nombre dlments ajouts dans votre fichier de configuration.
Heureusement, Visual Studio 2005 prsente un diteur de configuration qui gre compltement
cette charge supplmentaire.
Pour ajouter une nouvelle section il vous sut de faire : Click droit sur le projet qui va tre
paramtr par la section  Add  New Items...  Settings File  Fournir le nom XXX de la section
 OK. Un nouvel lment XXX.settings est ajout au projet. Si vous slectionn cet lment,
lditeur de paramtres de configuration apparat. Pour chaque paramtre, vous pouvez choisir
son nom, son type, sil est un paramtre utilisateur ou un paramtre de lapplication et une
valeur par dfaut.
Un fichier source XXX.Designer.cs est associ llment XXX.settings. Cest un fichier gnr
qui contient la dfinition de la classe nomme XXX qui drive de ApplicationSettingBase. Cette
dfinition est constamment synchronise avec les modifications eectues dans lditeur. Aussi,
comme dans tous fichier gnr il ne faut pas y insr votre propre code.

Remarques sur les sections de configuration


En page 718 nous expliquons comment se servir de lAPI de protection des donnes par encryption pour sauvegarder des paramtres de configuration sous une forme encrypte.
Vous avez la possibilit de valider les modifications apportes aux paramtres de configuration
avant leur sauvegarde eective. Ceci est expliqu dans larticle How to : Validate Application
Settings des MSDN.

Dploiement des assemblages : XCopy vs. Rpertoire GAC

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.

Dploiement des assemblages :


XCopy vs. Rpertoire GAC
.NET propose deux types de stratgies pour dployer les assemblages dune application.

La stratgie XCopy (qualifie aussi de stratgie de dploiement dassemblage titre priv)


consiste simplement copier les fichiers de lapplication dans un rpertoire. Parce quon ne
peut pas faire plus simple, cest la stratgie choisir par dfaut.

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

Chapitre 3 : Construction, configuration et dploiement des applications .NET

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.

Assemblages partags et rpertoire GAC


Le dploiement dassemblages XCopy ne convient pas aux assemblages qui sont partags par
plusieurs applications installes sur la mme machine. En eet, si lon utilisait cette stratgie
pour un assemblage partag, il serait prsent sur le disque dur autant de fois quil y a dapplications qui lutilisent. Hormis le gaspillage despace, cette stratgie a pour principal dfaut dobliger changer plusieurs fichiers en plusieurs endroits lorsque lassemblage partag volue. La
stratgie appliquer pour les assemblages partags consiste utiliser un rpertoire spcialement
conu pour les stocker. Ce rpertoire sappelle le cache global des assemblages. Il est aussi nomm
rpertoire GAC (Global Assembly Cache en anglais). Le rpertoire GAC est en gnral le rpertoire : C:\Windows\Assembly\GAC . Ce rpertoire spcial est connu du CLR et par consquent,
il est connu lexcution de toutes les applications .NET.

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.

Dploiement des assemblages : XCopy vs. Rpertoire GAC

65

Le stockage cte cte dans le GAC rsout lenfer des DLLs


Si on le considre comme un rpertoire Windows, le rpertoire GAC a une structure interne
labore dont vous navez heureusement pas vous proccuper. Cette structure est ncessaire
du fait que le GAC peut contenir plusieurs versions dun mme assemblage. Cette particularit
sappelle le stockage cte cte (side by side en anglais) des direntes versions dun mme assemblage. Lorsquun assemblage rfrence un assemblage partag, il utilise son nom fort. Or, le
numro de version fait partie du nom fort. Il ny a donc pas de risque de charger une mauvaise
version.
Le stockage cte cte rsout le problme connu sous le nom denfer des DLLs. (DLL hell en
anglais). Ce problme survient lorsque lon remplace une librairie dynamique Windows (i.e une
DLL pour Dynamic Link Library) par sa nouvelle version. Le moindre problme de compatibilit
entre les versions des DLLs implique que les application qui utilisent cette DLL et qui fonctionnaient correctement jusqualors avec lancienne version rencontrent des problmes dexcution
avec la nouvelle. Ce problme est si important quil a une grande part de responsabilit dans
la rputation dinstabilit des systmes dexploitation Windows. En plus de rduire linstabilit
du systme, le stockage cte cte dissipe lnorme contrainte de la compatibilit ascendante
souvent impose au processus de dveloppement dune nouvelle version.
Le seul problme avec cette technique de stockage cte cte est que le disque dur garde potentiellement toutes les versions de chaque assemblage. Pour les assemblages de tailles raisonnables, cela nest pas vraiment un problme. Pour des assemblages volumineux, il est du devoir
de lditeur de lapplication de faire en sorte de partager un maximum de donnes entre les
direntes versions.

Excution cte cte


Lexcution cte cte dun assemblage consiste autoriser lexcution simultane de plusieurs
versions de cet assemblage. Lattribut .NET AssemblyFlags autorise les versions dun assemblage
tre excutes cte cte dans un mme domaine dapplication ou dans un mme processus
ou seulement sur la mme machine.
Il vaut mieux viter dautoriser lexcution cte cte dun mme assemblage dans un domaine
dapplication ou mme dans un processus. En eet, cette possibilit est surtout utile pour permettre plusieurs applications dutiliser simultanment un assemblage, chacune ayant recours
la version qui lui convient. Il est plutt maladroit den arriver ce que de telles applications
sexcutent au sein dun mme processus ou pire, au sein dun mme domaine dapplication.
En outre, lexcution cte cte dun assemblage peut, dans certains cas, introduire des problmes trs diciles corriger. Ces problmes surviennent lorsquune mme ressource est utilise simultanment par plusieurs versions dun assemblage. Il y a un risque que les direntes
versions de lassemblage ne saccordent pas sur la faon dutiliser la ressource. La consquence
est un comportement imprvisible.

Visualiser et manipuler le GAC


Pour visualiser le GAC partir dun explorateur classique il existe lextension du shell ShFusion.
dll installe automatiquement lors du dploiement de la plateforme .NET sur une machine.
Cette extension permet de visualiser et de manipuler dune manire claire tous les assemblages
partags dans le GAC. Pour utiliser cette extension, il sut de visualiser le rpertoire GAC avec
un explorateur Windows, comme sur la Figure 3-1 :

66

Chapitre 3 : Construction, configuration et dploiement des applications .NET

Figure 3 -1 : Visualisation du rpertoire GAC


Loutil GACUtil.exe utilisable en ligne de commande, permet aussi de visualiser et de manipuler
le rpertoire GAC. GACUtil.exe a de nombreuses options bien dcrites dans les MSDN. Par
exemple loption -l permet de lister tous les assemblages partags et loption -i suivie du nom
du fichier dun assemblage contenant le manifeste permet dinsrer un assemblage nom fort
dans le rpertoire GAC.

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.

Assemblage de stratgie dditeur (Publisher policy)


La problmatique
Malgr le problme connu sous le nom denfer des DLLs, le modle de partage des DLLs par
plusieurs applications a un avantage par rapport au modle de partage des assemblages bas
sur la prsence du numro de version dans le nom fort.

Assemblage de stratgie dditeur (Publisher policy)

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.

Cration dun assemblage de stratgie dditeur


Concrtement un assemblage de stratgie dditeur est compos de deux modules. Un module
est un fichier de configuration XML dextension .config qui contient les informations de redirection souhaites par lditeur. Lautre module ne contient quun manifeste et est construit par

68

Chapitre 3 : Construction, configuration et dploiement des applications .NET

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.

policy indiquera au CLR que cest un assemblage de stratgie dditeur ;

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 /version sapplique lassemblage de stratgie dditeur lui-mme et non Foo2.


dll. Le CLR prendra toujours la stratgie dditeur avec la version la plus leve pour un
couple de numros majeur/mineur donn.

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.

Loption /linkresource prcise le nom du fichier de configuration au format XML qui


contient les informations de redirection de la stratgie dditeur. Comprenez bien que

Introduction au dploiement dapplications .NET

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.

Introduction au dploiement dapplications .NET


Lenvironnent de dveloppement Visual Studio 2005 permet la cration de projets spcialement
ddis aux dploiements dapplications. Six types de projets de dploiement sont disponibles
dans le menu File  New  Project...  Other Project Types  Setup and Deployment :

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.

MSI vs. .cab vs. XCopy vs. ClickOnce vs. NTD


Vous avez principalement le choix entre ces cinq techniques pour dployer une application
.NET qui nest pas une application web ASP.NET. On peut diviser ce type de dploiement en
deux catgories :

70

Chapitre 3 : Construction, configuration et dploiement des applications .NET

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.

MSI vs. ClickOnce


Voici un tableau qui devrait vous aider dcider lorsque vous hsitez entre le dploiement avec
MSI ou le dploiement avec ClickOnce. Ces deux technologies ne sont pas antagonistes. Il est
courant que le socle/les prrequis dune application se dploient avec MSI tandis la partie fonctionnelle, plus lgre pour le systme dexploitation mais aussi plus volutive, se dploie avec
ClickOnce :
Fonctionnalit

ClickOnce

MSI

Installation de fichiers.

Cration de raccourcis dans le menu des programmes

X
X

Cration de raccourcis bureau et autres.


Installation de classes COM sans enregistrement dans la base des registres (voir page 287).

Installation de classes COM et de composants COM+.

Association dextension de fichiers.

Installation de services Windows.

Installation dassemblages dans le GAC.

Gestion de ODBC.

Modification dans la base des registres.

Dployer une application avec un fichier cab

71

Self rparation

Interaction avec lutilisateur linstallation.

Choix du rpertoire dinstallation des fichiers.

Installation possible pour tous les utilisateurs.

Actions spciales au moment de linstallation ou de la dsinstallation.

Manipulation des droits sur les objets Windows.

Conditions dinstallation et vrification de la version et de ltat du systme dexploitation.

Vrification et tlchargement automatique des mises jour de lapplication.

Gestion de la scurit volue base sur le mcanisme CAS du CLR.

Installation de partie de lapplication la demande.

Possibilit de revenir la version prcdente aprs une mise jour.

Dployer une application avec un fichier cab


Vous avez la possibilit dajouter un projet de dploiement par fichiers cab votre solution,
comme le montre la Figure 3 -2 :

Figure 3 -2 : Un projet cab insr dans une solution


Vous pouvez ajouter des fichiers produits ou contenus par les autres projets de la solution
(Project output) comme illustr sur la Figure 3-3. Ici nous avons ajout les fichiers MonApplication.
exe et MaLibrairie.dll en slectionnant le type Primary Output pour chacun de ces projets.
Aprs compilation dun projet de dploiement cab, un fichier dextension .cab est produit. Avec
un outil de visualisation darchives vous pouvez examiner le contenu du fichier cab, et extraire
les fichiers contenus comme le montre la Figure 3 -4. Notez quun avantage de lutilisation de ce
type darchive, est que les fichiers sont compresss.
Un fichier dextension .osd est contenu dans le fichier cab. Ce fichier au format XML dcrit le
contenu du fichier cab :

72

Chapitre 3 : Construction, configuration et dploiement des applications .NET

Figure 3 -3 : Ajout dans un projet de dploiement cab

Figure 3 -4 : Utiliser Winzip pour lanalyse dun fichier cab


<?XML version="1.0" ENCODING=UTF-8?>
<!DOCTYPE SOFTPKG SYSTEM "http://www.microsoft.com/standards/osd/osd.dtd">
<?XML::namespace href="http://www.microsoft.com/standards/osd/msicd.dtd"
as="MSICD"?>
<SOFTPKG NAME="CabProject" VERSION="1,0,0,0">
<TITLE> CabProject </TITLE>
<MSICD::NATIVECODE>
<CODE NAME="MonApplication">
<IMPLEMENTATION>
<CODEBASE FILENAME="MonApplication.exe">
</CODEBASE>
</IMPLEMENTATION>
</CODE>
</MSICD::NATIVECODE>
<MSICD::NATIVECODE>
<CODE NAME="MaLibrairie">
<IMPLEMENTATION>
<CODEBASE FILENAME="MaLibrairie.dll">
</CODEBASE>

Dployer une application avec la technologie MSI

73

</IMPLEMENTATION>
</CODE>
</MSICD::NATIVECODE>
</SOFTPKG>

Dployer une application avec la technologie MSI


Ajouter les fichiers de votre application
Lorsque vous avez ajout le projet setup votre solution, vous disposez de la vue Systme de
fichiers (File System en anglais). Cette vue contient par dfaut trois rpertoires : Le rpertoire
de lapplication ; Le bureau ; Le menu dmarrer.
Les fichiers que vous allez mettre dans ces rpertoires durant la construction du projet setup
seront automatiquement copis dans les rpertoires correspondants, sur la machine cible, lors
de linstallation. En gnral on fait en sorte de placer dans ces rpertoires les assemblages non
partags, produits des autres projets de la solution. Ceci est illustr sur la Figure 3-5. Remarquez
que cette opration se fait de la mme manire que dans un projet de dploiement utilisant un
fichier cab.

Figure 3 -5 : La vue systme de fichiers

Installer des raccourcis


Il est trs facile dajouter des raccourcis sur le bureau ou dans le menu dmarrer partir de la
vue systme de fichiers. Il sut de cliquer droit sur lexcutable, dajouter un raccourci et de
dplacer le raccourci dans le rpertoire souhait (bureau ou menu dmarrer ).

Ajouter un assemblage partag, dans le rpertoire GAC


Pour ajouter un assemblage partag dans le rpertoire GAC partir dun projet de dploiement
setup project , il faut dabord voir le rpertoire GAC dans la vue systme de fichier . Pour
cela, il sut de cliquer droit dans lespace rserv au rpertoire, et dajouter le rpertoire GAC,
comme sur la Figure 3 -6. Ensuite vous navez plus qu ajouter vos assemblages partags dans ce
rpertoire. De nombreux autres rpertoires spciaux peuvent tre ajouts.

Les proprits du projet


Vous avez en fait les deux types de proprits du projet.

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

Chapitre 3 : Construction, configuration et dploiement des applications .NET

Figure 3 -6 : Ajout du rpertoire GAC

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.

Figure 3 -7 : Proprits dun projet setup

Manipuler la base des registres


Vous avez la possibilit dajouter des cls dans la base des registres relatives votre application
lors de son installation. Il sut pour cela de slectionner la vue Registre et de modifier les cls,

Dployer une application avec la technologie ClickOnce

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.

Figure 3 -8 : Configurer la base des registres

Eectuer des actions personnalises durant linstallation


Il peut tre trs utile de lancer un excutable durant linstallation ou la dsinstallation dune application. Par exemple, vous pouvez crer un excutable qui vrifie certains paramtres de configuration sur votre machine, ncessaires au bon fonctionnement de lapplication. Vous pouvez
aussi souhaitez enregistrer un objet COM durant linstallation et le dsenregistrer durant la
dsinstallation. Dans ce dernier exemple, il est ncessaire dajouter le fichier regsvr32.exe
la liste des fichiers quinstalle lapplication. Ensuite, vous navez plus qu slectionner la vue
actions personnalises et ajouter des rfrences comme illustr par la Figure 3-9 :

Figure 3 -9 : Ajouter des actions personnalises

Modifier linterface utilisateur de linstallation


Vous pouvez modifier simplement le cours de linstallation en modifiant les fentres de
dialogue proposes par Visual Studio dans la vue Interface Utilisateur, comme illustr sur la
Figure 3 -10. Vous pouvez ajouter de nombreux dialogues, comme illustr sur la Figure 3-11.

Dployer une application avec la technologie ClickOnce


Pour tirer partie de la technologie ClickOnce, il est absolument ncessaire davoir assimil la
technologie Code Access Security (CAS) expose dans le chapitre 6 La gestion de la scurit .

76

Chapitre 3 : Construction, configuration et dploiement des applications .NET

Figure 3 -10 : Enchanement des interfaces utilisateurs

Figure 3 -11 : Dialogues possibles pour linstallation

Organisation du rpertoire de dploiement


Commenons par dcrire larborescence de fichiers qui permet le dploiement dune application avec ClickOnce. Elle doit se situer dans un rpertoire de dploiement qui peut tre un rpertoire virtuel dIIS, un rpertoire FTP, un rpertoire partag au sein dun intranet ou un rpertoire dun CD. La technologie ClickOnce requiert deux fichiers particuliers en plus de lensemble
des fichiers de lapplication dployer :

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.

Le manifeste du dploiement : Cest un fichier XML dextension .application. qui rfrence


un fichier manifeste de lapplication. Il contient aussi les paramtres du dploiement.

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

Dployer une application avec la technologie ClickOnce

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.

Concevoir un dploiement ClickOnce


Il existe plusieurs possibilits pour crer les fichiers manifestes, numres ici de la plus pratique
la plus fastidieuse :

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.

Construire ces fichiers XML la main en se basant sur la documentation ocielle.

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

Chapitre 3 : Construction, configuration et dploiement des applications .NET

Figure 3 -12 : Visual Studio et ClickOnce

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.

Dployer une application avec la technologie ClickOnce

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.

Dploiement de lapplication et impratifs de scurit


Pour dployer lapplication sur une machine, il sut dexcuter le fichier manifeste de dploiement. Dans le cas dun dploiement web, cela se fait en gnral par lintermdiaire dune page
qui contient un lien vers ce fichier. Dans les autres cas il sut de double cliquer ce fichier (
partir du rpertoire FTP, du rpertoire partag intranet ou du rpertoire du CD).
moins que les deux fichiers manifestes aient t signs avec un certificat X.509 vrifiable par
la machine cible, une boite de dialogue apparat signalant lutilisateur que lapplication quil
est en train dinstaller a t conue par un diteur inconnue. Il a alors le choix de continuer
linstallation ou non.
Si lditeur de lapplication a pu tre vrifi grce au certificat, lensemble des permissions demandes dans le manifeste de lapplication lui est accord. Il en est de mme si lapplication est
installe partir dun CD. Sinon, un certain ensemble de permissions CAS peut lui tre accord
par les stratgies de scurit CAS. Tout dpend de la zone do linstallation est eectue (internet
untrusted, internet trusted, intranet etc).
Si lensemble des permissions dcrit dans le manifeste de lapplication nest pas compltement
inclus dans lensemble des permissions que les stratgies de scurit CAS sont prtent lui accorder alors une boite de dialogue informe lutilisateur. Sil choisit de continuer linstallation,
lensemble des permissions dcrit dans le manifeste de lapplication sera accord chaque excution de lapplication, sinon linstallation est annule. La littrature anglo saxonne qualifie cette
tape de permission elevation.
Une fois ces deux barrires potentielles passes (au pire nous en sommes trois clicks ),
ClickOnce installe lapplication dans un rpertoire ddi cette application. Nous reviendrons
sur ce rpertoire. Un raccourci vers lapplication est install dans le menu des programmes
si lapplication est disponible oine. Lapplication peut tre dsinstalle partir du menu
Ajout/Suppression de programmes. Le manifeste de dploiement a la possibilit de spcifier que
lapplication doit tre dmarre ds que le linstallation est termine.
Lapplication nest installe que pour lutilisateur courant. Si plusieurs utilisateurs dune machine ont besoin de cette application, elle devra tre installe pour chacun deux dans des rpertoires dirents.
Lapplication des stratgies de scurit de la machine ne se fait qu linstallation et lors des mises
jour de lapplication. Une fois que lapplication est installe elle sexcutera chaque fois dans
le cadre des permissions spcifies dans le manifeste.

80

Chapitre 3 : Construction, configuration et dploiement des applications .NET

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

public class MyClass {


public override string ToString() {
return "Bonjour de MyLib." ;
}
}
Exemple 3-17 :

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

Dployer une application avec la technologie ClickOnce

81

// lorsque lapplication nest pas d


eploy
ee
// do`u le test de la propri
et
e IsNetworkDeployed.
ApplicationDeployment currentDeployment =
ApplicationDeployment.CurrentDeployment ;
currentDeployment.DownloadFileGroup("MyGroup") ;
Console.WriteLine("Telechargement...") ;
}
return Assembly.Load("MyLib") ;
}
}
class Foo {
public static void FooFct() {
MyClass a = new MyClass() ;
Console.WriteLine(a) ;
}
}

Mises jour dune application installe avec ClickOnce


Les outils Visual Studio 2005, mageui.exe et mage.exe vous permettent de paramtrer la faon
dont une application dploye avec ClickOnce gre ses mises jour. Vous pouvez spcifier quand
lapplication doit vrifier que des mises jour sont disponibles ( chaque excution ou priodiquement). Vous pouvez aussi spcifier quand ces mises jour doivent tre tlcharges :

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.

Facilits pour manipuler lensemble des permissions CAS requises


par une application
Les projets Visual Studio 2005 de type console ou application Windows Forms prsentent un menu
Properties  Security qui facilite grandement la gestion des permissions CAS lors du dveloppement dune application :
Ce menu permet de spcifier si lapplication a besoin de la permission FullTrust pour tre
excute ou non. Dans le second cas, une grille vous aide construire lensemble exact des permissions dont lapplication besoin pour sexcuter. Cest cet ensemble qui sera spcifi dans le
manifeste de lapplication. Lorsquun type de permission est slectionn, vous pouvez cliquer le
bouton Properties pour obtenir une fentre qui permet de configurer finement la permission.

82

Chapitre 3 : Construction, configuration et dploiement des applications .NET

Figure 3 -13 : Visual Studio et les permissions CAS


Visual Studio 2005 vous aide comparer cet ensemble avec les ensembles de permissions accords
par dfaut par les stratgies de scurit. Pour cela, il faut choisir partir de quelle zone lapplication sera dploye. Un avertissement signale quand une permission non accorde par dfaut
la zone slectionne est demande. Un avertissement signifie quau moment de linstallation
partir de cette zone, la technologie ClickOnce demandera lutilisateur de choisir sil souhaite
lever lensemble des permissions, ce qui fait toujours mauvais eet !
Il est fastidieux de dterminer lensemble exact de permissions dont une application a besoin
pour sexcuter correctement. Loutil permcalc.exe fourni avec Visual Studio 2005 permet de
calculer cet ensemble en analysant le code IL de lassemblage excutable ainsi que le code IL
des assemblages rfrencs (rcursivement). Selon la taille de votre application, ce calcul peut
prendre un certain temps, de lordre de la minute. Vous pouvez vous servir de cet outil en ligne
de commande pour produire lensemble des permissions au format XML. Ce contenu peut alors
tre intgr dans le manifeste de lapplication. Le bouton Calculate Permissions du menu Security
permet dinvoquer cet outil et dobtenir son rsultat dans la grille des permissions.
Visual Studio 2005 vous permet de dboguer votre application dans le contexte de scurit dfini
par lensemble de permissions que vous avez construit dans le menu Security. Cette possibilit est
trs pratique puisque cest aussi le contexte de scurit dans lequel sexcutera lapplication chez
les clients. Pour cela, Visual Studio 2005 gnre la compilation un assemblage nomm [Nom de
lapplication].vshost.exe dans le rpertoire de sortie qui contient aussi lassemblage [Nom

Dployer une application avec la technologie ClickOnce

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.

Dtails sur linstallation et lexcution dune application dploye


avec ClickOnce
Si vous ouvrez le gestionnaire des tches sur une machine qui est en train dexcuter une application nomme XXX dploye avec ClickOnce, vous pourrez constater quil ny a pas de processus en
cours nomm XXX.exe. En eet, une application ClickOnce qui na pas la permission FullTrust
sexcute au sein dun processus nomm AppLaunch.exe. Il soccupe de la gestion de lensemble
des permissions CAS accordes lapplication. Le menu ajout pour une application dploye
avec ClickOnce fait rfrence un fichier rfrence dapplication Windows dextension .appref-ms.
Lors de louverture dun tel fichier Windows excute des objets COM dfinis dans le composant
dfshim.dll. Ces objets sont responsables du lancement de AppLaunch.exe.
Lors du lancement dune application dploye avec ClickOnce vous pouvez aussi observer lapparition du processus dfsvc.exe. Cest le moteur dexcution de ClickOnce. Cest ce processus
qui soccupe de la vrification et du tlchargement des mises jour. Ce processus sauto dtruit
aprs 15 minutes dinactivit.
Les fichiers dune application dploye avec ClickOnce sont installs dans un sous rpertoire du
cache des applications dployes avec ClickOnce. Ce cache est propre chaque utilisateur et une
taille borne (de lordre de 100 Mo). Cest le rpertoire cach \Documents and Settings\[Nom
de lutilisateur]\Local Settings\Apps\. Le nom du rpertoire dune de ces sous rpertoires est compos de valeurs de hachages calcules sur lapplication lors de linstallation.
Une association entre ce rpertoire et le fichier rfrence dapplication est prsente dans la
base des registres. Vous pouvez retrouver ce rpertoire soit par lintermdiaire de la proprit
AppDomain.CurrentDomain.BaseDirectory qui reprsente le rpertoire dhbergement de
lassemblage de lapplication, soit en lanant une recherche des fichiers de votre application
sous le rpertoire Apps. Lors de la mise jour dune application, un nouveau rpertoire frre du
prcdent est cr et ClickOnce fait en sorte quil ny ait que deux rpertoires consacrs cette
application : celui de la version prcdente et celui de la version courante.
Lors du dploiement dune application, ClickOnce fait la dirence entre les fichiers propres
lapplication (essentiellement larborescence de rfrencement des assemblages obtenue
partir de lassemblage excutable de lapplication, assemblages satellites compris) et les fichiers
de donnes de lapplication (les fichiers de configuration, les fichiers de base de donnes
.mdb, les fichiers XML etc). Les fichiers de donnes sont dploys dans un sous rpertoire
du cache des donnes des applications dployes avec ClickOnce ( savoir \Documents and Settings\[Nom de lutilisateur]\Local Settings\Apps\Data). Ce rpertoire de donnes de
lapplication est programmatiquement accessible par la proprit string ApplicationDeployment.DataDirectory{get;} et par la proprit stringSystem.Windows.Forms.Application.
LocalUserAppDataPath\{get;\}. Il faut que lapplication ait les permissions CAS daccs ce
rpertoire pour pouvoir exploiter les donnes. Pour cette raison, vous pouvez prfrer utiliser
le mcanisme de stockage isol dcrit en page 205 qui ne requiert que la permission de faire du
stockage isol. En eet, cette permission est accorde par dfaut aux zones internet et intranet.

84

Chapitre 3 : Construction, configuration et dploiement des applications .NET

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.

Dployer une application avec la technologie


No Touch Deployment
Lorsque le framework .NET est install sur une machine, vous pouvez dmarrer un assemblage
tlchargeable sur le web directement partir dinternet explorer. Cette possibilit est nomme
No Touch Deployment (NTD).
Supposons que lassemblage Foo.exe qui a le code suivant, soit tlchargeable partir de
lURL : \texttt{www.smacchia.com/Foo.exe}. Nous utilisons le fait que la proprit Assembly.
CodeBase contienne lURL partir de laquelle a t tlcharge un assemblage :
Foo.cs

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 :

Figure 3 -14 : Excution dun assemblage partir du web


Vous remarquez quinternet explorer ne vous a pas demand votre permission pour tlcharger
et dmarrer lassemblage. En eet, le fait que lexcutable soit un assemblage .NET a t dtect.
Ainsi, internet explorer dlgue la gestion de la scurit au CLR. Le CLR est hberg dans le
processus de internet explorer par un hte du moteur dexcution particulier introduit en page
95.

Et si .NET nest pas install sur la machine cible ?

85

Lexcution de lassemblage Foo.exe ne requiert aucune permission sensible. Le CLR nentrave


pas son excution car lapplication des stratgies de scurit permet par dfaut lexcution dassemblages tlchargs partir du web, tant quil ne demande pas de permissions particulires.
Cependant, le CLR naurait pas permis lassemblage Foo.exe deectuer une action sensible,
comme accder un fichier du disque dur ou modifier une cl de la base des registres, moins
bien sur, que la stratgie de scurit accorde une certaine confiance au site \url{www.smacchia.
com}. Un assemblage excut partir du web peut ne pas avoir de nom fort.

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.

Et si .NET nest pas install sur la machine cible ?


Lors de la compilation dun projet setup MSI le compilateur ache cet avertissement :

86

Chapitre 3 : Construction, configuration et dploiement des applications .NET


WARNING: This setup does not contain the .NET Framework which must be
installed on the target machine by running dotnetfx.exe before this
setup will install. You can find dotnetfx.exe on the Visual Studio .NET
Windows Components Update media. Dotnetfx.exe can be redistributed
with your setup.

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.

dotnetfx.exe se trouve dans le rpertoire \wcu\dotNetFramework\dotnetfx.exe si vous


avez obtenu .NET partir dun DVD.

dotnetfx.exe se trouve dans le rpertoire \dotNetFramework\dotnetfx.exe si vous avez


obtenu .NET partir dun CD.

dotnetfx.exe peut facilement tre tlcharg partir du site de Microsoft.

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 :

hbergement de plusieurs applications dans un mme processus Windows ;

compilation du code IL en code machine ;

gestion des exceptions ;

destruction des objets devenus inutiles ;

chargement des assemblages ;

rsolution des types.

Le CLR est conceptuellement proche de la couche logicielle communment nomm machine


virtuelle en Java.

Les domaines dapplication


Notion de domaine dapplication
Un domaine dapplication (application domain en anglais, en gnral nomm AppDomain) peut
tre vu comme un processus lger. Un processus Windows peut contenir plusieurs domaines

88

Chapitre 4 : Le CLR (le moteur dexcution des applications .NET)

dapplications. La notion de domaine dapplication est particulirement utilise pour quun


mme serveur physique hberge un grand nombre dapplications. Par exemple, la technologie
ASP.NET utilise les domaines dapplication pour hberger plusieurs applications web dans
un mme processus Windows. Les stress test de Microsoft crent jusqu 1000 applications web
simples dans un mme processus. Le gain de performance apport par la notion de domaine
dapplication est double :

La cration dun domaine dapplication consomme beaucoup moins de ressources que la


cration dun processus Windows.

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.

Domaines dapplication et threads


Ne confondez pas les termes unit dexcution (les threads) et unit disolation dexcution (les domaines dapplication).
Il ny a pas de notion dappartenance entre les threads dun processus et les domaines dapplication de ce mme processus. Rappelons que ce nest pas le cas pour les processus, puisquun thread
appartient un seul processus, et chaque processus a un ou plusieurs threads. Concrtement
un thread nest pas confin un domaine dapplication et un instant donn plusieurs threads
peuvent sexcuter dans le contexte dun mme domaine dapplication.
Soit deux domaines dapplication DA et DB hbergs dans un mme processus. Supposons quune
mthode dun objet A, dont lassemblage contenant sa classe se trouve dans DA, appelle une
mthode dun objet B, dont lassemblage contenant sa classe se trouve dans DB. Dans ce cas cest
le mme thread qui excute la mthode appelante et la mthode appele. Ce thread traverse la
frontire entre les domaines dapplications DA et DB.
Les concepts de thread et de domaine dapplication sont donc orthogonaux.

Les domaines dapplication

89

Dchargement dun domaine dapplication


Une fois quun assemblage est charg dans un domaine dapplication, vous navez pas la possibilit de le dcharger de ce domaine. En revanche, vous pouvez dcharger un domaine dapplication tout entier. Cette opration est lourde de consquences. Les threads en cours dexcution
dans le domaine dapplication dcharger doivent tre avorts par le CLR. Des problmes se
posent si certains dentre eux sont en train dexcuter du code non gr. Les objets grs contenus dans le domaine dapplication doivent tre collects.
Nous vous dconseillons davoir une architecture qui compte sur le fait de dcharger rgulirement des domaines dapplication. Nous verrons nanmoins que ce type darchitecture constitue parfois un mal ncessaire pour implmenter des serveurs qui ncessitent un grand taux de
disponibilit (type 99,999% du temps comme SQL Server 2005).

Lisolation des domaines dapplication


Lisolation entre domaines application est le fruit des caractristiques suivantes :

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

Chapitre 4 : Le CLR (le moteur dexcution des applications .NET)

Lancer une nouvelle application dans le processus courant


La classe AppDomain contient la mthode statique CreateDomain() qui permet de crer un nouveau domaine dapplication dans le processus courant. Cette mthode est surcharge en plusieurs formes. Pour utiliser cette mthode, vous devez prciser :

(obligatoire) Un nom pour le nouveau domaine dapplication.

(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).

Les deux proprits importantes dun objet de type System.AppDomainSetup sont :

ApplicationBase : Cette proprit dfinit le rpertoire de base du domaine dapplication.


Ce rpertoire est notamment utilis par le mcanisme de localisation dassemblages charger dans ce domaine dapplication.

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

Les domaines dapplication

91

// Nomme le thread courant.


Thread.CurrentThread.Name = "MonThread" ;
// Cree un objet de type AppDomainSetup.
AppDomainSetup info = new AppDomainSetup() ;
info.ApplicationBase = "file:///"+ Environment.CurrentDirectory ;
// Cree un domaine dapplication sans param`
etres de s
ecurit
e.
AppDomain newDomaine = AppDomain.CreateDomain(
"NouveauDomaine", null, info) ;
Console.WriteLine(
"Thread:{0} Appel `a ExecuteAssembly() `
a partir du appdomain {1}",
Thread.CurrentThread.Name,
AppDomain.CurrentDomain.FriendlyName) ;
// Charge lassemblage AssemblyACharger.exe dans
// newDomain puis lexecute.
newDomaine.ExecuteAssembly("AssemblyACharger.exe") ;
// Decharge le nouveau domaine
// ainsi que lassemblage quil contient.
AppDomain.Unload(newDomaine) ;
}
}
Cet exemple ache :
Thread:MonThread Appel `a ExecuteAssembly() `
a partir du appdomain
AssemblyChargeur.exe
Thread:MonThread Vous avez le bonjour du domaine : NouveauDomaine
Notez la ncessit de lajout "file:///" pour prciser que lassemblage se trouve en local. Si ce
ntait pas le cas on aurait pu utiliser "http:///". Dans ce cas, lassemblage aurait pu tre charg
partir du web.
Cet exemple illustre aussi que le nom du domaine dapplication par dfaut est le nom du module principal de lassemblage lanc (ici AssemblyChargeur.exe).

Excuter du code dans le contexte dun domaine dapplication


Grce la mthode dinstance AppDomain.DoCallBack() vous avez la possibilit dexcuter du
code dun assemblage du domaine dapplication courant dans le contexte dun autre domaine
dapplication. Pour cela, ce code doit tre contenu dans une mthode que vous rfrencez
laide dun dlgu de type System.CrossAppDomainDelegate. Tout ceci est illustr par lexemple
suivant :
Exemple 4-4 :
using System ;
using System.Threading ;
public class Program {
public static void Main() {
Thread.CurrentThread.Name = "MonThread" ;
AppDomain newDomaine = AppDomain.CreateDomain(
"NouveauDomaine") ;

92

Chapitre 4 : Le CLR (le moteur dexcution des applications .NET)


CrossAppDomainDelegate deleg = new CrossAppDomainDelegate(Fct);
newDomaine.DoCallBack(deleg);
AppDomain.Unload(newDomaine) ;
}
public static void Fct() {
Console.WriteLine(
"Thread:{0} Fct() execut
ee dans {1}",
Thread.CurrentThread.Name,
AppDomain.CurrentDomain.FriendlyName) ;
}
}

Cet exemple ache :


Thread:MonThread Fct() executee dans

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.

Les vnements dun domaine dapplication


La classe AppDomain prsente les vnements suivants :
vnement

Description

AssemblyLoad

Dclench lorsquun assemblage vient dtre charg.

AssemblyResolve

Dclench lorsquun assemblage charger na pu tre trouv.

DomainUnload

Dclench lorsque le domaine dapplication est sur le point dtre


dcharg.

ProcessExit

Dclench lorsque le processus se termine (dclench avant


DomainUnload).

ReflectionOnlyPreBindAssemblyResolve

Dclench juste avant le chargement dun assemblage destin


tre utilis par le mcanisme de rflexion.

ResourceResolve

Dclench lorsquune ressource na pu tre trouve.

TypeResolve

Dclench lorsquun type na pu tre trouv.

UnhandledException

Dclench lorsquune exception nest pas rattrape dans le code du


domaine dapplication.

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 :

Les domaines dapplication

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.

Echanger des informations entre domaines


Vous avez la possibilit de stocker des donnes dans un domaine dapplication grce aux mthodes SetData() et GetData() de la classe AppDomain. Comme le montre lexemple suivant,
chacune de ces donnes est indexe par une chane de caractres :
Exemple 4-6 :
using System ;
using System.Threading ;
public class Program {
public static void Main() {
AppDomain newDomaine = AppDomain.CreateDomain(
"NouveauDomaine") ;
CrossAppDomainDelegate deleg = new CrossAppDomainDelegate(Fct) ;
newDomaine.DoCallBack(deleg) ;
// Recup`ere dans lappDomain par d
efaut lentier index
e par la
// string "UnEntier" dans lappDomain NouveauDomaine.
int unEntier = (int) newDomaine.GetData("UnEntier");
AppDomain.Unload(newDomaine) ;
}
public static void Fct() {
// Cette methode sexecute dans lappDomain NouveauDomaine.
// Indexe la valeur 691 par la string "UnEntier".
AppDomain.CurrentDomain.SetData("UnEntier", 691);

94

Chapitre 4 : Le CLR (le moteur dexcution des applications .NET)


}
}

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.

Chargement du CLR dans un processus grce lhte du


moteur dexcution
Les DLLs mscorsvr.dll mscorwks.dll
Chaque version du CLR est publie avec deux DLLs :

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.

Notion dhte du moteur dexcution


Le fait que .NET ne soit encore intgr aucun systme dexploitation entrane que le chargement du CLR la cration dun processus, doit tre pris en charge par le processus lui-mme.
La tche de charger le CLR dans le processus incombe une entit appele lhte du moteur
dexcution (runtime host en anglais). Lhte du moteur dexcution tant l pour charger le CLR,
une partie de son code est forcment non gr, puisque cest le CLR qui gre le code. Cette
partie soccupe de charger le CLR, de le configurer, et de passer le thread courant en mode gr.

Chargement du CLR dans un processus grce lhte du moteur dexcution

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

Figure 4 -1 : Dcoupage en couches de lhbergement du CLR


Il existe plusieurs htes du moteur dexcution et vous avez mme la possibilit de crer vos
propres htes du moteur dexcution. Le choix de tel ou tel hte du moteur dexcution pour
une application a un impact sur les performances de lapplication et ltendue des fonctionnalits utilisables par lapplication. Les htes du moteur dexcution dj existants fournis par
Microsoft sont :

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 ASP.NET : Cre un domaine dapplication pour chaque


application web. Une application web est identifie par son rpertoire virtuel racine
ASP.NET. Si une requte web sadresse une application dj charge dans un domaine,
la requte est automatiquement route vers ce domaine par lhte.

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.

Plusieurs versions du CLR sur la mme machine


mscorlib tant un assemblage nom fort, plusieurs versions peuvent cohabiter cte
cte sur la mme machine. De plus, toutes les versions du CLR sont dans le rpertoire
%windir%\Microsoft.NET\Framework . Tous les fichiers concernant une version du framework

96

Chapitre 4 : Le CLR (le moteur dexcution des applications .NET)

.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.

Chargement du CLR avec CorBindToRuntimeEx()


Voici le prototype de la fonction CorBindToRuntimeEx() qui charge la DLL cale, qui charge
son tour la DLL contenant le CLR :
HRESULT CorBindToRuntimeEx(
LPWSTR
pwszVersion,
LPWSTR
pwszBuildFlavor,
DWORD
flags,
REFCLSID
rclsid,
REFIID
riid,
LPVOID *
ppv) ;
Ce prototype est spcifi dans le fichier mscoree.h et le code de cette fonction est dans la DLL
cale mscoree.dll.

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.

flags : Ce paramtre est constitu par un ensemble de drapeaux.


En utilisant le drapeau STARTUP_CONCURRENT_GC on indique que lon souhaite que le
ramasse-miettes soit excut en mode concurrent, dit aussi mode simultan. Ce mode
permet un thread de raliser une bonne partie du travail du ramasse-miettes sans avoir
interrompre les autres threads de lapplication. Dans le cas dune excution non simultane

Chargement du CLR dans un processus grce lhte du moteur dexcution

97

du ramasse-miettes, le CLR utilise rgulirement les threads de lapplication pour eectuer


le travail du ramasse-miettes. Les performances globales du mode non concurrent sont
meilleures. En revanche le mode concurrent est souhaitable dans le cas dinterfaces utilisateurs, car il permet une plus grande ractivit de linterface.
On peut aussi positionner dautres drapeaux pour indiquer que lon souhaite que les assemblages soient chargs dune manire neutre par rapport aux domaines (loaded domainneutrally en anglais) ou non. Cela signifie que toutes les parties en lecture des assemblages
(le code, les structures...) ne seront prsentes physiquement quune seule fois dans le processus, mme si plusieurs domaines dapplication chargent le mme assemblage (dans le mme
esprit que le mapping des DLLs entre processus sous les systmes dexploitation Windows).
Un assemblage charg dune manire neutre par rapport aux domaines nappartient donc
pas un domaine spcifique. Cela prsente le principal inconvnient quil ne peut pas tre
dcharg. Il faut donc dtruire puis relancer tout un processus lors de la mise jour dun
tel assemblage. En outre il faut que le mme ensemble de permissions lui soit accord par
la stratgie de scurit de chaque domaine dapplication sinon il est charg plusieurs fois.
Chaque constructeur de classe dun assemblage charg dune manire neutre est invoqu
pour chaque domaine dapplication et il existe une copie de chaque champ statique de
chaque classe pour chaque domaine dapplication. En interne cela est possible grce une
table dindirection gre par le CLR. Ceci implique une petite baisse des performances. Cependant, charger un assemblage dune manire neutre par rapport aux domaines, permet
de ne le charger quune seule fois. En consquence, cette pratique est plus conomique en
terme de consommation de la mmoire, do un eet positif sur les performances. En outre,
chacune des mthodes dun tel assemblage nest compile par le compilateur JIT quune
seule fois.
Vous avez trois drapeaux possibles :

STARTUP_LOADER_OPTIMIZATION_SINGLE_DOMAIN : Aucun assemblage nest charg dune


manire neutre par rapport aux domaines. Cest ce comportement qui est pris par dfaut.
STARTUP_LOADER_OPTIMIZATION_MULTI_DOMAIN : Tous les assemblages sont chargs
dune manire neutre par rapport aux domaines. Aucun hte du moteur dexcution
nutilise cette option ce jour.
STARTUP_LOADER_OPTIMIZATION_MULTI_DOMAIN_HOST : Seuls les assemblages partags
contenus dans le GAC sont chargs dune manire neutre par rapport aux domaines.

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

Chapitre 4 : Le CLR (le moteur dexcution des applications .NET)

renvoye par la fonction CorBindToRuntimeEx(). Cette interface prsente entre autres deux mthodes Start() et Stop() dont les noms illustrent leurs actions.

Exemple de code C++ non gr dun hte du moteur dexcution


propritaire
Dans la trs grande majorit des projets, vous naurez pas crer votre hte du moteur dexcution. Cependant, il est rassurant de savoir que ceci est possible et instructif den avoir dj vu
un.
Pour tre bien compris, ce code ncessite une bonne connaissance de la technologie COM. Noublions pas que les systmes dexploitation type Windows 2000/XP utilisent encore COM comme
mcanisme de communication/modlisation. Le CLR prsente la possibilit dtre configur
et manipul par du code non gr, par lintermdiaire dinterfaces COM. Parmi ces interfaces
COM, linterface ICCLRRuntimeHost joue un rle prpondrant. Nous avons vu un peu plus
haut quune telle interface peut tre obtenue en appelant la fonction CorBindToRuntimeEx().
Voici donc le code C++ du plus basique des htes du moteur dexcution :
Exemple 4-7 :
// Pour compiler ce code, il faut vous lier avec
// la biblioth`eque statique mscoree.lib.
#include <mscoree.h>
// Limport doit se faire sur une seule ligne.
#import <mscorlib.tlb> raw_interfaces_only ...
... high_property_prefixes("_get","_put","_putref") ...
... rename("ReportEvent","ReportEventG
er
e") rename_namespace("CRL")
// Utilise lespace de nom ComRuntimeLibrary.
using namespace CRL ;
ICLRRuntimeHost * pClrHost = NULL ;
void main (void){
// Obtient une interface COM ICorRuntimeHost sur le CLR.
HRESULT hr = CorBindToRuntimeEx(
NULL, // On demande la derni`
ere version du CLR.
NULL, // On demande la version workstation du CLR.
0,
CLSID_CLRRuntimeHost,
IID_ICLRRuntimeHost,
(LPVOID *) &pClrHost) ;
if (FAILED(hr)){
printf("Echec de lobtention du ptr ICLRRuntimeHost !") ;
return ;
}
printf("Pointeur ICCLRRuntimeHost obtenu.\n") ;
printf("Lance le CLR.\n") ;
pClrHost->Start();

Chargement du CLR dans un processus grce lhte du moteur dexcution

99

// Ici, on peut utiliser notre pointeur COM sur le CLR.


pClrHost->Stop();
printf("CLR stoppe.\n") ;
pClrHost->Release();
printf("Ciao !\n") ;
}
Supposons que nous ayons un assemblage MyManagedLib.dll stock dans le rpertoire C:\Test
compil partir de ce code :
Exemple 4-8 :

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() ;
...

Modifier la configuration du CLR partir dun hte du moteur


dexcution propritaire
Il faut savoir que lorsque le CLR est vu comme un objet COM, linterface ICLRRuntimeHost nest
pas la seule interface prsente par cet objet COM. Vous pouvez en fait manipuler le CLR
travers les interfaces COM suivantes :

ICLRRuntimeHost permet de crer et de dcharger des domaines dapplications, de grer le


cycle de vie du CLR et de crer des preuves pour le mcanisme de scurit CAS.

100

Chapitre 4 : Le CLR (le moteur dexcution des applications .NET)

ICorConfiguration permet de spcifier au CLR certaines interfaces callback pour pouvoir


tre averti de certains vnements. Ces interfaces callback sont : IGCThreadControl IGCHostControl IDebuggerThreadControl. Les vnements prsents par ces interfaces sont
beaucoup moins fins que ceux prsents par lAPI de profiling du CLR, prsente un peu
plus bas.

ICorThreadPool permet de manipuler le pool de threads .NET du processus et de modifier


certains paramtres de configuration.

IGCHost permet dobtenir des informations sur le fonctionnement du ramasse-miettes et de


modifier certains paramtres de configuration.

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);
...

Spcificits de lhte du moteur dexcution de SQL Server 2005


Comme nous lavons mentionn, lhte du moteur dexcution de SQL Server 2005 est trs particulier du fait des contraintes de fiabilit, de scurit et de performance hors normes imposes
par un tel SGBD.
La contrainte prpondrante de ce type de serveur est la fiabilit. Les trois mcanismes rgion
dexcution contrainte (CER), finaliseur critique et rgion critique (CR) ont t ajouts au CLR pour
renforcer la fiabilit dune application .NET. Ils font lobjet de la section 4 Facilits fournies
par le CLR pour rendre votre code plus fiable , page 125 du prsent chapitre.
La seconde contrainte est la scurit. Pour viter que du code malveillant utilisateur soit charg
par inadvertance, tous les assemblages utilisateurs sont chargs par lhte du moteur dexcution partir de la base de donnes. Cela implique une phase de prchargement des assemblages
dans la base par ladministrateur DB (le DBA). Durant cette phase, le DBA peut prciser que
lassemblage appartient une des catgories SAFE, EXTERNAL_ACCESS et UNSAFE selon le niveau
de confiance quil accorde son code. Lensemble des permissions qui sera accord lassemblage lexcution par le mcanisme CAS est fonction de cette catgorie et donc, de ce niveau
de confiance. Enfin, certaines fonctionnalits du framework .NET considres comme sensibles,
telles que certaines classes de lespace de noms System.Threading, ne sont pas exploitables
partir dun assemblage charg dans SQL Server 2005.
La troisime contrainte est la performance. On la satisfait en exploitant les ressources dune
manire optimale. Dans ce contexte, les ressources les plus prcieuses sont les threads et les pages
mmoires charges en mmoires vive. Lide est de minimiser le nombre de context switching

Chargement du CLR dans un processus grce lhte du moteur dexcution

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

Chapitre 4 : Le CLR (le moteur dexcution des applications .NET)

Responsabilit
Chargement
blages

des

Interfaces
par lhte.
assem-

implmentes

Interfaces implmentes par


le CLR.

IHostAssemblyManager
IHostAssemblyStore

ICLRAssemblyReferenceList
ICLRAssemblyIdentityManager

Scurit

IHostSecurityManager
IHostSecurityContext

ICLRHostProtectionManager

Gestion des checs

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

Profiler vos applications


Cette section pour but de vous sensibiliser une fonctionnalit particulirement utile du
CLR : la possibilit de profiler trs finement son excution. En dautres termes, vous pouvez
demander au CLR dexcuter une de vos mthodes non gres lorsquun vnement particulier
survient tel que le commencement de la compilation JIT dune mthode ou la fin du chargement dun assemblage. Il est assez logique que ces callbacks soient des mthodes non gres. En
eet, ces mthodes sont censes nous permettre dobserver ltat du CLR donc il vaut mieux que
ce ne soit pas ce dernier qui prenne en charge leurs excutions.
Pour exploiter le profiling du CLR il faut que vous construisiez une classe COM implmentant
linterface ICorProfilerCallback. Cette interface est dfinie dans le fichier corprof.h.qui se
trouve dans le rpertoire SDK\v2.0\Include de linstallation de Visual Studio. Elle contient environs 70 mthodes. Une fois linterface ICorProfilerCallback implmente, il faut que le CLR
sache que cest cette implmentation quil doit utiliser pour raliser les callbacks. Pour communiquer le CLSID de cette implmentation au CLR, vous navez pas besoin de crer votre hte du

Localisation et chargement des assemblages lexcution

103

moteur dexcution. Il sut de positionner correctement les deux variables denvironnement


Cor_Enable_Profiling et Cor_Profiler. Cor_Enable_Profiling doit tre positionne une
valeur non nulle pour spcifier que le CLR doit raliser les callbacks. Cor_Profiler doit tre
positionne avec le CLSID ou le ProgID de limplmentation de ICorProfilerCallback.
Nous nallons pas dtailler cette possibilit car larticle The .NET Profiling API and the DNProfiling Tool de Matt Pietrek du numro de Dcembre 2001 de MSDN Magazine (disponible
gratuitement en ligne) le fait dj. Nous vous conseillons vivement de tlcharger le code de cet
article est de faire quelques essais sur vos propres applications .NET. Vous prendrez ainsi toute
la mesure de la quantit de tches ralises par le CLR ! Le code fourni avec cet article a aussi
lavantage de montrer comment utiliser un masque pour navoir accs qu certaines catgories
de callback. La manipulation ncessaire pour utiliser ce code est trs facile :

Enregistrez la classe COM sur votre machine ;

ouvrez une fentre de commande ;

positionnez les variables denvironnement en excutant le fichier batch fourni ;

lancez votre application partir de cette fentre de commande.

Localisation et chargement des assemblages


lexcution
Que lon applique la stratgie de dploiement des assemblages titre priv, ou la stratgie de
dploiement des assemblages partags, cest le CLR qui localise et charge les assemblages lexcution. Plus exactement, ces tches incombent au sous systme chargeur dassemblages (assembly
loader en anglais) du CLR plus communment nomm fusion. Lide gnrale est que le processus de localisation des assemblages par le CLR est configurable et intelligent.

Configurable dans le sens o un administrateur peut facilement dplacer des assemblages


tout en permettant quils soient encore localisables. Configurable aussi dans le sens o lon
peut rediriger lutilisation de certaines versions vers dautres versions (de deux faons direntes, avec les assemblages de stratgie dditeur, ou avec un fichier de configuration prsent plus loin).

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.

Ces deux contraintes sont satisfaites grce un lalgorithme de localisation de fusion.


Nous avons vu page 15 quun assemblage pouvait tre constitu de plusieurs fichiers nomms
modules. Ici, nous parlons du processus de localisation dun assemblage, cest--dire du processus de localisation du module principal dun assemblage, (celui avec le manifeste). Rappelons
que tous les modules dun mme assemblage doivent se trouver imprativement dans le mme
rpertoire.

104

Chapitre 4 : Le CLR (le moteur dexcution des applications .NET)

Quand le processus de localisation est-il dmarr ?


Le processus de localisation du CLR est souvent sollicit lorsque le code dun assemblage en
cours dexcution besoin de charger un autre assemblage. Si la localisation choue, elle se solde
par lenvoi de lexception System.IO.FileNotFoundException. Le processus de localisation est
utilis lors de :

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.

Cet algorithme nest pas utilis lors de :

Lutilisation de la mthode AppDomain.ExecuteAssembly() que nous avons vu au dbut de


ce chapitre lors de la prsentation des domaines dapplication.

Lutilisation de la mthode statique Assembly.LoadFrom() qui accepte le chemin (absolu ou


relatif partir du rpertoire dexcution de lapplication) et le nom de lassemblage.

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.

Utilisation de llment CodeBase


Si le nom fort nest pas correctement fourni ou sil est fourni mais que lassemblage nest pas
trouv dans le rpertoire GAC alors peut tre que lassemblage doit tre charg partir dune
URL (Unique Resource Locator). Cette possibilit est la base du mcanisme de dploiement No
Touch Deployment dcrit en page 84.
En eet, le fichier de configuration de lapplication peut avoir un lment <codebase> relatif
lassemblage localiser. Dans ce cas, cet lment dfinit une URL et le CLR tentera de charger
lassemblage partir de cette URL. Si lassemblage nest pas trouv cette URL, la localisation
chouera. Si lassemblage localiser est dfini avec un nom fort dans le fichier de configuration,
alors lURL peut tre une adresse internet, une adresse intranet ou un rpertoire de la machine
courante. Si lassemblage localiser nest pas dfini avec un nom fort, lURL ne peut tre quun
sous rpertoire du rpertoire du rpertoire de lapplication.
Voici un extrait dun fichier de configuration dune application avec llment codebase> :

Localisation et chargement des assemblages lexcution

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

Chapitre 4 : Le CLR (le moteur dexcution des applications .NET)


C:\AppDir\fr-FR\Foo.dll
C:\AppDir\fr-FR\Foo\Foo.dll
C:\AppDir\Path1\fr-FR\Foo.dll
C:\AppDir\Path1\fr-FR\Foo\Foo.dll
C:\AppDir\Path2\Bin\fr-FR\Foo.dll
C:\AppDir\Path2\Bin\fr-FR\Foo\Foo.dll
C:\AppDir\fr-FR\Foo.exe
C:\AppDir\fr-FR\Foo\Foo.exe
C:\AppDir\Path1\fr-FR\Foo.exe
C:\AppDir\Path1\fr-FR\Foo\Foo.exe
C:\AppDir\Path2\Bin\fr-FR\Foo.exe
C:\AppDir\Path2\Bin\fr-FR\Foo\Foo.exe

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.

Schma rcapitulatif de lalgorithme de localisation


Vous pouvez vous servir de loutil fuslogvw.exe pour analyser les logs produits par fusion. Cet
outil est trs pratique pour dterminer les causes dun chec du chargement dun assemblage.

Llment <assemblyBinding> du fichier de configuration


Le fichier de configuration dun assemblage ASM peut contenir des paramtres utiliss par fusion lorsque le code de ASM dclenche la localisation et le chargement dun autre assemblage.
Le fichier dinstallation peut contenir un lment <probing> qui spcifie un ou plusieurs sous
rpertoires que le mcanisme de probing doit sonder. Il peut aussi contenir un lment <dependentAssembly> pour chaque assemblage localiser potentiellement. Chacun de ces lments
<dependentAssembly> peut contenir les informations suivantes sur la faon dont cet assemblage
doit tre localis :

Llment <publisherPolicy> dtermine si les ventuelles stratgies dditeur relatives


lassemblage charger doivent tre prises en compte lors de sa localisation.

Llment <codebase>, dcrit dans la section prcdente, dfinit une URL partir de laquelle lassemblage localiser doit tre charg.

Llment <bindingRedirect> permet de rediriger le numro de version comme le ferait


une stratgie dditeur. La stratgie dditeur sapplique toutes les applications clientes
dun assemblage partag, alors quici seule lapplication paramtre par ce fichier tient
compte de cet lment.

Voici quoi peut ressembler un fichier de configuration (notez la ressemblance avec le fichier
de configuration dune stratgie dditeur) :

Localisation et chargement des assemblages lexcution

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 localis lURL

ASM
est-il localis
lURL spcifie ?

ASM se localise dans le


cache de tlchargement
Lvnement
AssemblyResolve
retourne-t-il un
assemblage ?
chec de AssemblyLoad()

O
ASM est localis dans le sous-rpertoire

ASM a t charg par une mthode

Figure 4 -2 : Lalgorithme du CLR de localisation dun assemblage


Exemple 4-10 :
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<probing privatePath="Path1;Path2/bin" />
<dependentAssembly>
<assemblyIdentity name="Foo1" culture= "neutral"
publicKeyToken="C64B742BD612D74A" />
<publisherPolicy apply="no" />
<bindingRedirect oldVersion="1.0.0.0" newVersion="2.0.0.0"/>
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Foo2" culture= "neutral"
publicKeyToken="C64B742BD612D74A" />
<codebase version="2.0.0.0"
href = "file:///C:/Code/Foo2.dll>"/>
</dependentAssembly>

108

Chapitre 4 : Le CLR (le moteur dexcution des applications .NET)


<dependentAssembly>
<assemblyIdentity name="Foo3" culture= "fr-FR"
publicKeyToken="C64B742BD612D74A" />
<codebase version="3.0.0.0"
href = "http://www.smacchia.com/Foo3.dll>"/>
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>

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.

Le mcanisme de Shadow Copy


Lorsquun assemblage est charg dans un processus, le ou les fichiers correspondants sont automatiquement verrouills par Windows. Vous ne pouvez ni les mettre jour ni les dtruire.
Vous pouvez seulement les renommer. Dans le cas dun serveur type ASP.NET, o un mme
processus hberge potentiellement plusieurs applications, ce comportement pourrait tre particulirement gnant puisquil nous obligerait redmarrer tous le processus chaque mise
jour dune seule application.
Heureusement, le chargeur dassemblages du CLR prsente le mcanisme dit de shadow copy.
Il consiste copier les fichiers dun assemblage dans un rpertoire cache ddi lapplication,
avant de le charger eectivement. Ainsi, les fichiers originaux de lassemblage peuvent tre mis
jour mme lorsque celui-ci est en cours dexcution. ASP.NET exploite cette possibilit et vrifie
priodiquement si un assemblage charg avec cette technique a t mis jour. Le cas chant,
lapplication est redmarre sans perte de requtes.
La proprit stringShadowCopyDirectories{get;set;} de la classe AppDomainSetup vous permet de prciser les rpertoires qui contiennent les assemblages qui doivent tre shadow copis .

Rsolution des types lexcution


Notion de chargement implicite et explicite dun assemblage
Nous avons souvent besoin dutiliser dans le code dun assemblage des types dclars dans
dautres assemblages. Il existe deux techniques pour exploiter les types dfinis dans un autre
assemblage :

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.

Rsolution des types lexcution

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).

Rfrencer un assemblage la compilation


Lorsque lassemblage A sexcute et lorsque le compilateur JIT compile une mthode de A qui
a besoin de types dfinis dans un assemblage B, B est implicitement charg par le CLR sil a t
rfrenc durant la compilation de A. Pour que A rfrence B, il faut avoir eectu une de ces
deux manipulations durant la compilation :

Soit vous compilez A en utilisant le compilateur csc.exe en ligne de commande ou dans


un script de construction. Il faut alors utiliser les options de compilation /reference /r et
/lib.

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

Chapitre 4 : Le CLR (le moteur dexcution des applications .NET)

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) :

.method private hidebysig static void Main() cil managed


{
.entrypoint
// Code size
9 (0x9)
.maxstack 2
.locals init (int32 V_0)
IL_0000: ldc.i4.3
IL_0001: ldc.i4.4
IL_0002: call
int32
[AssemblageBibliotheque]MesTypes.UneClasse::Somme(int32,int32)
IL_0007: stloc.0
IL_0008: ret
} // end of method Program::Main

La compilation Juste temps (JIT Just In Time)

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

Le type est dfini dans un autre assemblage


non encore charg
Comment
Lassemblage
ce type est-il
peut-il tre
rfrenc dans la table
localis ?
TypeRef ?
Le type est dfini dans un module de
lassemblage non encore charg
Le module
est-il dans le mme
rpertoire que le module
principal ?

Lexception System.IO.FileNotFoundException
est lance

O
Charge le module

Cration dune instance


de System.Type
dcrivant ce type

Charge lassemblage

Figure 4 -3 : Rsolution dun type lexcution

La compilation Juste temps (JIT Just In Time)


La portabilit au niveau binaire
Nous rappelons que les applications .NET sont, quel que soit leur langage source, compiles en
code IL. Le code IL est stock dans une section prvue cet eet, dans les assemblages et les
modules. Lapplication reste code en IL, jusquau moment o elle est excute.
Comme son nom lindique, le langage IL (Intermediate Langage) est un langage objet intermdiaire entre les langages .NET de haut niveau (C  , VB.NET...) et le langage machine. Lide est
que la compilation du code IL vers le langage machine natif, se fasse durant lexcution de lapplication, quand on connat les types de processeurs de la machine. Cela permet aux applications
.NET distribues sous forme dassemblages contenant du code IL, dtre excutables sur toutes
machines et tous systmes dexploitation supportant la plateforme .NET. Le point important
souligner est que pour distribuer une application .NET sur dirents systmes dexploitation,
il ny a pas besoin de compiler plus dune fois le code source de haut niveau (i.e le code source
C  , VB.NET...). On dit que les applications .NET sont portables au niveau binaire.

Comprendre le mcanisme de compilation Just In Time


La compilation du code IL en langage machine seectue durant lexcution de lapplication.
On pourrait imaginer quun des deux scnarios suivants soit appliqu :

Lapplication est entirement compile en langage machine ds son lancement.

112

Chapitre 4 : Le CLR (le moteur dexcution des applications .NET)


Les instructions IL sont interprtes les unes aprs les autres. On parle de langage interprt.

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.

Intercepter le premier appel grce au stub


Pour raliser la compilation JIT le CLR utilise une astuce bien connue dans les mcanismes
darchitecture distribue type RPC, DCOM .NET Remoting ou Corba. chaque fois quune classe
est charge par le CLR, un stub (ou souche en franais) est associ chacune des mthodes de la
classe. En architecture distribue un stub est une petite couche logicielle ct client qui intercepte les appels aux mthodes pour les convertir en appels distants (en DCOM le terme proxy
dsigne cette notion de stub). Dans le cas du JIT le stub dune mthode est matrialis par du
code qui intercepte le premier appel celle-ci.
Ce code contient un branchement vers une fonction du compilateur JIT. Cette fonction vrifie le
code IL puis le compile en langage natif. Le code natif rsultant est stock dans lespace mmoire
du processus, puis excut. Bien entendu le stub est alors remplac par un saut vers ladresse du
code natif de la mthode. Ainsi chaque mthode nest compile quune fois.

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.

Optimisations ralises par le compilateur JIT


Le compilateur JIT ralise des optimisations. En voici quelques exemples pour fixer les ides :

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().

La compilation Juste temps (JIT Just In Time)

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.

Acronymes JIT et JITA


Pour ceux qui viennent du monde COM/DCOM/COM+ lacronyme JIT peut leur rappeler
lacronyme JITA (Just In Time Activation, dcrit page 296). Les deux mcanismes sont dirents, ils
ont des buts dirents et sont utiliss dans des technologies direntes. Cependant lide sousjacente est la mme : on ne mobilise des ressources pour une entit (compilation dune mthode
IL pour JIT, activation dun objet COM pour JITA) que lorsque lentit est eectivement utilise
(juste avant que lentit soit utilise). On qualifie souvent les mcanismes qui utilisent cette ide
avec ladjectif paresseux (lazy en anglais).

Compilation avant excution : loutil ngen.exe


Microsoft fournit loutil ngen.exe capable de compiler les assemblages avant leur excution. Cet
outil fonctionne donc comme un compilateur classique et remplace le mcanisme JIT. Le vrai
nom de ngen.exe est Native Image Generator . Loutil ngen.exe est utiliser si vous constatez
que le mcanisme JIT introduit une baisse de performance notable (par exemple en utilisant les
compteurs de performance de la compilation JIT dcrits dans la section suivante). Cependant
le compilateur normal ralise un grand nombre doptimisations inaccessibles ngen.exe. Lutilisation du compilateur normal est en gnral prfrable.
La compilation par ngen.exe se fait en gnral linstallation de lapplication. Vous navez pas
manipuler les nouveaux fichiers contenant le code en langage machine, appels images natives.
Ceux-ci sont automatiquement stocks dans un rpertoire spcial de votre machine appel le
cache des images natives (Native Image Cache en anglais). Ce rpertoire est accessible partir de
ngen.exe avec les options /show et /delete. Ce rpertoire est aussi visible lorsquun utilisateur
visualise le cache global des assemblages car le cache des images natives est inclus dans ce rpertoire. Cette visualisation est dcrite page 65).

114

Chapitre 4 : Le CLR (le moteur dexcution des applications .NET)

Option de ngen.exe

Description

/show[nom assemblage
|nom repertoire]

Permet de visualiser lensemble des images natives contenues


dans le Native Image Cache .Si vous faite suivre cette option
du nom dun assemblage, vous ne verrez que les images natives
ayant le mme nom.Si vous faite suivre cette option du nom
dun rpertoire, vous ne verrez que les images natives des assemblages contenues dans ce rpertoire

/delete[nom assemblage
|nom repertoire]

Supprime lensemble des images natives contenues dans le


Native Image Cache .Si vous faite suivre cette option du nom
dun assemblage, vous ne supprimerez que les images natives
ayant le mme non.Si vous faite suivre cette option du nom
dun rpertoire, vous ne supprimerez que les images natives des
assemblages contenues dans ce rpertoire

/debug

Produit une image native utilisable par un dbogueur.

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.

Compteurs de performance de la compilation JIT


Vous avez accs aux six compteurs suivants de performance du JIT dans la catgorie ".NET CLR
Jit".
Nom du compteur, fournir sous
forme dune chane de caractres.

Description

"# of IL Methods JITted"

Compte le nombre de mthodes compiles. Ce


compteur ninclut pas les mthodes dj compiles
par ngen.exe.

"# of IL Bytes JITted"

Compte le nombre doctets de code IL compil. Les


mthodes pitches ne sont pas soustraites de ce
total.

"Total # of IL Bytes Jitted"

Compte le nombre doctet de code IL compil. Les


mthodes pitches sont contenues dans ce total.

"% Time in Jit"

Pourcentage du temps pass dans le compilateur JIT.


Ce compteur est mis jour chaque fin de compilation JIT.

La compilation Juste temps (JIT Just In Time)

115

"IL Bytes Jitted / sec"

Nombre moyen doctets de code intermdiaire compil par seconde.

"Standard Jit Failures"

Nombre de fois o une mthode considre comme


invalide na pu tre compile par le JIT.

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

Chapitre 4 : Le CLR (le moteur dexcution des applications .NET)


Not Displayed:0
--> Appel `a f().
# of Methods Jitted:3
# of IL Bytes Jitted:120
Total # of IL Bytes Jitted:120
IL Bytes Jitted / sec:0
Standard Jit Failures:0
% Time in Jit:0,02865955
Not Displayed:0

Prcisons que les compteurs de performances sont aussi visualisables avec loutil perfmon.exe
(accessible avec Menu dmarrer  Excuter...  perfmon.exe).

Gestion du tas par le ramasse-miettes


Introduction au ramasse-miettes
Dans les langages .NET, la destruction des objets grs nengage aucunement la responsabilit
du programmeur. Le problme de la destruction dun objet de type valeur est trs simple
rsoudre. Lobjet tant physiquement allou sur la pile du thread courant, lorsque la pile se
vide cet endroit, lobjet est dtruit. Le problme de la destruction dun objet de type rfrence
est beaucoup plus ardu. Pour simplifier la tche du dveloppeur, la plateforme .NET fournit
un ramasse-miettes (garbage collector ou GC en anglais) qui prend en charge automatiquement la
rcupration de la mmoire des objets dsallous. On parle de tas gr.
Lorsquil ny a plus de rfrences vers un objet, ce dernier devient inaccessible par le programme.
Le programme nen a donc plus besoin. Le ramasse-miettes fonctionne sur ce principe : il
marque les objets accessibles. Les objets non marqus sont donc inaccessibles par le programme,
et doivent tre dtruits. Un problme est le fait que le ramasse-miettes dsalloue un objet inaccessible seulement quand il le dcide (en gnral lorsque le programme a besoin de mmoire).
Le dveloppeur a une marge de manuvre restreinte sur la conduite du ramasse-miettes. Par
rapport dautres langages, comme le langage C++, cela introduit un certain non dterminisme
dans lexcution des programmes. Une consquence est que les dveloppeurs se sentent frustrs
de ne pas avoir un contrle total. En revanche, avec ce systme, les problmes rcurrents de
fuite de mmoire sont beaucoup moins nombreux.

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.

Problmatique des algorithmes possibles pour un ramasse-miettes


Le but nest pas de faire un expos des dirents algorithmes de ramasse-miettes existants, mais
de sensibiliser le lecteur aux problmes qui se posent aux concepteurs du ramasse-miettes. Ainsi

Gestion du tas par le ramasse-miettes

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.

Algorithme du ramasse-miettes .NET


Une collecte des objets par le ramasse-miettes est dclenche automatiquement par le CLR lorsquune pnurie potentielle de mmoire est pressentie. Lalgorithme de cette prise de dcision
nest pas document par Microsoft.
Le terme de gnration dfinit le laps de temps entre deux collectes. Chaque objet appartient
donc la gnration qui la vue natre. De plus, chaque instant lquation suivante est vrifie :
{Nombre de gnration quil y a eu dans ce processus} = 1+{Nombre de collectes quil y a eu dans
ce processus}.
Les gnrations sont numrotes. Le numro dune gnration est augment chaque collecte
jusqu ce quil atteigne un numro de gnration maximal (gal 2 dans limplmentation
actuelle du CLR). Par convention la gnration 0 est la gnration la plus jeune. Lorsquun objet
doit tre allou sur le tas, il fait partie de la gnration 0.
Une consquence de ce systme de gnrations est que les objets dune mme gnration sont
contigus en mmoire comme lindique la Figure 4-4 :

tape 1 : dfinir les racines de larbre des rfrences


vers les objets actifs
Les racines de larbre de rfrences vers les objets actifs (i.e les objets ne pas dsallouer) sont
principalement, les objets rfrencs par les champs statiques des classes, les objets rfrencs

118

Chapitre 4 : Le CLR (le moteur dexcution des applications .NET)


A

Gnration 2

Gnration 1

Gnration 0

(A B C D E et F sont des objets)

Figure 4 -4 : Gnrations dobjets


dans les piles des threads du processus et les objets dont ladresse physique (ou un oset bas sur
celle-ci) est contenue dans un registre de lunit centrale.

tape 2 : fabriquer larbre et marquer les objets qui


sont encore rfrencs
Le ramasse-miettes construit larbre partir des racines, en ajoutant les objets rfrencs par
les objets dj dans larbre, et en itrant cette opration. Chaque objet rfrenc par larbre est
marqu comme actif. Lorsque lalgorithme rencontre un objet dj marqu comme actif, il ne
le prend pas en compte afin dviter les problmes de cycle de rfrencement dobjets. Cette
tape se termine lorsque le ramasse-miettes ne peut plus collecter un objet rfrenc, qui ne soit
dj marqu comme actif. Le ramasse-miettes se sert des mtadonnes de type pour trouver les
rfrences contenues dans un objet partir de la classe de lobjet.
Sur la Figure 4-5, on voit que les objets A et B sont des objets qui constituent les racines de larbre.
A et B rfrencent C. B rfrence E et C rfrence F. Lobjet D nest donc pas marqu comme actif.
Objets racines
F
A
B

C
E

Figure 4 -5 : Larbre des rfrences du ramasse-miettes

tape 3 : dsallouer les objets inactifs


Le ramasse-miettes parcourt linairement le tas et dsalloue les objets non marqus comme
actifs. Certains objets ont besoin que leur mthode Finalize() soit appele pour pouvoir tre
physiquement dtruit. Cette mthode nomme finaliseur, est dfinie par la classe Object. Elle
doit tre appele avant la destruction dun objet dont la classe redfinit cette mthode. Les invocations des finaliseurs sont prises en charge par un thread ddi de faon ne pas surcharger le
thread qui eectue la collecte des objets. Une consquence est que les emplacements mmoires
des objets qui ont un finaliseur survivent une collecte.
Le tas nest pas ncessairement entirement parcouru, soit parce que le ramasse-miettes a collect susamment de mmoire, soit parce que nous avons faire une collecte partielle. Lalgorithme de parcours de larbre des rfrences est profondment impact par cette possibilit de
collecte partielle car il se peut que des objets anciens (de la gnration 2) rfrencent des objets
jeunes (de la gnrations 0 ou 1). Bien que la frquence de dclenchement des collectes dpend
compltement de votre application vous pouvez vous baser sur ces ordres de grandeur : une

Gestion du tas par le ramasse-miettes

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

Figure 4 -6 : Destruction des objets inactifs

tape 4 : dfragmentation du tas


Le ramasse-miettes dfragmente le tas, cest--dire que les objets actifs sont dplacs vers le bas du
tas pour combler les espaces mmoires vides qui contenaient les objets dsallous ltape
prcdente. Ladresse du haut du tas est recalcule et chaque gnration est incrmente. Les
objets anciens sont vers le bas du tas et les objets rcents vers le haut. De plus les objets actifs
crs au mme moment sont aussi physiquement proches. Seuls les objets jeunes sont souvent
examins. Ainsi, ce sont toujours les mmes pages mmoire qui sont souvent sollicites. Ce fait
est trs important pour obtenir de bonnes performances.
Sur la Figure 4-7 (par rapport la Figure 4 -6) le ramasse-miettes a incrment les numros de
gnration. On suppose que la classe de lobjet D navait pas de finaliseur. Ainsi, la mmoire de
lobjet D a t dsallou et le tas a t dfragment (E et F ont t bougs pour combler lespace
mmoire anciennement allou D). Notez que A et B nont pas vu leur numro de gnration
augmenter puisquil tait dj dans la gnration 2.
A

B
Gnration 2

Gnration 1

Gnration 0

Figure 4 -7 : Dfragmentation du tas


Certains objets sont inamovibles (on dit aussi pingls ) cest--dire quils ne peuvent tre
bougs physiquement par le ramasse-miettes. Cette particularit sappelle to pin an object en
anglais (pin veut dire punaise/pingle en franais). Un objet est inamovible lorsquil est point
par du code non protg. Plus de dtails ce sujet sont disponibles en page 505.

tape 5 : recalculer les adresses contenues dans les rfrences


Les adresses mmoire de certains objets actifs ont pu tre modifies par ltape prcdente. Il
faut donc parcourir larbre des objets actifs et actualiser les rfrences avec les nouvelles adresses.

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

Chapitre 4 : Le CLR (le moteur dexcution des applications .NET)

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.

Tas spcial pour les gros objets


Tous les objets dont la taille est infrieure une certaine taille sont traits dans le tas gr dcrit
dans les tapes prcdentes. Cette taille est non documente par Microsoft. Un ordre de grandeur est de 20 100 Ko. Les objets dont la taille est suprieure ce seuil sont stocks dans un
autre tas spcial, pour des raisons de performances. En eet, dans ce tas, les objets ne sont pas
physiquement bougs par le ramasse-miettes. Une page mmoire Windows a une taille de 4 ou
8Ko en fonction du processeur sous-jacent. Chacun de ces objets est stock sur un nombre entier
de page mmoire mme sil na pas une taille exactement multiple de 4 ou 8Ko. Ceci induit un
peu de gaspillage mais ce dfaut naecte pas significativement le gain de performance. Cette
dirence de stockage est compltement transparente pour le dveloppeur.

Ramasse-miettes et applications multithreads


Lexcution dune collecte du ramasse-miettes peut se faire avec un des threads applicatifs si elle
est dclanche manuellement ou avec un thread du CLR lorsque celui-ci dcide quune collecte
doit avoir lieu. Avant de commencer une collecte, le CLR doit stopper lexcution des autres
threads de lapplication pour viter quils modifient le tas. Pour cela, direntes techniques
existent. On peut citer linsertion de point protgs (safe point en anglais) par le compilateur JIT,
qui permettent aux threads applicatifs de vrifier si une collecte est en attente de commencement. Notez que la gnration 0 du tas est en gnral dcoupe en portions (appeles arnes),
une par thread, pour viter les problmes de synchronisation lis aux accs concurrents au tas.
Rappelons enfin que les finaliseurs sont excuts par un thread du CLR ddi cette tche.

Les rfrences faibles (weak reference)


La problmatique
Lors de lexcution dune application, chaque instant, chaque objet est soit actif, cest--dire
que lapplication a encore une rfrence vers lui, soit inactif. Lorsquun objet passe du stade actif
au stade inactif, cest--dire lorsque lapplication vient de dtruire la dernire rfrence vers cet
objet, il ny a plus aucun espoir daccder lobjet.
En fait il existe un troisime stade intermdiaire entre actif et inactif. Lorsque lobjet est dans
ce stade, lapplication peut laccder, mais le ramasse-miettes peut le dsallouer. Il y a, priori,
contradiction, car si lobjet est accessible il est rfrenc et sil est rfrenc il ne peut tre dsallou. En fait, il faut introduire la nouvelle notion de rfrence faible (weak reference en anglais)
pour pouvoir comprendre ce stade intermdiaire. Lorsquun objet est rfrenc par une rfrence faible, il est la fois accessible par lapplication et dsallouable par le ramasse-miettes.

Gestion du tas par le ramasse-miettes

121

Pourquoi utiliser des rfrences faibles ?


Le dveloppeur peut utiliser une rfrence faible sur un objet si ce dernier satisfait toutes les
conditions suivantes :

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.

Comment utiliser des rfrences faibles ?


Lutilisation des rfrences faibles est particulirement aise grce la classe System.WeakReference. Un exemple valant mieux quun long discours, examinez le code C  suivant :
Exemple 4-14 :
class Program {
public static void Main() {
// obj est une reference forte vers lobjet cr
e
e ci-dessous.
object obj = new object() ;
// wobj est une reference faible vers notre objet.
System.WeakReference wobj = new System.WeakReference(obj);
obj = null ; // Destruction de la r
ef
erence forte obj.
// ...
// Ici lobjet est potentiellement d
etruit par le GC.
// ...
// Creation dune reference forte `
a partir de la r
ef
erence faible.
obj = wobj.Target;
if (obj == null) {

122

Chapitre 4 : Le CLR (le moteur dexcution des applications .NET)


// Le GC a detruit lobjet, il nest plus accessible !
}
else {
// Lobjet existe toujours , nous pouvons lutiliser et il
// ne peut plus etre d
etruit par le GC.
}
}
}

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.

Rfrences faibles courtes et rfrences faibles longues


Il y a deux constructeurs pour la classe WeakReference :
WeakReference(object target) ;
WeakReference(object target, bool trackResurrection) ;
Dans le premier constructeur le paramtre trackResurrection est positionn implicitement
false. Si ce paramtre est positionn true lapplication peut encore accder lobjet entre
linstant o la mthode Finalize() a t appele et linstant o lemplacement mmoire de
lobjet est rellement modifi (en gnral par la recopie dun autre objet dans cet emplacement
mmoire lors de la dfragmentation du tas). Dans ce cas on dit que cest une rfrence faible
longue (long weak reference en anglais). Si le paramtre trackResurrection est positionn false
lapplication ne peut plus accder lobjet ds que la mthode Finalize() a t appele. Dans
ce cas on dit que cest une rfrence faible courte (short weak reference en anglais).
Malgr le gain potentiel (mais minime) des rfrences faibles longues il vaut mieux ne jamais
les utiliser, car elles sont diciles maintenir. En eet, une volution du corps de la mthode
Finalize() qui ne tiendrait pas compte du fait quil y a des rfrences faibles longues sur des
instances de cette classe pourrait entraner la rsurrection dobjet dans un tat incorrect.

Agir sur le comportement du ramasse-miettes


avec la classe System.GC
On peut se servir des mthodes statiques de la classe System.GC pour agir sur le comportement du ramasse-miettes ou seulement analyser ce comportement. Lide est bien videmment
damliorer les performances de nos applications. Cependant, Microsoft ayant investi beaucoup
de ressources optimiser le ramasse-miettes .NET, nous vous conseillons de nutiliser les services
de la classe System.GC que lorsque vous tes certain du gain de performance produit par vos
modifications. Voici la proprit et les mthodes statiques de cette classe :

static int MaxGeneration{ get ; }


Cette proprit renvoie le nombre maximal de gnration du tas gr -1. Par dfaut,
dans limplmentation Microsoft courante de .NET, cette proprit vaut 2 et est garantie
constante durant le cycle de vie dune application.

Gestion du tas par le ramasse-miettes

123

static void WaitForPendingFinalizers()


Cette mthode suspend le thread qui lappelle, jusqu ce que lensemble des finaliseurs ait
t excut par le thread ddi cette tche.

static void Collect()


static void Collect( int generation )
Cette mthode dclenche une collecte du ramasse-miettes. Vous avez la possibilit de dclancher une collecte partielle, auquel cas le ramasse-miettes ne soccupera que des objets
dont le numro de gnration est entre generation et 0. generation ne peut tre suprieur
MaxGeneration. Lors de lappel de la surcharge sans paramtre, le ramasse-miettes collecte
toutes les gnrations.
Cette mthode est en gnral invoque mauvais escient, parce que le dveloppeur espre
quelle va rsoudre les problmes de mmoire inhrents au design de son application (trop
dobjets allous, trop de rfrences entre objets, fuite de mmoire etc).
Il est cependant intressant de dclancher une collecte du ramasse-miettes juste avant lappel de traitements critiques, qui seraient gns par une surcharge de la mmoire ou une
baisse de performance subite, due au dclenchement ramasse-miettes. Dans ce cas, il est
conseill dappeler les mthodes dans cet ordre afin dtre certains que lon commence notre
traitement avec le maximum de mmoire possible :
// Lance une premi`ere collecte.
GC.Collect()
// Attend que tous les finaliseurs aient
et
e ex
ecut
es.
GC.WaitForPendingFinalizers()
// Lib`ere la memoire de chaque objet dont le finaliseur
// vient detre execute.
GC.Collect()

static int CollectionCount( int generation )


Retourne le nombre de collectes dj eectues pour la gnration indique. Cette mthode peut tre utile pour dtecter si une collecte eu lieu durant un intervalle de temps.

static int GetGeneration( object obj )


static int GetGeneration( WeakReference wo )
Retourne le numro de gnration de lobjet rfrenc soit par une rfrence forte (premier
cas) soit par une rfrence faible (deuxime cas).

static void AddMemoryPressure( long pressure )


static void RemoveMemoryPressure ( long pressure )
La problmatique sous-jacente lutilisation de ces mthodes est une consquence du fait
que le ramasse-miettes ne tient pas compte de la mmoire non gre dans ses algorithmes.
Imaginez que 32 instances de la classe Bitmap qui ont chacune une taille de 32 octets maintiennent chacune une rfrence vers un bitmap non gr de 6Mo. Sans lutilisation de ces
mthodes, le ramasse-miettes se comporterait comme si seulement 32x32 octets taient allous, savoir, il ne jugerait pas ncessaire de dclancher des collectes. Une bonne pratique
est dutiliser ces mthodes dans les constructeurs et les finaliseurs de vos classes dont les
instances maintiennent des grosses zones de mmoire non gres. Nous prcisons que la
classe HandleCollector dcrite en page 277 permet de fournir le mme genre de service.

static long GetTotalMemory( bool forceFullCollection )

124

Chapitre 4 : Le CLR (le moteur dexcution des applications .NET)


Retourne une estimation de la taille courante en octets du tas gr. Vous pouvez aner
lestimation en mettant forceFullCollection true. Dans ce cas la mthode devient bloquante, si une collecte est en cours dexcution. La mthode retourne lorsque la collecte est
termine ou avant si lattente dpasse un certain intervalle de temps. La valeur retourne
sera plus prcise si la mthode retourne lorsque le ramasse-miettes a fini sa tche.

static void KeepAlive( object obj )


Garantit que obj ne peut tre dtruit par le ramasse-miettes durant lexcution de la mthode appelant KeepAlive(). KeepAlive() doit tre appele la fin du corps de la mthode.
Vous pouvez penser que cette mthode ne sert rien puisquelle ncessite une rfrence
forte vers lobjet qui garantit par son existence ce comportement de non destruction. En
fait, le compilateur JIT optimise le code natif produit en positionnant une variable locale
de type rfrence nulle aprs sa dernire utilisation et avant la fin du corps de la mthode.
La mthode KeepAlive() ne fait que dsactiver cette optimisation.

static void SuppressFinalize(object obj)


La mthode Finalize() ne sera pas appele par le ramasse-miettes sur lobjet pass en paramtre. Rappelez-vous que lappel Finalize() sur tous les objets avant la fin du processus,
est un comportement garanti par le ramasse-miettes.
La mthode Finalize() devrait logiquement, contenir du code pour dsallouer des ressources possdes par lobjet. Cependant on ne matrise pas le moment de lappel Finalize(). Ainsi on cre souvent une mthode spcialement ddie cette dsallocation de
ressources que lon appelle quand on le souhaite. En gnral on utilise la mthode IDisposable.Dispose() cet eet. Cest dans cette mthode spciale que lon doit appeler SuppressFinalize() puisque aprs son invocation il ny aura plus lieu dappeler Finalize().
Plus de dtails ce sujet sont disponibles en page 423.

static void ReRegisterForFinalize(object obj)


La mthode Finalize() de lobjet pass en paramtre sera appele par le ramasse-miettes.
On utilise en gnral cette mthode dans deux cas :

1er cas : Lorsque la mthode SuppressFinalize() a dj t appele et que lon change


davis (cette pratique est nanmoins viter car elle signale une faiblesse de conception).

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

Facilits fournies par le CLR


pour rendre votre code plus fiable
Les exceptions asynchrones du CLR et la fiabilit du code gr
Nous esprons que le contenu du prsent chapitre vous a convaincu quexcuter du code dune
manire gre est un progrs dterminant. Il est temps maintenant de sintresser la face obscure des environnements grs. Dans un tel environnement, des traitements coteux peuvent
tre dclenchs implicitement par le CLR pratiquement nimporte quel moment de lexcution. Cette particularit implique quil est impossible de prvoir quand une pnurie de ressource
peut survenir. Voici quelques exemples classiques de tels traitements coteux dclenchs inopinment par le CLR (cette liste est loin dtre exhaustive) :

Chargement dun assemblage.

Excution dun constructeur de classe.

Collecte dobjets par le ramasse miettes.

Compilation JIT dune classe.

Parcours de la pile CAS.

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

Chapitre 4 : Le CLR (le moteur dexcution des applications .NET)

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.

Les rgions dexcution contraintes (CER)


Pour viter de faire tomber le processus tout entier, il faut pouvoir dcharger un domaine dapplication lorsquune exception asynchrone y survient. On parle de recyclage de domaines dapplication. Toute la problmatique dun tel recyclage est de le raliser proprement, sans fuite de mmoire et sans corrompre ltat gnral du processus. Il faut donc un mcanisme permettant de se
protger ponctuellement des exceptions asynchrones. Sans un tel mcanisme, il est impossible
de garantir que les ressources non gres dtenues au moment o une exception asynchrone
survient soient dsalloues correctement.
Le framework .NET 2.0 vous permet dindiquer au CLR les portions de code o la leve inopine
dune exception asynchrone serait catastrophique. Si une exception asynchrone doit survenir,
lide est de forcer le CLR la lever soit avant lexcution de la portion de code soit aprs mais
pas pendant. On nomme de telles portions de code des rgions dexcution contraintes (Constrained
Execution Regions en anglais ou plus simplement CER).
Pour viter de lever une exception asynchrone durant lexcution dune CER, le CLR doit effectuer un certains nombre de prparation juste avant de commencer lexcuter. Lide est de
tenter de dclencher la pnurie de ressource si celle-ci doit avoir lieu avant mme que la CER
soit excute. Typiquement, le CLR compile en code natif toutes les mthodes susceptibles dtre
excutes. Pour connatre ces mthodes, il parcourt statiquement le graphe dappel ayant pour
racine la CER. En outre, le CLR sait retenir une exception de type ThreadAbortException qui
survient pendant lexcution dune CER jusqu la fin de lexcution.
Le dveloppeur doit veiller ne pas allouer de mmoire au sein dune CER. Cette contrainte
est particulirement forte puisque de multiples conditions implicites peuvent dclencher une
allocation mmoire. On peut citer les instructions de boxing, les accs un tableau multidimensionnel ou les manipulations des objets de synchronisation.

Dfinir vos propres CER


Une CER se dfinit par lappel la mthode statique void PrepareConstrainedRegion() de la
classe System.Runtime.CompilerServices.RuntimeHelpersjuste avant la dclaration dun bloc

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.

static void PrepareMethod(RuntimeMethodHandle m)


Le CLR parcours statiquement le graphe des mthodes appeles dans une CER afin de les
compiler en code natif. Un tel parcours statique ne peut dtecter quelle version dune mthode virtuelle est appele. Aussi, il incombe au dveloppeur de forcer la compilation de
la version appele avant lexcution de la CER.

static void PrepareDelegate(Delegate d)


Les dlgus qui vont tre invoqus durant lexcution dune CER doivent tre prpars au
pralable en ayant recours cette mthode.

static void RunClassConstructor(type t)


Vous pouvez forcer lexcution dun constructeur de classe avec cette mthode. Naturellement, lexcution na lieu que si le constructeur de classe concern na pas dj t invoqu.

Les portails de mmoire (memory gates)


Dans le mme esprit que la mthode statique ProbeForSufficientStack() vous pouvez avoir
recours la classe System.Runtime.MemoryFailPoint qui sutilise comme suit :
// On est sur le point deffectuer une op
eration qui a besoin
// dau plus 15 Mb de memoire.
using( new MemoryFailPoint(15) ) {
// Effectue loperation ...
}
Contrairement la mthode ProbeForSufficientStack() la quantit de mmoire indique
nest pas rserve. Le constructeur de cette clase value seulement si lors de son excution cette
quantit de mmoire pourrait tre demande au systme dexploitation par le CLR sans quune
exception de type OutOfMemoryException soit leve. Notez quentre le moment o seectue
cette demande et le droulement eectif de lopration, les conditions peuvent avoir volues,
notamment parce quun autre thread a demand beaucoup de mmoire. Malgr cette faiblesse
lutilisation de cette technique est gnralement ecace.

128

Chapitre 4 : Le CLR (le moteur dexcution des applications .NET)

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.

Les contrats de fiabilit


Le framework .NET 2.0 prsente lattribut System.Runtime.ConstrainedExecution.ReliabilityContractAttribute qui ne sapplique quaux mthodes. Cet attribut permet de documenter
le niveau de gravit maximal auquel on peut sattendre si une exception asynchrone survient
lors de lexcution dune mthode marque. Ces niveaux de gravit sont dfinis par les valeurs
de lnumration System.Runtime.ConstrainedExecution.Consistency :
Consistency

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

La mthode marque ne peut corrompre aucun tat.

Une deuxime valeur de type System.Runtime.ConstrainedExecution.Consistency sapplique


chaque attribut ReliabilityContractAttribute. Elle permet de documenter si la mthode
peut mettre en dfaut les garanties dune ventuelle CER qui lappelle. Bien entendu, si la mthode peut corrompre un des tats processus ou domaine dapplication, elle peut mettre en
dfaut les garanties et ainsi il faut prvoir la valeur Cer.None.
Les contrats de fiabilit constituent un moyen de documenter votre code. Sachez cependant
quils sont aussi exploits par le CLR lorsque celui-ci parcours le graphe dappels statique lors de
la prparation de lexcution dune CER. Si une mthode sans contrat de fiabilit susant est
rencontre, ce parcours sarrte (puisque de toutes faons le code est non fiable) mais la CER sera
quand mme excute. Ce comportement dangereux a t dcid par les ingnieurs de Microsoft
car trop peu de mthodes du framework sont pour linstant annotes avec un contrat de fiabilit
susant. Notez que seuls les trois contrats de fiabilit suivants sont considrs comme susant
pour une CER :
[ReliabilityContract(Consistency.MayCorruptInstance, Cer.MayFail)]
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]

CLI et CLS

129

Les finaliseurs critiques


Le CLR considre que le code dun finaliseur dune classe qui drive de la classe System.
Runtime.ConstrainedExecution.CriticalFinalizerObject est une CER. On parle alors de
finaliseur critique. En plus dtre des CER, les finaliseurs critiques seront, au sein dune mme
collecte, tous excuts aprs lexcution de tous les finaliseurs normaux. Cela garantit que les
ressources les plus critiques, celles sur lesquelles les objets grs dpendent, seront libres en
dernier.
Le mcanisme de finaliseur critique est notamment exploit par les classes du framework
responsables du cycle de vie dun handle win32 ( savoir System.Runtime.InteropServices.
CriticalHandle et System.Runtime.InteropServices.SafeHandle dcrites en page 278).
Les autres classes du framework qui drivent de la classe CriticalFinalizerObject sont
System.Security.SecureString (voir page 227) et System.Threading.ReaderWriterLock (voir
page 158).

Les rgions critiques


Un second mcanisme de renforcement de la fiabilit est prvu en plus des CERs. Lide est de
fournir une information au CLR pour quil sache quand une ressource partage entre plusieurs
threads est mise jour. Une portion de code responsable de la mise jour dune telle ressource
est nomme rgion critique (Critical Region en anglais ou CR). Pour dfinir le commencement et
la fin dune rgion critique il sut dappeler les mthode BeginCriticalRegion() et EndCriticalRegion() de la classe Thread.
Si une exception asynchrone survient dans une rgion critique ltat de la ressource partage
entre plusieurs threads est potentiellement corrompu. Le thread courant va tre dtruit mais
cela ne sut pas garantir que lapplication va pouvoir continuer normalement son excution. En eet, dautres threads peuvent avoir accs aux donnes corrompues et ainsi, avoir un
comportement imprvisible. La seule solution envisageable est de dcharger le domaine dapplication courant et cest eectivement ce quil se passe. Ce comportement de propager un problme local un thread au domaine dapplication tout entier est nomm politique de lescalade
(escalation policy en anglais). Un second eet inhrent la notion de rgion critique est de forcer
le CLR dcharger le domaine dapplication courant si une demande dallocation mmoire
choue.
Si vous avez recours aux classes de verrous du framework (la classe Monitor et mot cl lock, la
classe ReaderWriterLock etc) pour synchroniser les accs vos ressources partages, vous naurez
pas besoin de dfinir explicitement les rgions critiques de votre code. En eet, les mthodes
dacquisition et de libration du verrou de ses classes applent implicitement les mthodes BeginCriticalRegion() et EndCriticalRegion().

En consquence, les rgions critiques sont principalement utiliser si vous dveloppez


votre propre mcanisme de synchronisation, ce qui, vous en conviendrez, nest pas une
tche courante.

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

Chapitre 4 : Le CLR (le moteur dexcution des applications .NET)

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

Introduction aux contraintes imposes aux langages .NET


Pour que les assemblages, compils partir dun langage, puissent tre grs par le CLR (ou
par une couche logicielle supportant le CLI) et utiliser toutes les classes et outils du framework
.NET, il faut que le langage et son compilateur respectent un ensemble de contraintes appeles
CLS (Common Langage Specification). Parmi ces contraintes on peut inclure le support des types
du CTS mais ce nest pas la seule contrainte.
Les contraintes imposes aux langages et leurs compilateurs par le CLS sont nombreuses. En
voici quelques-unes parmi les plus couramment cites :

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 consommateur sil peut instancier et utiliser les


membres publics des classes publiques contenues dans des assemblages compatibles avec
le CLS.

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.

Les langages C  , VB.NET et C++/CLI supportent ces deux niveaux de compatibilit.

Le point de vue du dveloppeur


Un assemblage est compatible avec le CLS (on dit CLS compliant en anglais) si les lments suivants sont compatibles avec le CLS :

La dfinition des types publics.

CLI et CLS

La dfinition des membres publics et des membres protgs des types publics.

Les paramtres des mthodes publiques et des mthodes protges.

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

Chapitre 5 : Processus, threads et gestion de la synchronisation

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 :

Le processus courant dans lequel linstance est utilise.

Un processus sur la mme machine autre que le processus courant.

Un processus sur une machine distante.

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.

Crer et dtruire un processus fils


Le petit programme suivant cre un nouveau processus, appel processus fils. Dans ce cas le
processus initial est appel processus parent. Ce processus fils excute le bloc note. Le thread du

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.

Empcher de lancer deux fois le mme programme sur la mme


machine
Cette fonctionnalit est requise par de nombreuses applications. En eet, il est courant quil
ny ait pas de sens lancer simultanment plusieurs fois une mme application sur la mme
machine. Par exemple il ny a pas de sens lancer plusieurs fois WindowMessenger sur la mme
machine.
Jusquici, pour satisfaire cette contrainte sous Windows, les dveloppeurs utilisaient le plus souvent la technique dite du mutex nomm dcrite page 154. Lutilisation de cette technique pour
satisfaire cette contrainte soure des dfauts suivants :

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.

Grce aux mthodes statiques GetCurrentProcess() (qui retourne le processus courant) et


GetProcesses() (qui retourne tous les processus lancs sur la machine) de la classe System.

136

Chapitre 5 : Processus, threads et gestion de la synchronisation

Diagnostics.Process, ce problme trouve une solution lgante et trs facile implmenter


expose par le programme suivant :
Exemple 5-2 :
using System.Diagnostics ;
class Program {
static void Main() {
if ( TestSiDejaLance() ) {
System.Console.WriteLine("Ce programme est d
ej`
a lanc
e.") ;
}
else{
// ici le code de lapplication
}
}
static bool TestSiDejaLance() {
Process processCurrent = Process.GetCurrentProcess() ;
Process[] processes = Process.GetProcesses() ;
foreach ( Process process in processes )
if ( processCurrent.Id != process.Id )
if ( processCurrent.ProcessName == process.ProcessName )
return true ;
return false ;
}
}
La mthode GetProcesses() peut aussi retourner tous les processus sur une machine distante
en communiquant comme argument le nom de la machine distante.

Terminer le processus courant


Vous pouvez dcider de terminer le processus courant en appelant une des mthodes statiques
Exit(int exitCode) ou FailFast(string message) de la classe System.Environment.
La mthode Exit() reprsente lalternative de choix. Le processus se terminera proprement et
retournera au systme dexploitation le code de sorti spcifi. La terminaison est propre dans le
sens o tous les finaliseurs des objets courants sont correctement appels et les ventuelles excutions de blocs finally en attente seront eectus par les dirents threads. En contrepartie,
la terminaison du processus prendra un certain temps.
Comme son nom lindique, lalternative FailFast() termine rapidement le processus. Les prcautions cites pour la mthode Exit() ne sont pas prises. Une erreur fatale contenant le message prcis est logue par le systme dexploitation. Cette mthode est utiliser si lors de la
dtection dun problme non rcuprable vous jugez que la continuation du programme peut
potentiellement engendrer une corruption de donne.

Les threads
Introduction
Un thread comprend :

Les threads

137

Un compteur dinstructions, qui pointe vers linstruction en cours dexcution.

Une pile.

Un ensemble de valeurs pour les registres, dfinissant une partie de ltat du processeur
excutant le thread.

Une zone prive de donnes.

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

Chapitre 5 : Processus, threads et gestion de la synchronisation

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 niveaux de priorit dexcution


Certaines tches sont plus prioritaires que dautres. Concrtement elles mritent que le systme
dexploitation leur alloue plus de temps processeur. Par exemple, certains pilotes de priphriques pris en charge par le processeur principal, ne doivent pas tre interrompus. Une autre
catgorie de tches prioritaires sont les interfaces graphiques utilisateurs. En eet, les utilisateurs
naiment pas attendre que linterface se rafrachisse.
Ceux qui viennent du monde win32, savent bien que le systme dexploitation Windows, sousjacent au CLR, assigne un numro de priorit chaque thread entre 0 et 31. Cependant, il nest
pas dans la philosophie de la gestion des threads sous .NET de travailler directement avec cette
valeur, parce que :

Elle est peu explicite.


Cette valeur est susceptible de changer au cours du temps.

Niveau de priorit dun processus


Vous pouvez assigner une priorit vos processus avec la proprit PriorityClass de la classe
System.Diagnostics.Process. Cette proprit est de type lnumration System.Diagnostics.
ProcessPriorityClass qui a les valeurs suivantes :

Les threads

139

Valeur de ProcessPriorityClass

Niveau de priorit correspondant

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.

Niveau de priorit dun thread


Chaque thread peut dfinir sa propre priorit par rapport celle de son processus, avec la proprit Priority de la classe System.Threading.Thread. Cette proprit est de type lnumration System.Threading.ThreadPriority qui prsente les valeurs suivantes :
Valeur
ThreadPriority

de

Eet sur la priorit du thread

Lowest

-2 units par rapport la priorit du processus

BelowNormal

-1 unit par rapport la priorit du processus

Normal

mme priorit que la priorit du processus

AboveNormal

+1 unit par rapport la priorit du processus

Highest

+2 units par rapport la priorit du processus

140

Chapitre 5 : Processus, threads et gestion de la synchronisation

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" ;

Crer et joindre un thread


Pour crer un nouveau thread dans le processus courant, il sut de crer une nouvelle
instance de la classe Thread. Les dirents constructeurs de cette classe prennent en argument un dlgu de type System.Threading.ThreadStart ou de type System.Threading.
ParametrizedThreadStart qui rfrence la mthode qui va tre excute par le thread cr.
Lutilisation dun dlgu de type ParametrizedThreadStart permet de passer un objet la
mthode qui va tre excute par un nouveau thread. Des constructeurs de la classe thread
acceptent aussi un paramtre entier permettant de fixer la taille maximale en octet de la pile
du thread cr. Cette taille doit tre au moins gale 128Ko (i.e 131072 octets). Aprs quune
instance de type Thread est cre, il faut appeler la mthode Thread.Start() pour eectivement
dmarrer le thread :
Exemple 5-3 :
using System.Threading ;
class Program {
static void f1() { System.Console.WriteLine("f1") ; }
void f2() { System.Console.WriteLine("f2") ; }
static void f3(object obj) { System.Console.WriteLine(
"f3 obj = {0}",obj) ; }
static void Main() {
// Specification explicite de la d
el
egation ThreadStart.
Thread t1 = new Thread(new ThreadStart(f1));
Program program = new Program();
// Utilisation de la possibilit
e C#2 dinf
erer
// le type dun delegue.
Thread t2 = new Thread( program.f2 );

Les threads

141

// Inference dun delegue de type ParametrizedThreadStart


// puisque f3() a un unique param
etre de type object.
Thread t3 = new Thread( f3 );
t1.Start() ; t2.Start() ; t3.Start("hello") ;
t1.Join() ; t2.Join() ; t3.Join() ;
}
}
Ce programme ache :
f1
f2
f3 obj = hello
Dans cet exemple, nous utilisons la mthode Join(), qui suspend lexcution du thread courant jusqu ce que le thread sur lequel sapplique cette mthode ait termin. Cette mthode
existe aussi en une version surcharge qui prend en paramtre un entier qui dfinie le nombre
maximal de millisecondes attendre la fin du thread (i.e un time out). Cette version de Join()
retourne un boolen positionn true si le thread sest eectivement termin.

Suspendre lactivit dun thread


Vous avez la possibilit de suspendre lactivit dun thread pour une dure dtermine en utilisant la mthode Sleep() de la classe Thread. Vous pouvez spcifier la dure au moyen dun
entier qui dsigne un nombre de millisecondes ou avec une instance de la structure System.
TimeSpan. Bien quune telle instance puisse spcifier une dure avec la prcision du dixime de
milliseconde (100 nano secondes) la granularit temporelle de la mthode Sleep() nest qu la
milliseconde.
// Le thread courant est suspendu pour une seconde.
Thread.Sleep(1000) ;
On peut aussi suspendre lactivit dun thread en appelant la mthode Suspend() de la classe
Thread, partir dun autre thread ou partir du thread suspendre. Dans les deux cas le
thread se bloque jusqu ce quun autre thread appelle la mthode Resume() de la classe Thread.
Contrairement la mthode Sleep(), un appel Suspend() ne suspend pas immdiatement
le thread, mais le CLR suspendra ce thread au prochain point protg rencontr. La notion de
point protg est prsente en page 120.

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).

Il sauto interrompt (il se suicide).

Il est interrompu par un autre thread.

142

Chapitre 5 : Processus, threads et gestion de la synchronisation

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 :

Suicide du thread principal

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.

Notion de threads foreground et background


La classe Thread prsente la proprit boolenne IsBackground. Un thread foreground est un
thread qui empche la terminaison du processus tant quil nest pas termin. loppos un
thread background est un thread qui est termin automatiquement par le CLR (par lappel
la mthode Abort()) lorsquil ny a plus de thread foreground dans le processus concern.
IsBackground est positionne false par dfaut, ce qui fait que les threads sont foreground par
dfaut.
On pourrait traduire thread foreground par thread de premier plan, et thread background par thread
de fond.

Introduction la synchronisation des accs aux ressources

143

Diagramme dtats dun thread


La classe Thread a le champ ThreadState de type lnumration System.Threading.ThreadState.
Les valeurs de cette numration sont :
Aborted
Running
Suspended
WaitSleepJoin

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

Figure 5 -1 : Diagramme dtat dun thread, simplifi

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

Chapitre 5 : Processus, threads et gestion de la synchronisation

compliques, soyez certains que lutilisation de la synchronisation est incontournable. Souvent


lutilisation de quelques rgles simples sut viter davoir grer la synchronisation. Parmi
ces rgles, citons la rgle danit entre threads et ressources que nous dcrivons un peu plus
loin dans ce chapitre.
Soyez conscient que la dicult de synchroniser les accs aux ressources dun programme vient
du dilemme entre une granularit des verrous trop fine et une granularit trop grossire. Si
vous synchronisez trop grossirement les accs vos ressources, vous simplifierez votre code
mais vous vous exposez des problmes de contention type goulot dtranglement. Si vous les
synchronisez trop finement, votre complexifierez votre code et terme vous ne pourrez plus le
maintenir. Vous tes alors expos aux problmes de deadlock et de race condition dcris ci aprs.
Avant daborder les mcanismes de synchronisation, il est ncessaire davoir une ide prcise
des notions de race conditions (situations de comptition en franais) et de deadlocks (interblocages
en franais).

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.

Synchronisation avec les champs volatiles et la classe Interlocked

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.

Synchronisation avec les champs volatiles


et la classe Interlocked
Les champs volatiles
Un champ dun type peut tre accd par plusieurs threads. Supposons que ces accs, en lecture
ou en criture, ne soient pas synchroniss. Dans ce cas, les nombreux mcanismes internes du
CLR pour grer le code font quil ny a pas de garantie que chaque accs en lecture au champ
charge la valeur la plus rcente. Un champ dclar volatile vous donne cette garantie. En langage
C  , un champ est dclar volatile si le mot-cl volatile est crit devant sa dclaration.
Tous les champs ne peuvent pas tre volatiles. Il y a une restriction sur le type du champ. Pour
quun champ puisse tre volatile, il faut que son type soit dans cette liste :

Un type rfrence.

Un pointeur (dans une zone de code non protge).

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

Chapitre 5 : Processus, threads et gestion de la synchronisation

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

Synchronisation avec la classe System.Threading.Monitor et le mot-cl lock


compteur-compteur++
compteur-compteur++
compteur-compteur-compteur++

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.

Autre possibilit dutilisation de la classe Interlocked


La classe Interlocked permet de rendre atomique une autre opration usuelle qui est la recopie
de ltat dun objet source vers un objet destination au moyen de la mthode statique surcharge
Exchange(). Elle permet aussi de rendre atomique lopration de comparaison des tats de deux
objets, et dans le cas dgalit, la recopie de cet tat vers un troisime objet au moyen de la
mthode statique surcharge CompareExchange().

Synchronisation avec la classe


System.Threading.Monitor et le mot-cl lock
Le fait de rendre des oprations simples atomiques (des oprations comme lincrmentation, la
dcrmentation ou la recopie dun tat), est indniablement important mais est loin de couvrir
tous les cas o la synchronisation est ncessaire. La classe System.Threading.Monitor permet
de rendre nimporte quelle portion de code excutable par un seul thread la fois. On appelle
une telle portion de code une section critique.

Les mthodes Enter() et Exit()


La classe Monitor prsente les mthodes statiques Enter(object) et Exit(object). Ces mthodes prennent un objet en paramtre. Cet objet constitue un moyen simple didentifier de
manire unique la ressource protger dun accs concurrent. Lorsquun thread appelle la mthode Enter(), il attend davoir le droit exclusif de possder lobjet rfrenc (il nattend que
si un thread a dj ce droit). Une fois ce droit acquis et consomm, le thread libre ce droit en
appelant Exit() sur ce mme objet.

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

Chapitre 5 : Processus, threads et gestion de la synchronisation

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 {

Synchronisation avec la classe System.Threading.Monitor et le mot-cl lock

149

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++){
lock( typeof(Program) ) { compteur *= compteur ; }
System.Console.WriteLine("compteur^2 {0}", compteur) ;
Thread.Sleep(10) ;
}
}
static void f2() {
for (int i = 0 ; i < 5 ; i++){
lock( typeof(Program) ) { compteur *= 2 ; }
System.Console.WriteLine("compteur*2 {0}", compteur) ;
Thread.Sleep(10) ;
}
}
}
linstar des blocs for et if, les blocs dfinis par le mot-cl lock ne sont pas tenus davoir des
accolades sils ne contiennent quune instruction. On aurait donc pu crire :
...
lock( typeof(Program) )
compteur *= compteur;
...
Lusage du mot-cl lock provoque bien la cration par le compilateur C  dun bloc try/finally
qui permet danticiper les leves dexceptions. Vous pouvez le vrifier avec un des outils Reflector
ou ildasm.exe.

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

Chapitre 5 : Processus, threads et gestion de la synchronisation


}
public void InstanceFct() {
lock (instanceSyncRoot) { /*...*/ }
}
}

Linterface System.Collections.ICollection prsente la proprit object SyncRoot{get;}.


La plupart des classes de collections (gnriques ou non) implmentent cette interface. Aussi,
vous pouvez vous servir de cette proprit pour synchroniser les accs aux lments dune collection. Ici, le pattern SyncRoot nest pas vraiment appliqu puisque lobjet sur lequel on synchronise les accs nest pas priv :
Exemple 5-9 :
using System.Collections.Generic ;
using System.Collections ;
public class Program {
public static void Main() {
List<int> list = new List<int>() ;
// ...
lock (((ICollection)list).SyncRoot) {
foreach (int i in list) {
// faire un traitement...
}
}
}
}

Notion de classe thread-safe


Une classe thread-safe est une classe dont chaque instance ne peut tre accde par plusieurs
threads la fois. Pour fabriquer une classe thread-safe, il sut dappliquer le pattern SyncRoot que
lon vient de voir toutes ses mthodes dinstances. Un moyen ecace pour ne pas encombrer le
code dune classe que lon souhaite tre thread-safe est fournir une classe drive wrapper threadsafe comme ceci :
Exemple 5-10 :
class Foo {
private class FooSynchronized : Foo {
private object syncRoot = new object() ;
private Foo m_Foo ;
public FooSynchronized(Foo foo) { m_Foo = foo ; }
public override bool IsSynchronized { get { return true ; } }
public override void Fct1(){ lock(syncRoot) { m_Foo.Fct1() ; } }
public override void Fct2(){ lock(syncRoot) { m_Foo.Fct1() ; } }
}
public virtual bool IsSynchronized { get { return false ; } }
public static Foo Synchronized(Foo foo){
if( ! foo.IsSynchronized )

Synchronisation avec la classe System.Threading.Monitor et le mot-cl lock

151

return new FooSynchronized( foo );


return foo ;
}
public virtual void Fct1() { /*...*/ }
public virtual void Fct2() { /*...*/ }
}
Un autre moyen est davoir recours lattribut System.Runtime.Remoting.Contexts.Synchronization prsent un peu plus loin dans ce chapitre.

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

Chapitre 5 : Processus, threads et gestion de la synchronisation

Les mthodes Wait(), Pulse() et PulseAll() de la classe Monitor


public static bool Wait(object [,int])
public static void Pulse(object)
public static void PulseAll(object)
Les trois mthodes Wait(), Pulse() et PulseAll() doivent tre utilises ensembles et ne
peuvent tre correctement comprises sans un petit scnario. Lide est quun thread ayant les
droits daccs exclusifs un objet dcide dattendre (en appelant Wait()) que ltat de lobjet
change. Pour cela, ce thread doit accepter de perdre momentanment les droits daccs exclusifs
lobjet afin de permettre un autre thread de changer ltat de lobjet. Ce dernier doit signaler
le changement avec la mthode Pulse(). Voici un petit scnario expliquant ceci dans les dtails :

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++){

Synchronisation avec des mutex, des vnements et des smaphores

153

System.Console.WriteLine("ThreadPong: Pong ") ;


Monitor.Pulse(balle);
Monitor.Wait(balle);
}
System.Console.WriteLine("ThreadPong: Bye!") ;
}
static void ThreadPingProc() {
System.Console.WriteLine("ThreadPing: Hello!") ;
lock (balle)
for(int i=0 ; i< 5 ; i++){
System.Console.WriteLine("ThreadPing: Ping ") ;
Monitor.Pulse(balle);
Monitor.Wait(balle);
}
System.Console.WriteLine("ThreadPing: Bye!") ;
}
}
Ce programme ache dune manire non dterministe :
ThreadPing:
ThreadPing:
ThreadPong:
ThreadPong:
ThreadPing:
ThreadPong:
ThreadPing:
ThreadPong:
ThreadPing:
ThreadPong:
ThreadPing:
ThreadPong:
ThreadPing:

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.

Synchronisation avec des mutex, des vnements et des


smaphores
La classe de base abstraite System.Threading.WaitHandle admet trois classes drives dont lutilisation est bien connue de ceux qui ont dj utilis la synchronisation sous win32 :

La classe Mutex (le mot mutex est la concatnation de mutuelle exclusion. En franais on
parle parfois de mutant)).

La classe AutoResetEvent qui dfinit un vnement repositionnement automatique. La


classe ManualResetEvent qui dfinit un vnement repositionnement manuelle. Ces deux
classes drivent de la classe EventWaitHandle qui reprsente un vnement au sens large.

154

Chapitre 5 : Processus, threads et gestion de la synchronisation


La classe Semaphore.

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.

Partage dobjets de synchronisation


Il est important de noter une autre grosse distinction entre le modle de synchronisation propos par les classes drives de WaitHandle et celui de la classe Monitor. Lutilisation de la classe
Monitor se cantonne un seul processus. Lutilisation des classes drives de WaitHandle fait appel des objets win32 non grs qui peuvent tre connus de plusieurs processus de la mme machine. Pour cela, certains constructeurs des classes drives de la classe WaitHandle prsentent
un argument de type string qui permet de nommer lobjet de synchronisation. Ces classes prsentent aussi la mthode OpenExisting(string) qui permet dobtenir une rfrence vers un
objet de synchronisation existant.
Vous pouvez en fait allez plus loin puisque la classe WaitHandle drive de la classe MarshalByRefObject. Ainsi un objet de synchronisation peut tre partag entre plusieurs processus de
machines direntes qui communiquent avec la technologie .NET Remoting.
En page 211 nous expliquons comment exploiter les types MutexSecurity, EventWaitHandleSecurity et SemaphoreSecurity de lespace de noms System.Security.AccessControl pour
manipuler les droits daccs Windows accords aux objets de synchronisation.

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.

Lutilisation de Monitor ne permet pas de se mettre en attente sur plusieurs objets.

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.

Synchronisation avec des mutex, des vnements et des smaphores

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

Chapitre 5 : Processus, threads et gestion de la synchronisation

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.

La dirence entre le repositionnement manuel et automatique est plus importante que


lon pourrait croire. Si plusieurs threads attendent sur un mme vnement repositionnement automatique, il faut signaler lvnement une fois pour chaque thread. Dans le
cas dun vnement repositionnement manuel il sut de signaler une fois lvnement
pour dbloquer tous les threads.
Le programme suivant cre deux threads, t0 et t1, qui incrmentent chacun leur propre compteur des vitesses direntes. t0 signale lvnement events[0] lorsquil est arriv 2 et t1
signale lvnement events[1] lorsquil est arriv 3. Le thread principal attend que les deux
vnements soient signals pour acher un message.
Exemple 5-14 :
using System ;
using System.Threading ;
class Program {
static EventWaitHandle[] events ;
static void Main() {
events = new EventWaitHandle[2] ;
// Position initiale de l
ev
enement : false.
events[0] = new EventWaitHandle(false,EventResetMode.AutoReset);
events[1] = new EventWaitHandle(false,EventResetMode.AutoReset);
Thread t0 = new Thread(ThreadProc0) ;
Thread t1 = new Thread(ThreadProc1) ;
t0.Start() ; t1.Start() ;
AutoResetEvent.WaitAll(events);
Console.WriteLine("MainThread: Thread0 est arriv
e `
a 2"
+
" et Thread1 est arriv
e `
a 3." ) ;
t0.Join();t1.Join() ;
}
static void ThreadProc0() {
for (int i = 0 ; i < 5 ; i++){
Console.WriteLine("Thread0: {0}", i) ;
if (i == 2) events[0].Set();
Thread.Sleep(100) ;
}
}
static void ThreadProc1() {
for (int i = 0 ; i < 5 ; i++){
Console.WriteLine("Thread1: {0}", i) ;
if (i == 3) events[1].Set();

Synchronisation avec des mutex, des vnements et des smaphores

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

Chapitre 5 : Processus, threads et gestion de la synchronisation


t.Name = "Thread" + i ;
t.Start() ;
Thread.Sleep(30) ;
}
}
static void WorkerProc() {
for (int j = 0 ; j < 3 ; j++){
semaphore.WaitOne();
Console.WriteLine(Thread.CurrentThread.Name + ": Begin") ;
Thread.Sleep(200) ; // Simule un travail de 200 ms.
Console.WriteLine(Thread.CurrentThread.Name + ": End") ;
semaphore.Release();
}
}
}

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

Synchronisation avec la classe


System.Threading.ReaderWriterLock
La classe System.Threading.ReaderWriterLock implmente le mcanisme de synchronisation
accs lecture multiple/accs criture unique . Contrairement au modle de synchronisation
accs exclusif oert par la classe Monitor ou Mutex, ce mcanisme tient compte du fait quun
thread demande les droits daccs en lecture ou en criture. Un accs en lecture peut tre obtenu
lorsquil ny a pas daccs en criture courant. Un accs en criture peut tre obtenu lorsquil
ny a aucun accs courant, ni en lecture ni en criture. De plus lorsquun accs en criture a

Synchronisation avec la classe System.Threading.ReaderWriterLock

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

Chapitre 5 : Processus, threads et gestion de la synchronisation


try{
// AcquireWriterLock() lance une
// ApplicationException si timeout.
rwl.AcquireWriterLock(1000);
Console.WriteLine(
"Debut Ecriture laRessource = {0}", laRessource) ;
Thread.Sleep(100) ;
laRessource++ ;
Console.WriteLine(
"Fin
Ecriture laRessource = {0}", laRessource) ;
rwl.ReleaseWriterLock() ;
}
catch ( ApplicationException ) { /* `
a traiter */ }
}
}

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

Synchronisation avec lattribut


System.Runtime.Remoting.Contexts.SynchronizationAttribute
Lorsque lattribut System.Runtime.Remoting.Contexts.Synchronization est appliqu une
classe, une instance de cette classe ne peut pas tre accde par plus dun thread la fois. On
dit alors que la classe est thread-safe.

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.

Synchronisation avec lattribut System...SynchronizationAttribute

161

Voici un exemple illustrant comment appliquer ce comportement :


Exemple 5-17 :
using System.Runtime.Remoting.Contexts ;
using System.Threading ;
[Synchronization(SynchronizationAttribute.REQUIRED)]
public class Foo : System.ContextBoundObject {
public void AfficheThreadId() {
System.Console.WriteLine("D
ebut : ManagedThreadId = " +
Thread.CurrentThread.ManagedThreadId ) ;
Thread.Sleep(1000) ;
System.Console.WriteLine("Fin : ManagedThreadId = " +
Thread.CurrentThread.ManagedThreadId ) ;
}
}
public class Program {
static Foo m_Objet = new Foo() ;
static void Main() {
Thread t0 = new Thread( ThreadProc ) ;
Thread t1 = new Thread( ThreadProc ) ;
t0.Start() ; t1.Start() ;
t0.Join() ; t1.Join() ;
}
static void ThreadProc() {
for (int i = 0 ; i < 2 ; i++)
m_Objet.AfficheThreadId() ;
}
}
Cet exemple ache :
Debut
Fin :
Debut
Fin :
Debut
Fin :
Debut
Fin :

: ManagedThreadId
ManagedThreadId
: ManagedThreadId
ManagedThreadId
: ManagedThreadId
ManagedThreadId
: ManagedThreadId
ManagedThreadId

=
=
=
=
=
=
=
=

27
27
28
28
27
27
28
28

La notion de domaine de synchronisation


Pour une bonne comprhension de ce qui suit, il faut avoir une connaissance des notions suivantes :

domaine dapplication (dcrit page 87),

contexte .NET et classe context-bound/context-agile (dcrit page 842),

intercepteur de messages (dcrit page 822).

162

Chapitre 5 : Processus, threads et gestion de la synchronisation

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

Figure 5 -2 : Processus, domaine dapplication, domaine de synchronisation, contexte.NET et


objet .NET
Puisque nous parlons de la synchronisation, et donc des applications multi-threaded, il est utile
de rappeler que les domaines dapplications et les threads dun processus sont deux notions orthogonales. En eet, un thread peut traverser librement la frontire entre deux domaines dapplication, par exemple en appelant un objet A situ dans le domaine dapplication DA, partir
dun objet B situ dans le domaine dapplication DB. De plus, le code dun domaine dapplication
peut tre excut simultanment par zro, un ou plusieurs threads.
En revanche, tout lintrt dun domaine de synchronisation rside dans le fait quil ne peut tre
partag simultanment par plusieurs threads. Autrement dit, les mthodes des objets contenus
dans un domaine de synchronisation ne peuvent tre simultanment excutes par plusieurs
threads. Ceci implique qu un instant donn, au plus un thread se trouve dans domaine de
synchronisation. On parle alors de droits daccs exclusifs au domaine de synchronisation. Encore
une fois, la gestion de ces droits daccs exclusifs est entirement assure par le CLR.

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

Synchronisation avec lattribut System...SynchronizationAttribute

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

Ce paramtre assure quune instance de la classe sur laquelle


sapplique lattribut Synchronization nappartiendra jamais
un domaine de synchronisation (que son objet crateur appartienne un domaine de synchronisation ou non).

SUPPORTED

Ce paramtre indique quune instance de la classe sur laquelle


sapplique lattribut Synchronization na pas besoin dappartenir un domaine de synchronisation. Nanmoins, une telle instance appartiendra au domaine de synchronisation de son objet
crateur si ce dernier en a un. Ce comportement est peu utile, car
le dveloppeur doit quand mme prvoir un autre mcanisme
de synchronisation au sein des mthodes de la classe. Cependant,
il peut ventuellement permettre de propager lappartenance
un domaine de synchronisation partir dun objet qui na pas
besoin dtre synchroniser (ce type dobjet est rare, puisquil ne
doit pas avoir dtat).

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).

Ces comportements peuvent se rsumer ainsi :


La
valeur
applique:

Est-ce que lobjet


crateur
est dans un
domaine
de
synchronisation ?

NOT_SUPPORTED Non

Lobjet cr rsidera...

... lextrieur de tout domaine de synchronisation.

Oui
SUPPORTED

Non

... lextrieur de tout domaine de synchronisation.

Oui

...dans le domaine de synchronisation de lobjet crateur.

164

Chapitre 5 : Processus, threads et gestion de la synchronisation

REQUIRED

REQUIRES_NEW

Non

...dans un nouveau domaine de synchronisation.

Oui

...dans le domaine de synchronisation de lobjet crateur.

Non

...dans un nouveau domaine de synchronisation.

Oui

La notion de rentrance dans les domaines de synchronisation


Dans un domaine de synchronisation D, lorsque le thread T1 qui a les droits daccs exclusifs
eectue un appel sur un objet situ hors de D, deux comportements peuvent alors tre appliqus
par le CLR :

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

Sil y a rentrance de T1 dans D alors


elle seffectue ce moment prcis

Figure 5 -3 : La rentrance dans un domaine de synchronisation


Certains constructeurs de la classe System.Runtime.Remoting.Contexts.Synchronization acceptent un boolen en paramtre. Ce boolen dfinit sil y a rentrance lors dun appel hors du
domaine de synchronisation courant. Lorsque ce boolen est positionn true, il y a rentrance.

Synchronisation avec lattribut System...SynchronizationAttribute

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

Chapitre 5 : Processus, threads et gestion de la synchronisation


Thread t1 = new Thread( ThreadProc ) ;
Thread t2 = new Thread( ThreadProc ) ;
t1.Start() ; t2.Start() ;
t1.Join() ; t2.Join() ;
}
static void ThreadProc() {
m_Objet.AfficheThreadId() ;
}
}

Ce programme ache ceci :


Foo1
Foo2
Foo1
Foo2
Foo3
Foo3
Foo3
Foo2
Foo1
Foo3
Foo2
Foo1

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.

Un autre attribut nomm Synchronization


Le framework .NET prsente un autre attribut ayant ce nom mais faisant partie dune autre
espace de noms. Cet attribut System.EntrepriseServices.Synchronization a la mme fina-

Le pool de threads du CLR

167

lit, mais il utilise le service dentreprise COM+ de synchronisation. Lutilisation de lattribut


System.Runtime.Remoting.Contexts.Synchronization est prfrable pour deux raisons :

Son utilisation est plus performante.

Ce mcanisme supporte les appels asynchrones, contrairement la version COM+.

La notion de service dentreprise COM+ est prsente page 295.

Le pool de threads du CLR


Introduction
Le concept de pool de threads nest pas nouveau. Cependant le framework .NET vous permet dutiliser un pool de threads beaucoup plus simplement que nimporte quelle autre technologie,
grce la classe System.Threading.ThreadPool.
Dans une application multithreads, la plupart des threads passent leur temps attendre des vnements. Concrtement vos threads sont globalement sous exploits. De plus, le fait de devoir
tenir compte de la gestion des threads lors du design de votre application est une dicult dont
on se passerait volontiers.
Lutilisation dun pool de threads rsout de faon lgante et performante ces problmes. Vous
postez des tches au pool qui se charge de les distribuer ses threads. Le pool est entirement
responsable de :

la cration et de la destruction de ses threads ;

la distribution des tches ;

lutilisation optimale de ses threads.

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 gres avec un systme de priorit.

Vos tches sont longues sexcuter (plusieurs secondes).

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.

Utilisation dun pool de threads


En .NET il ny a quun pool de threads par processus. Ainsi toutes les mthodes de la classe
ThreadPool sont statiques puisquelles sappliquent lunique pool. Le framework .NET utilise
ce pool pour les appels asynchrones, dcrits un peu plus loin dans ce chapitre, les mcanismes
dentr/sortie asynchrones ou les timers dcrits dans la prochaine section.
Le nombre maximal de threads du pool est de 25 threads par processeur pour traiter les oprations asynchrones (completion port thread ou thread I/O en anglais) et de 25 threads ouvrier (worker
thread en anglais) par processeur. Ces deux limites par dfaut sont modifiables tout moment en
appelant la mthode statique ThreadPool.SetMaxThreads(). Si le nombre maximal de threads

168

Chapitre 5 : Processus, threads et gestion de la synchronisation

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

Chapitre 5 : Processus, threads et gestion de la synchronisation

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

Appel asynchrone dune mthode

171

Enfin, sachez que la proprit ISynchronizeInvoke Timer.SynchronizingObject vous permet


de prciser le thread qui doit excuter la tche. Linterface ISynchronizeInvoke est prsente
un peu plus loin dans ce chapitre.

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.

La classe System.Windows.Forms.Timer class


La philosophie dutilisation de la classe System.Windows.Forms.Timer est plus proche de celle
de la classe System.Timers.Timer que celle de la classe System.Threading.Timer. La particularit de cette classe est quelle dmarre ses tches toujours avec le thread ddi la fentre concerne. Ceci vient du fait que cette implmentation utilise en interne le message Windows WM_TIMER.
Vous ne devez pas utiliser cette classe pour dmarrer des tches dont lexcution prend plus
quune fraction de seconde sous peine de geler votre interface graphique. Un exemple dutilisation de ce type de timer peut tre trouv dans le chapitre consacr Windows Forms en page
702.

Appel asynchrone dune mthode


Pour aborder cette section, il est ncessaire davoir compris la notion de dlgu, explique
en page 379.
On dit dun appel de mthode quil est synchrone lorsque le thread du cot qui ralise lappel,
attend que la mthode soit excute avant de continuer. Ce comportement consomme des ressources puisque le thread est bloqu pendant ce temps. Lors dun appel sur un objet distant,
cette dure est potentiellement immense, puisque le cot dun appel rseau reprsente des milliers, voire des millions, de cycles processeurs. Cependant cette attente nest obligatoire que dans
le cas o les informations retournes par lappel de la mthode sont immdiatement consommes aprs lappel. Dans la programmation en gnral et dans les architectures distribues en
particulier, il arrive souvent quun appel de mthode eectue une action et ne retourne que
linformation dcrivant si laction sest bien passe ou non. Dans ce cas, le programme na pas
forcment besoin de savoir immdiatement si laction sest bien passe. On peut dcider dessayer de recommencer cette action plus tard si on apprend quelle a chou.
Pour grer ce type de situation on peut utiliser un appel asynchrone. Lide est que le thread
qui ralise un appel de mthode sur un objet, retourne immdiatement, sans attendre la fin

172

Chapitre 5 : Processus, threads et gestion de la synchronisation

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 ;

Appel asynchrone dune mthode

173

System.Console.WriteLine("Thread#{0} : WriteSomme() somme = {1}",


Thread.CurrentThread.ManagedThreadId , somme) ;
return somme ;
}
static void Main() {
Deleg proc = WriteSomme ;
System.IAsyncResult async= proc.BeginInvoke(10, 10, null, null);
// Possibilite de faire quelquechose ici...
int somme = proc.EndInvoke(async);
System.Console.WriteLine("Thread#{0} : Main()
somme = {1}",
Thread.CurrentThread.ManagedThreadId , somme) ;
}
}
Ce programme ache :
Thread 15 : WriteSomme() Somme = 20
Thread 18 : Main()
Somme = 20
Un appel asynchrone est matrialis par un objet dont la classe implmente linterface
System.IAsyncResult. Dans cet exemple la classe sous-jacente est System.Runtime.Remoting.
Messaging.AsyncResult. Lobjet AsyncResult est retourn par la mthode BeginInvoke(). Il
est pass en argument de la mthode EndInvoke() pour identifier lappel asynchrone.
Si une exception est lance lors dun appel asynchrone, elle est automatiquement intercepte
et stocke par le CLR. Le CLR relancera lexception lors de lappel EndInvoke().

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

Chapitre 5 : Processus, threads et gestion de la synchronisation


public delegate int Deleg(int a, int b) ;
// Position initiale de lev
enement : false.
static AutoResetEvent ev = new AutoResetEvent(false);
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) ;
ev.Set();
}
static void Main() {
Deleg proc = WriteSomme ;
IAsyncResult async = proc.BeginInvoke(10, 10,
new AsyncCallback(SommeFinie), null) ;
Console.WriteLine(
"{0} : BeginInvoke() appel
ee ! Attend l
ex
ecution de SommeFinie()",
Thread.CurrentThread.ManagedThreadId ) ;
ev.WaitOne();
Console.WriteLine(
"{0} : Bye... ", Thread.CurrentThread.ManagedThreadId ) ;
}
}

Cet exemple ache :


12
14
14
12

:
:
:
:

BeginInvoke() appelee ! Attend l


ex
ecution de SommeFinie()
Somme = 20
Procedure de finalisation Somme = 20
Bye...

Si vous enlevez le mcanisme dvnement, cet exemple ache ceci :


12 : BeginInvoke() appelee ! Attend l
ex
ecution de SommeFinie()
12 : Bye...
Lapplication nattend pas la fin de lexcution du traitement asynchrone et de sa procdure de
finalisation.

Passage dun tat la procdure de finalisation


Si vous ne le positionnez pas null, le dernier paramtre de la mthode BeginInvoke() reprsente une rfrence vers un objet utilisable la fois dans le thread qui dclenche lappel

Appel asynchrone dune mthode

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 ) ;
}
}

Appels sans retour (One-Way)


Vous avez la possibilit dappliquer lattribut System.Runtime.Remoting.Messaging.OneWay
nimporte quelle mthode, statique ou non. Cet attribut indique au CLR que cette mthode
ne retourne aucune information. Mme si une mthode qui retourne une valeur de retour ou
des arguments de retour (i.e dfinis avec le mot-cl out) est marque avec cet attribut, elle ne
retourne rien.

176

Chapitre 5 : Processus, threads et gestion de la synchronisation

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.

Anit entre threads et ressources


Vous pouvez grandement simplifier la synchronisation des accs vos ressources en utilisant la
notion danit entre threads et ressources. Lide est daccder une ressource toujours avec
le mme thread. Ainsi, vous supprimez le besoin vous protger des accs concurrents puisque
la ressource nest jamais partage. Le framework .NET prsente plusieurs mcanismes pour implmenter ce concept danit.

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() ;

Anit entre threads et ressources

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

Thread local storage


La notion danit entre threads et ressources peut tre implmente laide du concept de
thread local storage (souvent nomm TLS). Ce concept nest pas nouveau et existe au niveau de
win32. Dailleurs le framework .NET se base sur cette implmentation.
Le concept de TLS utilise la notion de slot de donnes. Un slot de donnes est une instance de la
classe System.LocalDataStoreSlot. Un slot de donnes peut tre vu comme un tableau dobjets. La taille de ce tableau est en permanence gale au nombre de threads dans le processus courant. Ainsi, chaque thread a son propre objet dans le slot de donnes. Cet objet est invisible des
autres threads. Pour chaque slot de donnes, le CLR soccupe dtablir la correspondance entre
les threads et leurs objets. La classe Thread fournit les deux mthodes suivantes pour accder en
lecture et en criture un objet stock dans un slot de donnes :
static public object GetData(LocalDataStoreSlot slot) ;
static public void SetData(LocalDataStoreSlot slot, object obj) ;

Les slots de donnes nomms


Vous avez la possibilit de nommer un slot de donnes pour lidentifier. La classe Thread fournit
les mthodes suivantes pour crer, obtenir ou dtruire un slot de donnes nomm :
static public LocalDataStoreSlot AllocateNamedDataSlot(string slotName) ;
static public LocalDataStoreSlot GetNamedDataSlot(string slotName) ;
static public void FreeNamedDataSlot(string slotName) ;
Le ramasse-miettes ne dtruit pas les slot de donnes nomms. Cette responsabilit incombe
donc au dveloppeur.
Le programme suivant utilise un slot de donnes nomm pour fournir un compteur chaque
thread du processus. Ce compte est incrment chaque appel de la mthode fServer(). Ce programme bnficie des TLS dans la mesure o la mthode fServer() ne prend pas de rfrence
vers un compteur en argument. Un autre avantage est que le dveloppeur na pas maintenir
lui-mme un compteur pour chaque thread.
Exemple 5-25 :
using System ;
using System.Threading ;
class Program {
static readonly int NTHREAD = 3 ;

// 3 threads `
a cr
eer.

178

Chapitre 5 : Processus, threads et gestion de la synchronisation


// 2 appels a` fServer() pour chaque thread cr
e
e.
static readonly int MAXCALL = 2 ;
static readonly int PERIOD = 1000 ; // 1 seconde entre chaque appel.
static bool fServer() {
LocalDataStoreSlot dSlot = Thread.GetNamedDataSlot("Compteur");
int compteur = (int)Thread.GetData(dSlot);
compteur++ ;
Thread.SetData(dSlot, compteur);
return !(compteur == MAXCALL) ;
}
static void ThreadProc() {
LocalDataStoreSlot dSlot = Thread.GetNamedDataSlot("Compteur");
Thread.SetData(dSlot, (int) 0);
do{
Thread.Sleep(PERIOD) ;
Console.WriteLine(
"Thread#{0} Jai appel
e fServer(), Compteur = {1}",
Thread.CurrentThread.ManagedThreadId ,
(int)Thread.GetData(dSlot)) ;
} while (fServer()) ;
Console.WriteLine("Thread#{0} bye",
Thread.CurrentThread.ManagedThreadId ) ;
}
static void Main() {
Console.WriteLine(
"Thread#{0} Je suis le thread principal, hello world",
Thread.CurrentThread.ManagedThreadId ) ;
Thread.AllocateNamedDataSlot("Compteur");
Thread thread ;
for (int i = 0 ; i < NTHREAD ; i++) {
thread = new Thread(ThreadProc) ;
thread.Start() ;
}
// Nous nutilisons pas un m
ecanisme pour attendre la
// terminaison des threads fils, aussi faut-il attendre
// assez longtemps pour les laisser finir leur travail.
Thread.Sleep( PERIOD * (MAXCALL + 1) ) ;
Thread.FreeNamedDataSlot("Compteur");
Console.WriteLine("Thread#{0} Je suis le thread principal,bye.",
Thread.CurrentThread.ManagedThreadId ) ;
}
}

Ce programme ache ceci :


Thread#1
Thread#3
Thread#4
Thread#5
Thread#3

Je suis le thread principal, hello world


Jai appele fServer(), Compteur = 0
Jai appele fServer(), Compteur = 0
Jai appele fServer(), Compteur = 0
Jai appele fServer(), Compteur = 1

Anit entre threads et ressources


Thread#3
Thread#4
Thread#4
Thread#5
Thread#5
Thread#1

179

bye
Jai appele fServer(), Compteur = 1
bye
Jai appele fServer(), Compteur = 1
bye
Je suis le thread principal, bye.

Les slots de donnes anonymes


Vous pouvez appeler la mthode statique AllocateDataSlot() de la classe Thread pour
crer un slot de donnes anonyme. Vous ntes pas responsable de la destruction dun slot
de donnes anonyme. En revanche, vous devez faire en sorte quune instance de la classe
LocalDataStoreSlot soit visible de tous les threads. Rcrivons le programme prcdent avec
la notion de slot de donnes anonyme :
Exemple 5-26 :
using System ;
using System.Threading ;
class Program {
static readonly int NTHREAD = 3 ;
// 3 threads `
a cr
eer.
// 2 appels `a fServer() pour chaque thread cr
e
e.
static readonly int MAXCALL = 2 ;
static readonly int PERIOD = 1000 ; // 1 seconde entre chaque appel.
static LocalDataStoreSlot dSlot;
static bool fServer() {
int Counter = (int)Thread.GetData(dSlot);
Counter++ ;
Thread.SetData(dSlot, Counter);
return !(Counter == MAXCALL) ;
}
static void ThreadProc() {
Thread.SetData(dSlot, (int) 0);
do{
Thread.Sleep(PERIOD) ;
Console.WriteLine(
"Thread#{0} Jai appel
e fServer(), Compteur = {1}",
Thread.CurrentThread.ManagedThreadId ,
(int)Thread.GetData(dSlot)) ;
} while (fServer()) ;
Console.WriteLine("Thread#{0} bye",
Thread.CurrentThread.ManagedThreadId ) ;
}
static void Main() {
Console.WriteLine(
"Thread#{0} Je suis le thread principal, hello world",
Thread.CurrentThread.ManagedThreadId ) ;
dSlot = Thread.AllocateDataSlot();
for (int i = 0 ; i < NTHREAD ; i++){
Thread thread = new Thread(ThreadProc) ;

180

Chapitre 5 : Processus, threads et gestion de la synchronisation


thread.Start() ;
}
Thread.Sleep(PERIOD * (MAXCALL + 1)) ;
Console.WriteLine("Thread#{0} Je suis le thread principal, bye",
Thread.CurrentThread.ManagedThreadId ) ;
}
}

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

Chapitre 5 : Processus, threads et gestion de la synchronisation

Cet exemple ache ceci :


Main Thread#1 Current user is PSMACCHIA\invit
e
Callback Thread#3 Current user is PSMACCHIA\invit
e
En .NET 1.1 cet exemple acherait ceci :
Main Thread#1 Current user is PSMACCHIA\invit
e
Callback Thread#3 Current user is PSMACCHIA\pat
En eet, en .NET 2.0 le pool de thread propage par dfaut le contexte du thread postant une
tche au thread excutant la tche. Ce nest pas le cas en .NET 1.1. Lutilisation de la mthode
ExecutionContext.SuppressFlow() permet de retrouver le comportement de .NET 1.1 en
.NET 2.0 :
Exemple 5-28 :
...
DisplayContext("Main") ;
ExecutionContext.SuppressFlow();
ThreadPool.QueueUserWorkItem( Callback, null ) ;
...
Cet exemple ache ceci :
Main Thread#1 Current user is PSMACCHIA\invit
e
Callback Thread#3 Current user is PSMACCHIA\pat
Lexemple suivant montre comment propager soit mme le contexte dexcution. Tout dabord
il faut le capturer avec la mthode ExecutionContext.Capture(). Ensuite, nous en crons une
copie que lon passe au thread du pool solicit. Ce dernier propage le contexte quon lui fournit en appelant la mthode ExceutionContext.Run(). Cette mthode prend en paramtre un
contexte dexcution et un dlgu. Elle invoque sur le thread courant la mthode rfrence
par le dlgu, en ayant au pralable positionn le contexte du thread courant :
Exemple 5-29 :
...
static void Main() {
...
WindowsIdentity.Impersonate(pJeton) ;
DisplayContext("Main") ;
ExecutionContext ctx = ExecutionContext.Capture();
ExecutionContext.SuppressFlow();
ThreadPool.QueueUserWorkItem(
SetContextAndThenCallback, ctx.CreateCopy() ) ;
CloseHandle(pJeton) ;
}
}
static void SetContextAndThenCallback(object o) {
ExecutionContext ctx = o as ExecutionContext;
ExecutionContext.Run(ctx, Callback, null);
}

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

Ce chapitre prsente les direntes facettes de la scurit sous .NET :

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.

Introduction Code Access Security (CAS)


Notion de code mobile
Le modle de dploiement des logiciels a considrablement volu avec la puissance accrue des
rseaux. Nous tlchargeons de plus en plus nos logiciels partir dInternet et nous utilisons
de moins en moins de supports physiques tel que le CD pour le dploiement. On utilise la
mtaphore de code mobile pour dsigner le code de ce type dapplication que lon distribue par
lintermdiaire de rseaux.
Les avantages de lutilisation dun rseau pour distribuer un logiciel sont nombreux : disponibilit immdiate, mise jour en temps rel etc. Cependant, le code mobile pose de gros problmes

186

Chapitre 6 : La gestion de la scurit

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.

Schma gnral de CAS


.NET dfinit une vingtaine de permissions (paramtrables) qui peuvent tre accordes ou non
lexcution du code dun assemblage. Chacune de ces permissions dfinit les rgles qui rgissent
laccs une ressource critique, comme la base des registres ou les fichiers et les rpertoires.
Accorder de la confiance un assemblage signifie quon lui accorde certaines permissions dont
il a besoin pour sexcuter correctement.
Le mcanisme CAS est utilis par le CLR lors de deux situations :

Lors du chargement dun assemblage le CLR lui attribue des permissions.

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.

Attribution des permissions lors du chargement dun assemblage


linstar des relations humaines, en .NET la confiance se mrite. Le CLR accorde de la confiance
un assemblage seulement sil peut en extraire un certain nombre preuves. Ces preuves sont
relatives la provenance et la non corruption des donnes contenues dans lassemblage.
Ltape daccord de permissions lassemblage en fonction des preuves quil a fourni, est entirement configurable. Le paramtrage de cette tape est stock dans des entits nommes stratgies
de scurit. Les informations contenues dans une stratgie de scurit ressemblent : Si les
informations contenues dans un assemblage prouvent quil a t produit par lentreprise XXX
alors on peut lui accorder cet ensemble de permissions . Nous exposerons comment configurer
les stratgies de scurit. On peut noter qu ce stade lapplication na pas encore reu la permission de sexcuter. Dailleurs il se peut quau vu du jeu de preuves cette permission ne lui soit

CAS : Preuves et permissions

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

Figure 6 -1 : Attribution des permissions un assemblage

Vrification des permissions lexcution


Avant deectuer une opration critique telle que laccs un fichier, le code du framework .NET
demande au CLR de vrifier si le code appelant en a la permission. Le code appelant nest pas
seulement reprsent par la mthode qui demande au framework .NET deectuer lopration
critique. Le CLR considre que le code appelant est lensemble des mthodes qui constitue
la pile du thread courant. Le CLR vrifie donc que tous les assemblages contenant toutes ces
mthodes ont chacun la permission requise. Ce comportement est appel parcours de la pile
dappels (stack walk en anglais). Le parcours de la pile empche la manipulation frauduleuse
dassemblages ayant un haut degr de confiance (comme ceux dvelopps par Microsoft) par des
assemblages dans lesquels on ne fait pas confiance.
Dans la figure suivante, on voit que la mthode File.OpenRead() demande au CLR de vrifier
que tous les appelants ont la permission FileIOPermissionAccess.Read sur un fichier avant de
procder son ouverture. Dans cet exemple, il faudrait que cette permission soit P3. Sinon une
exception de type SecurityException serait automatiquement lance par le CLR.
Nous aurons loccasion dexpliquer quune permission peut tre matrialise par un objet .NET
et que lon peut appeler la mthode Demand() sur un tel objet afin de sassurer quau stade de
lappel, la permission est accorde tous les appelants.

CAS : Preuves et permissions


Quest ce quune preuve ?
Une preuve est une information extraite partir dun assemblage. Le mot preuve est utilis dans
le sens o si lon extrait telle ou telle information partir de lassemblage, alors cela prouve de

188

Chapitre 6 : La gestion de la scurit


Permissions accordes par le CLR
lors du chargement des assemblages

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

Figure 6 -2 : Le parcours de la pile


manire irrfutable un fait. Ces faits concernent la provenance et la non falsification de lassemblage entre le moment o il a t cr par un compilateur chez lditeur de logiciel et le moment
o il est excut chez le client.

Les types de preuves standard du Framework .NET


Voici la liste des huit types de preuves que lon peut extraire dun assemblage, et donc, la liste
des faits qui peuvent tre prouvs partir dun assemblage. Chacune de ces preuves peut tre
matrialise par une instance dune classe du framework .NET que nous prcisons. Ces classes
font partie de lespace de noms System.Security.Policy.

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.

CAS : Preuves et permissions

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

Chapitre 6 : La gestion de la scurit


pas dinformation dordonnancement temporel comme le fait une version (par exemple
la version 2.3 vient toujours aprs la version 2.1). En outre, mme un changement mineur
dans le code dun assemblage sut modifier compltement la valeur de hachage, contrairement la version. Ce type de preuves peut tre reprsent par une instance de la classe
System.Security.Policy.Hash.

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).

Une collection pour stocker les preuves propritaires.

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

CAS : Preuves et permissions

191

System.Security.Policy.Zone
System.Security.Policy.Url
System.Security.Policy.StrongName
System.Security.Policy.Hash

Qui fournit les preuves ?


Les preuves (evidences en anglais) dun assemblage sont fournies au CLR juste avant quun assemblage soit charg dans un domaine dapplication :

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.

Les permissions standard


Une trentaine de permissions standard permettent de dfinir lensemble des ressources systmes
exploites par un assemblage. Chacune de ces permissions est matrialise par une classe qui drive de la classe System.Security.CodeAccessPermission. Cette classe contient des mthodes qui
permettent partir du code de sassurer quon a une permission, de demander une permission
de refuser une permission etc. Bien videmment ces mthodes ne permettent pas dobtenir une
permission que les stratgies de scurit ne nous accordent pas. Nous dtaillerons lutilisation
de ces classes la fin de cette section. Voici la liste des classes de permissions standard :
System.Security.Permissions.EnvironmentPermission
System.Security.Permissions.FileDialogPermission
System.Security.Permissions.FileIOPermission
System.Security.Permissions.IsolatedStoragePermission

192

Chapitre 6 : La gestion de la scurit


System.Security.Permissions.ReflectionPermission
System.Security.Permissions.RegistryPermission
System.Security.Permissions.UIPermission
System.Security.Permissions.DataProtectionPermission
System.Security.Permissions.KeyContainerPermission
System.Security.Permissions.StorePermission
System.Security.Permissions.SecurityPermission
System.Configuration.UserSettingsPermission
System.Security.Permissions.ResourcePermissionBase
System.Diagnostics.EventLogPermission
System.Diagnostics.PerformanceCounterPermission
System.DirectoryServices.DirectoryServicesPermission
System.ServiceProcess.ServiceControllerPermission
System.Net.DnsPermission
System.Net.SocketPermission
System.Net.WebPermission
System.Net.NetworkInformation.NetworkInformationPermission
System.Net.Mail.SmtpPermission
System.Web.AspNetHostingPermission
System.Messaging.MessageQueuePermission
System.Drawing.Printing.PrintingPermission
System.Data.Common.DBDataPermission
System.Data.OleDb.OleDbPermission
System.Data.SqlClient.SqlClientPermission
System.Data.Odbc.OdbcPermission
System.Data.OracleClient.OraclePermission
System.Data.SqlClient.SqlNotificationPermission
System.Transactions.DistributedTransactionPermission

Les permissions didentit


Les permissions didentit (identity permission en anglais) : Pour pratiquement chaque preuve apporte par un assemblage, le CLR accorde lassemblage une permission didentit. Les classes
prvues pour les permissions didentit sont :
System.Security.Permissions.PublisherIdentityPermission
System.Security.Permissions.SiteIdentityPermission
System.Security.Permissions.StrongNameIdentityPermission
System.Security.Permissions.UrlIdentityPermission
System.Security.Permissions.GacIdentityPermission
System.Security.Permissions.ZoneIdentityPermission
Ces classes drivent aussi de la classe CodeAccessPermission ce qui permet de les traiter comme
toutes les autres permissions partir du code. Ce qui direncie les permissions didentit des
permissions prsentes ci-dessus, est le fait que laccord ou non dune permission didentit ne
dpend pas dune stratgie de scurit mais seulement des preuves fournies par lassemblage. En
outre, une permission didentit ne vous permet pas de raliser une action que vous nauriez pu
raliser sans elle. Une telle permission nest utilise que dans le cadre de vrifications.

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.

Comprenez que la mta-permission UnmanagedCode est une espce de super permission


puisquelle permet de saranchir de toutes les autres permissions en donnant laccs
lAPI win32. De mme la mta-permission SkipVerification peut tre utilise de faon
contourner les vrifications du CLR. En consquence, du code mobile ne devrait jamais
avoir une des permissions UnmanagedCode ou SkipVerification.

Les permissions propritaires


Vous pouvez dfinir vos propres permissions pour laccs vos ressources. Larticle Implementing a Custom Permission des MSDN dcrit lutilisation de cette possibilit en dtail.

CAS : Accorder des permissions en fonction des preuves


avec les stratgies de scurit
Nous allons clarifier dans la prsente section ce quest une stratgie de scurit, de quoi une
stratgie de scurit se compose, selon quel algorithme une stratgie de scurit est applique
et enfin, quelles sont les configurations par dfaut des stratgies de scurit.

Les niveaux de stratgie de scurit


Appliquer une stratgie de scurit (security policy en anglais) un assemblage permet dobtenir
un ensemble de permissions accordes en fonction des preuves que le mcanisme CAS a pu
obtenir partir de lassemblage.
.NET prsente quatre stratgies de scurit. Lensemble des permissions accordes un assemblage est lintersection des ensembles des permissions accordes par chacune de ces stratgies
cet assemblage. Le choix de lintersection au profit de lunion a t fait car le modle de scurit
est bas sur laccord de permissions, et non le retrait de permissions.
Stratgie de scurit

Configure par...

Sapplique...

Entreprise

un administrateur.

au code gr contenu dans les assemblages


situs sur les machines dune entreprise.

194

Chapitre 6 : La gestion de la scurit

Machine

un administrateur.

au code gr contenu dans les assemblages


stocks sur la machine.

Utilisateur

un administrateur ou
lutilisateur concern.

au code gr contenu dans les processus qui


sexcutent avec les droits de lutilisateur
concern.

lhte du domaine
dapplication.

au code gr contenu dans le 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).

Quelle est la composition dune stratgie de scurit ?


Une stratgie de scurit se compose comme ceci :

Des groupes de code (code groups en anglais) stocks dans une arborescence,

Une liste densembles de permissions (permission sets en anglais),

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.

Algorithme utilis lors de lapplication dune stratgie de scurit

Si lassemblage fait partie de la liste dassemblages auxquels la stratgie de scurit donne


son entire confiance, elle lui accorde la super permission nomme FullTrust que nous dcrivons un peu plus loin.

Sinon, lalgorithme commence parcourir larborescence des groupes de code selon les
rgles suivantes :

Lalgorithme vrifie si lassemblage est membre de chaque groupe de code racine.

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.

Si un assemblage appartient deux groupes de code exclusifs de la mme stratgie de scurit,


aucune permission ne lui sera accorde.
Comprenez bien que nous avons parl ici de lalgorithme de lapplication dun seul niveau de
stratgie de scurit. Rappelez-vous quau final, lensemble de permissions accord lassemblage est lintersection des ensembles de permissions accords par chaque niveau de stratgie
de scurit. Une consquence est quun assemblage qui une stratgie de scurit donne son
entire confiance naura pas obligatoirement toutes les permissions. Il sut quau moins une
autre stratgie de scurit applique ne lui fasse pas entirement confiance.

Configuration par dfaut des stratgies de scurit


Par dfaut, la stratgie de scurit machine est configure avec larborescence de groupe de
code de la Figure 6-3. Comme vous pouvez le voir, il y a un groupe de code selon la zone do
provient lassemblage. On rappelle quune zone .NET dfinit la provenance dun assemblage
et que tout assemblage fournit une preuve quant la zone dont il est issu. Notez aussi que
par dfaut, la stratgie de scurit machine fait entirement confiance dans les assemblages
signs avec la cl prive correspondant au jeton de cl publique de Microsoft ou de lECMA.
Par dfaut les autres stratgies de scurit (entreprise, utilisateur et domaine dapplication) font
entirement confiance tous les assemblages. Donc par dfaut tous se passe comme sil ny avait
que la stratgie de scurit machine .

Configurer des stratgies de scurit


Il existe deux outils permettant de configurer les stratgies de scurit :

196

Chapitre 6 : La gestion de la scurit


Lgende :
Microsoft_Strong_Name
Nom du groupe de code
SN.PublicKey=MS
Preuve apporter par un
assemblage pour en tre membre

FullTrust

Nom de lensemble de permissions


accord aux assemblages membres

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

Zone=site non fiable

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

Figure 6 -3 : Configuration par dfaut de la stratgie de scurit machine

Loutil graphique de configuration : .NET Framework Configuration Tool mscorcfg.msc. Cet


outil gre aussi dautres aspects de .NET comme le Remoting. Vous pouvez lancer cet outil
comme ceci : Panneau de configuration  Outils dadministration  Microsoft .NET Framework
2.0 Configuration.

Un outil utilisable en ligne de commande : caspol.exe.

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) :

Ajouter/modifier/supprimer des ensembles de permissions.

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

Figure 6 -4 : Vue gnrale de loutil de configuration de .NET Framework

Ajouter/supprimer des assemblages en qui la politique fait confiance.

Ajouter/modifier/supprimer des groupes de code dans larborescence.

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

%runtime install path%\vXXXX\Config\Enterprisesec.config

198

Windows 98/Me

Chapitre 6 : La gestion de la scurit

%runtime install path%\vXXXX\Config\Enterprisesec.config

Stratgie de scurit machine


Windows XP/2000/NT

%runtime install path%\vXXXX\Config\Security.config

Windows 98/Me

%runtime install path%\vXXXX\Config\Security.config

Stratgie de scurit utilisateur


Windows XP/2000/NT

%USERPROFILE%\Application
config\vXXXX\Security.config

data\Microsoft\CLR

security

Windows 98/Me

%WINDIR%\username\CLR security config\vXXXX\Security.config

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.

CAS : La permission FullTrust


Dans la section prcdente, nous avons cit la permission particulire FullTrust que lon pourrait traduire par confiance aveugle. Cette permission permet de dsactiver les vrifications du
mcanisme CAS. Les assemblages qui ont cette permission ont par consquent toutes les permissions standard et personnalises. Du point de vue de CAS il y a deux types dassemblages : ceux
qui ont la permission FullTrust et ceux qui ne lont pas. CAS naccorde ces derniers quune
confiance partielle, (partially trusted assemblies en anglais). En particulier, CAS naccorde quun
confiance partielles aux assemblage qui ont lensemble de permission nomm Everything. En
eet, cet ensemble accorde par dfaut toutes les permissions standard mais ne prend pas en
compte les permissions personnalises.
Par dfaut, le code des assemblages signs ne peut tre invoqu que par des assemblages qui
ont la permission FullTrust. Cela vient du fait que seuls les assemblages signs peuvent tre
placs dans le GAC et peuvent donc tre exploits malicieusement par du code mobile. En eet,
les assemblages non signs dj prsents sur une machine ne peuvent pas tre exploits par du
code mobile puisque celui-ci ne peut pas anticiper lendroit o ils sont stocks, ni deviner les
fonctionnalits implmentes.
Cet aspect de la technologie CAS peut se rvler limitatif, aussi, vous avez la possibilit de le
dsactiver en marquant votre assemblage sign avec lattribut dassemblage System.Security.
AllowPartiallyTrustedCallersAttribute. Soyez conscient que vous exposez vos clients
de gros risques si vous distribuez largement une bibliothque de classes marques avec cet

CAS : Vrifier les permissions imprativement partir du code source

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.

CAS : Vrifier les permissions imprativement


partir du code source
Vrifier les permissions partir du code ne signifie absolument pas soctroyer des permissions
que les stratgies de scurit ne nous ont pas accord. Ceci est bien videmment impossible.
Nous allons commencer par exposer comment vrifier les permissions imprativement partir
du code (i.e en appelant des mthodes spcialises dans la vrification). Nous nous intresserons
ensuite aux attributs permettant de vrifier dclarativement les permissions (i.e le code dune
mthode marque par un tel attribut doit avoir telle permission). Nous conclurons par une
comparaison de ces deux faons de faire.

Les classes CodeAccessPermissions et PermissionSet


La classe CodeAccessPermission ainsi que ses classes drives permettent deectuer des
oprations sur les permissions accordes au code en cours dexcution principalement au
moyen des mthodes Demand(), Deny()/RevertDeny(), PermitOnly()/RevertPermitOnly() et
Assert()/RevertAssert().
Les instances de la classe System.Security.PermissionSet reprsentent des collections de permissions. Cette classe prsente aussi les quatre mthodes cites. Elle permet donc de faciliter les
oprations portant sur plusieurs permissions.

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

Chapitre 6 : La gestion de la scurit


try{
cap.Demand();
// Lis le fichier "C:\data.txt".
}
catch ( SecurityException ){
// Vous navez pas la permission de lire "C:\data.txt".
}
}
}

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.

Les mthodes Deny() RevertDeny() PermitOnly()


et RevertPermitOnly()
La mthode Deny() des classes CodeAccessPermission et PermissionSet permet de signaler les
permissions dont notre code na pas besoin. Bien que la plupart des dveloppeurs aient dautres
tches faire que de signaler les permissions dont ils nont pas besoin, ceci constitue une bonne
pratique. La mthode Deny() permet de sassurer que du code tiers que lon appelle naura pas
certaines permissions. Cette pratique permet aussi davoir une vision globale de ce quune application utilise et permet ds le dpart dun projet de fixer des restrictions. Voici un exemple qui
montre comment refuser les permissions dangereuses de modification du rpertoire systme et
de la base des registres.
Exemple 6-4 :
using System.Security ;
using System.Security.Permissions ;
class Program {
static void Main() {
PermissionSet ps = new PermissionSet(PermissionState.None) ;
ps.AddPermission( new FileIOPermission(
FileIOPermissionAccess.AllAccess,@"C:\WINDOWS")) ;
ps.AddPermission( new RegistryPermission(
RegistryPermissionAccess.AllAccess, string.Empty )) ;
ps.Deny();
// Ici on ne peut modifier les fichiers syst`
emes
// et les donnees de la base des registres.
CodeAccessPermission.RevertDeny();
}
}
Aucune exception ne sera leve si votre code ne disposait pas de ces permissions avant lappel
Deny().Il est a noter que les restrictions lies lappel de la mthode Deny() ne sont appliques
que dans la mthode qui linvoque ainsi que dans les mthodes appeles partir de celle-ci. Dans
tous les cas, la sortie de la mthode ayant invoque les restrictions de droits, les paramtres par

CAS : Vrifier les permissions imprativement partir du code source

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.

Les mthodes Assert() et RevertAssert()


La mthode Assert() permet de spcifier quun appelant na pas besoin davoir une ou plusieurs
permissions. Pour cela, la mthode Assert() supprime le parcours de la pile dappel pour ces
permissions partir de l o elle est appele. La mthode qui appelle Assert() doit avoir la
mta-permission SecurityPermission(Assertion). En outre elle doit aussi avoir la/les permissions(s) concerne(s) pour que la suppression du parcours de la pile dappel ait eectivement
lieu. Dans le cas contraire lappel Assert() na aucun eet et aucune exception nest lance.
Lappel de la mthode Assert() peut introduire des vulnrabilits dans le code qui lappelle
mais dans certaines situations son utilisation se rvle absolument ncessaire. Par exemple, la
classe standard FileStream utilise en interne le mcanisme P/Invoke pour accder aux fichiers
et toutes ses mthodes suppriment le parcours de la pile pour la mta-permission SecurityPermission(UnmanagedCode). Sans cet artifice, tout code qui souhaiterait avoir accs un fichier devrait avoir la mta-permission SecurityPermission(UnmanagedCode) en plus de la permission
FileIOPermission, ce qui nest clairement pas acceptable.
Pour supprimer le parcours de la pile dappel pour vrifier que tous les appelants ont la
mta-permission SecurityPermission(UnmanagedCode), il est prfrable de marquer la mthode concerne ou sa classe avec lattribut System.Security.Suppress\-Unmanaged\-Code\
-Security\-Attribute plutt que dutiliser la mthode Assert(). En eet, cet attribut indique
au compilateur JIT quil ne faut pas produire le code pour vrifier que tous les appelants ont la
mta-permission SecurityPermission(UnmanagedCode) lors dun appel du code non gr.
Lexemple suivant supprime le parcours de la pile dappel pour la permission de lire la base des
registres :
Exemple 6-5 :
using System.Security ;
using System.Security.Permissions ;
class Program {
static void Main() {
CodeAccessPermission cap = new RegistryPermission(
RegistryPermissionAccess.NoAccess, string.Empty ) ;
cap.Assert() ;
// Lis la base des registres.
RegistryPermission.RevertAssert() ;
}
}
Vous ne pouvez appeler Assert() plusieurs fois conscutivement dans une mme mthode. Ceci
provoque la leve dune exception. Pour appeler Assert() plusieurs fois conscutivement dans

202

Chapitre 6 : La gestion de la scurit

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.

Les mthodes FromXml() et ToXml()


Les mthodes FromXml() et ToXml() permettent de construire ou de sauver un ensemble de
permissions complexe laide de documents XML.

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.
}
}

CAS : Vrifier les permissions dclarativement partir du code source

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.

CAS : Vrifier les permissions dclarativement partir


du code source
Une alternative existe lutilisation imprative des classes PermissionSet et CodeAccessPermission pour manipuler les permissions directement partir du code source. Cette alternative utilise des attributs standard qui peuvent sappliquer ventuellement aux mthodes, aux types ou
lassemblage lui-mme. Chacune des classes drives de la classe CodeAccessPermission reprsentant un type de permission a un attribut standard qui lui correspond. Par exemple lattribut
RegistryPermissionAttribute reprsente les permissions relatives laccs de la base des registres,
exactement comme la classe RegistryPermission. LExemple 6-5 peut ainsi tre rcrit comme
ceci :
Exemple 6-7 :
using System.Security.Permissions ;
class Program{
[RegistryPermission(SecurityAction.Assert)]
static void Main(){
// Lis la base des registres.
}
}
Les valeurs de lnumration System.Security.Permissions.SecurityAction permettent de spcifier la manipulation souhaite. On peut citer les valeurs Demand, Deny, PermitOnly et Assert qui
ont les mmes eets que leurs mthodes homonymes expliques dans la section prcdentes.
Cependant, lnumration SecurityAction prsente des valeurs qui permettent de raliser des
oprations non prsentes par les classes CodeAccessPermission et PermissionSet :
Valeur de SecurityAction

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

Force le compilateur JIT vrifier quune ou plusieurs permissions sont


accordes la mthode, sans prendre en compte les permissions accordes aux mthodes appelantes. Cette action est plus permissive que Demand mais moins coteuse aussi, puisquelle nest vrifie qu la compilation de la mthode par le JIT et pas chaque appel.

204

Chapitre 6 : La gestion de la scurit

Manipuler les permissions au chargement de lassemblage


Lnumration SecurityAction prsente aussi les trois valeurs suivantes destines tre utiliss
au niveau dun assemblage entier. On les utilise pour signifier au CLR que lorsquil charge lassemblage, il doit procder des oprations sur lensemble des permissions :
Valeur de SecurityAction

Description de laction

RequestMinimum

Spcifie une ou plusieurs permissions sans lesquels lassemblage ne peut


tre charg.

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

Lorsquun assemblage est charg, spcifie une ou plusieurs permissions


qui ne doivent pas tre accordes lassemblage.

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.

Imprative vs. Dclarative


Lutilisation dattributs .NET prsente les inconvnients suivants (par rapport la vrification
imprative des permissions) :

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).

CAS : Facilits pour tester et dboguer votre code mobile

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.

La possibilit dutiliser certains de ces attributs sur lassemblage entier.

CAS : Facilits pour tester et dboguer votre code mobile


.NET 2.0 prsente de nouvelles facilits pour tester et dboguer votre code mobile. La classe,
System.Security.SecurityException prsente une dizaine de nouvelles proprits permettant
de rcolter beaucoup plus dinformation lorsque lon rattrape une exception de ce type. On peut
citer la proprit AssemblyName FailedAssemblyInfo{get;} qui contient le nom de lassemblage qui est la source de lchec des vrifications de scurit. Comprenez bien que cette facilit
est double tranchant : si un telle exception est analyse par un individu mal intentionn, elle
lui fourniront autant dinformation pour lui permettre dexploiter les vulnrabilits de votre
code.
En page 81 nous exposons les facilits prsentes par Visual Studio 2005 pour la prise en compte
des permissions CAS lors du dveloppement dune application.

CAS : La permission de faire du stockage isol


La prsente section a pour objectif dexpliquer une permission particulire que lon peut accorder ou non un assemblage. Cette permission de faire du stockage isol (isolated storage en
anglais) est la rponse la problmatique suivante :
Donner la permission une application daccder au disque dur tmoigne dune trs grande
confiance dans cette lapplication. A priori, peu dapplications dont le code est mobile peuvent
prtendre un tel degr de confiance. Cependant, la plupart des applications ont besoin de
stocker des donnes de manire persistantes, donc sur le disque dur. Ces donnes sont souvent
des journaux dactivit ou des prfrences utilisateur. Ne pas autoriser ces applications stocker
leurs donnes peut les rendre instables, et autoriser ces applications accder au disque dur est
trs dangereux.
Autoriser une application faire du stockage isol consiste lui permettre daccder un rpertoire qui lui est rserv sur le disque dur. Vous pouvez spcifier une taille maximale pour
ce rpertoire. Lapplication ne peut pas avoir accs aux fichiers qui ne sont pas situs dans ce
rpertoire. Deux applications direntes auront chacune leur rpertoire pour faire du stockage
isol. La responsabilit de nommer ou de localiser un tel rpertoire sur une machine o lapplication est installe incombe totalement .NET. Un tel rpertoire est parfois nomm bac sable
(sandbox en anglais).
En fait, le mcanisme de stockage isol va un peu plus loin que ce qui vient dtre dit. Le choix du
nom et de la localisation du rpertoire peut non seulement se faire sur lidentit de lassemblage,
mais aussi sur lidentit de lutilisateur excutant lapplication ou (non exclusif) sur lidentit
du domaine dapplication contenant lassemblage. Chacune de ces identits est appele porte
(scope en anglais). Concrtement lapplication aura plusieurs rpertoires de stockage isol, un
pour chaque condition dexcution (i.e un pour chaque valeur du produit cartsien des portes). Chaque fois que lapplication sexcute dans les mmes conditions, elle utilise le mme rpertoire. La classe System.IO.IsolatedStorage.IsolatedStorageFile prsente plusieurs m-

206

Chapitre 6 : La gestion de la scurit

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:

Figure 6 -5 : Rpertoire pour faire du stockage isol

Support .NET pour les utilisateurs et rles Windows


La plupart des applications prsentent dirents niveaux dutilisations. Par exemple, dans une
banque, tous les caissiers utilisateurs dune application bancaire ne sont pas autoriss transfrer une somme de lordre du million deuros. De mme, tous les utilisateurs dune telle application ne sont pas autoriss configurer les protocoles daccs aux bases de donnes. Enfin

Support .NET pour les utilisateurs et rles Windows

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.

Introduction la scurit sous Windows


Les systmes dexploitation Windows 95/98/Me nont pas de contexte de scurit. En revanche,
sous les systmes dexploitation Windows NT/2000/XP/2003/Vista toute excution de code seectue dans un contexte de scurit. chaque processus Windows est associ une identit Windows
que lon nomme principal. Pour simplifier, vous pouvez considrer quun principal est un utilisateur Windows. Pour linstant, nous considreront quun thread sexcute dans le contexte du
principal de son processus. Nous verrons que sous certaines conditions cette rgle peut tre mise
en dfaut.
Windows associe chacune de ses ressources (fichiers, registre etc) un ensemble de rgles daccs.
Lorsque quun thread tente daccder une ressource Windows, Windows vrifie que le principal
associ au thread est autoris par les rgles daccs.
Windows prsente la notion de groupe dutilisateurs. Chaque utilisateur appartient un ou plusieurs groupes. Les rgles daccs aux ressources peuvent tre configures en fonction des utilisateurs et en fonction des groupes. Ainsi, un administrateur peut autoriser ou refuser laccs une
ressource tous les utilisateurs dun groupe en spcifiant un seul groupe plutt quen spcifiant
N utilisateurs.
Parmi les rles classiques Windows on peut citer le rle administrateur, utilisateur, invit etc. La
notion de groupe permet dimplmenter naturellement le concept de rle jou par un utilisateur. chaque rle correspond un groupe Windows et un utilisateur Windows joue un rle sil
fait partie du groupe correspondant.
chaque authentification dun utilisateur Windows cre une session logon. Une session logon
est matrialise par un jeton de scurit (security token en anglais). Le principal dun processus
est aussi matrialis par un jeton de scurit. Lorsquun processus cre un nouveau processus,
ce dernier hrite automatiquement du jeton de scurit de son crateur et volue donc dans le
mme contexte de scurit. Nous prcisons quen page 135 nous expliquons comment le framework .NET vous permet de crer un processus dans un contexte de scurit dirent de celui de
son processus parent.
Lors du dmarrage, Windows cre automatiquement trois sessions logon : la session systme, la
session locale et la session rseau. Ceci explique le fait que des applications peuvent sexcuter
sur une machine sans quun utilisateur nest t logu (notamment on utilise pour cela la notion
de service Windows). Cette possibilit est particulirement exploite dans le cas de serveurs qui
sont susceptibles dtre reboots automatiquement.
.NET prsente plusieurs espaces de noms et de nombreux types permettant dexploiter programmatiquement la scurit Windows. Comprenez bien que ces types ne font quencapsuler les fonctions et les structures Win32 ddies la scurit.

208

Chapitre 6 : La gestion de la scurit

Les interfaces IIdentity et IPrincipal


Le framework .NET prsente les deux interfaces suivantes qui permettent de reprsenter les notions proches didentit et de principal :
interface System.Security.Principal.IIdentity{
string
AuthenticationType{get;}
bool
IsAuthenticated{get;}
string
Name{get;}
}
interface System.Security.Principal.IPrincipal{
IIdentity
Identity {get;}
bool
IsInRole(string role) ;
}
Tandis que linterface IIdentity reprsente laspect authentification (qui on est ?) linterface
IPrincipal reprsente laspect autorisation (quest ce quon est autoris faire ?). Ces interfaces sont notamment utilises pour manipuler la scurit Windows avec les implmentations
System.Security.Principal.WindowsIdentity et System.Security.Principal.WindowsPrincipal.
Par exemple le programme suivant rcupre lidentit de lutilisateur associ au contexte de
scurit Windows sous-jacent :
Exemple 6-10 :
using System.Security.Principal ;
class Program{
static void Main(){
IIdentity id = WindowsIdentity.GetCurrent();
System.Console.WriteLine( "Nom : "+id.Name) ;
System.Console.WriteLine( "Authentifi
e ? : "+id.IsAuthenticated) ;
System.Console.WriteLine(
"Type dauthentification : "+id.AuthenticationType) ;
}
}
Ce programme ache :
Nom : PSMACCHIA\pat
Authentifie ? : True
Type dauthentification : NTLM
Le framework .NET prsente dautres implmentations des interfaces IIdentity et IPrincipal
relatives dautres mcanismes de scurit et vous pouvez fournir vos propres implmentations
pour dvelopper vos propres mcanismes. On peut ainsi citer la classe System.Web.Security.
FormsIdentity exploite par ASP.NET et la classe System.Web.Security.PassportIdentity exploite par le mcanisme Passport.
Le couple de classes System.Security.Principal.GenericIdentity et System.Security.Principal.GenericPrincipal peut tre utilis comme base limplmentation de vos propres mcanismes
dauthentification/autorisation mais rien ne vous empche dimplmenter directement les interfaces IIdentity et IPrincipal.

Support .NET pour les utilisateurs et rles Windows

209

Les identificateurs de scurit Windows


Pour identifier les utilisateurs et les groupes, Windows utilise des identificateurs de scurit (SID).
Les SID peuvent tre vus comme des gros nombres uniques dans le temps et dans lespace,
un peu comme les GUID. Les SID peuvent tre reprsents par une chane de caractres au
format SDDL (Security Descriptor Definition Language un format de reprsentation textuel non
XML) donnant quelques prcisions quant lentit reprsente. Par exemple on peut dduire
du SID suivant "S-1-5-21-1950407961-2111586655-839522115-500" quil reprsente un administrateur car il contient le nombre 500 en dernire position (501 aurait indiqu un invit).
Le framework .NET prsente les trois classes suivantes dont les instances reprsentent des SID :
System.Object
System.Security.Principal.IdentityReference
System.Security.Principal.NTAccount
System.Security.Principal.SecurityIdentifier
La classe NTAccount permet de reprsenter le SID sous une forme lisible par les humains tandis que la classe SecurityIdentifier est utilise pour communiquer un SID Windows. Chacune de ces classes prsente une mthode IdentityReference Translate(Type targetType)
permettant dobtenir une reprsentation dirente dun mme SID.
La classe WindowsIdentity prsente la proprit SecurityIdentifier User{get;} permettant de rcuprer le SID de lutilisateur Windows courant. Lnumration System.Security.
Principal.WellKnownSidType reprsente la plupart des groupes Windows fournis par dfaut.
Enfin, la classe SecurityIdentifier prsente la mthode bool IsWellKnown(WellKnownSidType) qui permet de tester si le SID courant appartient au groupe Windows prcis. Lexemple
suivant exploite tout ceci pour tester si lutilisateur courant est un administrateur :
Exemple 6-11 :
using System.Security.Principal ;
class Program {
static void Main() {
WindowsIdentity id = WindowsIdentity.GetCurrent() ;
SecurityIdentifier sid = id.User ;
NTAccount ntacc = sid.Translate(typeof(NTAccount)) as NTAccount ;
System.Console.WriteLine( "SID:
" + sid.Value) ;
System.Console.WriteLine( "NTAccount: " + ntacc.Value) ;
if ( sid.IsWellKnown(WellKnownSidType.AccountAdministratorSid) )
System.Console.WriteLine("...is administrator.") ;
}
}
Cet exemple ache :
SID:
S-1-5-21-1950407961-2111586655-839522115-500
NTAccount: PSMACCHIA\pat
...is administrator.

Impersonation du thread Windows


Par dfaut, un thread Windows volue dans le contexte de scurit de son processus. Nanmoins,
on peut, partir du code, associer le contexte de scurit dun thread avec un utilisateur en

210

Chapitre 6 : La gestion de la scurit

utilisant la fonction win32 WindowsIdentity.Impersonate(IntPtr jeton). Il faut au pralable


loguer lutilisateur et obtenir un jeton de scurit avec la fonction LogonUser(). Cet utilisateur
nest pas ncessairement lutilisateur du contexte de scurit du processus. Lorsque le contexte
de scurit dun thread est associ un utilisateur, on dit que le thread eectue un emprunt
didentit (impersonation en anglais). Cette technique est expose par lexemple suivant :
Exemple 6-12 :
using System.Runtime.InteropServices ;
using System.Security.Principal ;
class Program{
[DllImport("Advapi32.Dll")]
static extern bool LogonUser(
string
sUserName,
string
sDomain,
string
sUserPassword,
uint
dwLogonType,
uint
dwLogonProvider,
out
System.IntPtr Jeton);
[DllImport("Kernel32.Dll")]
static extern void CloseHandle(System.IntPtr Jeton) ;
static void Main(){
WindowsIdentity id1 = WindowsIdentity.GetCurrent() ;
System.Console.WriteLine( "Avant impersonation : " +id1.Name) ;
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) ;
WindowsIdentity id2 = WindowsIdentity.GetCurrent() ;
System.Console.WriteLine("Pendant impersonation : "+id2.Name) ;
// Ici, le thread Windows sous-jacent
// a lidentite de lutilisateur invit
e.
CloseHandle(pJeton) ;
}
}
}
Ce programme ache :
Avant impersonation : PSMACCHIA\pat
Pendant impersonation : PSMACCHIA\invit
e
La mthode WindowsIndentity.GetCurrent() accepte une surcharge prenant un paramtre un
boolen. Lorsque le boolen est true cette mthode nous retourne lidentit de lutilisateur
seulement si le thread est en cours demprunt didentit. Lorsque le boolen est false cette

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.

Support .NET pour les contrles des accs


aux ressources Windows
Introduction au contrle des accs aux ressources Windows
Aprs avoir introduit les notions de groupes et dutilisateurs Windows nous allons nous intresser la seconde partie de la scurit sous Windows : les contrles des accs aux ressources.
Une ressource Windows peut tre un fichier, un objet de synchronisation Windows (mutex, vnement...), une entre de la base des registres etc. Chaque type de ressource prsente des droits
daccs qui lui sont propres. Par exemple, on peut citer le droit daccs dajouter des donnes
un fichier et le droit daccs dobtenir la possession dun mutex. Aucun de ces droits daccs na
de sens sorti du contexte de son type de ressource.
Chaque ressource contient physiquement des informations qui permettent Windows de dduire quel utilisateur a quel droit daccs sur la ressource. Ces informations sont contenues dans
une structure associe la ressource que lon nomme descripteur de scurit (SD). Un SD contient
notamment un SID reprsentant lutilisateur qui a cr ou qui dtient la ressource et une liste
nomme Discretionary Access Control List (DACL). Bien que stock sous une forme binaire, un
SD peut tre reprsent par une chane de caractres au format SDDL.
Lorsquun thread qui sexcute dans le contexte de scurit dun utilisateur tente dobtenir un
ou plusieurs droits daccs sur une ressource, Windows dtermine sil peut obtenir les droits
partir de la DACL de la ressource.
Une DACL est une liste ordonne dAccess Control Element (ACE). Un ACE est une structure qui
associe un SID une liste de droits daccs. Une DACL contient deux types dACE :

les ACE qui accordent leurs droits daccs leur SID ;


les ACE qui refusent leurs droits daccs leur SID.

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

Chapitre 6 : La gestion de la scurit

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).

.NET et la manipulation de SD spcifiques


Les types relatifs des ressources spcifiques consistent en une hirarchie de types reprsentant
les SD, une hirarchie de types reprsentant les ACE et des numrations reprsentant les droits
daccs. Les classes suivantes permettent de reprsenter les descripteurs de scurit :
System.Object
System.Security.AccessControl.ObjectSecurity
System.Security.AccessControl.DirectoryObjectSecurity
System.DirectoryServices.ActiveDirectorySecurity
System.Security.AccessControl.CommonObjectSecurity
Microsoft.Iis.Metabase.MetaKeySecurity
System.Security.AccessControl.NativeObjectSecurity
System.Security.AccessControl.EventWaitHandleSecurity
System.Security.AccessControl.FileSystemSecurity
System.Security.AccessControl.DirectorySecurity
System.Security.AccessControl.FileSecurity
System.Security.AccessControl.MutexSecurity
System.Security.AccessControl.RegistrySecurity
System.Security.AccessControl.SemaphoreSecurity
Ces classes acceptent des paramtres spcifiques reprsentant les ACE pour remplir les DACL et
SACL. Notez quil y a des classes qui reprsentent les ACE de la DACL (access rule) et des classes
qui reprsentent les ACE de la SACL pour laudit (audit rule) :
System.Object
System.Security.AccessControl.AuthorizationRule
System.Security.AccessControl.AccessRule
Microsoft.Iis.Metabase.MetaKeyAccessRule
System.Security.AccessControl.EventWaitHandleAccessRule
System.Security.AccessControl.FileSystemAccessRule
System.Security.AccessControl.MutexAccessRule
System.Security.AccessControl.ObjectAccessRule
System.DirectoryServices.ActiveDirectoryAccessRule
System.DirectoryServices.[*]AccessRule
System.Security.AccessControl.RegistryAccessRule
System.Security.AccessControl.SemaphoreAccessRule
System.Security.AccessControl.AuditRule
Microsoft.Iis.Metabase.MetaKeyAuditRule

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

Chapitre 6 : La gestion de la scurit

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).

.NET et la manipulation de SD gnriques


.NET prsente des types gnraux pour la manipulation des SD. La hirarchie de types suivante
permet de reprsenter des SD :
System.Object
System.Security.AccessControl.GenericSecurityDescriptor
System.Security.AccessControl.CommonSecurityDescriptor
System.Security.AccessControl.RawSecurityDescriptor
La hirarchie de type suivante permet de reprsenter des ACL, des DACL et des SACL :
System.Object
System.Security.AccessControl.GenericAcl
System.Security.AccessControl.CommonAcl
System.Security.AccessControl.DiscretionaryAcl
System.Security.AccessControl.SystemAcl
System.Security.AccessControl.RawAcl
La hirarchie de type suivante permet de reprsenter des ACE :
System.Object
System.Security.AccessControl.GenericAce
System.Security.AccessControl.CustomAce
System.Security.AccessControl.KnownAce
System.Security.AccessControl.CompoundAce
System.Security.AccessControl.QualifiedAce
System.Security.AccessControl.CommonAce
System.Security.AccessControl.ObjectAce
Lexemple suivant montre comment crer un SD, comment ajouter des ACE son DACL, puis
comment transformer ce SD en un SD de ressource spcifique Windows (le type mutex en loccurrence) :
Exemple 6-14 :
using System ;
using System.Security.AccessControl ;
using System.Security.Principal ;
class Program {
static void Main() {
// Cree un nouveau descripteur de s
ecurite.
CommonSecurityDescriptor csd = new CommonSecurityDescriptor(
false, false, string.Empty) ;
DiscretionaryAcl dacl = csd.DiscretionaryAcl ;
// Ajoute un ACE a son DACL.

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 :

Security Descriptor : D:(A;;0xffffffff;;;WD)(A;;0x180000;;;LA)


-->SID : TOUT LE MONDE
Type de droits dacc`es : Allow
Tous les droits !
-->SID : PSMACCHIA\pat
Type de droits dacc`es : Allow
Droits : TakeOwnership, Synchronize

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

Chapitre 6 : La gestion de la scurit

.NET et la notion de rle


De la mme faon quun thread Windows sexcute dans un contexte de scurit Windows, un
thread gr a la possibilit de sexcuter dans un contexte de scurit de votre choix. Vous pouvez
alors exploiter un mcanisme de scurit de type rle/utilisateur autre que celui de Windows,
tel que celui dASP.NET par exemple. Ceci est possible car la classe Thread prsente la proprit
IPrincipal CurrentPrincipal{get;set;}. Un principal peut tre associ un thread gr de
trois faons direntes :

Soit vous associez explicitement un principal un thread gr avec la proprit Thread.


CurrentPrincipal.
Soit vous dfinissez une politique de principal pour un domaine dapplication. Lorsquun
thread gr excutera le code du domaine dapplication, la politique de principal du domaine lui associera ventuellement un principal, sauf dans le cas o le thread a t associ
explicitement un principal, auquel cas celui-ci nest pas modifi.
Soit vous pouvez dcider que tous les threads qui sont crs dans un domaine ou qui pntrent dans un domaine sans avoir de principal .NET auront un principal particulier. Pour
cela il sut de prciser ce principal avec la mthode void AppDomain.SetThreadPrincipal
(IPrincipal).

Ces trois oprations ncessitent la mta-permission CAS SecurityPermissionFlag.ControlPrincipal pour tre menes bien.

Positionner la politique de principal dun domaine dapplication


Lexemple suivant positionne la politique de principal du domaine dapplication courant WindowsPrincipal. Cest--dire que lorsquun thread gr excute le code contenu dans le domaine
dapplication, sil ny a pas eu de principal associ explicitement ce thread gr, alors le principal associ au contexte de scurit Windows sous-jacent lui est associ :
Exemple 6-15 :
using System.Security.Principal ;
class Program{
static void Main(){
System.AppDomain.CurrentDomain.SetPrincipalPolicy(
PrincipalPolicy.WindowsPrincipal);
IPrincipal pr = System.Threading.Thread.CurrentPrincipal;
IIdentity id = pr.Identity ;
System.Console.WriteLine( "Nom : "+id.Name) ;
System.Console.WriteLine( "Authentifi
e ? : "+id.IsAuthenticated) ;
System.Console.WriteLine(
"Type dauthentification : "+id.AuthenticationType) ;
}
}
Ce programme ache :
Nom : PSMACCHIA\pat
Authentifie ? : True
Type dauthentification : NTLM

.NET et la notion de rle

217

Les autres politiques de principal possibles pour un domaine dapplication sont :

Pas de principal associ au thread (PrincipalPolicy.NoPrincipal). Dans ce cas la proprit


Thread.CurrentPrincipal vaut null par dfaut.

Un principal non authentifi associ au thread (PrincipalPolicy.UnauthenticatedPrincipal).


Dans ce cas le CLR associe une instance de GenericPrincipal non authentifie la proprit Thread.CurrentPrincipal.

Cette dernire alternative constitue la politique de principal prise par dfaut par tous les domaines dapplication.

Vrifier lappartenance un rle


Vous pouvez vrifier le rle dun principal dun thread gr de trois faons direntes :

Vous pouvez utiliser la mthode IsInRole() prsente par linterface IPrincipal.


Exemple 6-16 :
using System.Security.Principal ;
class Program{
static void Main(){
IPrincipal pr = System.Threading.Thread.CurrentPrincipal ;
if( pr.IsInRole( @"BUILTIN\Administrators" ) ){
// Ici, le principal est un administrateur.
}
else
System.Console.WriteLine(
"Lexecution de ce programme requiert les droits dAdministrateur") ;
}
}
Le lecteur attentif aura remarqu que dans lExemple 6-11 nous avons exploit une autre
technique pour vrifier quun utilisateur Windows est membre dun groupe Windows. Cette
technique, base sur lnumration WellKnownSidType, est prfrable dans le cas particulier
dutilisateurs et de rles Windows. La raison est que le nom dun groupe Windows dire selon la langue utilise en paramtre (par exemple Administrateurs en franais, Administrators
en anglais).

Vous pouvez utiliser la classe System.Security.Permissions.PrincipalPermission. Bien que


cette classe ne drive pas de la classe CodeAccessPermission, elle implmente linterface
IPermission. Cette classe prsente aussi les mthodes classiques permettant de manipuler
les permissions (FromXml() ToXml() etc). Cette technique ore lavantage pour les dveloppeurs, de grer les rles dune manire intgre par rapport la gestion des permissions.
Exemple 6-17 :
using System.Security.Permissions ;
class Program{
static void Main(){
try{
PrincipalPermission prPerm = new PrincipalPermission(

218

Chapitre 6 : La gestion de la scurit


null, @"BUILTIN\Administrators" );
prPerm.Demand();
// Ici, le principal est un administrateur.
}
catch(System.Security.SecurityException){
System.Console.WriteLine(
"Lexecution de ce programme requiert les droits dAdministrateur") ;
}
}
}
Un autre avantage de cette technique est quelle permet de vrifier plusieurs rles dun seul
coup :
Exemple 6-18 :
...
PrincipalPermission prPermAdmin = new PrincipalPermission(
null, @"BUILTIN\Administrators" ) ;
PrincipalPermission prPermUser = new PrincipalPermission(
null, @"BUILTIN\Users" ) ;
System.Security.IPermission prPerm = prPermAdmin.Union(prPermUser);
prPerm.Demand() ;
...
linstar de la gestion des permissions, vous pouvez utiliser lattribut .NET PrincipalPermission spcialement conu pour grer les rles .NET :
Exemple 6-19 :
using System.Security.Permissions ;
class Program{
[PrincipalPermission(
SecurityAction.Demand,
Role= @"BUILTIN\Administrators")]
static void Main(){
// Ici, le principal est un administrateur.
}
}

La comparaison entre la technique dutilisation de la classe PrincipalPermission et la technique


dutilisation dattributs .NET est faite dans la section page 204.

Scurit base sur les rles sous COM+


COM+ est une technologie Microsoft permettant une classe (.NET ou non) dutiliser des fonctionnalits appeles services dentreprise. Parmi ces services dentreprise, il existe un service
dentreprise de gestion de la scurit partir de rles. Pour chaque composant servi (i.e qui utilise COM+), vous pouvez associer les rles requis pour lexploitation du composant puis aecter
des rles chaque utilisateurs. Les rles COM+ peuvent ventuellement tre dirents des rles
Windows mais en pratique, on utilise souvent les rles Windows. Lorsquun assemblage contient
des composants servis, il peut vrifier lappartenance dun utilisateur un rle en utilisant

.NET et les algorithmes symtriques de cryptographie

219

lattribut System.EnterpriseServices.SecurityRole sur lassemblage tout entier ou seulement sur


certaines classes ou interfaces de lassemblage. Pour plus dinformation quant lexploitation
du service dentreprise de gestion des rles COM+ et quant lunification des notions de rle
Windows et de rle COM+ vous pouvez vous rfrer larticle Unify the Role-Based Security
Models for Enterprise and Application Domains with .NET de Juval Lowy disponible lURL
http://msdn.microsoft.com/msdnmag/issues/02/05/rolesec/ .

.NET et les algorithmes symtriques de cryptographie


Un peu de thorie
Nous allons expliquer comment Julien et Mathieu peuvent schanger des messages dune manire confidentielle en utilisant un algorithme symtrique dencryptions. Les algorithmes symtriques sont bass sur un systme de paire de cls. Avant de pouvoir encrypter un message M,
Julien et Mathieu doivent choisir un algorithme symtrique et se fabriquer une paire de cls
(S,P). Nommons P(M) un message M encrypt avec la cl P et S(M) un message M encrypt avec
la cl S. Les proprits dun algorithme symtrique sont les suivantes :

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)

Figure 6 -6 : Echange de messages crypts avec un algorithme symtrique


Les algorithmes symtriques ne sont pas secrets et sont largement dissqus dans de nombreuses
publications. Dans la mesure o la publication de lalgorithme permet des milliers de mathmaticiens den examiner les faiblesses ventuelles on peut mme dire que publier lalgorithme
de cryptographie participe la robustesse de lalgorithme.
Seules les cls doivent tre gardes confidentielles. Ceci est un principe de la cryptographie
apparu relativement rcemment, il y a une trentaine dannes. Cest vraiment une nouveaut

220

Chapitre 6 : La gestion de la scurit

puisque pendant des sicles, les algorithmes ont constitu la pierre angulaire de la cryptographie.

Le framework .NET et les algorithmes symtriques


Le framework .NET prsente une implmentation pour les algorithmes symtriques les plus
connus savoir les algorithmes DES, RC2, Rinjdael et Triple DES. Voici la hirarchie de classes.
Seules les classes qui ne sont pas en gras sont abstraites. Ainsi, vous pouvez dcider de fournir
votre propre implmentation de ces algorithmes.
System.Object
System.Security.Cryptography.SymmetricAlgorithm
System.Security.Cryptography.DES
System.Security.Cryptography.DSECryptoServiceProvider
System.Security.Cryptography.RC2
System.Security.Cryptography.RC2CryptoServiceProvider
System.Security.Cryptography.Rijndael
System.Security.Cryptography.RinjdaelManaged
System.Security.Cryptography.TripleDES
System.Security.Cryptography.TripleDESCryptoServiceProvider
Lalgorithme DES (Digital Encryption Standard) est lalgorithme symtrique le plus utilis.
Lexemple suivant illustre lutilisation de la classe DESCryptoServiceProvider pour crypter
et dcrypter une chane de caractre. En plus dune cl, nous fournissons cet algorithme un
vecteur dinitialisation (Initialization Vector en anglais IV). Un vecteur dinitialisation peut tre vu
comme un nombre en gnral choisi alatoirement et utilis pour initialiser lalgorithme :
Exemple 6-20 :
using System.Security.Cryptography ;
class Program{
static void Main() {
string sMsg = "Le message `
a encrypter !" ;
string sEnc, sDec ;
DESCryptoServiceProvider des = new DESCryptoServiceProvider();
System.Text.Encoding utf = new System.Text.UTF8Encoding();
byte[] key = utf.GetBytes("12345678");
byte[] iv = { 1, 2, 3, 4, 5, 6, 7, 8 };
ICryptoTransform encryptor = des.CreateEncryptor(key, iv);
ICryptoTransform decryptor = des.CreateDecryptor(key, iv);
{
byte[] bMsg = utf.GetBytes(sMsg) ;
byte[] bEnc = encryptor.TransformFinalBlock(bMsg, 0,
bMsg.Length);
sEnc = System.Convert.ToBase64String(bEnc) ;
}
{
byte[] bEnc = System.Convert.FromBase64String(sEnc) ;
byte[] bDec = decryptor.TransformFinalBlock(bEnc, 0,
bEnc.Length);

.NET et les algorithmes asymtriques de cryptographie (cl publique/cl prive)

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() ;
...

.NET et les algorithmes asymtriques de cryptographie


(cl publique/cl prive)
Un peu de thorie
Les algorithmes symtriques prsentent deux faiblesses :

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

Chapitre 6 : La gestion de la scurit


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).

En plus de ces trois proprits un algorithme asymtrique a les deux proprits suivantes :

Il est ais de calculer la cl S lorsque lon connat la cl P.


Il est trs dicile de calculer la cl P si lon connat la cl S.

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

Figure 6 -7 : Echange de messages crypts avec un algorithme symtrique


On dit que S est la cl publique ou partage (S pour Shared en anglais). On dit que P est la cl prive
(P pour Private en anglais). On nomme parfois les algorithmes asymtriques les algorithmes cls
publiques/cls prives.
Un problme dauthentification subsiste nanmoins. Puisque la cl Sm est connu de tous un tiers
peut parfaitement envoyer un message crypt Sm(M) Mathieu en se faisant passer pour Julien.
Ce problme peut tre contourn en utilisant lastuce suivante : Julien peut envoyer le message
crypt Sm(Pj(M)) Mathieu. Mathieu peut alors dcrypter ce message puisquil dtient les cl
Pm et Sj et que Sj(Pm( Sm(Pj(M)) )) = Sj(Pj(M)) = M. En outre Mathieu peut tre certains
que Julien est lenvoyeur puisque seul Julien connat la cl Pj.

Notion de session scurise


Un autre problme subsiste : le cot des calculs des algorithmes asymtriques connus est environs 1000 fois plus lev que le cot des calculs des algorithmes symtriques connus. En pratique, les protocoles scuriss dchange de messages utilisent un algorithme asymtrique pour
changer une paire de cl dun algorithme symtrique puis utilise lalgorithme symtrique pour
crypter les messages. La paire de cl de lalgorithme symtrique nest alors valable que pour une
session dchange de messages. On parle alors de cl de session et de session scurise.

.NET et les algorithmes asymtriques de cryptographie (cl publique/cl prive)

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%.

Algorithme asymtriques et signature numrique


En plus de la possibilit de crypter des donnes, les proprits des algorithmes asymtriques
peuvent tre utilises pour signer numriquement des donnes. Signer numriquement des
donnes signifie quun consommateur de donnes peut tre absolument certains que celui qui
a produit des donnes dtient une certaine cl prive. On parle dauthentification de donnes
si le consommateur peut en plus tre certains que seul celui qui a produit la donne dtient la
cl prive.
Pour comprendre ce qui va tre expos il est ncessaire de comprendre ce quest une valeur
de hachage. Une valeur de hachage est un nombre calcul partir dun ensemble de donnes.
Ce calcul a la particularit de fournir deux nombres dirents pour deux ensembles de donnes dirents, de manire quasiment sre. Le framework .NET prsente dans lespace de noms
System.Security.Cryptography les implmentations des principaux algorithmes de hachage.
On peut citer les algorithmes SHAx, RIPEMD160 ou MD5.
Supposons que Mathieu veuille convaincre Julien quil est lauteur du fichier FOO. Il lui faut
dabord calculer une valeur de hachage x du fichier. Ensuite Mathieu calcule Pm(x) partir de
sa cl prive. Enfin Mathieu intgre la valeur Pm(x) et sa cl publique Sm dans le fichier FOO (par
exemple au dbut du fichier).
Julien connat au pralable la cl publique Sm de Mathieu. Julien rcupre le fichier FOO. Il extrait Pm(x) et la cl publique du fichier. Il vrifie que cest bien la cl publique de Mathieu. Il
peut donc calculer x de deux manires direntes :

En calculant la valeur de hachage du fichier FOO (auquel il a t Pm(x) et la cl publique).

En calculant x partir de Pm(x) et de la cl publique Sm car Sm( Pm(x) )=x.

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

Chapitre 6 : La gestion de la scurit

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.

Le framework .NET et lalgorithme RSA


Le framework .NET prsente deux classes permettant dexploiter lalgorithme RSA : La classe
RSACryptoServiceProvider pour lencryption de donnes et la classe DSACryptoServiceProvider pour signer numriquement des donnes (DSA pour Digital Signature Algorithm).
Voici la hirarchie des classes :
System.Object
System.Security.Cryptography.AsymmetricAlgorithm
System.Security.Cryptography.DSA
System.Security.Cryptography.DSACryptoServiceProvider
System.Security.Cryptography.RSA
System.Security.Cryptography.RSACryptoServiceProvider
Lexemple suivant expose lutilisation de la classe RSACryptoServiceProvider pour crypter une
chane de caractres. La mthode ExportParameter(bool) permet de rcuprer la cl publique
ou la paire de cl publique/prive selon quelle est appele avec la valeur false ou true :
Exemple 6-22 :
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() ;
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
RSAParameters publicKey = rsa.ExportParameters(false);
RSAParameters publicAndPrivateKey = rsa.ExportParameters(true);
{
RSACryptoServiceProvider rsaEncryptor = new
RSACryptoServiceProvider();
rsaEncryptor.ImportParameters(publicKey);
byte[] bMsg = utf.GetBytes(sMsg) ;

LAPI de protection des donnes (Data Protection API)

225

byte[] bEnc = rsaEncryptor.Encrypt(bMsg, false);


sEnc = System.Convert.ToBase64String(bEnc) ;
}
{
RSACryptoServiceProvider rsaDecryptor = new
RSACryptoServiceProvider();
rsaDecryptor.ImportParameters(publicAndPrivateKey);
byte[] bEnc = System.Convert.FromBase64String(sEnc) ;
byte[] bDec = rsaDecryptor.Decrypt(bEnc, false);
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: WVswU5B1JMKdrGrESNngo+s7K/+kvz3o8UaxB5E0sjdejNDmjsuvGEKMP
P3q3OuRXB4k7B5yLwcnaJK2guVmK3ysN+OgmsheOX0UlqUBlzp2EzVsaqpzUQGHxe6k
toOBILR4PU1Jqyq1kESSTfMx9jfTDnMEJ3l1Op+wpQX5DFMs=
Decrypte: Le message `a encrypter!

La taille des cls


Traditionnellement, la taille des cls des algorithmes symtriques est de 40, 56 et 128 bits. La
taille des cls pour les algorithmes asymtriques a tendance tre plus leve. Pour vous donner
une ide, une cl de 40 bits ne rsiste que quelques minutes une attaque dtermine tandis
qu ma connaissance, aucune cl de 128 bits na encore t craque . Pour chaque implmentation du framework dun algorithme vous pouvez obtenir la taille des cls utilises. Certaines
implmentations vous permettent de fixer cette taille.
Bien entendu plus une cl contient de bits plus elle est sre. Lgalement, vous ne pouvez pas
utiliser nimporte quelle taille. Aussi, les implmentations des algorithmes dencryptions fournissent la proprit int[] LegalKeySizes[]{get;}.

LAPI de protection des donnes (Data Protection API)


LAPI de protection de donnes de Windows
Depuis Windows 2000, les systmes dexploitation Windows prsentent une API de cryptographie nomme DPAPI (Data Protection API). Cette API est implmente dans la DLL systme
crypt32.dll. Elle a ceci de particulier quelle se base sur les crdits accords au couple login/mot
de passe de lutilisateur courant pour grer les cls. Elle peut aussi se baser sur lidentit dun
processus, lidentit dune session Windows ou lidentit de la machine courante. En eet, bien
souvent nous souhaitons crypter des donnes pour garantir leur confidentialit au niveau dun
utilisateur, dun processus, dune session ou dune machine. Dans ces cas, lutilisation de DPAPI
nous vite davoir grer des cls.

226

Chapitre 6 : La gestion de la scurit

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

LAPI de protection des donnes (Data Protection API)

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

Chapitre 6 : La gestion de la scurit


Une mme chane de caractres peut tre duplique dans la mmoire du processus du fait
que le ramasse miettes se rserve le droit de bouger les instances de types rfrence.
Les chanes de caractres sont stockes en mmoire sans aucune sorte dencryptions.
Le fait que les chanes de caractres soient immuables implique qu chaque modification
dune chane, son ancienne version rside en mmoire pendant une dure que lon ne matrise pas.
Une autre consquence du caractre immuable des chanes de caractres est que vous navez
pas la possibilit de nettoyer les octets utiliss pour la stocker lorsque vous nen avez plus
besoin ( moins dutiliser la technique dcrite en page 508).

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) ; }
}
}

LAPI de protection des donnes (Data Protection API)

229

Protection des donnes dans vos configurations


La classe System.Configuration.Configuration permet de stocker des donnes encryptes
dans le fichier de configuration de lapplication. Lexemple suivant montre comment stocker
une version encrypte de la chane de caractres MonPassword dans un paramtre de configuration nomm TagPassword. Notez que seule la sauvegarde ncessite une manipulation spciale
pour prciser que lon souhaite stocker une version encrypte. La dcryption de la donne est
eectue dune manire transparente lors de son chargement :
Exemple 6-26 :
using System.Configuration ;
class Program {
static void Main() {
SavePwd("MonPassword") ;
System.Console.WriteLine(LoadPwd()) ;
}
static void SavePwd(string pwd) {
Configuration cfg = ConfigurationManager.OpenExeConfiguration(
ConfigurationUserLevel.None) ;
cfg.AppSettings.Settings.Add("TagPassword", pwd) ;
cfg.AppSettings.SectionInformation.ProtectSection(
"RsaProtectedConfigurationProvider" );
cfg.Save() ;
}
static string LoadPwd() {
Configuration cfg = ConfigurationManager.OpenExeConfiguration(
ConfigurationUserLevel.None) ;
return cfg.AppSettings.Settings["TagPassword"].Value ;
}
}
Voici le fichier de configuration gnr par lexemple prcdent. On voit quil contient une
section protectedData qui dclare que la section appSettings est encrypte et une section
appSetings qui contient les donnes encryptes :
<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">
<protectedData>
<protectedDataSections>
<add name="appSettings"
provider="RsaProtectedConfigurationProvider"
inheritedByChildren="false" />
</protectedDataSections>
</protectedData>
<appSettings>
<EncryptedData Type="http://www.w3.org/2001/04/xmlenc#Element"
xmlns="http://www.w3.org/2001/04/xmlenc#">
<EncryptionMethod
Algorithm="http://www.w3.org/2001/04/xmlenc#tripledes-cbc" />
<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">

230

Chapitre 6 : La gestion de la scurit


...
<CipherData>
<KeyName>Rsa Key</KeyName>
<CipherValue>EuZpuz7Rj(...)qalkG3VbQ=</CipherValue>
</CipherData>
...
</KeyInfo>
<CipherData>
<CipherValue>50cJLWVB(...)JHsGvLTY</CipherValue>
</CipherData>
</EncryptedData>
</appSettings>
</configuration>

Protection des donnes changes par lintermdiaire dun rseau


Dans le chapitre consacr au flot de donnes, nous prsentons en page 660 les dirents protocoles dchange scuris de donnes par lintermdiaire dun rseau.

Authentifier vos assemblages avec la technologie


Authenticode et les certificats X.509
Authenticode vs. Nom fort
Bien avant la distribution publique de la plateforme .NET, les systmes Windows exploitaient la
technologie connue sous le nom dAuthenticode pour authentifier les fichier excutables. Cette
technologie sapparente la technologie du nom fort qui fait lobjet de la section page 28 dans le
sens o elle permet de signer numriquement un assemblage. Cependant ces deux technologies
ont des buts dirents :

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.

En outre ces deux technologies prsentent des dirences notables :

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.

Authentifier vos assemblages avec la technologie Authenticode

231

Les certificats et les Certificate Authorities


Un certificat contient une cl publique et des informations sur un diteur. Il permet de garantir que la cl publique a bien t fabrique par lditeur rfrenc. Un certificat contient aussi
dautres informations telles que la date limite de validit. La technologie du nom fort ne prsente pas de certificat. Bien que certaines cls publiques soient bien connues et permettent donc
didentifier un diteur (comme la cl publique de Microsoft par exemple) il ny a pas de moyens
standard pour obtenir la certitude quune cl publique utilise dans la technologie du nom fort
a t fabrique par un diteur lambda.
Plusieurs standards de certificats existent. La technologie Authenticode utilise le standard X.509
qui stocke les certificats dans des fichiers dextension .cer. Le standard CMS/Pkcs7 qui stocke
les certificats dans des fichiers dextension .p7b est aussi trs rpandu car il est utilis par le
protocole Secure/Multipurpose Internet Mail Extensions (S/MIME) pour signer et crypter les mails.
Un fichier sign avec la technologie Authenticode contient un certificat X.509. Windows peut
alors demander une autorit extrieure si le certificat est valide. Ces autorits extrieures sont
nommes Certificate Authorities (CA) ou parfois aussi tiers de confiance. Ce sont des entreprises
dont le but est de valider ou non une certification. On compte parmi les CA les plus connus les
organismes VeriSign ou EnTrust. Concrtement, un diteur de logiciel paie un CA pour que ce
dernier valide son certificat auprs de ses clients. Le CA aura au pralable eectu un ensemble
de vrification sur le srieux de lditeur de logiciel.

Les certificats racines


En pratique, il existe un mcanisme permettant Windows de ne pas contacter systmatiquement un CA lors de la vrification dun certificat. En eet, chaque systme Windows maintient une liste de certificats (nomme Certificate Store en anglais). Les certificats contenus dans
cette liste sont nomms les certificats racines. La liste des certificats racines prsente par dfaut
par Windows est disponible dans larticle Microsoft Root Certificate Program Members des
MSDN.
Vous pouvez visualiser ajouter ou retirer des certificats dans cette liste avec loutil certmgr.exe.
Lorsquun diteur de logiciel demande un CA de publier son certificat, ce dernier signe le
certificat de lditeur avec son propre certificat. Si le certificat du CA est un certificat racine,
Windows na pas besoin deectuer un accs rseau pour demander au CA de valider le certificat de lditeur puisquil a mathmatiquement tous les lments ncessaires pour eectuer la
vrification lui mme.
On peut imaginer que le CA contact par lditeur est de moindre importance. Dans ce cas, le
certificat de ce CA nest probablement pas un certificat racine des systmes Windows. Cependant,
rien nempche ce CA de moindre importance de demander un CA bien connu de lui signer
son certificat. On voit donc samorcer une chane de certifications qui en pratique, vite aux
centaines de millions de machines utilisant Windows de contacter les CA lors de linstallation
de logiciels.

Les implications de la technologie Authenticode


Lorsque Windows installe ou tlcharge un logiciel qui na pas de certificat ou dont le certificat ne
peut tre vrifi, il ache une fentre de dialogue vous demandant si vous autorisez lexcution
du programme.

232

Chapitre 6 : La gestion de la scurit

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.

Quand utilise-t-on le mcanisme de rflexion ?


Nous avons recens quelques catgories dutilisation du mcanisme de rflexion. Elles font lobjet des prochaines sections du prsent chapitre. Le mcanisme de rflexion peut tre utilis dans
les cas suivants :

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

Chapitre 7 : Rflexion, liens tardifs, attributs

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.

Quest ce que le mcanisme de rflexion apporte de nouveau ?


Lide sous-jacente du mcanisme de rflexion nest pas nouvelle. Cela fait longtemps que lon
peut analyser dynamiquement le contenu dun excutable, notamment grce des informations dauto description. Le format TLB (prsent page 279) a t conu cet eet. Prcisons que
les donns au format TLB sont produites partir de donnes au format IDL (Interface Definition Language). Le langage IDL peut donc aussi tre vu comme un format dauto description.
Le mcanisme de rflexion propos par .NET va beaucoup plus loin que le format TLB et le
format IDL :

Il est trs simple utiliser grce certaines classes de base.

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 :

Il y a une classe dont les instances reprsentent des assemblages


(System.Reflection.Assembly) ;

une classe dont les instances reprsentent des classes (System.Type) ;

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

Figure 7 -1 : Classes utilises par le mcanisme de rflexion


On peut regretter le fait que lon ne puisse pas descendre au niveau de linstruction IL mais
seulement au niveau de dun tableau doctets reprsentant le corps dune mthode en IL. Pour
cela, vous devez avoir recours certaines bibliothques telles que Cecil (dveloppe par JeanBaptiste Evain), ILReader (dveloppe par Lutz Roeder) ou Rail (dveloppe par luniversit de
Coimbra). Sachez cependant quen .NET 2005 la rflexion sait traiter les types gnriques, les
mthodes gnriques ainsi que les contraintes (voir page 498).

Analyser les assemblages contenus dans un domaine dapplication


Voici un exemple qui expose lanalyse des mtadonnes de type avec les classes de lespace de
noms System.Reflection. En fait, nous prsentons ici un assemblage qui analyse ses propres
mtadonnes de type. Pour obtenir une instance de la classe Assembly reprsentant cet assemblage, nous avons recours la mthode statique Assembly.GetExecutingAssembly() :

236

Chapitre 7 : Rflexion, liens tardifs, attributs

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()

Extraire des informations partir des mtadonnes


Lobjet de la prsente section est de prsenter un petit programme qui exploite le mcanisme
de rflexion pour acher lensemble des classes dexceptions contenues dans les assemblages
System et mscorlib. Nous nous sommes bass sur le fait que toutes les classes dexception drivent de la classe System.Exception. Les types dexceptions qui ne drivent pas directement de
la classe System.Exception sont marqus avec une apostrophe.
On pourrait simplement modifier ce programme pour acher lensemble des classes dattribut du Framework en se basant sur le fait que toutes ces classes drivent de la classe System.
Attribute.
Exemple 7-2 :
using System ;
using System.Reflection ;
class Program {
static void Main() {
// Fabrique le nom fort de lassemblage System.
// Le numero de version est le m
eme pour tous les asm syst`
emes.
// Cest celui de mscorlib qui contient System.Object.
string systemAsmStrongName = "System, Culture = neutral, " +

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

Affiche le nom de la classe si elle repr


esente un
attribut. Place une apostrophe si la classe ne
derive pas directement de System.Excception.
(bDeriveDException)
if (bDeriveDirectement)
Console.WriteLine(" " + t) ;
else
Console.WriteLine(" " + t) ;
}// end foreach(Type...
}// end foreach(Assembly...
}// end Main
}

Notez lutilisation de la mthode Assembly.ReflectionOnlyLoad() dans lexemple prcdent.


Cette mthode permet de spcifier au CLR que lassemblage charg ne sera utilis que par le
mcanisme de rflexion. En consquence, le CLR interdira lexcution du code dun assemblage
charg avec cette mthode. En outre, le chargement dun assemblage en mode Reflection Only
est sensiblement plus rapide car le CLR saranchit des vrifications relatives la scurit. La

238

Chapitre 7 : Rflexion, liens tardifs, attributs

classe Assembly prsente la proprit bool ReflectionOnly{get;} qui permet de savoir quun
assemblage a t charg par cette mthode.

Les liens tardifs


Avant daborder cette section, il est prfrable davoir de bonnes bases en programmation
objet, notamment en ce qui concerne le mcanisme de polymorphisme. Ce sujet est trait
dans le chapitre 12.

Que veut dire crer un lien avec une classe ?


Tout dabord, il faut sentendre sur ce que veut dire crer un lien avec une classe (to bind a
class en anglais). On va parler de couches logicielles plutt que dassemblages car ce qui va tre
dit est valable pour dautres technologies.
Un lien avec une classe est cr entre la couche logicielle utilisatrice de la classe (qui instancie
la classe puis utilise les instances) et la couche logicielle qui dfinit la classe. Concrtement, ce
lien est la correspondance entre les appels aux mthodes de la classe dans la couche logicielle
utilisatrice, et lemplacement physique des mthodes dans la couche logicielle qui dfinit la
classe. Cest ce lien qui permet au thread daller continuer sa course dans le code de la mthode
dune classe lorsque celle-ci est appele.
En gnral, on distingue trois types de liens avec une classe : les liens prcoces (early bind en
anglais) crs pendant la compilation, les liens dynamiques (dynamic bind en anglais) dont une
partie est cre la compilation et lautre lexcution, et les liens tardifs (late binding en anglais)
crs pendant lexcution.

Liens prcoces et liens dynamiques


Les liens prcoces sont crs par le compilateur, lors de la fabrication dun assemblage partir du
code source crit dans un langage .NET. On ne peut donc pas crer de liens prcoces avec une
mthode virtuelle ou abstraite. En eet, le mcanisme de polymorphisme dcide lexcution
quel est le corps dune mthode virtuelle ou abstraite appeler, partir de la classe relle de
lobjet sur lequel la mthode est appele. Dans ce cas, on qualifie le lien de lien dynamique. Dans
la premire dition du prsent ouvrage ainsi que dans dautres documents, les liens dynamiques
sont parfois nomms lien tardif implicite puisquils sont crs implicitement par le mcanisme
de polymorphisme et puisquils sont finaliss tardivement, durant lexcution.
Intressons-nous ici aux liens prcoces, ceux crs avec des mthodes statiques ou avec des mthodes dinstances ni virtuelles ni abstraites. Si lon sen tient la dfinition de crer un lien
avec une classe de la section prcdente, il nexiste pas de liens prcoces en .NET. En eet, il
faut attendre que le corps dune mthode soit JIT compile en langage machine avant de
connatre son emplacement physique dans lespace dadressage du processus. Cette information
demplacement physique du corps de la mthode ne peut donc pas tre connue du compilateur
qui cre lassemblage. Nous avons vu page 111 que pour rsoudre ce problme, le compilateur
qui cre lassemblage insre le jeton de mtadonnes correspondant la mthode appeler,
lendroit du code IL o lappel a lieu. Lorsque le corps de la mthode est JIT compile lexcution, le CLR stocke en interne la correspondance entre la mthode et lemplacement physique

Les liens tardifs

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

Chapitre 7 : Rflexion, liens tardifs, attributs

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/).

On peut souhaiter utiliser la technique de liens tardifs partir de programmes crits en


langages compils, tels que C  . Lide est que lemploi dun lien tardif permet dintroduire
un degr de flexibilit dans larchitecture gnrale de votre application. Cette technique
est en fait un design pattern populaire nomm plugin que nous prsenterons la fin de la
prsente section.

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.

Certains prconisent lutilisation de liens tardifs la place du polymorphisme. En eet, puisque


au moment de lappel seul le nom et la signature de la mthode sont pris en compte, le type de
lobjet sur lequel la mthode est appele importe peu. Il sut que limplmentation de lobjet
prsente une mthode avec ce nom et cette signature. Nous dconseillons nanmoins cette utilisation car elle est trs permissive et ne force pas les concepteurs de lapplication se focaliser
sur des interfaces abstraites.
part les cas qui viennent dtre exposs, vous naurez pas utiliser de liens tardifs explicites.
Ne les utiliser pas pour le plaisir davoir des liens tardifs explicites dans vos applications car :

Vous perdrez lnorme bienfait de la vrification syntaxique du compilateur.

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.

Cration dune instance dune classe non connue la compilation


Si une classe ou une structure nest pas connue la compilation, on ne peut linstancier avec
loprateur new. Le framework .NET prvoit donc des classes qui permettent dinstancier des types
non connus la compilation.

Prciser un type
Intressons-nous aux direntes techniques qui permettent de prciser un type :

Les liens tardifs

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).

Dautres acceptent une instance de la classe System.Type. Dans un domaine dapplication,


chaque instance de System.Type reprsente un type et il ne peut y avoir deux instances
reprsentant le mme type. Pour obtenir une instance de System.Type, il existe de nombreuses faons :

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 :

Code de lassemblage Foo.dll

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 ;
}
}
}

Utilisation de la classe System.Activator


La classe System.Activator prsente les deux mthodes statiques CreateInstance() et CreateInstanceFrom() qui permettent de crer une instance dune classe non connue la compilation. Par exemple :
Exemple 7-4 :
using System ;
using System.Reflection ;
class Program {
static void Main() {

242

Chapitre 7 : Rflexion, liens tardifs, attributs


Assembly assembly = Assembly.Load("Foo.dll") ;
Type type = assembly.GetType("NMFoo.Calc") ;
object obj = Activator.CreateInstance(type);
// obj est une reference vers une instance de NMFoo.Calc.
}
}

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 ;

ventuellement le nom de assemblage qui contient la classe ;

ventuellement une liste darguments pour le constructeur.

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.

Utilisation de la classe System.AppDomain


La classe System.AppDomain prsente les quatre mthodes non statiques CreateInstance(),
CreateInstanceAndUnWrap(), CreateInstanceFrom() et CreateInstanceFromAndUnwrap() qui
permettent de crer une instance dune classe non connue la compilation. Par exemple :
Exemple 7-5 :
using System ;
using System.Reflection ;
class Program {
static void Main() {
object obj = AppDomain.CurrentDomain.CreateInstanceAndUnwrap(
"Foo.dll", "NMFoo.Calc");
// obj est une reference vers une instance de NMFoo.Calc.
}
}
Ces mthodes sont similaires aux mthodes de la classe System.Activator, prsentes prcdemment. Cependant, elles permettent de choisir le domaine dapplication dans lequel lobjet
doit tre cr. De plus, les versions AndUnwarp() fournissent directement une rfrence vers
lobjet. Cette rfrence est obtenue partir de linstance de ObjectHandle.

Les liens tardifs

243

Utilisation de la classe System.Reflection.ConstructorInfo


Une instance de la classe System.Reflection.ConstructorInfo rfrence un constructeur. La
mthode Invoke() de cette classe construit en interne un lien tardif vers le constructeur, puis
linvoque au moyen de ce lien. Elle permet donc de crer une instance du type du constructeur.
Par exemple :
Exemple 7-6 :
using System ;
using System.Reflection ;
class Program {
static void Main() {
Assembly assembly = Assembly.Load ("Foo.dll") ;
Type type = assembly.GetType("NMFoo.Calc") ;
ConstructorInfo constructorInfo =
type.GetConstructor( new Type[0] ) ;
object obj = constructorInfo.Invoke(new object[0]) ;
// obj est une reference vers une instance de NMFoo.Calc.
}
}

Utilisation de la classe System.Type


La mthode non statique InvokeMember() de la classe System.Type permet de crer une instance dune classe non connue la compilation, la condition quelle soit appele avec la valeur
CreateInstance de lnumration BindingFlags. Par exemple :
Exemple 7-7 :
using System ;
using System.Reflection ;
class Program {
static void Main() {
Assembly assembly = Assembly.Load("Foo.dll") ;
Type type = assembly.GetType("NMFoo.Calc") ;
Object obj = type.InvokeMember(
null, // pas besoin de nom pour invoquer le constructeur
BindingFlags.CreateInstance,
null, // pas besoin de binder
null, // pas besoin dun objet cible car on le cr
ee
new Object[0]) ; // pas de param`
etre
// obj est une reference vers une instance de NMFoo.Calc.
}
}

Cas particuliers
Avec les mthodes prsentes, vous pouvez crer une instance de pratiquement nimporte
quelle classe ou structure. Deux cas particuliers sont noter :

244

Chapitre 7 : Rflexion, liens tardifs, attributs

Pour crer un tableau il faut appeler la mthode statique CreateInstance() de la classe


System.Array.

Pour crer un dlgu il faut appeler la mthode statique CreateDelegate() de la classe


System.Delegate.

Utiliser les liens tardifs avec le framework


Maintenant que nous savons crer des instances de types inconnus la compilation, intressonsnous la cration de liens tardifs vers les membres de ces types afin de pouvoir utiliser ces instances. L aussi, plusieurs faons existent.

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.

Si le membre est trouv, elle cre un lien tardif.

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 :

Les liens tardifs


public object InvokeMember(
string
name,
BindingFlags invokeAttr,
Binder
binder,
object
target,
object[]
args
) ;

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).

Lier une fois, invoquer N fois


De mme que nous avons vu quune instance de la classe ConstructorInfo permet de se lier
tardivement et dinvoquer un constructeur, une instance de la classe System.Reflection.
MethodInfo permet de se lier tardivement et dinvoquer nimporte quelle mthode. Lavantage dutiliser la classe MethodInfo plutt que la mthode Type.InvokeMember() rside dans
lconomie de la recherche du membre chaque invocation, do une lgre optimisation.
Lexemple suivant illustre tout ceci :

246

Chapitre 7 : Rflexion, liens tardifs, attributs

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) ;
}
}

Le compilateur VB.NET cre des liens tardifs


En eet, faisons une petite parenthse VB.NET et observons le fait que ce langage utilise en secret des liens tardifs lorsque loption Strict est positionne Off. Par exemple le programme
VB.NET suivant...
Exemple 7-10 :

VB.NET et les liens tardifs

Option Strict Off


Module Module1
Sub Main()
Dim obj = AppDomain.CurrentDomain.CreateInstanceAndUnwrap(
"Foo.dll", "NMFoo.Calc")
Dim result As Integer = obj.Sum(7, 8)
End Sub
End Module
...est quivalent au programme C  suivant :
Exemple 7-11 :
using System ;
using System.Reflection ;
using Microsoft.VisualBasic.CompilerServices;
class Program {
static void Main() {
object obj = AppDomain.CurrentDomain.CreateInstanceAndUnwrap(
"Foo.dll", "NMFoo.Calc") ;
object[] parametres = new object[2] ;
parametres[0] = 7 ;
parametres[1] = 8 ;

Les liens tardifs

247

int result = (int)LateBinding.LateGet(obj, null, "Sum",


parametres, null, null) ;
}
}
Cette facilit proposer aux dveloppeurs VB.NET peut vite se rvler catastrophique la fois
pour les performances et si le programme est obfusqu. Do lintrt de positionner loption
Strict On.

La bonne faon dutiliser les liens tardifs avec des interfaces


Il existe une autre manire totalement dirente que celles que nous avons expos pour exploiter une classe ou une structure non connue la compilation avec des liens tardifs. Cette faon
prsente lnorme avantage de ne pas dgrader les performances par rapport lutilisation dun
lien prcoce ou dynamique. En revanche, vous devez vous contraindre une certaine discipline
pour appliquer cette recette (qui est en fait un design pattern connu sous le nom de plugin).
Lide est quil faut faire en sorte que le type inconnu la compilation implmente une interface
qui elle, est connue la compilation. Pour cela, nous sommes contraints de crer un troisime
assemblage spcialement pour hberger cette interface. Rcrivons notre exemple avec la classe
Calc au moyen de 3 assemblages :
Exemple 7-12 :

Code de lassemblage contenant linterface (InterfaceAsm.cs)

namespace NMFoo {
public interface ICalc {
int Sum(int a, int b) ;
}
}
Exemple 7-13 :

Code de lassemblage contenant la classe cible (ClassAsm.cs)

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

Chapitre 7 : Rflexion, liens tardifs, attributs


static void Main() {
ICalc obj = AppDomain.CurrentDomain.CreateInstanceAndUnwrap(
"ClassAsm.dll", "NMFoo.CalcAvecInterface") as ICalc ;
int result = obj.Sum(7, 8);
}
}

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

Rfrence la compilation pour pouvoir


utiliser linterface ICalc

Chargement explicite lors de


AppDomain.CurrentDomain.
CreateInstanceFromAndUnWrap()
Assemblage contenant la classe
CalcAvecInterface

Assemblage contenant
linterface ICalc

Rfrence la compilation pour pouvoir


utiliser linterface ICalc

Figure 7 -2 : Organisation des assemblages dans le design pattern plugin


Dans la philosophie du design pattern plugin, les donnes ncessaires la mthode CreateInstanceAndUnwrap() pour crer une instance (en loccurrence les deux chanes de caractres "Foo.dll" et "NMFoo.CalcAvecInterface") sont en gnral stockes dans un fichier de
configuration. Tout lintrt est alors de pouvoir choisir une implmentation simplement en
modifiant un fichier de configuration et sans aucune recompilation.
Une variante du plugin consiste utiliser une classe abstraite plutt quune interface.
Enfin, sachez quil est aussi possible dutiliser un dlgu pour pouvoir se lier tardivement
vers une mthode. Bien que cette technique soit plus performante que lutilisation de la classe
MethodInfo, on lui prfre en gnral le plugin.

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.

Quand a t-on besoin des attributs ?


Tout lintrt dutiliser un attribut rside dans le fait que linformation quil contient est insre
dans lassemblage. Cette information peut alors tre consomme divers moments par divers
consommateurs pour divers desseins :

Un attribut peut tre consomm par le compilateur. Lattribut System.ObsoleteAttribute


que nous venons de voir constitue un bon exemple de consommation dattribut par le compilateur. Certains attributs standard destins ntre consomms que par le compilateur
ne sont pas stocks dans lassemblage. Par exemple, lattribut SerializationAttribute ne
marque pas directement un type mais indique au compilateur que ce type est srialisable.
En consquence, le compilateur positionne certains drapeaux sur le type concern qui seront consomms par le CLR lexcution. De tels attributs sont nomms pseudo-attributs.

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.

Quelques prcisions sur les attributs

Un attribut est ncessairement dfinit par une classe qui drive de la classe System.
Attribute.

250

Chapitre 7 : Rflexion, liens tardifs, attributs

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.

Elments du code source sur lesquels sappliquent les attributs


Les attributs sappliquent des lments de votre code source. Voici exactement quels sont ces
lments. Ils sont dfinis par les valeurs de lnumration AttributeTargets.
Nom des lments

Champ dapplication de lattribut

All

Tous les lments du code source savoir : lassemblage lui-mme,


les classes, les membres des classes, les dlgus, les vnements, les
champs, les interfaces, les mthodes, les modules, les paramtres, les
proprits, les valeurs de retour et les structures.

Assembly

Lassemblage lui-mme.

Class

Les classes.

Constructor

Les constructeurs.

Delegate

Les dlgus.

Enum

Les numrations.

Event

Les vnements.

Field

Les champs.

GenericParameter

Les paramtres gnriques.

Interface

Les interfaces.

Method

Les mthodes.

Module

Les modules.

Les attributs

251

Parameter

Les paramtres des mthodes.

Property

Les proprits des classes.

ReturnValue

Les valeurs de retour des mthodes.

Struct

Les structures.

Quelques attributs standard du Framework .NET


La bonne comprhension dun attribut dcoule de la bonne comprhension de la situation
dans laquelle il faut lutiliser. Aussi, chaque attribut standard est dcrit dans le chapitre qui le
concerne.
Quelques attributs standard relatifs la gestion de la scurit sont prsents page 203.
Quelques attributs standard relatifs au mcanisme P/Invoke sont prsents dans la section page
265.
Quelques attributs standard relatifs lutilisation de COM partir dapplications. NET sont
prsents dans la section page 281.
Quelques attributs standard relatifs au mcanisme de srialisation sont prsents page 790.
Quelques attributs standard relatifs au mcanisme de srialisation XML sont prsents page 780.
Quelques attributs standard relatifs aux assemblages sont prsents page 25.
Quelques attributs standard permettant de personnaliser le comportement dun dbogueur
sont prsents en page 617.
Lattribut System.Runtime.Remoting.Contexts.Synchronization qui permet dimplmenter
un mcanisme de synchronisation est prsent page 160.
Lattribut ConditionalAttribute qui permet dindiquer au compilateur C  sil doit compiler
ou non certaines mthodes est prsent page 314.
Lattribut ThreadStaticAttribute qui sert modifier le comportement des threads vis--vis des
champs statiques est prsent page 176.
Lattribut CLSCompliantAttribute qui sert indiquer au compilateur sil doit faire certaines
vrifications est prsent page 131.
Lattribut ParamArrayAttribute qui sert implmenter le mot cl C  params est prsent page
405.
Lattribut CategoryAttribute est prsent page 685.

Exemple dun attribut personnalis


Un attribut personnalis (custom attribute en anglais) est un attribut que vous crez vous-mme
en faisant driver une de vos classes de la classe System.Attribute. linstar des attributs validateurs de champs dcris au dbut de cette section, on peut imaginer de multiples situations
o lon peut bnficier dattributs personnaliss. Lexemple que nous prsentons ici sinspire de
limplmentation de loutil open-source NUnit.

252

Chapitre 7 : Rflexion, liens tardifs, attributs

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).

La mthode Program.TestAssembly(Assembly) permet dexcuter toutes les mthodes


contenues dans lassemblage pass en rfrence et marques avec lattribut TestAttribute.
Pour simplifier, nous supposerons que ces mthodes sont publiques, non statiques et ne
prennent pas darguments. Nous sommes contraint dutiliser un lien tardif pour invoquer
les mthodes marques.

Le programme suivant satisfait ce cahier des charges.


Exemple 7-15 :
using System ;
using System.Reflection ;
[AttributeUsage(AttributeTargets.Method,AllowMultiple=false)]
public class TestAttribute : System.Attribute {
public TestAttribute() {
Console.WriteLine("TestAttribute.ctor() par d
efaut.") ;
}
public TestAttribute(int nTime) {
Console.WriteLine("TestAttribute.ctor(int).") ;
m_nTime = nTime ;
}
private int m_nTime = 1 ;
private bool m_Ignore = false ;
public bool Ignore { get { return m_Ignore ; }
set { m_Ignore = value ; } }
public int nTime { get { return m_nTime ; }
set { m_nTime = value ; } }
}
class Program{
static void Main(){
// Le programme sanalyse lui-m
eme
TestAssembly(Assembly.GetExecutingAssembly()) ;
}
static void TestAssembly(Assembly assembly) {
// Pour toutes les methodes de tous les types de assembly

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

Chapitre 7 : Rflexion, liens tardifs, attributs


[Test()]
public void NePlantePas() {
Console.WriteLine("NePlantePas()") ;
}
[Test(Ignore = true)]
public void PlanteMaisIgnore() {
Console.WriteLine("PlanteMaisIgnore()") ;
throw new ApplicationException() ;
}
}

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 :

On marque notre classe TestAttribute avec un attribut de type AttributeUsage.


On se sert de la valeur Method de lnumration AttributeTarget pour signifier au compilateur que lattribut TestAttribute ne peut sappliquer quaux mthodes.
On positionne false la proprit AllowMultiple de la classe AttributeUsage pour signifier quune mthode ne peut accepter plusieurs attributs de type TestAttribute. Notez la
syntaxe particulire pour initialiser la proprit AllowMultiple. On dit que AllowMultiple
est un paramtre nomm.
On se sert aussi de la syntaxe du paramtre nomm dans la dclaration de lattribut Test
qui marque la mthode Foo.PlanteMaisIgnore().
On se sert du fait que lorsquune exception est lance et non rattrape lors de lexcution dune mthode invoque au moyen dun lien tardif, cest une exception de type
TargetInvocationException qui est rcupre par lappelant. Lexception initiale est alors
rfrence par la proprit InnerException de lexception rcupre.
Pour viter davoir clater le code sur plusieurs assemblages, ce programme se teste luimme (en fait seules les mthodes de la classe Foo sont teste puisque ce sont les seules
mthodes marques avec lattribut TestAttribute). Voici lorganisation des assemblages
que lon aurait si le code tait clat :
La ressemblance de ce schma avec celui de la Figure 7 -2 nest pas fortuite. Dans les deux
cas, nous exploitons un lment inconnu la compilation par lintermdiaire dun lment
connu de tous la compilation (un attribut dans ce cas, une interface prcdemment).

Construction et utilisation dynamique dun assemblage


Assemblage contenant la
mthode TestAssembly()

Rfrence la compilation pour pouvoir


utiliser la classe TestAttribute

Chargement explicite, par exemple


avec la mthode Assembly.Load()
Assemblage contenant la classe
F(x) tester

255

Assemblage contenant la classe


TestAttribute

Rfrence la compilation pour pouvoir


utiliser la classe TestAttribute

Figure 7 -3 : Organisation des assemblages

Les attributs conditionnels


C  2 introduit la notion dattribut conditionnel. Un attribut conditionnel nest pris en compte par
le compilateur que si un symbole est dfini. Lexemple suivant illustre cette notion dattribut
conditionnel au travers dun projet constitu de trois fichiers :
Exemple 7-16 :
[System.Diagnostics.Conditional("_TEST")]
public class TestAttribute : System.Attribute { }
Exemple 7-17 :
#define _TEST
[Test] // Le compilateur marque la classe Foo1 avec lattribut Test.
class Foo1 { }
Exemple 7-18 :
#undef _TEST
[Test] // Le compilateur ne marque pas la classe Foo2
// avec lattribut Test.
class Foo2 { }
Dans lexemple de la section prcdente, les attributs conditionnels pourraient tre exploits
pour gnrer une version de test et une version destine la production partir du mme code.
En page 314 nous dcrivons une autre utilisation de lattribut Conditional.

Construction et utilisation dynamique dun assemblage


Lespace de noms System.Reflection.Emit prsente un ensemble de classes permettant de fabriquer lexcution dun assemblage, de nouveaux assemblages. Cest en fait de la gnration
de code, mis part que le code nest pas gnr sous la forme de fichiers sources C  ou C++, mais
bel et bien sous la forme dun assemblage prt tre excut. Vous avez ensuite la possibilit
de sauver lassemblage dune manire persistante. Le mot Emit sapparente au fait dmettre
du code.

256

Chapitre 7 : Rflexion, liens tardifs, attributs

Pourquoi construire dynamiquement un assemblage ?


Lorsque vous fabriquez un assemblage avec les classes de lespace de noms System.Reflection.
Emit, le code est mit en langage IL. Ce langage est brivement prsent dans la section page
41. Bien que le langage IL soit fondamental, il est matris par beaucoup moins de dveloppeurs
que le langage C  ou le langage VB.NET. Pour la plupart des dveloppeurs, il est donc beaucoup
plus fastidieux de gnrer du code en langage IL, que de gnrer un fichier source C  qui sera
compil ultrieurement. En fait, il est toujours prfrable de gnrer du code en un langage
structur comme C  ou VB.NET.
Mais alors, pourquoi donc gnrer dynamiquement des assemblages ?
Il existe en fait au moins trois cas de figure, prsents dans les MSDN larticle Reflection Emits
Application Scenarios :

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.

Construction et utilisation dynamique dun assemblage

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.

Solution1 : La mthode globale


Fort de cette connaissance, la solution immdiate qui vient lesprit est :
Exemple 7-19 :
using System ;
using System.Diagnostics;
class Program {
static int Evalue(int x,int[] Coefs) {
int tmp = 0;
int degre = Coefs.GetLength(0);
for(int i=degre-1 ; i>= 0 ; i--)
tmp = Coefs[i]+x*tmp;
return tmp;
}
static void Main() {
Stopwatch sw = Stopwatch.StartNew();
int [] Coefs = {30139,-13735,83,66};
for( int x = -26 ; x<= 19 ; x++ )
for( int i = 0 ; i<10000000 ; i++)
Evalue(x,Coefs);
Console.WriteLine("Duree :" + sw.Elapsed );
}
}
Puisque nous focalisons nos tests sur la performance, il ny a pas lieu ici de vrifier la primarit
des entiers retourns par la mthode Evalue().

Solution 2 : La mthode particulire


Si nous navions pas eu la contrainte de dfinir les coecients du polynme hors de la mthode
Evalue() nous aurions pu crire le programme de cette faon :
Exemple 7-20 :
using System ;
using System.Diagnostics;
class Program {
static int Evalue(int x) {

258

Chapitre 7 : Rflexion, liens tardifs, attributs


return 30139-x*(13735-x*(83+x*66));
}
static void Main() {
Stopwatch sw = Stopwatch.StartNew();
for( int x = -26 ; x<= 19 ; x++ )
for( int i = 0 ; i<10000000 ; i++)
Evalue(x);
Console.WriteLine("Duree :" + sw.Elapsed );
}
}

Comparaison des performances des deux solutions


La dure obtenue sur une machine de rfrence pour la premire solution (la mthode globale)
est de 17,81 secondes. La dure obtenue sur la mme machine pour la seconde solution (la mthode particulire) est de 3,87 secondes soit un gain de 4,6 par rapport la premire mthode.
Beaucoup dinstructions machines, comme le passage par rfrence du tableau, la recherche des
coecients dans le tableau et la gestion de la boucle, ont disparu dans la seconde solution.
Ces rsultats montrent bien que lvaluation dun polynme particulier est beaucoup plus rapide que lutilisation dun algorithme global dvaluation dun polynme.
Deux problmes potentiels pourraient ventuellement fausser ces tests de performance.

Problme potentiel 1 : Le ramasse-miettes pourrait se dclencher dans un des tests et pas


dans lautre. Nous avons vrifi que le ramasse-miettes ne se dclenche pas, tant donne
la faible quantit de mmoire demande par ces programmes.

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.

Une solution performante grce la cration dynamique


dun assemblage
Le problme de la mthode particulire par rapport la mthode globale est que les coecients
dans la mthode Evalue() doivent tre connus la compilation. Le polynme ne peut pas
tre fourni au moment de lexcution, mais seulement au moment de la compilation. Grce
au mcanisme de construction dun assemblage lexcution, nous allons pouvoir utiliser la
mthode particulire, plus rapide, tout en acceptant le polynme lexcution. En eet, il sut
de gnrer le code IL de la mthode Evalue() de la mthode particulire.
Pour le polynme donn en exemple dans la section prcdente, le code IL gnr par le compilateur C  dans la seconde solution est celui-ci :
.method private hidebysig static int32 Calc(int32 x) cil managed
{
// Code size
28 (0x1c)
.maxstack 7
.locals init ([0] int32 CS$00000003$00000000)

Construction et utilisation dynamique dun assemblage


IL_0000:
IL_0005:
IL_0006:
IL_000b:
IL_000c:
IL_000e:
IL_000f:
IL_0011:
IL_0012:
IL_0013:
IL_0014:
IL_0015:
IL_0016:
IL_0017:
IL_0018:
IL_001a:
IL_001b:
} // end of

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 ;

public interface IPolynome {


int Evalue(int x) ;
}
class Polynome {
public IPolynome polynome ;
public Polynome(int[] coefs) {
Assembly asm = BuildCodeInternal(coefs) ;
polynome = (IPolynome)asm.CreateInstance("PolynomeInternal") ;
}
private Assembly BuildCodeInternal(int[] coefs) {
AssemblyName asmName = new AssemblyName() ;

260

Chapitre 7 : Rflexion, liens tardifs, attributs


asmName.Name = "EvalPolAsm" ;
// Construit un assemblage dynamique.
AssemblyBuilder asmBuilder =
Thread.GetDomain().DefineDynamicAssembly(
asmName, AssemblyBuilderAccess.Run) ;
// Construit un module dans lassemblage.
ModuleBuilder modBuilder =
asmBuilder.DefineDynamicModule("MainMod") ;
// Ajoute la classe PolynomeInternal dans le module.
TypeBuilder typeBuilder = modBuilder.DefineType(
"PolynomeInternal", TypeAttributes.Public) ;
typeBuilder.AddInterfaceImplementation(typeof(IPolynome)) ;
// Implemente la methode int Evalue(int)
MethodBuilder methodBuilder = typeBuilder.DefineMethod(
"Evalue",
MethodAttributes.Public | MethodAttributes.Virtual,
typeof(int),
// type retour
new Type[] { typeof(int) }) ; // argument
// Gen`ere le code IL `a partir du tableau des coefficients.
ILGenerator ilGen = methodBuilder.GetILGenerator() ;
int deg = coefs.GetLength(0) ;
for (int i = 0 ; i < deg - 1 ; i++) {
ilGen.Emit(OpCodes.Ldc_I4, coefs[i]) ;
ilGen.Emit(OpCodes.Ldarg, 1) ;
}
ilGen.Emit(OpCodes.Ldc_I4, coefs[deg - 1]) ;
for (int i = 0 ; i < deg - 1 ; i++) {
ilGen.Emit(OpCodes.Mul) ;
ilGen.Emit(OpCodes.Add) ;
}
ilGen.Emit(OpCodes.Ret) ;
// Indique que la methode Evalue() ...
// ... implemente celle de linterface IPolynome.
MethodInfo methodInfo = typeof(IPolynome).GetMethod("Evalue") ;
typeBuilder.DefineMethodOverride(methodBuilder, methodInfo) ;
typeBuilder.CreateType() ;
return asmBuilder ;
}
}
class Program {
static void Main() {
Stopwatch sw = Stopwatch.StartNew();

Construction et utilisation dynamique dun assemblage

261

int[] coefs = { 30139, -13735, 83, 66 } ;


Polynome p = new Polynome(coefs) ;
for (int x = -26 ; x <= 19 ; x++)
for (int i = 0 ; i < 10000000 ; i++)
p.polynome.Evalue(x) ;
Console.WriteLine("Duree :" + sw.Elapsed);
}
}

Comparaison avec les deux mthodes prcdentes


La dure obtenue sur la machine de rfrence pour cette troisime solution est de 4.36 secondes.
Cest 1.12 fois plus lent quavec la deuxime solution, mais la cration de lassemblage ne rentre
pratiquement pas en ligne de compte, puisquelle consomme entre 2 et 4 centimes de seconde.
On peut supposer que cette petite baisse de performance est due lutilisation dune mthode
non statique, qui oblige passer une rfrence vers une instance chaque appel de Evalue().
De plus lappel une mthode dfinie dans une interface est plus coteux que lappel une
mthode non virtuelle et non abstraite. En eet, linstruction IL callvirt est utilise pour lappel une mthode dfinie dans une interface, alors que linstruction IL call est utilise pour
lappel une mthode statique. Or, le compilateur JIT gnre du code machine consommant
moins de cycle horloge pour linstruction IL call que pour linstruction IL callvirt. Nous
verrons un peu plus tard, que cette petite baisse de performance est aussi due au fait que nous
ne tenons pas compte que certains coecients du polynme peuvent tre stocks sur un octet
(en loccurrence, les coecients 83 et 66). Or, le compilateur C  profite de cette situation pour
utiliser des instructions IL spcialement optimises pour manipuler des entiers sur un octet.
En revanche cette troisime solution est 4.1 fois plus rapide que la premire solution, tout en
respectant les mmes contraintes (i.e les coecients du polynmes sont dfinies hors de la mthode Evalue()). Cest un gain norme, rsultant directement de la cration dynamique dun
assemblage. Une optimisation est encore possible si lon tient compte des coecients nuls.

Description technique du code


Les types intressants dans le code prcdent sont :
AssemblyName
AssemblyBuilder
ModuleBuilder
TypeBuilder
MethodBuilder
ILGenerator
MethodInfo
Ils sont gnralement utiliss dans cet ordre, de la mme faon que dans cet exemple. Il existe
de nombreux autres types dtaills dans les MSDN. Ces autres types prennent notamment en
charge la gestion des exceptions, la gestion des branchements, la gestion des autres lments
dune classe (proprits, vnements).

Possibilit de sauver lassemblage sur le disque


Si vous aviez voulu sauver lassemblage sur le disque il aurait fallu :

262

Chapitre 7 : Rflexion, liens tardifs, attributs

Nommer le module (par exemple MainMod.dll).

Utiliser AssemblyBuilderAccess.RunAndSave au lieu de AssemblyBuilderAccess.Run.

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

Code IL produit dynamiquement par notre programme


.method public virtual instance
int32 Evalue(int32 A_1) cil managed
{
.override [DynamicAssembly3]
IPolynome::Evalue
// Code size 30 (0x1e)
.maxstack 7
IL_0000: ldc.i4 0x75bb
IL_0005: ldarg.1
IL_0006: ldc.i4 0xffffca59
IL_000b: ldarg.1
IL_000c: ldc.i4 0x53
IL_0011: ldarg.1
IL_0012: ldc.i4 0x42
IL_0017: mul
IL_0018: add
IL_0019: mul
IL_001a: add
IL_001b: mul
IL_001c: add
IL_001d: ret
} // end of method CPolynomeInternal::Evalue

Les dirences sont dues aux faits suivants :

Nous avons produit dynamiquement une mthode non statique.

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.

Construction et utilisation dynamique dun assemblage

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

Chapitre 8 : Interoprabilit .NET code natif / COM / COM+

Utiliser les mots-cls static et extern devant la dclaration de la mthode.

Garder le mme nom de fonction que celui spcifi dans la DLL.

Donner un nom chaque argument.

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

Impact au niveau des performances


Lors de chaque appel dune fonction dune DLL native, deux tapes sont eectues en interne.
Lensemble de ces tapes est appel P/Invoke Marshalling :

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.

Infrer les dfinitions des fonctions standard


Microsoft ne fournit pas les dfinitions gres de ses fonctions natives. Pour obtenir ces dfinitions, nous vous conseillons de consulter le site www.pinvoke.net.

Tableau de conversion des types


Le prototype de Beep() dans la DLL Kernel32.dll est le suivant :
BOOL Beep(DWORD dwFreq,DWORD dwDuration) ;
Pour appeler Beep(), nous avons d convertir le type BOOL win32 en bool .NET et le type DWORD
win32 en uint .NET. Voici le tableau de conversion des types win32 dans les types .NET.
Equivalent C 

Type win32

Type .NET

LPSTR, LPCSTR, LPWSTR,


LPCWSTR

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

Chapitre 8 : Interoprabilit .NET code natif / COM / COM+

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.

Passage darguments par pointeurs


En page 119 nous expliquons que le ramasse-miettes est amen dplacer les objets de type
rfrence en mmoire tout moment. Si vous passez ladresse dun objet de type rfrence par
lintermdiaire dun pointeur du code non gr, vous vous exposez priori au risque que
le ramasse-miettes bouge cet objet en mmoire durant lappel. Heureusement le mcanisme
P/Invoke est capable de dtecter cette situation. Le cas chant, il pinglera lobjet concern en
mmoire et fournira un pointeur au code natif (la notion dpinglage dobjet en mmoire est
expose en page 505). Lobjet sera automatiquement dfix lors du retour de lappel. Un
autre problme se pose alors. Certaine fonctions natives stockent les pointeurs qui leurs sont
passs de faon les utiliser dune manire asynchrone, aprs lappel. Par exemple la fonction
win32 ReadFileEx() dfinie comme suit...
BOOL ReadFileEx(
HANDLE hFile,
LPVOID lpBuffer,
DWORD nNumberOfBytesToRead,
LPOVERLAPPED lpOverlapped,
LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
) ;
...lit les donnes dun fichier dune manire asynchrone. Pour cela, elle stocke les pointeurs
lpBuffer et lpOverlapped. Si vous invoquez cette fonction partir de votre code gr, il est
absolument ncessaire dpingler explicitement les buers passs durant toute la dure de la
lecture asynchrone et pas seulement durant lappel la fonction.
Il faut donc tre particulirement vigilant car les problmes engendrs par la non fixation dun
objet utilis dune manire asynchrone par du code gr se manifestent rarement et de manire
alatoires.

Passage de chanes de caractres


Pour passer une chane de caractres lors de lappel dune mthode native par lintermdiaire
du mcanisme P/Invoke, il vous sut dutiliser le type System.String. Sachez cependant que
les fonctions natives ayant des paramtres dentrs ou de retours de type chanes de caractres
existent chacune en deux versions : une version ANSI suxe par un A et une version UNICODE suxe par un W. Pour choisir entre ces deux versions, lattribut DllImport prsente
le paramtre nomm CharSet qui peut prendre les valeurs Auto, Unicode ou Ansi. La valeur
Auto est quivalente la valeur Ansi sur les systmes dexploitation Windows 98 et Windows
Me. Lexemple suivant montre comment passer deux chanes de caractres lors de lappel de la
mthode MessageBox() :

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) ;
}
}

Rcuprer une chane de caractre


Nous allons montrer ici comment exploiter une chane de caractres retourne par une fonction
implmente dans une DLL native. Dans du code non gr il y a deux faons de procder :

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

Chapitre 8 : Interoprabilit .NET code natif / COM / COM+

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) ;
}
}

Passage de structures et dunions


Lors de lutilisation dune structure dans un appel P/Invok , il faut tenir compte de la faon dont les champs sont stocks dans les instances de la structure. La plupart du temps les
champs sont stocks de manire squentielle dans lordre de leur dclaration. Cest--dire quils
sont stocks les uns aprs les autres, et le calcul de la position dun champ par le compilateur se fait en sommant la taille des champs dclars avant ce champ. Cependant, dans le cas
dune union, les champs sont stocks au mme emplacement, ils ont tous la mme position. Les
unions nexistant pas dans .NET, il faut utiliser conjointement les attributs System.Runtime.
InteropServices.StructLayout System.Runtime.InteropServices.FieldOffset lors de la dclaration dune structure destine tre utilise dans des appels de fonctions implmentes dans
des DLLs natives.
Par exemple la structure Point peut tre dclare en C  comme ceci :
[StructLayout(LayoutKind.Sequential)]
public struct Point {
public int x ;
public int y ;
}
La structure Point peut aussi tre dclare en C  comme cela :
[StructLayout(LayoutKind.Explicit)]
public struct Point {

Le mcanisme P/Invoke

271

[FieldOffset(0)] public int x ;


[FieldOffset(4)] public int y ;
}
Lunion _Union dclare comme ceci en C :
union _Union {
int
i ;
char c ;
float f ;
}
...peut tre dclare en C  comme cela :
[StructLayout(LayoutKind.Explicit)]
public struct Point {
[FieldOffset(0)] public int i ;
[FieldOffset(0)] public char c ;
[FieldOffset(0)] public float f ;
}
Notez quavec lattribut System.Runtime.InteropServices.MarshalAs, vous pouvez indiquer
au P/Invoke marshaller comment transformer certains types. Par exemple la structure win32 suivante :
typedef struct {
int
wStructSize ;
int
x ;
int
y ;
int
dx ;
int
dy ;
int
wMax ;
TCHAR rgchMember[2];
} HELPWININFO ;
...pourrait tre dclare en C  comme cela :
[StructLayout(LayoutKind.Sequential)]
public struct HELPWININFO {
int
wStructSize ;
int
x ;
int
y ;
int
dx ;
int
dy ;
int
wMax ;
[MarshalAs(UnmanagedType.ByValArry, SizeConst =2)]
public char[] rgchMember;
}

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

Chapitre 8 : Interoprabilit .NET code natif / COM / COM+

ment de la fonction. Cela permet dindiquer au P/Invoke marshaller de ne convertir largument


qu lentre ou qu la sortie de la fonction. Par dfaut tous les arguments sont convertis
lentre et la sortie.

Dlgus et pointeur non gr de fonction


Vous pouvez invoquer une fonction dfinie dans une DLL native par lintermdiaire dun dlgu fabriqu partir dun pointeur non gr de fonction. En eet, grce aux mthodes statiques
GetDelegateForFunctionPointer() et GetFunctionPointerForDelegate() de la classe Marshal
les notions de dlgus et de pointeurs sur fonction sont interchangeables :
Exemple 8-6 :
using System ;
using System.Runtime.InteropServices ;
class Program {
internal delegate bool DelegBeep(uint iFreq, uint iDuration) ;
[DllImport("kernel32.dll")]
internal static extern IntPtr LoadLibrary(String dllname) ;
[DllImport("kernel32.dll")]
internal static extern IntPtr GetProcAddress(IntPtr hModule,
String procName) ;
static void Main() {
IntPtr kernel32 = LoadLibrary( "Kernel32.dll" );
IntPtr procBeep = GetProcAddress( kernel32, "Beep" );
DelegBeep delegBeep = Marshal.GetDelegateForFunctionPointer(
procBeep , typeof( DelegBeep ) ) as DelegBeep;
delegBeep(100,100);
}
}

Introduction linteroprabilit avec C++/CLI


En plus dexposer des facilits pour exploiter le mcanisme P/Invoke dune manire transparente, le langage C++/CLI prsente la particularit unique de pouvoir fabriquer des assemblages
contenant la fois du code natif et du code gr. Il est alors opportun de raliser une petite
incursion dans ce langage afin de vous donner les cls pour dcider si vos besoins dinteroprabilit en justifient lutilisation.

Le mcanisme It Just Works (IJW)


La finalit du mcanisme IJW (acronyme pour It Just Works qui pourrait se traduire par a marche
tout simplement !) du langage C++/CLI est la mme que celle du mcanisme P/Invoke prsent
dans la section prcdente : permettre dappeler des fonctions natives partir du code gr. En
apparence, ces deux mcanismes sont dirents puisque IJW nutilise pas dattributs. Du point
de vue du CLR en revanche, les mcanismes IJW et P/Invoke sont une seule et mme possibilit.
Le code IL gnr pour un appel avec le mcanisme P/Invoke est donc quivalent au code IL
gnr avec le mcanisme IJW. Pour sen convaincre, rcrivons en C++/CLI lExemple 8-1 :

Introduction linteroprabilit avec C++/CLI

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.

Types grs et types non grs


Dans la version 2 du langage C++/CLI, un type marqu avec un des mots-cls ref, value,
interface ou enum est un type gr tandis quun type non marqu par un de ces mots-cls
est natif. En outre, une mthode dfinie aprs le #pragma managed sera compile en langage
IL tandis quune mthode dfinie aprs le #pragma unmanaged sera compile en code natif. Le
langage C++/CLI permet :

274

Chapitre 8 : Interoprabilit .NET code natif / COM / COM+

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 ;
}

Introduction linteroprabilit avec C++/CLI

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.

Utiliser des objets grs partir du code natif


Le problme fondamental lors de lutilisation dun objet gr par du code natif est que vous ne
pouvez pas utiliser de pointeur vers lobjet gr moins de le fixer en mmoire. Cependant, le
ramasse-miettes perd beaucoup en ecacit lorsquil y a des objets fixs. Il a donc fallu trouver
une autre notion que celle de pointeur pour rfrencer les objets grs partir du code natif.
Le ramasse-miettes implmente un systme de pointeurs logiques, aussi nomms handles. Un
handle est un numro sur quatre octets. Le ramasse-miettes fait en sorte que chaque objet gr
ait son propre handle. Le code natif peut rfrencer un objet gr partir de son handle.
Un handle vers une instance dun type gr T est reprsente en C++/CLI avec lexpression T.
Le framework .NET prsente la structure gre System.Runtime.InteropServices.GCHandle qui
permet de faire la passerelle entre les objets grs et leurs handles. Cependant, le code natif na
pas directement accs aux types grs. Vous navez donc pas la possibilit dutiliser la structure
gre GCHandle() ni la syntaxe T partir du code natif.
Pour rfrencer un objet gr de type T partir du code natif, vous devez utiliser une instance de
gcroot<T>. gcroot<T> est une structure native qui utilise le mcanisme de template de C++/CLI
et dont les mthodes sont en IL. Il est instructif de regarder la dfinition de gcroot<T> dans le
fichier gcroot.h. Vous y verrez :

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

Chapitre 8 : Interoprabilit .NET code natif / COM / COM+


Le destructeur de la structure gcroot<T> libre le handle. Lorsque le code natif appelle
ce destructeur il signifie donc au ramasse-miettes que lobjet gr sous-jacent pourra tre
dtruit si il ny a plus dautres rfrences vers celui-ci.

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) ;
}

.NET et les Handles win32

277

int m_Length ;
} ;
#pragma managed
int main() {
TypeNatifCodeNatif* obj = new TypeNatifCodeNatif("Bonjour") ;
Console::WriteLine( obj->m_Length ) ;
delete obj ;
}

.NET et les Handles win32


Les handles du ramasse-miettes prsents dans la section prcdente sont conceptuellement proches des handles win32 que nous allons voir. Cependant, ces deux sortes de
handles appartiennent des domaines compltement disjoints.

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

Chapitre 8 : Interoprabilit .NET code natif / COM / COM+

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.

Les classes SafeHandle et CriticalHandle


Avant la version 2.0 de .NET, un handle tait rfrenc par une instance du type IntPtr. Cela
posait plusieurs problmes :

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.

Utilisation de COM partir de .NET


Avec le temps, la technologie COM est devenue de plus en plus complique utiliser et comprendre, tel point que certaines entreprises se sont spcialises dans le dveloppement de composants COM. Au dbut du projet .NET, Microsoft avait le souhait de construire une nouvelle
technologie en saranchissant des contraintes de compatibilit ascendante avec les technologies existantes. Cependant, Microsoft ayant prconis lutilisation de la technologie COM depuis
plusieurs annes, il existe maintenant des millions de composants COM travers le monde.
Il ntait pas envisageable dobliger les entreprises re-dvelopper tous leurs composants sous
.NET, et il tait obligatoire de ne pas construire .NET au-dessus de COM. La seule solution
tait de construire un ensemble de classes et doutils qui permettraient dutiliser les composants
COM sans les recompiler, partir du code dvelopp sous .NET. Cest ce que nous allons exposer.

Utilisation de COM partir de .NET

279

Mtadonnes de types et bibliothque de types


Bien que rpondant au mme besoin, la cration de liens prcoces avec les classes COM et la
cration de liens prcoces avec les classes .NET sont techniquement parlant, trs dirents. La
dirence principale est que le code gr .NET a besoin de consulter les mtadonnes de
types avant de crer un lien avec une mthode dune classe .NET (que le lien soit prcoce
ou tardif). Cela vient du fait que le code IL utilise la notion de jeton de mtadonnes la place
de la notion dadresse. Voici quelques autres dirences :

Les classes COM ne se manipulent quau travers dinterfaces COM.

Le passage des arguments ne se fait pas de la mme manire.

Les classes COM nadmettent pas de constructeurs avec paramtres.

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.

Assemblages inter-oprables et classes Runtime Callable Wrapper


Microsoft a cr loutil type library importer (tlbimp.exe) pour construire un assemblage .NET
directement partir dun composant COM dont la bibliothque de types est disponible. Un
assemblage cr partir de tlbimp.exe est qualifi dassemblage interoprable (interop assembly
en anglais). Nous verrons comment utiliser partir de .NET des classes COM dun composant
COM dont la bibliothque de types nest pas accessible, dans la prochaine section. la fin de la
prsente section, nous verrons que lutilisation de Visual Studio peut viter davoir manipuler
loutil tlbimp.exe.
Voici un fichier IDL (Interface Definition Langage) qui dcrit un composant COM nomm
COMComposant. Il est clair que ce composant COM admet une bibliothque de types qui
contient la classe COM CClassCOM. Cette classe admet une interface propritaire qui sappelle
IClasseCOM. Cette interface prsente la fonction CalcSomme() qui calcule la somme de ses deux
premiers paramtres et la retourne dans le troisime paramtre, qui est un paramtre de sortie.
Exemple :

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

Chapitre 8 : Interoprabilit .NET code natif / COM / COM+


[ version(1.0),
uuid(8A4F713C-910F-4EE0-8A26-B870FBE58596),
custom(a817e7a1-43fa-11d0-9e44-00aa00b6770a,
"{23391CA9-529E-43CF-9BF6-C636BF96BF26}"),
helpstring("COMComposant 1.0 Type Library") ]
library COMComposant {
importlib("stdole2.tlb") ;
importlib("olepro32.dll") ;
[
version(1.0),
uuid(1E3B6413-7E63-42B5-874D-E0A27A42190C),
helpstring("CClasseCOM Class")
]
coclass CClasseCOM{
interface IClasseCOM;
};
}

Voici la ligne de commande utiliser pour obtenir un assemblage interoprable partir de ce


composant COM. tlbimp.exe nous ore la possibilit dajouter un espace de noms avec la directive /namespace. Cet outil prsente de nombreuses autres directives dcrites dans les MSDN
larticle Type Library Importer (tlbimp.exe).
>tlbimp.exe COMComposant.dll /out:AsmCOMComposant.dll
/namespace:TestInterop
Si on analyse lassemblage AsmCOMComposant.dll avec ildasm.exe, on saperoit quil contient
entre autres une classe nomme CClasseCOMClass et une interface nomme IClassCOM. Linterface IClassCOM prsente la mthode CalcSomme() et la classe CClasseCOMClass implmente
linterface IClassCOM.
Limplmentation de la mthode CalcSomme() dans la classe CClasseCOMClass ne contient
que le code pour transformer lappel dune mthode dune type .NET, en un appel vers la
mthode COM IClasseCOM.CalcSomme(). Dans le jargon .NET on dit que la classe .NET
CClasseCOMClass est une classe Runtime Callable Wrapper (RCW). En terminologie design patterns
(Gof), on dit dune telle classe quelle est un adaptateur, car elle adapte linterface de lobjet COM
une interface que le client peut appeler. On peut aussi dire que CClasseCOMClass est une classe
proxy, dans le sens ou tous les appels sont dabord intercepts, pour eectuer un traitement sur
les arguments. Aprs interception, les appels sont eectus directement sur la classe COM sousjacente.
Voici le code dun programme C  qui utilise lassemblage interoprable AsmCOMComposant.dll.
Naturellement il faut qu la compilation de ce programme, on prcise une rfrence vers
AsmCOMComposant.dll :
Exemple 8-11 :
using TestInterop ;
class Program {
static void Main() {
IClasseCOM foo = new CClasseCOMClass() ;
int result = foo.CalcSomme(2, 3) ;

DotNETClient.cs

Utilisation de COM partir de .NET


Processus

281
Assemblage inter-oprable

CLR
IInterface1
Client
.NET

IInterface2

IInterface1
RCW

IInterface2

Objet
COM

IUnknown

Figure 8 -1 : Utilisation dune classe COM partir dun client .NET


System.Console.WriteLine("Resultat :{0}", result) ;
}
}
Notez qu la dirence dune classe COM, une classe RCW peut tre directement appele, sans
passer par aucune interface. On aurait pu crire :
...
CClasseCOMClass foo = new CClasseCOMClass() ;
int Result = foo.CalcSomme(2,3) ;
...
Si le classe COM supporte plusieurs interfaces propritaires, tlbimp.exe gnrera autant dinterfaces .NET. En outre, toutes les mthodes de ces interfaces seront dclares et accessibles publiquement dans la classe RCW.
Lutilisation de Visual Studio peut viter davoir manipuler loutil tlbimp.exe. Il sut dajouter
une rfrence au projet directement vers le composant COM, exactement comme si vous rajoutiez une rfrence vers un autre assemblage (voir la Figure 8 -2). Lassemblage interoprable
contenant les classes RCW sera automatiquement construit et plac dans le mme rpertoire
que lassemblage concern.
Les assemblages interoprables et les composants COM dune application .NET font bien videmment partie des fichiers dployer avec votre application .NET.

Accder aux classes dun composant COM sans utiliser


de bibliothque de types
Le framework .NET prsente la possibilit de crer ses propres classes et interfaces RCW pour accder aux instances dune classe COM sans utiliser la bibliothque de types et loutil tlbimp.exe.
Cette fonctionnalit est essentiellement utilise lorsquun composant COM na pas de bibliothque de types, ou lorsquun composant COM a une bibliothque de types non disponible au
moment du dveloppement.
Cette fonctionnalit est assez fastidieuse utiliser. Il faut redfinir entirement chaque interface
COM et chaque classe COM que vous utilisez. Pour chaque argument et chaque valeur de retour
de chaque mthode il faut utiliser lattribut System.Runtime.InteropServices.MarshalAs.
Pour le composant COM de la page 279, il faudrait crire :

282

Chapitre 8 : Interoprabilit .NET code natif / COM / COM+

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) ;
}
}

Utilisation de COM partir de .NET

283

Import dun ActiveX avec Visual Studio .NET


Un ActiveX est le terme employ pour dcrire une classe COM graphique. On peut aussi utiliser
le terme contrle ActiveX ou le terme OCX. Les contrles ActiveX sont stocks dans des composants COM dextension .dll ou dextension .ocx. Il existe un trs grand nombre de contrles ActiveX disponibles gratuitement ou non sur internet. Il existe des OCX pour visionner des images,
des animations, des OCX pour parcourir les rpertoires, pour diter des numros de tlphones
etc. De plus chaque technologie graphique (Flash de Macromedia, MapPoint de Microsoft etc) est
en gnral fournie avec un contrle ActiveX permettant de lutiliser. Comme vous le voyez il
existe un trs vaste choix de contrles ActiveX. La bonne nouvelle est que Visual Studio permet
de les utiliser dans vos programmes .NET aussi simplement que sils taient des contrles graphiques .NET. Dans le chapitre consacr la technologie Windows Form nous exposons comment utiliser des contrles graphiques .NET mais intressons-nous ici limport de contrles
ActiveX dans vos formulaires .NET grce Visual Studio.
Il est prfrable de prparer le terrain, et dinsrer un onglet spcial pour nos imports de
contrles ActiveX dans la bote outils (toolbox) des composants graphiques de Visual Studio.
Pour cela cliquez droit sur cette boite outils et choisissez le menu Ajouter un onglet (Add
Tab) et donnez un nom votre nouvel onglet (par exemple ActiveX ). Votre bote outils
doit ressembler maintenant celle de la Figure 8-3.

Figure 8 -3 : Prparation de la bote outils pour importer des OCX


Vous pouvez maintenant ajouter un contrle ActiveX en cliquant droit sur longlet ActiveX
et en choisissant le menu Personnaliser la bote outils (Customize Toolbox...). Vous arrivez sur
la fentre de la Figure 8-4 qui vous permet dajouter soit un contrle ActiveX soit un contrle
graphique .NET dans votre onglet.
Ici nous avons choisi le contrle ActiveX Microsoft Rich Textbox Control qui permet dacher un
document au format RTF. Lorsque nous ajoutons un tel contrle dans une WinForm, nous avons
accs aux fentres de proprits de ce contrle. En interne, un assemblage interoprable a t
cr, contenant les classes RCW adquates.

Spcificits de COM prendre en compte


en manipulant une classe RCW
Gestion du cycle de vie dun objet COM partir de .NET
Le cycle de vie dun objet COM suit des rgles trs prcises qui sont compltement encapsules dans les classes RCW. Notamment les appels la fonction CoCreateInstance(), qui permet de crer un objet COM, sont encapsuls. Les appels aux mthodes de linterface IUnknow
(QueryInterface() qui permet de naviguer entre les interfaces, AddRef() et Release() qui permettent de grer le compteur interne de rfrences) sont aussi encapsuls.

284

Chapitre 8 : Interoprabilit .NET code natif / COM / COM+

Figure 8 -4 : Ajout dun OCX dans la bote outils


Une classe RCW est une classe gre. Vous ne pouvez dcider exactement quand elle sera dtruite, puisque cette responsabilit incombe au ramasse-miettes. Or, une classe RCW ne dtruit
son objet COM sous-jacent que lorsquelle est elle-mme dtruite. A priori, vous ne pouvez donc
pas dcider du moment de la destruction dun objet COM dans vos programmes .NET. Cette
situation est gnante car en gnral, larchitecture dune classe COM prend en compte le fait
que le client peut choisir le moment de la destruction des instances de cette classe COM.
La classe System.Marshal.InteropServices.Marshal prsente la mthode int ReleaseComObject(object) pour pallier ce problme. Cette mthode prend en paramtre un object qui doit
tre lobjet RCW concern. En interne, un appel cette mthode provoque lappel la mthode
Release() de lobjet COM et ne fait donc que dcrmenter dune unit le compteur interne
de rfrence de lobjet COM. La nouvelle valeur de ce compteur interne est retourne, et vous
pouvez donc tester si lobjet COM est eectivement dtruit en vrifiant que cette valeur vaut
bien 0. Une fois quun objet COM est dtruit, nous vous conseillons de mettre immdiatement
nul les rfrences vers lobjet RCW concern, car toute utilisation de cet objet provoque lenvoi
de lexception InvalidComObjectException. Par exemple :
...
IClasseCOM foo = new CClasseCOMClass() ;
int result = foo.CalcSomme(2,3) ;
System.Runtime.InteropServices.Marshal.ReleaseComObject(foo);
foo = null;
...

Gestion des types de donnes COM partir de .NET


La conversion entre les types de donnes COM et les types de donnes .NET ne pose pas de
problme. Il est nanmoins utile de prciser les conversions suivantes :

Les classes RCW convertissent les Basic String (BSTR) de COM en des instances de la classe
System.String.

Utilisation de COM partir de .NET

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.

Gestion des erreurs COM partir de .NET


La gestion des erreurs est radicalement dirente entre .NET et COM :

.NET gre les erreurs au moyen dexceptions, gres par le CLR.

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.

Gestion des apartments COM partir de .NET


COM permet de grer les anits entre les objets COM et les threads au moyen dapartments
COM. Concrtement, vous pouvez vous assurer que les mthodes des instances dune classe
COM ne peuvent tre excutes que par certains threads. Un processus peut contenir plusieurs
apartments COM. Chaque objet COM est contenu dans un processus et appartient un apartment COM. Chaque thread du processus appartient un apartment COM. Si un thread dun
apartment COM doit eectuer lappel dune mthode dun objet COM qui nest pas dans le
mme apartment COM (voire dans un autre processus), linterface COM concerne doit tre
marshalle de faon pouvoir tre utilise par le thread client. Cette opration de marshalling est ncessaire car le code des mthodes de la classe dun objet COM est toujours excut
par le ou les threads de lapartment COM de lobjet.
Il existe deux sortes dapartment COM les Single Thread Apartment (STA) et les Multi Thread Apartment (MTA) qui contiennent respectivement un thread ou plusieurs threads. Un processus peut
contenir plusieurs STA mais ne peut contenir plus dun MTA. Durant lvolution de la technologie COM, la notion de STA est apparue avant la notion de MTA. Le but de STA tait de dcharger
les dveloppeurs du souci des accs concurrents aux ressources dans une classe COM. En eet,
dans le mode STA, cest le mme thread qui gre les dirents appels aux direntes instances
dune mme classe COM. La gestion STA sest rvle insusante pour certaines applications
et il a fallu introduire la notion de MTA, qui a redonne au dveloppeur la responsabilit de
grer les accs concurrents aux ressources.
Les threads grs de .NET connaissent cette notion dapartment COM. Plus exactement, un
thread gr sait si son thread Windows sous-jacent est STA ou MTA. Vous pouvez positionner
la proprit ApartmentState dune instance de la classe Thread avec une des valeurs de lnumration System.Threading.ApartmentState, savoir STA, MTA ou Unknown (qui est la valeur
prise par dfaut). Cette proprit ne peut tre modifie quune seule fois dans lexistence dun
thread gr.

286

Chapitre 8 : Interoprabilit .NET code natif / COM / COM+

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(){
}
...

Lien tardif explicite sur les classes COM


Bien avant la venue de .NET et de son mcanisme de rflexion, la technique de lien tardif explicite tait disponible sur les classes COM dont les interfaces tendaient linterface IDispatch.
IDispatch prsente notamment la mthode GetDispID() qui permet davoir la position de la
dclaration dune mthode dans une interface, en fonction du nom de la mthode. Cette position sappelle DISPID. IDispatch prsente aussi la mthode Invoke() qui permet dappeler une
mthode en connaissant son DISPID. Lutilisation de ces deux mthodes permet respectivement
de crer des liens tardifs explicites avec des classes COM et dinvoquer la mthode avec un tel
lien.
Vous pouvez exploiter le mcanisme de liens tardifs explicites IDisptach de COM, partir du
mcanisme de liens tardifs explicites de .NET. Pour utiliser cette possibilit avec la classe du
composant COM de la page 279 il faudrait crire :
Exemple 8-13 :
using System ;
using System.Reflection ;
class Program {
static void Main() {
Type type = Type.GetTypeFromProgID("COMComposant1.CClasseCOM");
// On peut aussi ecrire :
// Type type = Type.GetTypeFromCLSID(
//
new System.Guid("1E3B6413-7E63-42B5-874D-E0A27A42190C")) ;
Object obj = Activator.CreateInstance(type) ;
Object[] args = new Object[] { 3, 4 } ;
int retVal = (int) type.InvokeMember(
"CalcSomme",
BindingFlags.InvokeMethod,
null,
obj,
args) ;
Console.WriteLine(retVal) ;
}
}
Notez lobtention du type partir de son PROGID ou de son CLSID, grce lune des mthodes
statiques de la classe Type, GetTypeFromProgID() ou GetTypeFromCLSID().

Utilisation de COM partir de .NET

287

Utiliser une classes COM sans lenregistrer dans le registre


Windows XP ainsi que les versions postrieures prsentent la possibilit dutiliser une classe COM
sans lenregistrer dans la base de registres. Lide est de fournir un fichier qui contient lassociation entre les CLSIDs et les DLLs COM des classes COM utilises pour chaque application. Lors
du lancement de lapplication, Windows XP charge les donnes de cette table. Durant lexcution, lorsquune classe COM doit tre charge, Windows XP consulte au pralable cette table. Si
la classe COM nest pas trouve par cette technique, le processus classique de localisation du
composant COM contenant la classe par consultation de la base des registre est alors enclench.
Vous pouvez exploiter cette technique nomme registration free COM (ou reg free COM) partir
dun assemblage .NET. Pour cela, il sut de fournir un fichier ayant le mme nom que lassemblage avec lextension .manifest contenant les donnes dassociation entre les CLSIDs et les
DLLs COM au format XML comme ceci (MyAsm.exe est le nom de lassemblage en loccurrence,
donc le nom du fichier est MyAsm.exe.manifest) :
Exemple :

MyAsm.exe.manifest

<?xml version="1.0" encoding="utf-8"?>


<assembly xsi:schemaLocation="urn:schemas-microsoft-com:asm.v1
assembly.adaptive.xsd"
manifestVersion="1.0"
xmlns:asmv2="urn:schemas-microsoft-com:asm.v2"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ... >
<assemblyIdentity name="MyAsm.exe" version="1.0.0.0" type="win32" />
<file name="[Chemin relatif ou absolu]/MyCOMComponent.dll"
asmv2:size="20480">
<typelib tlbid="{ea995c49-e5d0-4f1f-8489-31239fc9d9d0}"
version="2.0" helpdir=""
resourceid="0" flags="HASDISKIMAGE" />
<comClass clsid="{97b5534f-3b96-40a4-88b8-19a3bf4eeb2e}"
threadingModel="Apartment"
tlbid="{ea995c49-e5d0-4f1f-8489-31239fc9d9d0}"
progid="MyCOMComponent.MyClass" />
<comClass ... />
</file>
<file ... />
...

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

Chapitre 8 : Interoprabilit .NET code natif / COM / COM+

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 :

Localiser la DLL composant COM.

La charger en mmoire avec la fonction win32 LoadLibrary().

Localiser la fonction DllGetClassObject() exporte par la DLL composant COM. Pour


cela, il faut avoir recours la fonction win32 GetProcAddress().

Appeler cette fonction DllGetClassObject() pour obtenir un objet COM implmentant


linterface COM IClassFactory.

Appeler la mthode IClassFactory.CreateInstance() pour crer un objet COM.

Encapsuler une classe .NET dans une classe COM


Notion de CCW
Dans un processus de migration dune application vers .NET, il peut arriver que certains composants soient migrs avant dautres. Il se peut que du code non gr doive instancier et utiliser une
classe .NET. cette fin, le framework .NET nous permet dencapsuler une classe .NET dans un
COM Callable Wrapper (CCW). Un CCW est un objet COM cr et gr automatiquement par le
CLR. Un tel objet COM encapsule un objet .NET, pour faire en sorte que ce dernier puissent tre
accessible comme un objet COM. Un CCW est cr durant lexcution par le CLR, partir des
mtadonnes de la classe .NET (et non partir dune quelconque bibliothque de types COM).
Il existe au plus un CCW par objet .NET, quel que soit le nombre de clients souhaitant lutiliser
comme un objet COM. Un objet .NET ayant un CCW peut aussi tre utilis par des clients .NET.
Dans ce cas, le fait que lobjet .NET ait un CCW est compltement transparent pour les clients
.NET. La Figure 8 -5 rsume linteraction entre le client non gr et lobjet .NET au moyen dun
CCW.
Processus
IInterface
Code
client
dobjets
COM

CLR

IInterface

IDispatch

Objet
.NET

CCW
IUnknown
Client .NET

Figure 8 -5 : Utilisation dun objet .NET par lintermdiaire dun CCW


Il est important de noter que le modle objet propos par COM est assez restrictif par rapport
au modle objet propos par .NET. Pour tre utilise comme une classe COM normale par
lintermdiaire dun CCW, une classe .NET doit satisfaire les contraintes suivantes :

Encapsuler une classe .NET dans une classe COM

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.

Produire une bibliothque de types COM dcrivant les classes CCW


partir dun assemblage .NET
Loutil Type Library Exporter tlbexp.exe permet de fabriquer une bibliothque de types COM
dcrivant les classes et interfaces .NET publiques contenues dans un assemblage. Montrons comment utiliser tlbexp.exe au moyen dun exemple simple. Voici du code C  :
Exemple 8-14 :

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

Chapitre 8 : Interoprabilit .NET code natif / COM / COM+


[
uuid(824A9F5F-39AE-35BB-8741-7C4C8F7DB26C),
version(1.0),
custom(90883F05-3D28-11D2-8F17-00A0C9A6186D,
dotNET2COM,
Version=0.0.0.0,
Culture=neutral,
PublicKeyToken=null)
]
library dotNET2COM{
importlib("mscorlib.tlb") ;
importlib("stdole2.tlb") ;
// Forward declare all types defined in this typelib
interface ICalc ;
interface _CCalc ;
[
odl,
uuid(72E3095D-B243-37F0-A95A-41ABBE13E029),
version(1.0),
dual,
oleautomation,
custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9, Test.ICalc)
]
interface ICalc : IDispatch {
[id(0x60020000)]
HRESULT CalcSomme(
[in] long a,
[in] long b,
[out, retval] long* pRetVal);
};
[
uuid(A51C81A3-4892-39EC-981A-AF77FB4CFD36),
version(1.0),
custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9, Test.CCalc)
]
coclass CCalc
[default]
interface
interface
};

{
interface _CCalc;
_Object;
ICalc;

[
odl,
uuid(84181003-CCB9-3219-B373-629AF4E3B246),
hidden,
dual,

Encapsuler une classe .NET dans une classe COM

291

oleautomation,
custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9, Test.CCalc)
]
interface _CCalc : IDispatch {
} ;
} ;
Plusieurs remarques peuvent tre faites :

La classe COM CCalc implmente les interfaces COM suivantes :

ICalc : Cette interface COM reprsente linterface .NET ICalc.

_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

Chapitre 8 : Interoprabilit .NET code natif / COM / COM+

Une interface ID a t produite automatiquement pour linterface COM ICalc. L aussi,


on aurait pu spcifier notre propre interface ID avec lattribut Guid.

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.

Enregistrer les CCW sur un systme dexploitation Microsoft


Nous avons vu dans la section prcdente comment fabriquer une bibliothque de types COM
partir dun assemblage .NET. Nous allons voir maintenant comment enregistrer cette bibliothque de types dans la base des registres dun systme dexploitation Microsoft. Rappelons que
pour tre utilisable, une classe COM doit imprativement tre enregistre dans la base des registres. Cest principalement linformation dassociation entre le classe ID de la classe COM et
le fichier (DLL ou excutable) contenant eectivement limplmentation de la classe, qui est
sauvegarde dans la base des registres. En eet, une application qui instancie une classe COM
ne connat la classe COM que par son classe ID et na aucune information quant au fichier qui
contient limplmentation de la classe. Il faut donc aller consulter la base des registres pour localiser limplmentation dune classe COM, au moins la premire fois que celle-ci est instancie
dans une application.
Pour enregistrer (ou ds-enregistrer ) les classes COM dun composant COM classique, il suffit dutiliser loutil regsvr32.exe. Pour enregistrer (ou dsenregistrer ) les classes .NET dun
assemblage comme des classes COM, il faut utiliser loutil Assembly Registration Tool regasm.exe
spcialement conu pour cette tche. Par exemple :
>regasm.exe dotNET2COM.dll
Cet outil est paramtrable, avec plusieurs options dcrites dans les MSDN larticle Assembly Registration Tool (Regasm.exe). Loption /regfile permet dindiquer regasm.exe quil
faut produire le fichier de mise jour de la base des registres, dextension .reg. Lutilisation de
loption /regfile entrane que la base des registres nest pas modifie. Lexcution dun fichier
.reg met jour la base des registres. Dans un fichier .reg, les informations de mise jour sont
encodes dans un format texte. Il est ainsi ais de les consulter et de les modifier. Par exemple :
>regasm.exe dotNET2COM.dll /regfile:dotNET2COM.reg
Voici le fichier produit :

Encapsuler une classe .NET dans une classe COM


Exemple :

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

Chapitre 8 : Interoprabilit .NET code natif / COM / COM+

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.

Utiliser un assemblage .NET comme un composant COM


Nous prsentons ici un exemple de code C++ non gr qui utilise la classe .NET CCalc comme
une classe COM. Grce limport de la bibliothque de types dotNET2COM.tlb, produite partir
de lassemblage dotNET2COM.dll avec tlbexp.exe ou regasm.exe, le code C++ peut eectuer un
lien prcoce avec la classe COM CCalc. Voici le code :
Exemple 8-15 :
#include "stdafx.h"
#import "dotNET2COM.tlb"
int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
int nRetCode = 0 ;
// Initialise le thread courant pour quil puisse utiliser COM.
HRESULT hr = ::CoInitialize(NULL);
dotNET2COM::ICalcPtr pI;
// Cree lobjet COM qui contient notre objet .NET.
hr = pI.CreateInstance( __uuidof(dotNET2COM::CCalc));
// Appel dune methode (grace au lien pr
ecoce produit par limport
// de la biblioth`eque de type).
int result = pI->CalcSomme(2,3);
// Lib`ere lobjet COM.
pI = NULL;
// Desinitialise le thread courant pour quil ne puisse
// plus utiliser COM.
::CoUninitialize();
return nRetCode ;
}
Comme lassemblage dotNET2COM.dll que nous avons produit ne peut pas tre plac dans le
GAC (car il na pas de nom fort puisquil nest pas sign), il faut obligatoirement quil soit dans le
rpertoire de lapplication. Noubliez pas denregistrer la classe CCalc avec regasm.exe (comme
dcrit dans la section prcdente) pour excuter cet exemple.

Exception .NET et CCW


Supposons quune lexception soit lance partir du code gr dun objet .NET utilis par du
code natif comme un objet COM. Dans ce cas, le CLR a la responsabilit de grer la transition
entre le code gr et le code non gr. Pour accomplir cette tche, il convertit au mieux les
informations de lexception en un code derreur dans un HRESULT. Le CLR utilise des rgles

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.

Grer le cycle de vie


Nous prcisons ici une faiblesse du mcanisme dutilisation dune classe .NET comme une classe
COM. Lorsquun tel objet nest plus utilis, cest--dire que lorsque le compteur interne de rfrences vers un objet COM CCW arrive 0, lobjet COM CCW est dtruit mais pas lobjet .NET
sous-jacent. Cet objet .NET sera dtruit par le ramasse-miettes ultrieurement, un moment
indtermin.
Contrairement au mcanisme dutilisation dun objet COM partir de .NET, il nexiste pas de
mcanisme spcial pour forcer la destruction dun objet .NET encapsul dans un objet COM
CCW. Ce comportement peut ne pas tre souhaitable, car lobjet .NET peut possder de prcieuses ressources quil faudrait librer ds quil nest plus utilis.
Un problme encore plus gnant peut survenir si lobjet .NET contient des rfrences vers des
objets COM, et que le dveloppeur du code non gr suppose quil ny a plus dobjet COM dans
le processus. Aprs la destruction de lobjet COM CCW le dveloppeur du code non gr peut
appeler la fonction CoUninitialize() pour indiquer quil na plus besoin de la bibliothque
COM dans le thread courant. Cependant, si ce thread est amen ultrieurement dtruire les
objets COM possds par lobjet .NET, il aura un comportement indtermin qui provoquera
srement un crash.
Pour viter les problmes de gaspillage de ressources et ce scnario catastrophe, il est conseill
de prvoir une mthode dans la classe .NET, spcialement prvue pour librer les ressources
dtenues par lobjet .NET. Cette mthode devra tre explicitement appele par le code non gr.

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

Chapitre 8 : Interoprabilit .NET code natif / COM / COM+

COM+ via .NET : les services dentreprises


Il tait ncessaire que le framework .NET ait une technologie de serveur dapplications. Pendant les dernires annes, Microsoft a mobilis beaucoup de ressources pour le dveloppement
du framework .NET. Paralllement, la technologie COM+ orait satisfaction, et beaucoup de
ressources avaient dj ts utilises pour mettre au point cette technologie. Microsoft a donc
prfr capitaliser sur COM+ plutt que de re-dvelopper une nouvelle technologie de serveurs
dapplication.
Chaque service dentreprise COM+ est exploitable via une classe .NET. Le framework .NET
prsente un ensemble dattributs, de classes et doutils permettant vos classes .NET dutiliser
les services dentreprise COM+. Ces attributs et ses classes sont sous lespace de noms System.
EnterpriseServices. Ils sont implments dans lassemblage System.EnterpriseServices.
dll qui se trouve dans le GAC. Lide principale est que le dveloppeur na pas connatre
COM+ dans les dtails pour pouvoir utiliser les services dentreprise COM+ dans ses classes
.NET.

Prsentation des services dentreprise COM+


La liste complte des services dentreprise COM+
La prsentation dtaille de tous les services dentreprise COM+ dpasse le cadre de cet ouvrage.
Voici la liste des services dentreprise de COM+ que vous pouvez assigner vos classes .NET :

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.

Activation juste temps (JITA Just In Time Activation) ;


Lide de ce mcanisme est de dsactiver un objet aprs les appels de certaines mthodes,
afin de le rendre utilisable par dautres clients. Rappelons que dsactiver un objet signifie
que COM+ appelle la mthode Deactivate() sur lobjet, puis le met dans le pool dinstances
du composant servi concern. Si un tel pool dobjets nexiste pas, la mthode Dispose() est
appele sur lobjet et il sera dtruit ultrieurement par le ramasse-miettes. Pour indiquer
que les instances dun composant servi supportent le mcanisme JITA, il sut dutiliser
lattribut System.EnterpriseServices.JustInTimeActivation dans la dclaration du composant servi concern.

Transactions distribues ;
COM+ permet dexploiter le serveur transactionnel de Windows nomm MS DTC (Distributed
Transaction Coordinator) afin de raliser des transactions distribues.

BYOT (Bring Your Own Transaction)


Ce mcanisme permet dassocier une transaction existante un composant qui a priori ne
fait pas partie de cette transaction. La transaction peut tre gre par MS DTC (le gestionnaire de Windows de transactions distribues), mais aussi par un autre milieu transactionnel
comme TIP (Transaction Internet Protocol).

Prsentation des services dentreprise COM+

297

COM Transaction Integrator (COMTI)


Ce mcanisme vous permet dintgrer les mainframes dvelopps avec les technologies CICS
(Customer Information Control System) et IMS (Information Management System) de IBM, dans
des objets COM classiques. Sous le terme COMTI sont regroups les outils et les classes
permettant cette intgration.

Compensating Resource Manager (CRMs)


La thorie des transactions sapplique, entre autres, aux bases de donnes relationnelles. Cependant, il ne faut pas oublier quelle constitue un cadre thorique gnral la protection
de lintgrit de ressources quelconques. Un composant servi CRM est un composant servi
propritaire qui gre des ressources (par exemple des fichiers). Un CRM peut participer
au sein dune transaction distribue gre par MS DTC (le gestionnaire Microsoft de transactions distribues). Lorsquun CRM participe une transaction, il doit pouvoir stocker
temporairement et de manire persistante ltat de ses ressources avant leur modification
de manire pouvoir revenir en arrire si la transaction choue. Le CRM doit tre capable
de valider les changements (commit) ou de les annuler (rollback) mme si le processus qui
le gre est dtruit durant la transaction. Si ce processus est eectivement dtruit durant la
transaction, les oprations de commit ou de rollback doivent pouvoir tre eectues lorsque
le processus est relanc. Contrairement ce que lon pourrait penser, crire un CRM est une
tche relativement aise. La plupart de la complexit est encapsule dans les classes fournies
par le framework .NET.

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).

vnements coupls (Loosely Coupled Events)


Ce mcanisme permet un client dune COM+ application dtre averti par le serveur lorsquun vnement se produit. Cela vite au client daller demander rgulirement au serveur
si lvnement sest produit.

Passage dune chane de caractres la construction dun objet


Ce mcanisme permet de pallier partiellement labsence darguments des constructeurs
des classes COM. Cependant la chane de caractres est la mme pour toutes les instances
dun composant servi. Vous avez la possibilit de modifier cette chane par lintermdiaire
de loutil Services de composants . La principale utilit de cette chane est de fournir une
chane de connexion vers une base de donnes.

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.

Appels asynchrones dconnects (Queued Components)


Ce mcanisme dappel asynchrone va plus loin que le mcanisme dappel asynchrone de
.NET. Contrairement ce mcanisme, les requtes des clients ne sont pas traites directement. Elles sont stockes dans une file dattente sous forme de messages. Durant cette opration, aucun objet serveur nest cr. Le client et le serveur peuvent mme tre physiquement dconnects. Tout ceci est transparent pour le client qui dialogue avec un objet proxy.

298

Chapitre 8 : Interoprabilit .NET code natif / COM / COM+


Tout ceci est transparent pour le serveur qui ultrieurement rcuprera ces requtes et les
traitera lorsquil sera connect avec la machine cliente. Notez que le mcanisme interne de
gestion de file dattente est Microsoft Message Queue (MSMQ). MSMQ doit tre installe la
fois sur la machine client et la machine serveur.

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+.

Synchronisation / activit COM+


Une activit COM+ est un arbre logique dappels entre instances de composants servis. Ce
mcanisme permet de synchroniser laccs des instances de composants servis entre plusieurs activits COM+. Le mcanisme dinterception dappels de COM+ fait en sorte quil
ne puisse y avoir plus dune activit COM+ qui utilise une instance dun composant servi
la fois.

Avez-vous besoin des services dentreprise COM+ ?


Parce que COM+ nest pas intgr .NET, il vaut mieux viter de lutiliser quand cela est possible. En eet, certaines fonctionnalits sont redondantes entre les briques logicielles du framework .NET et les services dentreprise COM+. Voici quelques-unes de ces fonctionnalits redondantes.
.NET 2.0 prsente un framework de dveloppement transactionnel qui est toujours prfrable
la gestion des transactions avec COM+. Ce framework fait lobjet du chapitre sur les transactions.
Bien souvent, le pooling dobjets COM+ est utilis pour viter davoir construire et dtruire
une connexion avec une base de donnes, pour chaque client. Soyez conscient que la fonctionnalit de pool de connexions est en gnral prvue par les fournisseurs de donnes ADO.NET
(voir page 718).
La fonctionnalit COM+ dactivation JITA dun objet ( Just In Time Activation ou activation juste temps ) permet de repousser la cration (ou lactivation) dun objet jusquau moment de sa premire utilisation relle par un client. Or, le mode WKO simple appel de .NET
Remoting fonctionne sur ce modle dactivation (voir page 795).
La notion dappels asynchrones de COM+ est un peu plus gnrale que celle de .NET, prsente
page 171. Elle est plus gnrale dans le sens o elle permet au client deectuer lappel mme si
le serveur nest pas joignable au moment de lappel. Dans ce cas, lappel est stock dans une file
dattente ct client, et sera eectu automatiquement lorsque le serveur redeviendra joignable.
Si vous navez pas besoin de cette fonctionnalit, mieux vaut utiliser les appels asynchrones
.NET.
La notion de rle dans le domaine de la scurit est la fois prsente en COM+, Windows et
.NET. Vous pouvez en savoir plus ce sujet page 216.
La possibilit de nautoriser au plus quun client utiliser un composant un instant donn,
est possible aussi bien avec COM+ quavec .NET (voir page 160).

Utiliser les services COM+ dans des classes .NET

299

Utiliser les services COM+ dans des classes .NET


Notion de composant servi (serviced component)
Un composant servi (serviced component en anglais) est une classe .NET utilisant un ou plusieurs
services dentreprise COM+. Pour utiliser au moins un service dentreprise COM+, une classe
.NET doit imprativement driver de la classe System.EnterpriseServices.ServicedComponent.
Un composant servi doit imprativement avoir un constructeur sans argument, aussi appel
constructeur par dfaut.
La classe ServicedComponent implmente linterface IDisposable et sa mthode Dispose(). La
classe ServicedComponent prsente aussi la mthode virtuelle protge Dispose(bool). Si le
boolen est positionn true, cela signifie quil faut librer les ressources gres et non gres.
Si le boolen est positionn false, cela signifie quil ne faut librer que les ressources non
gres.
Voici donc quoi ressemble le squelette dun composant servi :
Exemple 8-16 :
using System ;
using System.EnterpriseServices ;
public class SystemeBancaire : ServicedComponent {
public SystemeBancaire() {
}
new public void Dispose() {
Dispose(true) ;
GC.SuppressFinalize(this) ;
}
protected override void Dispose(bool bDisposing){
// Lib`ere les ressources non g
er
ees.
if( bDisposing ){
// Lib`ere les ressources g
er
ees.
}
base.Dispose(bDisposing) ;
}
}

Dclarer les services dentreprise utiliss dans un composant servi


Pour signaler au compilateur quune classe drivant de ServicedComponent utilise tel ou tel
service dentreprise, il faut que la classe soit dclare avec les attributs .NET correspondant
aux services dentreprise souhaits. Ces attributs sont tous dans lespace de noms System.
EnterpriseServices. Par exemple lattribut Transaction permet de paramtrer le mode transactionnel utilis dans une classe. Lattribut ObjectPooling permet dindiquer que les instances
de la classe font partie dun pool. Pour utiliser conjointement ces deux services dentreprise et
les paramtrer dans notre classe SystemeBancaire, il faudrait la dclarer comme ceci :

300

Chapitre 8 : Interoprabilit .NET code natif / COM / COM+


...
[Transaction(TransactionOption.Required)]
[ObjectPooling(MinPoolSize=5,MaxPoolSize=30)]
public class SystemeBancaire : ServicedComponent {
...

Contexte COM+ et utilisation des services dentreprise


dans un composant servi
Un contexte COM+ est un conteneur logique dans un processus, qui hberge les objets qui utilisent les mmes services dentreprise COM+.

Attention, bien que conceptuellement proche de la notion de contexte .NET prsente


page 842, la notion de contexte COM+ est dirente. Certains documents parlent de
contextes grs (.NET) et de contextes non grs (COM+).
La plomberie interne pour utiliser un service dentreprise peut tre assez lourde. La notion
de contexte permet de partager cette plomberie entre plusieurs objets afin damliorer les performances. La gestion des contextes est transparente pour le dveloppeur. Lors de la cration
dune instance dun composant servi, COM+ soccupe de vrifier sil existe un contexte dans le
processus qui utilise les mmes services dentreprise. Si tel est le cas, linstance du composant
servi utilisera ce contexte, sinon un nouveau contexte avec les services dentreprise adquats est
cr.
Sachez que si le client dune instance dun composant servi nest pas dans le mme contexte
COM+, COM+ cre un objet proxy pour le client. Lintrt est que chaque appel au composant
servi est dabord intercept en interne par COM+ avant dtre excut. Chaque retour dappel
est aussi intercept par COM+. Ce que fait COM+ durant cette interception dpend compltement des services dentreprise utiliss. Le mcanisme JITA, dcrit un peu plus loin, constitue
un exemple intressant de ce que peut faire COM+ durant ces interceptions. Notez que ce mcanisme dobjets proxy et dinterception est transparent pour lutilisateur.
Du point de vue du dveloppeur dun composant servi .NET, le contrle de lutilisation
des services dentreprise se fait par lintermdiaire de la classe System.EnterpriseServices.
ContextUtil. Cette classe ne contient que des membres statiques. Supposons quun composant
servi utilise le service dentreprise de gestion des transactions et que vous souhaitiez avorter
la transaction courante dans le code dune mthode. Il sut dappeler la mthode ContextUtil.SetAbort(). On ne peut pas dire quun composant servi .NET appartient un contexte
COM+, car le composant servi est gr mais pas le contexte. On dit plutt quun composant
servi utilise un contexte pour avoir accs ses services dentreprise. La Figure 8-6 rsume tout
ceci :

Notion de COM+ application


Une COM+ application est une collection de composants servis. Les composants servis dune
COM+ application peuvent tre dans dirents assemblages. En revanche, tous les composants

Utiliser les services COM+ dans des classes .NET

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

Gestion interne des services dentreprise

Figure 8 -6 : Contextes COM+ et composants servis .NET

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 :

ApplicationName : prcise le nom de la COM+ application. Par exemple :


[assembly : ApplicationName("Serveur bancaire.")]

ApplicationID : prcise lidentificateur unique de la COM+ application.


[assembly : ApplicationID("301E31A3-E011-432b-9D7E-5643253EEE89")]

Description : donne une description de la COM+ application.


[assembly : Description("Permet dacc
eder aux informations bancaires.")]

ApplicationAccessControl : permet de configurer des paramtres de scurit de la COM+


application.

ApplicationQueuing : Permet de configurer lutilisation de files de messages dans la COM+


application.

ApplicationActivation : Il existe deux modes dactivation dune COM+ application, le


mode dactivation librairie et le mode dactivation serveur. La description de ces modes fait
lobjet de la prochaine section.

Si plusieurs assemblages configurent la mme COM+ application, un paramtre donn de la


COM+ application prendra la valeur spcifie dans le dernier assemblage install. Si un paramtre de la COM+ application nest positionn par aucun assemblage, il sera positionn une
valeur par dfaut, lors de la cration de la COM+ application.

302

Chapitre 8 : Interoprabilit .NET code natif / COM / COM+

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

Figure 8 -7 : Catalogue COM+ et COM+ applications

Les modes dactivation dune COM+ application


Chaque COM+ application a un mode dactivation qui est soit librairie soit serveur .

Mode dactivation librairie


Le mode dactivation librairie indique que les assemblages contenant les composants servis
dune COM+ application doivent tre chargs dans le processus du client de la COM+ application. La Figure 8 -8 expose larchitecture gnrale mise en uvre dans le mode dactivation
librairie .

Utiliser les services COM+ dans des classes .NET

303

Processus client

Client non gr

CLR
Client .NET

Proxy

Instance dun
composant servi

Proxy

Client Callable Wrapper


COM+ contexte
COM+ interception

Figure 8 -8 : Mode dactivation librairie dune application COM+

Mode dactivation serveur


Le mode dactivation serveur indique que les assemblages contenant les composants servis
dune COM+ application doivent tre chargs dans un processus prvu pour hberger les COM+
applications. Ce processus est lanc automatiquement par le systme lors de la premire utilisation dune COM+ application. Lexcutable dllhost.exe est utilis pour lancer ce processus.
On dit dun tel processus quil est subrog (surrogate en anglais). Pour quune COM+ application
puisse tre utilise par des clients distants (i.e qui ne sont pas sur la mme machine) il faut
naturellement quelle ait le mode dactivation serveur . La Figure 8 -9 expose larchitecture
gnrale mise en uvre dans le mode dapplication serveur .
Processus dun client non gr
Client non gr
Proxy

Processus dun client gr


CLR

Client .NET

Runtime Callable Wrapper

Cette ligne reprsente ventuellement un rseau dans le cas de clients distants

Processus client (dllhost.exe)


CLR
Instance dun
composant servi

Client Callable Wrapper


COM+ contexte
COM+ interception

Figure 8 -9 : Mode dactivation serveur dune application COM+

Installation des composants servis dans une COM+ application


Linstallation des composants servis contenus dans un assemblage dans une COM+ application
a besoin que lassemblage ait un nom fort. Cest--dire que lassemblage doit tre sign numriquement avec loutil sn.exe. Au moment de lexcution, le CLR trouvera cet assemblage soit
dans le GAC, soit dans le rpertoire courant de lapplication.

Les tapes de linstallation


Linstallation des composants servis contenus dans un assemblage, dans une COM+ application,
contient les quatre tapes suivantes :

304

Chapitre 8 : Interoprabilit .NET code natif / COM / COM+

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.

Les direntes faons de raliser linstallation


La classe System.EnterpriseServices.RegistrationHelper permet dinstaller (ou de dsinstaller) les composants servis dun assemblage. Elle permet dexcuter les quatre tapes vues cidessus dans une mme transaction. Aprs avoir utilis la classe RegistrationHelper vous pouvez donc tre certain que soit toutes les tapes ont t eectues avec succs, soit rien na t
modifi car une des tapes a chou.
Concrtement, il existe trois faons dinstaller les composants servis dun assemblage. Chacune
de ces faons utilise la classe RegistrationHelper dune manire masque ou non. Quelle que
soit la faon utilise, seul un utilisateur ayant les droits dadministrateur peut raliser cette opration.

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(

Utiliser les services COM+ dans des classes .NET

305

"SystemeBancaire.dll", // Nom de lassemblage.


ref sCOMPlusAppName,
ref sTypeLibName,
InstallationFlags.CreateTargetApplication) ;
}
}
Largument CreateTargetApplication indique quune nouvelle COM+ application doit
tre cre. Si ce nest pas possible lexception RegistrationException est lance.

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.

Visualisation et manipulation du catalogue COM+


Vous pouvez visualiser et manipuler le catalogue COM+, et donc les COM+ applications prsentes sur une machine, en utilisant le composant logiciel enfichable (snap-in en anglais) Services
de composants (component services en anglais). La Figure 8-10 prsente une copie dcran de cet
outil :

Figure 8 -10 : Loutil Services de composants


Loutil services de composants permet un administrateur de modifier les paramtres dune
COM+ application. Cependant, il est conseill de ne modifier que les paramtres relatifs linstallation dune COM+ application, comme les paramtres de scurit.

306

Chapitre 8 : Interoprabilit .NET code natif / COM / COM+

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.

Concevoir un client dun composant servi


Du point de vue dun client gr, il ny a aucune dirence entre lutilisation dune instance
dune classe normale et lutilisation dune instance dun composant servi. Il est conseill dutiliser la syntaxe C  avec le mot-cl using pour appeler la mthode Dispose() dune manire
implicite. Par exemple :
public class Program {
static void Main(){
using( SystemeBancaire sys = new SystemeBancaire() ){
//... utilise sys
} //appelle sys.Dispose() dune mani`
ere implicite
}
}
Du point de vue dun client non gr, lutilisation dune classe composant servi passe par lutilisation de la classe COM dfinie par le Com Callable Wrapper.

Le langage C  2.0 et la
comparaison C  2.0/C++

9
Les concepts fondamentaux
du langage

Organisation du code source


C++ C  Malgr une syntaxe relativement proche de celle du C++, les espaces de noms
jouent un rle plus important dans C  .
En C++ on les utilisait pour empcher les collisions entre identificateurs.
En C  , les espaces de noms remplacent la directive prprocesseur #include. En eet, en C  il ny
a plus de fichiers den-tte (.h). Pour utiliser une ressource contenue dans un espace de noms
et dclare dans un autre fichier source C  , il sut de dclarer quon utilise lespace de noms
concern. Ce fichier source peut ventuellement faire partie dun autre assemblage que celui
dans lequel on utilise la ressource.
Cette possibilit dutilisation de ressources dclares dans un autre assemblage est aussi exploite dans lutilisation de ressources dfinies dans des bibliothques. Par exemple toutes les classes
de base du framework .NET sont accessibles au travers de lespace de noms System.
Une autre grosse dirence est que lon ne peut sparer la dclaration et la dfinition (le corps)
dune mthode ou dune classe, comme on pouvait le faire en C++ grce loprateur de rsolution de porte.
Autre dirence importante : il nexiste plus de fonctions globales. Il ne peut y avoir que des
mthodes, cest--dire des fonctions dclares lintrieur dun type (classe ou structure).
En C  , lutilisation des espaces de noms pour empcher les collisions entre identificateurs est
toujours dactualit. De plus pour tendre le nom dun identificateur en le faisant prcder du
nom de lespace de noms, on nutilise plus loprateur de rsolution de porte du C++ (loprateur :: qui a une autre application en C  2) mais un point.

310

Chapitre 9 : Les concepts fondamentaux du langage

Enfin les possibilits de dfinir des alias de noms despace de noms, et dimbriquer des espaces
de noms, sont prsentes en C  .

Les espaces de noms


Une ressource dans le code source peut tre dclare et dfinie lintrieur dun espace de noms.
Si une ressource nest dclare dans aucun espace de noms elle fait partie dun espace de noms
global et anonyme. Un espace de noms nomm Foo par exemple, se dclare comme suit :
namespace Foo{
// Ici, la definition delements.
}
On a la possibilit dimbriquer les espaces de noms :
namespace Foo1{
// Ici, la definition delements.
namespace Foo2{
// Ici, la definition del
ements.
// La qualification compl`
ete de Foo2 est Foo1.Foo2.
}
// Ici, la definition delements.
}
On a la possibilit de dcouper un espace de noms. Par exemple, pour taler des ressources qui
doivent tre dans le mme espace de noms sur plusieurs fichiers sources. Pour cela il sut de dclarer un ou plusieurs espaces de noms avec le mme nom pour chaque fichier source concern.
Notez quavec les types partiels de C  2, une ressource a maintenant la possibilit dtre dclare
sur plusieurs fichiers sources.
La technique de dcoupage dun espace de noms est dautant plus puissante que le dcoupage
peut se faire sur les fichiers sources de dirents modules (du mme assemblage ou non).
Les espaces de noms constituent un moyen de partitionner le code source dans des blocs distincts et organiss, selon une hirarchie que les architectes du projet ont tabli.
Enfin, mentionnons que les documentations anglo saxonnes utilisent souvent les expressions
Foo et Bar pour nommer les entits quelconques de leurs exemples (un peu comme nous utilisons parfois toto). Ces expressions sont issues de lacronyme FUBAR (Fucked Up Beyond All
Repairs) qui peut se traduire par cest irrparable, cest foutu. Cet acronyme a vraisemblablement
t invent lors de la seconde guerre mondiale pour dsigner une situation dsespre.

Utilisation des ressources contenue dans un espace de noms


Les types de ressources dclars lintrieur dun espace de noms sont :

Organisation du code source

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

Chapitre 9 : Les concepts fondamentaux du langage


using Foo.* ;

Structure dun projet C 


Le code source dun projet crit en C  est rpartit sur un ou plusieurs fichiers texte dont lextension est .cs. Prcisons quun projet C  est lensemble des fichiers sources C  concerns dans
la fabrication dun assemblage par le compilateur C  . Lorganisation dun projet C  peut tre
rsume par la Figure 10 -1 :
Projet

Namespace 1

Namespace 2

fichier1.cs

...

Namespace 3

Ressource D
Ressource E

fichier2.cs

Ressource C

...

Ressource A

fichier3.cs

Ressource B

Figure 9 -1 : Les lments structurels dun projet C 


En tenant compte des remarques suivantes :

Le mme espace de noms peut se trouver sur plusieurs fichiers.

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.

Organisation assemblages / espaces de noms


Une bonne pratique consiste nommer un assemblage avec le nom de lespace de noms racine
quil contient. Microsoft applique cette rgle pour nommer ses assemblages standard. Ainsi, lorganisation des classes de bases standard apparat immdiatement la lecture des noms des assemblages : System.dll, System.Drawing.dll, System.Drawing.Design.dll, System.Runtime.
Remoting.dll, System.Security.dll etc.

Les tapes de la compilation


C++ C  Attention, ltape de la cration de liens (link) du C++ nexiste plus en C  . En eet
le compilateur C  ne se sert pas de fichiers intermdiaires dextension .obj.
En revanche les tapes du prprocessing et la compilation elle-mme sont toujours l. En C 
beaucoup de directives prprocesseur du C++ disparaissent, et quelques nouvelles voient le jour.
C  La compilation consiste fabriquer un assemblage partir de fichiers sources crits en
C  . La notion dassemblage fait lobjet du chapitre 2 Assemblages, modules, langage IL . La

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 assemblage, contenu dans un fichier dont lextension est .exe ou .dll,

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

Chapitre 9 : Les concepts fondamentaux du langage

Constantes symboliques et compilation conditionnelle


Vous avez la possibilit de dfinir une entit avec la directive #define. Selon la prsence ou non
de lentit, le code source peut tre modifi avec lutilisation des directives #if #elif #else et
#endif :
Exemple 9-2 :
#define MACRO1
public class Program {
public static void Main() {
#if (MACRO1)
System.Console.WriteLine("MACRO1 d
efinie.") ;
#elif (MACRO2)
System.Console.WriteLine("MACRO2 d
efinie et MACRO1 non d
efinie") ;
#else
System.Console.WriteLine("MACRO2 et MACRO1 non d
efinies") ;
#endif
}
}
Lenvironnement de dveloppement Visual Studio met en gris les lignes qui ne seront pas prises
en compte lors de la compilation. En outre, les constantes symboliques doivent tre dfinies
avant toute utilisation de la directive C  using.
On a aussi la possibilit de dfinir une constante symbolique lors de la compilation en ligne.
Par exemple :
>csc.exe prepross.cs /define:MACRO2
La directive #undef annule la dfinition dune constante symbolique, par exemple :
Exemple 9-3 :
#define MACRO1
#undef MACRO1
public class Program {
public static void Main() {
#if (MACRO1)
System.Console.WriteLine("MACRO1 d
efinie.") ;
#elif (MACRO2)
System.Console.WriteLine("MACRO2 d
efinie et MACRO1 non d
efinie") ;
#else
System.Console.WriteLine("MACRO2 et MACRO1 non d
efinies") ;
#endif
}
}

Constantes symboliques et lattribut ConditionalAttribute


Il existe une alternative lgante (mais restrictive) lutilisation de #if #elif #else et #endif.
Cette alternative est lattribut ConditionalAttribute qui permet de dfinir une mthode en

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

Les directives #error et #warning


La directive #error peut prvenir des conflits de dfinitions de constantes symboliques la compilation.
Le programme suivant ne compilera pas, et le compilateur achera lerreur suivante : MACRO1
et MACRO2 ne peuvent etre definies en m
eme temps
Exemple 9-5 :
#define MACRO1
#define MACRO1
#define MACRO2
#if MACRO1 && MACRO2
#error MACRO1 et MACRO2 ne peuvent
etre d
efinies en m
eme temps
#endif
public class Program {
public static void Main() {
#if (MACRO1)
System.Console.WriteLine("MACRO1 d
efinie. ") ;
#elif (MACRO2)
System.Console.WriteLine("MACRO2 d
efinie et MACRO1 non d
efinie") ;

316

Chapitre 9 : Les concepts fondamentaux du langage


#else
System.Console.WriteLine("MACRO2 et MACRO1 non d
efinies") ;
#endif
}
}

La directive #warning fonctionne sur le mme principe, mis part quelle narrte pas la compilation mais gnre un avertissement.

Les directives #pragma warning disable et restore


Les directives #pragma warning disable et #pragma warning restore permettent de dsactiver la production davertissements du compilateur C  . Leur syntaxe dutilisation est illustre
par lexemple suivant :
Exemple 9-6 :
class Program {
static void Main() {
#pragma warning disable 105
// Desactive lavertissement CS0105 dans ce bloc.
#pragma warning restore 105
#pragma warning disable
// Tous les avertissements sont d
esactiv
es dans ce bloc.
#pragma warning restore
#pragma warning disable 105, 251
// Les avertissements CS0105 et CS0251 sont d
esactiv
es dans ce bloc.
#pragma warning restore 105
// Seul lavertissement CS0251 est d
esactiv
e dans ce bloc.
#pragma warning restore
// Tous les avertissements sont activ
es dans ce bloc.
#pragma warning disable
// Desactive tous les avertissements jusqu`
a la fin du fichier.
}
}

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.

Les directives #region et #endregion


Les directives #region et #endregion sont trs pratiques car elles permettent de plier/dplier par
un simple clic, une rgion de code. Cette possibilit est notamment utilisable dans les IDE Visual
Studio et SharpDevelop. La Figure 9 -2 illustre ces eets.

Figure 9 -2 : Eet des directives prprocesseur #region et #endregion


#endregion peut optionnellement rappeler le mme texte que #region. Il est galement possible dimbriquer ces directives.

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

Chapitre 9 : Les concepts fondamentaux du langage


Produit un module si
loption /target:module
est utilise

xx
Fichier1.cs
Modules rfrencs avec loption
/addmodule :

Module.netmodule

xx
Module1.netmodule

Produit un assemblage
librairie si loption
/target:library est utilise

Librairies rfrences avec loption


/reference : ou /r :

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

Figure 9 -3 : Entres et sorties du compilateur C  csc.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

Nous avons illustr ici les options suivantes :

Le compilateur csc.exe

319

Option

Description

/target /t

Spcifie le format du produit de la compilation. Le format peut


tre :/target:library Une librairie (extension .dll)/target:module
Un module (extension .netmodule)/target:exe Un excutable en mode
console (extension .exe). Cest le format pris par dfaut si loption target
nest pas prcise./target:winexe Un excutable prsent dans une
fentre (de type Windows Form). Lextension du fichier sera aussi .exe.

/out

Spcifie le nom du fichier produit par la compilation.

/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

Dfinie une constante symbolique.

/optimise /o

Enclenche la compilation optimise (compatible avec loption debug).

/warn /w

Rgle le niveau davertissement. Ce niveau varie entre 0 (pas davertissement) et 4 (tous les avertissements sont achs).

/debug

Gnre des informations ncessaires au dboguage, dans un fichier


qui porte le mme nom que le fichier produit, avec lextension pdb
(programme database). (Compatible avec loption optimise).

/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

Rfrence les assemblages (excutable ou librairies) utiliss par le produit


de la compilation. On peut utiliser les ressources dun assemblage rfrenc, dans le code de lassemblage rfrenant. Dans ce cas, lassemblage
rfrenc sera charg par le CLR lors de la premire utilisation de lune
de ses ressources.Si le produit est un assemblage excutable, il ne faut pas
rfrencer un assemblage excutable. Si le produit nest pas un excutable,
on peut rfrencer au plus un assemblage excutable.

/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

Chapitre 9 : Les concepts fondamentaux du langage

/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");
}
}

Qualificateur dalias despace de noms


Il peut y avoir un conflit entre un espace de noms et un alias. Les trois assemblages suivants
illustrent ce cas :
Exemple 9-9 :

Code de Asm1.dll

namespace Foo.IO{ public class Stream{} }


Exemple 9-10 :
namespace Custom.IO{ public class Stream{} }

Code de Asm2.sll

Les alias
Exemple 9-11 :

321
Code de Program.exe qui r
ef
erence Asm1.dll et Asm2.dll

using FooIO = Foo.IO;


using CusIO = Custom.IO;
class Program{
static void Main(){
FooIO.Stream stream1 = new FooIO.Stream();
CusIO.Stream stream2 = new CusIO.Stream();
}
}
Grce lutilisation des alias FooIO et CusIO vous pouvez utiliser les deux types Stream dans
Program.exe sans avoir rcrire les espaces de noms en entier. Cependant ce programme ne
compile plus si vous modifiez le code de Asm2.dll comme ceci :
Code de Asm2.dll

Exemple 9-12 :
namespace Custom.IO{ public class Stream{} }
namespace FooIO{ public class Stream{} }

En eet, le compilateur sera incapable de dterminer si FooIO.Stream dsigne le type FooIO.


Stream dfini dans Asm2.dll ou le type Foo.IO.Stream de Asm1.dll alias FooIO.Stream..
Pour viter que lvolution dune bibliothque empche votre code de compiler vous pouvez
avoir recours au qualificateurs dalias despace de noms ::. Le compilateur infre que lidentificateur gauche du qualificateur dalias despace de noms est un alias. Dans notre exemple, il faut
donc rcrire Program.cs comme ceci :
Exemple 9-13 :

Code de Program.exe qui r


ef
erence Asm1.dll et Asm2.dll

using FooIO = Foo.IO;


using CusIO = Custom.IO ;
class Program {
static void Main(){
FooIO::Stream stream1 = new FooIO::Stream();
CusIO.Stream stream2 = new CusIO.Stream() ;
}
}
Cette technique du qualificateur dalias despaces de noms est ecace car la porte dun alias est
au plus le fichier dans lequel il est dfinit. Une volution dans un assemblage externe ne peut
donc pas introduire subrepticement une erreur de compilation.

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

Chapitre 9 : Les concepts fondamentaux du langage


const int Console = 691 ;
static void Main() {
// KO : Le compilateur essaye dacc
eder `
a Program.Console.
Console.WriteLine("Hello 1") ;
// KO : Le compilateur essaye dacc
eder Program.System.
System.Console.WriteLine("Hello 2") ;
}

}

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");
}
}

Les alias externes


Les alias externes servent utiliser simultanment deux types dclars dans deux assemblages
dirents mais qui ont le mme nom et qui sont dans le mme espace de noms. Cette situation
peut par exemple survenir si vous devez utiliser dans le mme programme deux versions dun
mme assemblage. La syntaxe des alias externes est illustre par cet exemple :
Code de Asm1.dll

Exemple 9-16 :
namespace FooIO{ public class Stream{} }

Code de Asm2.sll

Exemple 9-17 :
namespace FooIO{ public class Stream{} }
Exemple 9-18 :

Code de Program.exe qui r


ef
erence Asm1.dll et Asm2.dll

extern alias AliasAsm1;


extern alias AliasAsm2;
class Program{
static void Main(){
AliasAsm1::FooIO.Stream stream1 = new AliasAsm1::FooIO.Stream();
AliasAsm2::FooIO.Stream stream2 = new AliasAsm2::FooIO.Stream();
}
}
Il faut alors compiler Program.exe comme ceci :

Commentaires et documentation automatique

323

>csc.exe /r:AliasAsm1=Asm1.dll /r:AliasAsm2=Asm2.dll Program.cs


Vous pouvez aussi prciser vos alias externes dans Visual Studio grce la proprit Aliases dans
les proprits des rfrences vers les assemblages bibliothques. Vous y remarquerez quune rfrence vers un assemblage peut supporter plusieurs alias.
Enfin, prcisons que le meilleur moyen dutiliser ces qualificateurs et autres alias est de ne pas
en avoir besoin en essayant dviter les conflits de noms des entits de vos programmes. En tant
quartefacts avancs de C  2, la majorit des dveloppeurs ne les matrisent pas et auront ainsi
des dicults relire du code qui les utilise.

Commentaires et documentation automatique


C++ C  Les commentaires en C  et C++ sont dclars de la mme faon. Cependant C 
accepte un nouveau type de commentaires destin la documentation du code. Ces nouveaux
commentaires sont dclars avec la balise ///.
La nouveaut dans les identificateurs (i.e les noms de variables, les noms des mthodes, les noms
des classes...) est que lon peut utiliser des lettres accentues.

Les commentaires
C

Il existe trois faons de commenter du texte dans un fichier source 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.

Utiliser la liste des tches de Visual Studio


Bien souvent, en tant que dveloppeur nous sommes obligs de repousser lcriture dune portion de code. Sous Visual Studio, si un commentaire de type // ou /* */ commence avec lun des
mots TODO, HACK ou UNDONE il est automatiquement ajout dans la liste des tches. Pour avoir accs cette fonctionnalit, il faut slectionner le menu Acher  liste des tches puis slectionner
Commentaires dans la combo box de la liste des tches qui apparat.

324

Chapitre 9 : Les concepts fondamentaux du langage

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.

La Figure 9 -4 rsume ceci :


Solution
Projet 1
Fichier1.c

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)

Figure 9 -4 : Production automatique de la documentation technique


Illustrons ceci avec un exemple. Le fichier de code source C  suivant...

Commentaires et documentation automatique

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

Chapitre 9 : Les concepts fondamentaux du langage


Soit vous utilisez loption /doc du compilateur C  csc.exe :
>csc.exe MonFichier.cs /doc:MonFichierDoc.XML

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  .

Le compilateur C  tient compte de la casse (i.e la dirence majuscule/minuscule).

Convention de nommage CamelCase et PascalCase


La convention de nommage PascalCase encourage le dveloppeur nommer ses identificateurs
avec des lettres minuscules sauf pour les premires lettres des mots. Par exemple : MaVariable,
UneFonction, UnIdentificateurPascalCase.
La convention de nommage CamelCase est similaire PascalCase mis part que le premier caractre est en minuscule. Par exemple : maVariable, uneFonction, unIdentificateurPascalCase.
Le nom CamelCase provient des bosses provoque par les caractres majuscules qui rapplent celles dun chameau (camel veut dire chameau en anglais). Il ny a pas de relation avec le
langage de programmation Camel.
En C  , il est conseill par Microsoft dutiliser la convention de nommage PascalCase pour les
identificateurs reprsentant des noms de mthodes, dvnements, de champs, de proprits,
de constantes, despaces de noms, de classes, de structures, de dlgations, dnumrations, dinterfaces et dattributs.
La convention CamelCase doit alors sappliquer aux noms de variables et de paramtres de mthodes.

Les structures de contrle

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 structures de contrle


C++ C 
et C  .

En ce qui concerne les structures de contrle, il y a peu de dirences entre C++

Lutilisation de linstruction switch a un peu chang. Le type de boucle, foreach, spcialement


adapt au parcours dlments dun tableau, fait son apparition.
C  Une structure de contrle est un lment du programme qui change le comportement
par dfaut de lunit dexcution (du thread). Rappelons que ce comportement par dfaut est
dexcuter les instructions les unes la suite des autres. En programmation, les structures de
contrle se dclinent gnralement en trois familles :

Les conditions, qui excutent (ou pas) un bloc de code qu une certaine condition, portant
gnralement sur les tats de variables et dobjets.

Les boucles, qui excutent en boucle un bloc dinstructions. Le programmeur a le choix


entre terminer de boucler aprs un certain nombre ditrations, ou terminer de boucler
une certaine condition, voire ne jamais terminer (boucle infinie).

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.

Les conditions (if/else, ?:, switch)


C++ C  En C  il nest pas possible dcrire if(i=1) au lieu de if(i==1). Loprateur daffectation ne renvoie pas un boolen. Cette erreur est particulirement dangereuse en C++, car
elle est non dtectable par le compilateur.
Linstruction switch subit aussi quelques dirences expliques plus loin.
Aucun autre changement nest noter sur les tests et conditions de type if/else entre C/C++
et C  .

Utilisation de if/else
C

Une condition se prsente sous cette forme :


if ( expression retournant un bool
een )
Bloc dinstructions `a executer si lexpression retourne true
else
Bloc dinstructions `a executer si lexpression retourne false

328

Chapitre 9 : Les concepts fondamentaux du langage

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.

Expressions qui retournent un boolen


Voici quelques exemples dexpressions, qui retournent un boolen :
bool b = true ; int
if( b )
if( !b )
if( b == true )
if( b == false)
if( i )
if( !i )
if( i == 4)
if( i != 4 )
if( i < 4)
if( i <= 4)
if( i < 4 && j > 6)

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

Les structures de contrle

if( i < 4 || i > 6)


if( i != 4 || b )

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

Chapitre 9 : Les concepts fondamentaux du langage

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.

default : lunit dexcution se branche sur le bloc dinstructions default si la valeur de la


variable est dirente de toutes les valeurs spcifies dans les blocs case. Notez que le bloc
default nest pas ncessairement en dernire position, bien que la plupart des dveloppeurs
lutilisent comme cela.

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 ;
}
}
}

Les structures de contrle

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 boolens, le type bool.


Les numrations.

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.

Les boucles (do,while,for,foreach)


C++ C  Un nouveau type de boucle apparat, les boucles foreach, spcialement adaptes
pour parcourir les lments dun tableau. Cependant, toutes les possibilits sduisantes de lutilisation de foreach ne sont vraiment dtailles qu la section page 563.
Les trois autres types de boucles en C/C++, do/while , while et for ont une utilisation similaires
en C  , de mme que pour les mots-cls continue et break.
C  On parle de boucles (loop en anglais), lorsquun bloc dinstruction (entre accolades) ou
une instruction (pas forcment entre accolades) est excut plusieurs fois conscutivement.

332

Chapitre 9 : Les concepts fondamentaux du langage

Les boucles de type while et do/while


Dans ces deux types de boucles, le fait que le programme sorte de la boucle dpend dune condition (similaire celles vues dans la section prcdente). Par exemple :
Exemple :

Boucle while

Boucle do/while

int i=0;int j=8 ;


while( i < 6 && j > 9) {
i++;j-- ;
}

int i=0;int j=8 ;


do {
i++;j-- ;
}
while( i < 6 && j > 9)

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.

Les boucles for


Les boucles for se prsentent ainsi :
for(Instruction(s) dinitialisation (s
epar
ees par des virgules) ;
Condition de sortie de boucle (sort si false) v
erifi
ee `
a chaque
debut de boucle ;
Instruction(s) effectuee(s) `
a chaque fin de boucle(s
epar
ee par
des virgules))
Bloc dinstructions `a executer `
a chaque boucle
La condition est similaire celles vues dans la section prcdente. Voici quelques exemples :
for(int i = 1 ; i<=6 ; i++) ...
//-------------------------------------------------------------------int i = 3;int j=5 ;
for( ; i<7&& j>1 ;i++ , j--)...
//-------------------------------------------------------------------for( int i =6 ; i<9 ; ) { ... i++;}
Les variables dclares dans les instructions dinitialisation de la boucle for doivent avoir des
noms dirents des variables extrieures et intrieures la boucle. Ces variables ne seront plus
visibles ds la sortie de la boucle.

Les boucles foreach


Les boucles de type foreach sont spcialement adaptes au parcours des lments dun tableau
ou dune collection. Par consquent nous dtaillons ceci lors de la prsentation des tableaux,
dans la section page 563.

Les instructions break et continue


Deux instructions existent pour modifier le cours dune boucle (de type while do/while for
ou foreach).

Linstruction continue fait passer directement litration suivante de la boucle.

Les structures de contrle

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

Chapitre 9 : Les concepts fondamentaux du langage

Si vous avez le choix, ayez recours des boucles plutt qu la rcursivit.

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.

Les branchements (goto)


Linstruction de branchement goto a eu son heure de gloire il y a bien longtemps. Lutilisation
de linstruction goto ne doit se faire que dans certains cas trs particuliers. Par exemple, linstruction goto peut se rvler fort utile dans le code dun compilateur ou dans du code gnr.
Certains prnent lutilisation de goto pour coder une machine tat. Dans ce cas il vaut mieux
ne pas lutiliser et faire une machine tat partir dune utilisation de switch dans une boucle
infinie.
Linstruction goto force lunit dexcution continuer sur les instructions aprs une tiquette.
Cette tiquette et le goto doivent tre absolument dans la mme mthode. De plus ltiquette
doit tre visible du goto, ce qui ne signifie pas quelle doit tre dans le mme bloc dinstructions.
Par exemple, le code suivant compile :
Exemple 9-24 :
class Program {
static void Main() {
int i = 0 ;
goto label2;
label1:
i++ ;
goto label3;
label2:
i-- ;
goto label1;
label3:
System.Console.WriteLine(i) ;
}
}
Il se peut quune mauvaise utilisation de goto amne un cas de variable non initialise. Le
compilateur C  dtecte ceci et produit une erreur.

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 :

Elle retourne le type void ou int.

Elle accepte un tableau de chanes de caractres facultatif en argument. Le cas chant,


les chanes contiennent les arguments en ligne de commande de lexcutable. La premire
chane de caractres reprsente le premier argument, la deuxime chane de caractres reprsente le deuxime argument ...

Voici direntes dfinitions possibles pour la mthode Main() :


static
static
static
static

void Main() {/*...*/}


int Main() {/*...*/}
void Main(string[] args) {/*...*/}
int Main(string[] args) {/*...*/}

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

Chapitre 9 : Les concepts fondamentaux du langage


System.Console.WriteLine("Somme de ces nombres :{0}", result) ;
}
}
}

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 

Ce chapitre contient la plupart des grosses dirences entre C/C++ et 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.

Stockage des objets en mmoire


Les concepts de threads (i.e unit dexcutions) et de processus (i.e tches ou espace dadressage)
sont requis pour comprendre cette section, et plus gnralement ce chapitre. Ces concepts sont
introduits au dbut du chapitre 5.

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

Chapitre 10 : Le systme de types

contenue dans lespace dadressage du processus du thread. Le thread se sert de sa pile principalement pour :

stocker les valeurs des arguments de la fonction couramment excute ;


stocker ladresse (dans le code machine natif) laquelle il faudra se brancher lorsque la
fonction retournera ;
stocker certains objets (mais pas tous).

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.

Allocations statiques et allocations dynamiques


C++ C  Un point commun de C++ et de C  est que les objets peuvent tre allous soit
dans la pile dun thread (on parle dallocation statique) soit dans le tas du processus (on parle
dallocation dynamique). La dirence entre C++ et C  est la faon dont le mode dallocation
(statique ou dynamique) est choisi :

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);).

Type valeur et type rfrence

339

En C  le choix du mode dallocation dun objet est fonction de son implmentation.. En


eet, nous allons voir quil existe deux sortes de types. Les types valeur dont les instances
sont alloues statiquement, et les types rfrence, dont les instances sont alloues dynamiquement.

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++ :

Il na pas choisir le type dallocation de ses variables.

Il na pas se soucier de la dsallocation de ses variables.

Type valeur et type rfrence


C++ C  Si vous tes programmeur C++ vous risquez dtre interpell par ces notions de
types valeur/rfrence. Nous allons voir quen C  loprateur new peut tre utilis pour allouer
dynamiquement, mais aussi statiquement une variable. Une consquence est que lon ne peut
plus fournir les arguments du constructeur directement aprs la dclaration dune variable. Rappelons quen C++ lutilisation de loprateur new est rserve pour allouer dynamiquement une
variable.

C  La notion de type valeur/rfrence est fondamentale, quelque soit le langage .NET


que vous utilisez.
Chaque type en C  est soit un type valeur (value type en anglais) soit un type rfrence (reference
type en anglais). Chaque objet est donc soit linstance dun type valeur, soit linstance dun type
rfrence. Une instance dun type valeur est alloue sur la pile du thread (allocation statique),
une instance dun type rfrence est alloue sur le tas du processus (allocation dynamique).
On ne manipule jamais directement des objets de type rfrence. On manipule ces objets par
lintermdiaire de rfrences. En revanche, on manipule toujours directement un objet de type
valeur. Voici un exemple pour illustrer ce fait (nous anticipons un peu la notion de structures,
qui dfinissent toujours des types valeur, et la notion de classes, qui dfinissent toujours des
types rfrence) :

340

Chapitre 10 : Le systme de types

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

Type valeur et type rfrence

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).

Notion de rfrence sur une instance de classe


C++ C  La notion de rfrence en C  est mi-chemin entre la notion de pointeur et de rfrence de C++. Comme une rfrence C++, une rfrence C  rfrence un objet, et les membres
publics de lobjet sont accessibles avec loprateur . .
linverse dune rfrence C++, et comme un pointeur C++, une rfrence C  peut tre nulle.
linverse dune rfrence C++, et comme un pointeur C++, une rfrence C  peut tre modifie. Cest--dire que pour une rfrence donne, lobjet rfrenc (point) nest pas ncessairement le mme au cours du temps.
linverse dun pointeur C++, et comme une rfrence C++, une rfrence C  ne donne pas
accs ladresse physique de lobjet et ne permet aucune manipulation dadresse (comme lincrmentation vers lobjet suivant) de type arithmtique des pointeurs .
C  Toute classe est un type rfrence et tout type rfrence est une classe ou une interface.
Toutes les classes hritent de la classe System.Object que nous allons bientt dcrire.
Il peut aussi y avoir une certaine ambigut entre les termes type, classe et implmentation. La
signification du terme type varie selon le contexte. On parle de type dune rfrence pour dsigner la classe ou linterface qui type une rfrence. On parle de type dun objet pour dsigner
son implmentation. Limplmentation dun objet dsigne la classe ou la structure dont il est
instance. Le terme type est donc plus gnral et plus abstrait que le terme implmentation qui lui
mme est plus gnral que le terme classe.
La notion de classe est un vaste sujet, qui fait lobjet du chapitre suivant. Nous allons nous intresser ici au fait quen tant que type rfrence, les instances des classes, sont EXCLUSIVEMENT
manipuls au travers de rfrences. Etudions lexemple suivant :
Exemple 10-2 :
class Personne {
public int m_Age ;
public string m_Nom ;
public Personne(int Age, string Nom) { m_Age = Age ; m_Nom = Nom ; }
}
class Program {
static void Main() {
Personne ref1 = null ; // ref1 ne r
ef
erence personne.
Personne ref2 = new Personne(50, "Raymond") ;

342

Chapitre 10 : Le systme de types


Personne ref3 = new Personne(48, "Josiane") ;
ref1 = ref2 ;
// ref1 r
ef
erence Raymond.
ref1.m_Age += 10 ;
// Raymond est vieilli de 10 ans !
ref1 = ref3 ;
// ref1 r
ef
erence Josiane.
ref1.m_Age += 10 ;
// Josiane est vieillie de 10 ans !
// ici ref2.m_Age == 60 , Raymond a 60 ans.
// ici ref3.m_Age == 58 , Josiane a 58 ans.
}
}

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.

Le CTS (Common Type System)


Les types .NET sont indpendants du langage
Un aspect trs intressant de .NET est que les types utiliss sont indpendants du langage dans
lequel nous crivons notre code source. Ceci est possible grce un systme de types commun
tous les langages .NET nomm le CTS (Common Type System). Le CTS est une spcification qui
dcrit les direntes caractristiques des dirents types connus par le CLR.
Lexistence du CTS rsout de trs nombreux problmes bien connus de ceux qui ont dj dvelopp des modules devant communiquer entre eux dans des langages dirents. Par exemple
les chanes de caractres en VB sont reprsentes par des instances du type BSTR et en C++ par
des pointeurs vers un tableau de char (et encore, ne mentionnons pas le type string de la
STL et le type CString des MFC). La consquence est quun module crit en C++, utilis par
un programme crit en VB, doit manipuler des instances de BSTR et utiliser des fonctions de
conversion. Cela rajoute de la complexit aux programmes et a donc un cot non ngligeable
(voire prohibitif si en plus on mlange des formats dencodages tels que ASCII ou UNICODE).

Le CTS (Common Type System)

343

Vue densemble du CTS


Le CTS se dcline en un ensemble de types valeur et rfrence. Le CTS vous laisse lopportunit
de dfinir vos propres types :
Types valeurs

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

Pointeurs non grs

Pointeurs non grs

Pointeurs de fonction non grs

Lgende

A drive de B

Type utilisateur Type connu du CLR Type connu du


CLR, non CLS

Figure 10 -1 : Vue densemble du CTS


Le reste de ce chapitre est ddi ltude des dirents types du CTS que lon peut classer comme
ceci :

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

Chapitre 10 : Le systme de types


et System.Array (dcrites respectivement page 370 et page 570). Notez que lutilisation de
la classe Array fait lobjet dune syntaxe particulire en C  et en VB.NET.

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 :

public Type GetType()


Renvoie le type dun objet.

public virtual String ToString()


Renvoie une chane de caractres dcrivant lobjet. Le comportement par dfaut est de retourner le nom du type, ce qui nest pas ce que souhaite le dveloppeur la plupart du temps.
Voici un exemple de redfinition et dutilisation de cette mthode :
Exemple 10-3 :
class Personne { // On aurait pu mettre struct `
a la place de class.
string m_Nom ;
int m_Age ;
public Personne(string Nom, int Age) { m_Nom = Nom ; m_Age = Age ; }
public override string ToString() {
return "Nom:" + m_Nom + " Age:" + m_Age ;
}
}

Comparer des objets

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.

public virtual void Finalize()


Cette mthode est appele lorsque lobjet est dtruit par le ramasse-miettes. En C  il est
impossible de redfinir explicitement cette mthode. Pour la redfinir, il faut utiliser une
syntaxe particulire explique en page 423.

protected object MemberwiseClone()


Cette mthode est explique un peu plus loin lors de la prsentation du clonage dobjet.

public static bool ReferenceEquals(object objA, object objB)

public virtual bool Equals(object obj)

public virtual int GetHashCode()


Ces trois mthodes sont expliques dans la section suivante.

Comparer des objets


Comparaison entre objets : identit vs. quivalence
Par dfaut il existe trois faons de comparer deux instances dun mme type rfrence : lutilisation dun des deux oprateurs == ou =! , lutilisation de la mthode dinstance Object.Equals() ou lutilisation de la mthode statique Object.ReferenceEquals(). Dans ce dernier cas, la comparaison se fait sur les rfrences et non sur ltat des objets. Deux rfrences sont
gales si elles rfrencent le mme objet. Cest la comparaison selon lidentit.
Par dfaut vous ne pouvez comparer deux instances dun mme type valeur quen utilisant la
mthode virtuelle Object.Equals(). Limplmentation par dfaut de cette mthode compare
les instances champs champs en ayant recours au mcanisme de rflexion. Deux instances dun
type valeur sont considres comme gales si leurs champs sont gaux deux deux. Cest la
comparaison selon lquivalence. Lexemple suivant illustre tout ceci :
Exemple 10-4 :
using System.Diagnostics ;
class TypeRef { public int state;}
struct TypeVal { public int state;}
public class Program {
public static void Main() {

346

Chapitre 10 : Le systme de types


// Comparaison selon lidentit
e.
TypeRef ref1 = new TypeRef() ; ref1.state = 3 ;
TypeRef ref2 = new TypeRef() ; ref2.state = 3 ;
Debug.Assert( ref1 != ref2 );
Debug.Assert( ! ref1.Equals(ref2) );
Debug.Assert( ! object.ReferenceEquals(ref1, ref2 ));
ref2 = ref1;
Debug.Assert( ref1 == ref2 );
Debug.Assert( ref1.Equals(ref2) );
Debug.Assert( object.ReferenceEquals(ref1, ref2) );
// Comparaison selon lequivalence.
TypeVal val1 = new TypeVal() ; val1.state = 3 ;
TypeVal val2 = new TypeVal() ; val2.state = 3 ;
Debug.Assert(val1.Equals(val2));
val1.state = 4;
Debug.Assert(!val1.Equals(val2));
}
}

Nous allons maintenant nous intresser aux possibilits oertes par le framework .NET pour
personnaliser la comparaison des instances de vos types.

Personnaliser le test dgalit entre deux objets


Si vous ne souhaitez que redfinir le test dgalit entre deux instances, il vaut mieux la fois rcrire la mthode Object.Equals() et redfinir les oprateurs == et != . Limplmentation
de vos oprateur doit alors appeler seulement la mthode Object.Equals(). Plus dinformation
concernant la redfinition des oprateurs sont disponibles en page 433.
Il est conseill de redfinir la mthode Equals() et les oprateurs dgalit pour tous les types
valeurs qui ont besoin davoir leurs instances compares. Vous y gagnerez en performance car
limplmentation par dfaut de la mthode Equals() pour les types valeur utilise la rflexion
et nest donc pas ecace.
Il est conseill que limplmentation de Object.Equals() dfinisse ce que lon appelle une relation dquivalence sur les instances du type concern. Pour cela il faut que limplmentation soit
rflexive (i.e x.Equals(x) retourne true pour tout instance x) symtrique (i.e x.Equals(y) est
gal y.Equals(x) pour toutes instances x et y) et transitive (i.e si x.Equals(y) et y.Equals(z)
sont gales true, alors x.Equals(z) est gale true pour toutes instances x, y et z).
Il est aussi conseill que la rcriture de la mthode Equals() dans une classe D drive de la
classe B, appelle la rcriture de la mthode Equals() dfinie dans la classe B si celle-ci existe.
En gnral, on dcide de personnaliser le test dgalit entre deux objets de type rfrence pour
obtenir un comportement de comparaison par quivalence. Cette pratique est notamment illustre par la faon dont les instances de la classe System.String sont compares. En eet, nous
verrons un peu plus loin que les chanes de caractres se comparent selon lquivalence.

Cloner des objets

347

Possibilit de stocker vos objets dans une table de hachage


Si vous ne redfinissez que la mthode Object.Equals() vous vous apercevrez que le compilateur C  2 met un avertissement vous conseillant de rcrire la mthode Object.GetHashCode().
Cet avertissement est mis indpendamment du fait que vous rcrivez ou non les oprateurs
dgalit. En fait, vous ne devez suivre ce conseil que si les instances du type concern peuvent
potentiellement servir de cls dans une table de hachage. Plus dinformation ce sujet sont
disponibles en page 584.

Personnaliser lordonnancement de vos objets


En plus de pouvoir comparer lgalit de vos instances, vous pouvez aussi dsirer pouvoir les
ordonner, par exemple pour les stocker dans une liste trie. Dans ce cas, vous avez le choix entre
implmenter linterface System.IComparable<T> sur le type des objets comparer ou implmenter linterface System.Collections.Generic.IComparer<T> dans une classe spcialise. Si
vous implmentez une de ces interfaces, il vaut mieux aussi surcharger les oprateurs == ,
!= , < , > , <= et >= de faon ce que les implmentations de ces surcharges applent limplmentation que vous avez fourni pour linterface.

Cloner des objets


Nous avons souvent besoin dobtenir une copie dun objet existant, cest--dire, de cloner un
objet. Il est plus rigoureux de dire que lon copie ltat de lobjet dans un autre objet. Pour
certaines classes, cloner une instance na pas de sens. Par exemple il ny a pas de sens copier
une instance de la classe Thread.
Pour les instances de types valeur, loprateur daectation = copie ltat de lobjet source
dans lobjet destination. La copie est ralise octet par octet. Par exemple, lopration de boxing
se sert de cette copie.
Pour les instances de types rfrence, loprateur daectation = copie la rfrence, et
non lobjet. Il est donc ncessaire de prvoir une mthode pour copier ltat dun objet
de type rfrence. Vous pouvez implmenter la mthode object Clone() de linterface
System.ICloneable, prvue cet eet. Cette interface ne prsente que cette mthode. Voici
un exemple qui illustre limplmentation de cette interface :
Exemple 10-5 :
class Article {
public string Description ;
public int Prix ;
}
class Commande : System.ICloneable {
public int Quantite ;
public Article Article ;
public override string ToString() {
return "Commande : " + Quantite + " x " + Article.Description +
" Cout total : " + Article.Prix * Quantite ;
}
public object Clone() {
Commande clone = new Commande();

348

Chapitre 10 : Le systme de types


// Copie superficielle
clone.Quantite = this.Quantite;
clone.Article = this.Article;
return clone;
}
}
class Program {
static void Main() {
Commande commande = new Commande() ;
commande.Quantite = 2 ;
commande.Article = new Article() ;
commande.Article.Description = "Chaussure" ;
commande.Article.Prix = 80 ;
System.Console.WriteLine(commande) ;
Commande commandeClone = commande.Clone() as Commande ;
commandeClone.Article.Description = "Veste" ;
System.Console.WriteLine(commande) ;
}
}

Cet exemple ache ceci :


Commande : 2 x Chaussure
Cout total : 160
Commande : 2 x Veste
Cout total : 160
Clairement, la modification faite sur larticle de la commande clone a t rpercute sur larticle de la commande originale. Cela est tout fait normal puisque dans ce programme il nexiste
quune seule instance de la classe Article. Elle est rfrence par les deux instances de la classe
Commande. On dit que la commande originale a subit une copie superficielle (shallow copy en anglais). La classe Object prsente la mthode protge MemberwiseClone() qui permet de raliser
une copie superficielle. Ainsi on peut rcrire lexemple prcdent comme ceci sans en modifier
la smantique :
Exemple 10-6 :
...
class Commande : System.ICloneable {
...
public object Clone() {
// Copie superficielle
return this.MemberwiseClone();
}
}
...
La notion de copie superficielle induit des bugs car en gnral il nest pas considr comme
normal quune modification sur un graphe dobjets clons soit rpercute sur le graphe original.
On peut lui prfrer la notion de copie en profondeur (deep copy en anglais) qui comme son nom
lindique, clone la totalit du graphe. Adaptons notre exemple la copie en profondeur :

Cloner des objets

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

Cout total : 160


Cout total : 160

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

Chapitre 10 : Le systme de types


return "Commande : " + Quantite + " x " + Article.Description +
" Cout total : " + Article.Prix * Quantite ;
}
// Constructeur par defaut.
public Commande() { }
// Constructeur de copie param`
etrable.
public Commande( Commande original , bool bDeepCopy) {
this.Quantite = original.Quantite;
if( bDeepCopy )
this.Article = original.Article.Clone() as Article;
else
this.Article = original.Article;
}
}
class Program {
static void Main() {
...
Commande commandeClone = new Commande( commande , true ) ;
...
}
}

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.

Le code IL de cette mthode Main() est le suivant :


.locals init ([0] int32 i)
IL_0000: ldc.i4.s
9
IL_0002: stloc.0
IL_0003: ldloc.0
IL_0004: box
[mscorlib]System.Int32
IL_0009: call
void Program::f(object)
IL_000e: ret
On voit que linstruction box du langage IL est prvue spcialement pour lopration de boxing.
Cette instruction place la rfrence vers la nouvelle instance sur le haut de la pile.

On profite de cet exemple pour souligner que le mot cl C  int est un alias vers le type
System.Int32.

Une optimisation dangereuse de lutilisation du boxing


Si vous tes concern par les performances, il faut savoir que lopration de boxing a un cot non
nul et non ngligeable. Le programme de lExemple 10-10 utilise deux fois le boxing alors que le
programme de lExemple 10-11 nutilise quune seule fois le boxing, ce qui le rend plus optimis.
Malgr les apparences, ces programmes nont pas le mme comportement car le premier ache
Rfrences direntes et le second ache Mmes rfrences . Cette optimisation est donc
assez dangereuse puisquelle modifie le comportement dune manire non vidente. Nous vous
conseillons de ne pas lutiliser.

352

Chapitre 10 : Le systme de types

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,

Les types primitifs

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 primitifs


C++ C 

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

Chapitre 10 : Le systme de types

Les types concernant la reprsentation des nombres entiers


Les types
mots-cls C 

Types du CTS correspondants

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.

Dfinition des constantes de ces types


Par dfaut une constante dun de ces types scrit simplement en base 10 (dcimal). Par exemple :
int i = 1024 ;
Cependant vous avez la possibilit dcrire une telle constante en base 16 (hexadcimale) :
int i = 0X00000400 ;
Voici un petit problme :
int i = 1000000000 ;
long j = 10 * i ;

// 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 :

Les types primitifs

355

int i = 1000000000 ; // i = 1 milliard


long j= 10L * i ;
// j egal `
a 10 milliards

Les types concernant la reprsentation des nombres rels


Motscls C 

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

Particularit des types float et double


Le type float (respectivement double) est dfini par la norme ANSI IEEE 754. Toutes les valeurs
de 32 (respectivement 64) bits ne reprsentent pas forcment un rel correct. Ces valeurs non
correctes sont reprsentes par le champ statique constant System.Single.Nan (respectivement
System.Double.Nan). Nan sinterprte par Not A Number .
Le type float (respectivement double) admet aussi deux autres valeurs trs pratiques reprsentant linfini positif et linfini ngatif. Lide est que les oprations et les fonctions usuelles
des rels vers les rels se comportent logiquement aux infinis. Les cas indtermins (comme
zro multipli par linfini) retourne la valeur Nan. Ces valeurs sont reprsentes par les champs
statiques constants PositiveInfinity et NegativeInfinity
Les types Single et Double prsentent les mthodes suivantes :
Mthodes

Description

public static float/double


Parse(string s)

Mme fonctionnalit que pour les types entiers.

public string ToString()

Mme fonctionnalit que pour les types entiers.

public static bool IsInfinity(float/double d)

Cette mthode statique retourne true si lentre est


infinie dans le type primitif concern.

public static bool IsNan(float/double d)

Cette mthode statique retourne true si lentre


nest pas valide dans le type primitif concern.

Les types single et double prsentent aussi les champs statiques constants suivants :

356

Chapitre 10 : Le systme de types

Champs

Description

Epsilon

Plus petite valeur positive non nulle reprsentable avec le type primitif concern.

MaxValue

Plus grande valeur positive reprsentable avec le type primitif


concern (existe aussi pour le type decimal).

MinValue

Oppos de MaxValue (existe aussi pour le type decimal).

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.

Pour que la constante soit convertie en float, il faut la suxer avec f ou F.


float
float
float
float

d1 = 1.2; // KO, la constante est un double qui ne peut


etre
// convertie implicitement en float (perte dinformation)
d2 = 1.2f ;
// OK
d3 = (float)1.2 ; // OK la conversion est explicite.
d4 = 12E-1F ;
// OK la conversion est explicite.

Pour que la constante soit convertie en decimal, il faut la suxer avec m ou M.


decimal d1 = 1.2 ; // KO, la constante est un double qui ne peut
etre
// convertie implicitement en decimal.
decimal d2 = 1.2m ;
// OK
decimal d3 = (decimal)1.2 ; // OK la conversion est explicite.
decimal d4 = 12E-1m ;
// OK la conversion est explicite.

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.

Les types primitifs

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()).

Le type reprsentant un caractre


Le mot-cl C  pour ce type est char. Une instance de ce type reprsente un caractre respectant
la norme UNICODE. Voici quelques exemples de constantes de type char :
char
char
char
char

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 )

Renvoie true si ch est une minuscule (les minuscules


accentues sont aussi considres comme des minuscules).

bool IsUpper(char ch )

Renvoie true si ch est une majuscule.

bool IsDigit(char ch )

Renvoie true si ch est un chire.

bool IsLetter (char ch )

Renvoie true si ch est une lettre.

bool IsLetterOrDigit(char ch)

Renvoie true si ch est une lettre ou un chire.

bool IsPunctuation(char ch )

Renvoie true si ch est un caractre de ponctuation.

bool IsWhiteSpace(char ch )

Renvoie true si ch est un espace.

char ToUpper(char ch )

Renvoie la majuscule de la lettre ch.

char ToLower(char ch )

Renvoie la minuscule de la lettre ch.

Conversions entre nombres entiers et chanes de caractres


Les types primitifs prsentent chacun les trois mthodes suivantes :

public static [le type primitif concern


e].Parse(string s)
Cette mthode statique parse la chane de caractres en entre de faon retourner la valeur
dans le type primitif concern. Deux exceptions peuvent tre lances :

358

Chapitre 10 : Le systme de types

-FormatException si la chane ne contient pas que des chires avec ventuellement un


des caractres + ou - en premire position.

-OverflowException si le nombre reprsent nentre pas dans lintervalle de valeurs du


type primitif concern.

public bool [le type primitif concern


e].TryParse(string s, out [le type primitif concerne])
Cette mthode statique parse la chane de caractres en entre de faon retourner la valeur
au moyen du paramtre out dans le type primitif concern. Cette mthode retourne un
boolen true si elle a pu eectivement parser une valeur dans le type concern, sinon elle
retourne false.
public string [le type primitif concern
e].ToString()
Cette mthode crit la valeur entire dans une chane de caractres. Cette mthode est la
rcriture de la mthode ToString() de la classe Object.

Lexemple suivant ache deux fois la chane de caractres "-234" :


Exemple 10-13 :
class Program {
static void Main() {
string s1 = "-234" ;
int i = System.Int32.Parse(s1);
string s2 = i.ToString();
System.Console.WriteLine(s1) ;
System.Console.WriteLine(s2) ;
}
}

Oprations sur les types primitifs


C++ C  En C  , le modulo sapplique aussi aux types rels. De plus le langage C  dispose
du mot-cl checked qui permet de forcer la vrification de toutes les conversions et oprations.
Une exception est lance si un problme est rencontr.

Oprations arithmtiques sur un mme type primitif


C

Les cinq oprations arithmtiques sont :

Oprateur

Opration

Laddition.

La soustraction.

La multiplication.

La division.

Le modulo (i.e le reste de la division)

Oprations sur les types primitifs

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;

Gestion de la division par zro


En ce qui concerne la division par zro sur un type entier et le type decimal, lexception
DivideByZeroException est lance.
En ce qui concerne la division par zro sur les types double et float, la variable prend la valeur
particulire NaN (Not a Number) (et non pas une valeur infinie comme on pourrait sy attendre).

Gestion des dpassements de capacit


Un dpassement de capacit est le fait dobtenir une valeur hors de lintervalle de valeurs autorises la suite dune opration. Un dpassement de capacit provoque les consquences suivantes :

Sur des variables de type decimal lexception OverflowException est lance.

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

Chapitre 10 : Le systme de types


Exemple 10-15 :
...

...

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.

Priorit des oprateurs


En ce qui concerne la priorit des oprateurs elle est dfinie comme suit :

+ - * / sont prioritaires sur %.

+ et --- sont de priorit gale.

* et / sont de priorit gale.

* et / sont prioritaires sur + et -.

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.

On peut toujours rendre une opration prioritaire en la mettant entre parenthses.

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)

...

Oprateurs de pr et post incrmentation et dcrmentation


C++ C  Une dirence avec C++ est quen C  , ces oprations sappliquent aussi aux types
rels. De plus, dans le cas dun type entier on peut grer le dpassement de capacit avec le motcl checked.
C  On peut incrmenter ou dcrmenter dune unit, une variable de type primitif entier
ou rel, au moyen des oprateurs ++ et --. Par exemple :

i++ ; quivaut i = i+1;

i-- ; quivaut i = i-1;

Oprations sur les types primitifs

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.

Oprations arithmtiques entre types primitifs dirents


C++ C  Les types primitifs invoqus tant dirents, les comportements des oprations
arithmtiques entre types primitifs dirents ne sont pas les mmes entre C++ et C  .
C  Lorsquune opration arithmtique seectue avec deux oprandes de types primitifs diffrents, le rsultat a un type dont lintervalle de valeur est au moins gal ou plus grand que
ceux des types des oprandes. Certains mariages de type sont interdits par le compilateur. Voici
le rsultat de mes propres essais, exposs dans un tableau :
Un des deux types

Lautre des deux types

Type
retourn

sbyte byte short ushort

sbyte byte short ushort

int

sbyte byte short ushort

int

int

sbyte byte short ushort int long

uint

long

Oprations entre types primitifs entiers

362

Chapitre 10 : Le systme de types

int long uint

int

long

ulong

int

ERREUR de
compilation

long

long

long

ulong

ulong

ulong

ulong

long

ERREUR de
compilation

Oprations entre types primitifs entiers et rels


sbyte byte short ushort int uint
long ulong

float

float

sbyte byte short ushort int uint


long ulong

double

double

sbyte byte short ushort int uint


long ulong

decimal

decimal

float double

decimal

ERREUR de
compilation

double

float

double

float

float

float

double

double

double

decimal

decimal

decimal

Oprations entre types primitifs rels

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.
...

Oprations sur les types primitifs

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.

Oprations binaires (bit bit)


C++ C  Rien de nouveau par rapport au C/C++ si ce nest que tout ceci est disponible en
64 bits grce aux types long et ulong.
C  Des oprations binaires (i.e bit bit) peuvent tre eectues entre oprandes de type int,
long , uint, ulong . Dans le cas dune opration binaire, les variables de type sbyte, short, byte,
ushort sont converties en type int :
Opration

Aussi nomme

Oprateur

AND

Et logique

&

OR

Ou logique

XOR

Ou exclusif logique

NOT

Inversion des bits

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 :

<< dcalage gauche.

>> dcalage droite.

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

Chapitre 10 : Le systme de types

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

// Ce champ est priv


e.
// Ce champ est public.

Les membres dune structure sont accessibles hors de la dfinition dune structure avec
loprateur point . .
...
Employee raymond ;
raymond.nom = "Raymond" ;
raymond.salaire = 3000 ;
...

// OK Le champ nom est public.


// KO Le champ salaire est priv
e.

Toute structure drive de la classe Object.

Une structure peut tre dfinie :

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 ne peut tre dfinie lintrieur dune mthode.

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

Chapitre 10 : Le systme de types


public string nom ;
public int GetSalaire(){return salaire;} // Cette m
ethode est publique.
}
...

Cependant les structures et les classes prsentent des dirences importantes :

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.

Le dveloppeur ne peut surcharger le constructeur par dfaut (i.e sans argument).

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.

Une numration ne peut tre dfinie lintrieur dune mthode.

Les numrations et les types entiers


Le compilateur considre que la valeur dune variable numration est un entier de type int.
Par consquent chaque valeur possible de lensemble des valeurs de lnumration est associe
une valeur entire. On peut alors :

Transtyper (caster) explicitement une variable dun type numration en un entier :


...
int i = (int) marque ;
...

Incrmenter ou dcrmenter une variable dun type numration :


...
Marque marque = Marque.Renault ;
Marque marque2 = marque++ ;
marque2
+= 2 ;
...

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

Chapitre 10 : Le systme de types

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 :

static string Format(Type type, object value, string format)


Cette mthode permet de rcuprer une chane de caractres correspondant la valeur de
lnumration : largument format doit tre gal "G" ou "g" pour renvoyer le nom (par
exemple "Renault"). Il doit tre "D" ou "d" pour renvoyer la valeur correspondante (par
exemple "100") .
Exemple 10-24 :
...
Marque marque = Marque.Renault ;
string s = System.Enum.Format( typeof( Marque ) , marque , "G" ) ;
// Ici la chane de caract`
eres s contient "Renault".
...

static object Parse(Type type, string value, bool ingnoreCase)


Convertit une chane de caractres en une des valeurs de lnumration. Si largument
ignoreCase vaut true, cette mthode ne tient pas compte des majuscules/minuscules :
Exemple 10-25 :
...
Marque marque = (Marque)
System.Enum.Parse(typeof(Marque),"ReNaUlt",true) ;
// Ici marque prend la valeur Marque.Renault.
...
Si la chane de caractres ne correspond aucune valeur, lexception ArgumentException
est gnre.

string [] GetNames(Type type)


Renvoie les dirents noms de lnumration dans un tableau de chane de caractres. Par
exemple :

Les numrations

369

Exemple 10-26 :
...
foreach( string s in System.Enum.GetNames( typeof(Marque) ) )
System.Console.WriteLine(s) ;
...

object[] GetValues(Type type)


Renvoie les direntes valeurs de lnumration dans un tableau dobjets. Par exemple
(soyez conscient quici la rfrence o rfrence tour tour chaque valeur de lnumration
boxe) :
Exemple 10-27 :
...
foreach( object o in System.Enum.GetValues( typeof(Marque) ) )
System.Console.WriteLine( o.ToString() ) ;
...

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

Chapitre 10 : Le systme de types

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.

Les chanes de caractres


La classe System.String
C++ C  Le framework .NET introduit la classe System.String pour stocker des chanes de
caractres et cela rompt compltement avec les traditionnels (et dangereux) char* et char[]
(adieu les strcpy() et autres fonctions dangereuses). La philosophie dutilisation de la classe
String est proche de celle du type string fourni par la STL du C++.
Une grosse dirence avec le type de la STL est quen C  , une instance de la classe String est immuable, elle ne peut tre modifie. Elle garde donc constamment la valeur qui lui a t donne
par son constructeur.
Une autre grosse dirence est que la convention C/C++ de terminer une chane de caractres
par le caractre nul nexiste plus en C  .
C  Le mot-cl C  string est un alias vers la classe System.String. Une instance de cette classe
reprsente une chane de caractres au format UNICODE.
La classe String est dclare comme sealed, cest--dire quaucune classe ne peut en driver. De
plus les instances de la classe String se comparent selon lquivalence.
Les instances de la classe String sont immuables (immutable en anglais). Cest--dire quune
chane de caractres est initialise par un constructeur, mais elle nest jamais modifie. Les
nombreuses mthodes de modification de chanes de caractres retournent toujours une
nouvelle instance de la classe String contenant la chane modifie. Ce comportement nuit
aux performances, aussi vous pouvez utiliser la classe System.Text.StringBuilder qui permet
de manipuler directement des chanes de caractres. Cette classe est prsente un peu plus loin.
Les trois caractristiques prcdentes (sealed, comparaison selon lquivalence et immuabilit des instances) font quen pratique les chanes de caractres se manipulent quasiment
comme des instances de type valeur. Cependant gardez bien lesprit que le type String
est une classe et est donc de type rfrence. Nous vous conseillons de revenir au dbut du
prsent chapitre pour tre bien conscient de tout ce que cela implique (manipulation seulement
par des rfrences, stockage des instances de String sur le tas, prise en compte par le ramassemiettes etc).

Les chanes de caractres

371

Chanes de caractres constantes rgulires


C++ C 

Contrairement C++, C  propose deux sortes de chanes de caractres constantes :

Les chanes de caractres constantes rgulires, similaires aux chanes de caractres constantes
de C++.

Les chanes de caractres constantes verbatim, dcrites dans la section suivante.

C

Voici un exemple de constantes de type string :


string

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

Par exemple le programme suivant ache "hel\nlo" :


Exemple 10-29 :
class Program {
public static void Main() {
string s = "hel\\nlo" ;
System.Console.WriteLine(s) ;
}
}

372

Chapitre 10 : Le systme de types

Chanes de caractres constantes verbatim


Le langage C  autorise la dfinition dune chane de caractres constante verbatim en ajoutant le
caractre @ juste devant la double apostrophe marquant le dpart de la chane. Une chane
de caractres constante verbatim a les particularits suivantes :

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 ?

Manipulation de chanes de caractres


La classe String prsente plusieurs mthodes pour la manipulation de chanes de caractres.
Par exemple on peut obtenir la taille dune chane, concatner deux chanes, rechercher une
chane lintrieur dune autre chane etc.

Les chanes de caractres

373

Les mthodes (toutes


publiques)

Description

int Length()

Renvoie le nombre de caractres.

static int Compare( string


s1,string s2)

Renvoie 0 si s1 est gal, caractre par caractre s2. Sinon


calcule un poids sur chaque chane et renvoie la dirence
Poid(s2)-Poid(s1). Le poids est calcul selon lordre des
caractres dans la norme UNICODE.

int CompareTo(string s)

Semblable Compare() mais cette mthode est non statique. Retourne Poids(de linstance)-Poids(de largument).

static string Concat( string


s1,string s2)

Retourne une nouvelle chane qui est la concatnation de


s1 puis s2. Notez que la notation s1 + s2 est quivalente.

static string Copy(string s)

Retourne une nouvelle chane qui est gale s.

bool EndWith(string s)

Retourne true si la chane reprsente par linstance courante se termine par la chane s.

static string Format( string


s, de un `
a quatre arguments)

Met en forme une chane de caractres partir dau plus


quatre arguments. Voir la section suivante pour la mise en
forme.

int IndexOf( char c )

Renvoie lindex du caractre c dans la chane reprsente


par linstance courante. Si le caractre est en premire position lindex vaut 0. Si le caractre est absent de la chane
cette mthode retourne -1. La mthode LastIndexOf() est
quivalente, mis part quelle balaye la chane de droite
gauche.

int IndexOf( char c ,int pos


)

Idem, mais la recherche commence partir du pos-ime


caractre.

int IndexOf( string s )

Idem, mais on recherche une autre chane au lieu dun caractre.

int IndexOf( string s , int


pos )

Idem, mais la recherche commence partir du pos-ime


caractre.

string Insert( int pos,


string 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.

string PadLeft( int n )

Retourne une nouvelle chane qui est la chane reprsente par linstance courante dans laquelle on a insr n espaces au dbut.

374

Chapitre 10 : Le systme de types

string PadLeft( int n , char


c )

Idem, mais le caractre de remplissage est c.

string PadRight( int n )

Idem, mais le remplissage se fait droite, avec des espaces.

string PadRight( int n ,


char c )

Mme chose mais le remplissage se fait droite, avec le caractre c.

string Remove( int pos, int


n )

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.

string Replace( char oldchar, char newchar )

Retourne une nouvelle chane qui est la chane reprsente par linstance courante dans laquelle on a remplac
chaque caractre oldchar par newchar.

string Split( char[] )

Retourne un tableau de nouvelles chanes obtenues partir de la chane reprsente par linstance courante. Un tableau de caractres sparateurs est pass en argument.

bool StartWith( string s )

Retourne true si la chane reprsente par linstance courante commence par la chane s.

string SubString(int pos,


int n)

Retourne une nouvelle chane gale lextraction de la


sous chane la pos-ime position de longueur n caractres.

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).

string Trim( char[] )

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).

Les chanes de caractres

375

Mise en forme dune chane de caractres


C++ C  En C/C++ la mise en forme des chanes de caractres utilise des indicateurs
(comme "%d") au sein de la chane de caractres fournie printf(). Ces indicateurs contiennent
une information sur le type de la variable acher.
En C  la mise en forme passe toujours par des indicateurs, mais ils sont dirents et ne
contiennent plus aucune information de type. En revanche ils contiennent linformation de
position dans la liste des arguments de la mthode Format().
C

Cette section dtaille laction de la mthode statique Format() de la classe string :


static string Format(string s, de un `
a quatre arguments) ;

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"

Utilisation de deux arguments.

"ab{1}cd{0}ef",i,d

"ab3.1415cd123ef"

Autre utilisation de deux arguments.

"ab{0}{0}cd ",i

"ab123123cd"

Double utilisation du mme argument

"ab{0}cd{1}ef",i

ERREUR `
a lex
ecution

Un deuxime argument est requis.

"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"

Mme chose mais le cadrage se fait gauche.

"ab{0:0000}cd",i

"ab0123cd"

Achage dau moins quatre chires, quitte


mettre des zros en tte.

376

Chapitre 10 : Le systme de types

"ab{0,6:0000}cd",i

"ab 0123cd"

Achage dau moins quatre chires quitte


mettre des zros en tte, reprsent sur 6 caractres, avec cadrage droite.

"ab{0:####}cd",i

"ab123cd"

Un caractre # remplacer nest pas utilis.

"ab{0:##}cd",i

"ab123cd"

Le format nest pas respect.

"ab{0:##.##}cd",d

"ab3,14cd"

Au plus deux dcimales sont aches.

"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"

Reprsentation hexadcimale avec des lettres


majuscules.

"ab{0:x}cd",I

"ab7bcd"

Reprsentation hexadcimale avec des lettres


minuscules.

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.

Les chanes de caractres

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.

La classe StringBuilder prsente les mthodes suivantes :

int EnsureCapacity(int capacity)


Assure que la capacit est au moins gale la valeur de capacity. Si ce nest pas le cas, la
capacit est augmente. La nouvelle capacit est retourne.

StringBuilder Append(...)
Ajoute des caractres la fin de la chane de caractres. Cette mthode existe en de nombreuses versions surcharges.

StringBuilder Insert(int index,...)


Insre des caractres la position spcifie par index. La position 0 signifie le premier caractre de la chane. Cette mthode existe en de nombreuses versions surcharges.

StringBuilder Remove(int startIndex,int length)


Supprime les caractres compris entre la position startIndex et la position startIndex+length. Lexception ArgumentOutOfRangeException est lance si une de ces positions
est ngative ou excde la taille de la chane de caractres.

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

Chapitre 10 : Le systme de types


System.Text.StringBuilder("hello") ;
Display(s) ;
//La chane : "hello"
// Length
: 5
// Capacity : 16
s.Insert( 4 ,
Display(s) ;
//La chane :
// Length
:
// Capacity :

"--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.

Les dlgations et les dlgus


Introduction
C++ C 

C  dfinit la notion de dlgus qui est absente en C++.

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.

Les dlgations et les dlgus

379

C  C  permet la cration de classes particulires avec le mot-cl delegate en post-fixe. On


appelle ces classes dlgations. Les instances des dlgations sont appeles dlgus.
Conceptuellement, un dlgu est une rfrence vers une ou plusieurs mthodes (statiques ou
non). On peut donc appeler/invoquer un dlgu avec la mme syntaxe que lappel dune
mthode. Ceci provoque lappel des mthodes rfrences. Notez que lappel de ces mthodes
est eectu par le mme thread qui ralise lappel au dlgu. On parle donc dappel synchrone.
Il existe de nombreuses dlgations de base, dfinies dans le framework .NET. Vous pouvez trouver la liste de ces dlgations dans larticle MulticastDelegate Hierarchy des MSDN. Comme
lindique cet article, chaque dlgation, quelle soit propritaire ou standard, est une classe drive de la classe System.MulticastDelegate. Cette classe est trs spciale, on ne peut en driver
explicitement. Seuls les compilateurs des langages .NET peuvent construire des classes qui drivent de MulticastDelegate. Grce certaines mthodes de la classe MulticastDelegate, un
dlgu peut aussi servir eectuer des appels asynchrones sur une mthode. Cette notion de
dlgus asynchrones fait lobjet dune autre section, page 171.

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.

Utilisation de dlgus avec des mthodes statiques


Voici un exemple utilisant deux dlgations :
Exemple 10-32 :
public class Program {
// Definition des delegations Deleg1 et Deleg2.
delegate void Deleg1();
delegate string Deleg2(string s);
static void f1() {
System.Console.WriteLine("Appel de f1.") ;
}
static string f2(string s) {
string _s=string.Format("Appel de f2 avec largument \"{0}\"",s) ;
System.Console.WriteLine(_s) ;
return _s ;
}
public static void Main() {
// Cree un delegue instance de Deleg1 r
ef
eren
cant la m
ethode f1().
Deleg1 d1 = new Deleg1(f1);
// Appel de f1() avec le del
egu
e d1.
d1();
// Cree un delegue instance de Deleg2 r
ef
eren
cant la m
ethode f2().
Deleg2 d2 = new Deleg2(f2);

380

Chapitre 10 : Le systme de types


// Appel de f2("hello") avec le d
el
egu
e d2.
string s = d2("hello");
}
}

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) ;

Infrence de la dlgation par le compilateur C  2


Le compilateur C  2 introduit une facilit avec la possibilit dinfrer le type dun dlgu que
lon cre. Vous pouvez ainsi assigner directement une mthode un dlgu cr implicitement. Le programme prcdent peut ainsi tre rcrit comme suit :
Exemple 10-33 :
public class Program {
delegate void Deleg1() ;
delegate string Deleg2(string s) ;
static void f1() {
System.Console.WriteLine("Appel de f1.") ;
}
static string f2(string s) {
string _s = string.Format(
"Appel de f2 avec largument \"{0}\"", s) ;
System.Console.WriteLine(_s) ;
return _s ;
}
public static void Main() {
Deleg1 d1 = f1; // Au lieu de
...new Deleg1(f1)...
d1() ;
Deleg2 d2 = f2; // Au lieu de
...new Deleg2(f1)...
string s = d2("hello") ;
}
}
Ne vous y trompez pas : ceci est juste une facilit dcriture. Si vous analysez le code IL produit
par cet exemple vous pourrez visualisez quil y a bien appel aux constructeurs des dlgations
Deleg1 et Deleg2.

Les dlgations et les dlgus

381

Utilisation de dlgus avec des mthodes non statiques


On peut obtenir le mme comportement avec des mthodes non statiques. Naturellement
lorsque lon fournit la mthode rfrencer, il faut la lier avec lobjet sur lequel elle doit tre
appele. Par exemple :
Exemple 10-34 :
using System ;
public class Article {
private 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() {
// Cree un article de prix 100.
Article article = new Article(100);
// Cree un delegue referen
cant IncPrix() sur lobjet article.
Deleg deleg = article.IncPrix;
int p1 = deleg(20) ;
Console.WriteLine(
"Prix de article apr`es incr
ement de 20 : {0}", p1) ;
int p2 = deleg(-10) ;
Console.WriteLine(
"Prix de article apr`es un d
ecr
ement de 10 : {0}", p2) ;
}
}
Ce programme ache ceci :
Prix de article apr`es increment de 20 : 120
Prix de article apr`es un decrement de 10 : 110

Utilisation de dlgus avec plusieurs mthodes


On peut rfrencer plusieurs mthodes (statiques ou non) de mmes signatures avec le mme
dlgu. Dans ce cas lappel au dlgu entrane lappel successif de ces mthodes par le mme
thread qui a provoqu lappel. Ces mthodes sont appeles dans lordre dans lequel elles ont t
ajoutes dans le dlgu, chacune avec les mmes arguments. Par exemple:
Exemple 10-35 :
using System ;
public class Article {
public int m_Prix = 0 ;
public Article(int Prix) { m_Prix = Prix ; }

382

Chapitre 10 : Le systme de types


public int IncPrix(int i) {
m_Prix += i ;
return m_Prix ;
}
}
public class Program {
public delegate int Deleg(int i) ;
public static void Main() {
// Cree trois articles de prix diff
erents.
Article a = new Article(100) ;
Article b = new Article(103) ;
Article c = new Article(107) ;
// Assigne 3 methodes `a un m
eme d
el
egu
e.
Deleg deleg = a.IncPrix;
deleg += b.IncPrix;
deleg += c.IncPrix;
// Incremente les trois prix de 20 en un seul appel `
a deleg.
int p1 = deleg(20);
// Ici p1 vaut 127, le prix du dernier article.
Console.WriteLine(
"Prix apr`es increment de 20 : a:{0} b:{1} c:{2}",
a.m_Prix , b.m_Prix , c.m_Prix) ;
// Decremente les trois prix de 10 en un seul appel `
a deleg.
int p2 = deleg(-10);
// Ici p2 vaut 117, le prix du dernier article.
Console.WriteLine(
"Prix apr`es un decrement de 10 : a:{0} b:{1} c:{2}",
a.m_Prix , b.m_Prix , c.m_Prix ) ;
}
}

Cet exemple ache :


Prix apr`es increment de 20 : a:120 b:123 c:127
Prix apr`es un decrement de 10 : a:110 b:113 c:117
Si la signature retourne une valeur dans le cas o il y a plusieurs mthodes rfrences cest la
valeur retourne par le dernier appel qui est renvoye par le dlgu.
Bien que cet exemple ne le montre pas, le mme dlgu peut la fois rfrencer des mthodes
statiques et des mthodes non statiques, de la mme classe ou de classes direntes. Il sut juste
que ces mthodes aient la mme signature et le mme type de valeur de retour.

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.

Les dlgations et les 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

Subtilit dans la manipulation des dlgus


Lexemple prcdent montre que lon peut ajouter (respectivement supprimer) un dlgu
un autre dlgu de mme classe avec loprateur += (respectivement -= ). Si le dlgu
ajout contient plusieurs rfrences de mthodes, ce sont toutes ces rfrences qui sont ajoutes
la fin de la liste des rfrences du dlgu cible. En revanche, dans le cas de la suppression,
il se peut que lopration ne puisse se faire si la sous liste de rfrences du dlgu supprim
napparat pas dans la liste de rfrence du dlgu cible. Lexemple suivant illustre ceci :
Exemple 10-37 :
using System ;
public class Article {

384

Chapitre 10 : Le systme de types


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) ;
// Construction du delegu
e ( a.IncPrix , b.IncPrix , c.IncPrix ).
Deleg deleg = a.IncPrix ;
Deleg deleg1 = b.IncPrix ;
deleg1 += c.IncPrix ;
deleg += deleg1 ;
deleg(10) ;
Console.WriteLine("a:{0} b:{1} c:{2}",
a.m_Prix , b.m_Prix , c.m_Prix ) ;
// Essai de suppression du d
el
egu
e ( a.IncPrix , c.IncPrix )
// non present dans ( a.IncPrix , b.IncPrix , c.IncPrix ).
Deleg deleg2 = a.IncPrix ;
deleg2 += c.IncPrix;
deleg -= deleg2 ;
deleg(10) ;
Console.WriteLine("a:{0} b:{1} c:{2}",
a.m_Prix , b.m_Prix , c.m_Prix ) ;
// Essai de suppression du d
el
egu
e ( a.IncPrix , b.IncPrix )
// present dans ( a.IncPrix , b.IncPrix , c.IncPrix ).
Deleg deleg3 = a.IncPrix ;
deleg3 += b.IncPrix;
deleg -= deleg3 ;
deleg(10) ;
Console.WriteLine("a:{0} b:{1} c:{2}",
a.m_Prix , b.m_Prix , c.m_Prix) ;
}
}

Cet exemple ache :


a:110 b:113 c:117
a:120 b:123 c:127
a:120 b:123 c:137

Les types nullables

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().

Les types nullables


Les concepteurs de C  2 ont ajout la notion de type nullable pour pallier une faiblesse des
types valeurs par rapport aux types rfrences. Il est donc essentiel davoir bien assimil ces deux
notions prsentes en dbut de chapitre avant de pouvoir aborder cette section.

La problmatique dune valeur nulle pour les types valeurs


Une rfrence de type un type rfrence est nulle lorsquelle ne rfrence aucun objet. Cest la
valeur prise par dfaut par toute rfrence. Il sut danalyser le code de nimporte quelle application pour sapercevoir que les dveloppeurs exploitent intensivement les rfrences nulles.
En gnral, lutilisation dune rfrence nulle permet de communiquer une information :

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

Chapitre 10 : Le systme de types

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

override bool Equals(object other) ;


override int GetHashCode() ;
T GetValueOrDefault() ;
T GetValueOrDefault(T defaultValue) ;
override string ToString() ;

}
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) ;
}
}

volution de la syntaxe C  : Nullable<T> et le mot cl null


La syntaxe C  vous permet dassigner et de comparer le mot cl null une instance de System.Nullable<T> :
Nullable<int> ni = null;
System.Diagnostics.Debug.Assert( ni == null ) ;

Les types nullables

387

Ces deux lignes de code sont quivalentes :


// Appel du constructeur par d
efaut qui positionne HasValue `
a false.
Nullable<int> ni = new Nullable<int>() ;
System.Diagnostics.Debug.Assert( !nullable1.HasValue ) ;
Lutilisation de la structure System.Nullable<T> est intuitive, mais peut rapidement amener
se poser des questions. Il nest pas vident que ces deux programmes sont quivalents (les deux
codes IL gnrs sont quivalents) :
Exemple 10-39 :
class Program{
static void Main(){
System.Nullable<int> ni1 = 3 ;
System.Nullable<int> ni2 = 3 ;
bool b = (ni1 == ni2) ;
System.Diagnostics.Debug.Assert(b) ;
System.Nullable<int> ni3 = ni1 + ni2 ;
ni1++;
}
}
Exemple 10-40 :
using System ;
class Program {
static void Main() {
Nullable<int> ni1 = new Nullable<int>(3) ;
Nullable<int> ni2 = new Nullable<int>(3) ;
bool b = (ni1.GetValueOrDefault() == ni2.GetValueOrDefault()) &&
(ni1.HasValue == ni2.HasValue);
System.Diagnostics.Debug.Assert( b ) ;
Nullable<int> ni3 = new Nullable<int>() ;
if (ni1.HasValue && ni2.HasValue)
ni3 = new Nullable<int>( ni1.GetValueOrDefault() +
ni2.GetValueOrDefault() ) ;
if (ni1.HasValue)
ni1 = new Nullable<int>( ni1.GetValueOrDefault() + 1 ) ;
}
}
En outre, cela peut sembler trange que la linstruction ni++ appele lorsque la variable ni est
sense tre null ne provoque pas une exception de type NullReferenceException.

volution de la syntaxe C  : quivalence entre Nullable<T> et T ?


En C  2, vous pouvez faire suivre le nom dun type valeur non nullable T par un point dinterrogation. Dans ce cas, le compilateur C  2 remplacera toute expression T? par Nullable<T>. Pour
simplifier, vous pouvez imaginer que ceci est un prtraitement eectu directement sur le code
source, un peu comme un prcompilateur. Ainsi, la ligne suivante...

388

Chapitre 10 : Le systme de types


int? i = null ;

...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.

En revanche, le compilateur vous empche de convertir implicitement un objet dun type


nullable dans le type sous-jacent. De plus, il est dangereux de raliser une telle conversion explicitement sans tests pralables puisque vous risquer de lever une exception de type
InvalidOperationException.
Exemple 10-43 :
class Program{
static void
int? ni1
int? ni2
int i1 =

Main(){
= null ;
= 9 ;
ni1 ;

int i2 = ni2 ;

// KO: Cannot implicitly convert


//
type int? to int.
// KO: Cannot implicitly convert

Les types nullables

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 ;

}
}

Pas de traitement spcial de bool? en C  2.0


Contrairement ce que vous avez peut tre vu dans les versions btas de C  2.0, en version finale
il ny a plus de traitement spcial du type bool? par les mots-cls if, while et for. Ainsi cet
exemple ne compile pas :
Exemple 10-44 :
class Program{
static void Main(){
bool? b = null ;
// Cannot implicitly convert type bool? to bool.
if ( b ) { /*...*/ }
}
}

Les types nullables et les oprations de boxing et de unboxing


Lors de la conception de .NET 2.0, jusquau dernier moment les types nullables reprsentaient
un artefact qui nimpactait pas le CLR. Ils taient implments seulement laide du compilateur C  2.0 et de la structure System.Nullable<T>. Cela posait un problme puisque lorsquune
instance dun type nullable tait boxe elle ne pouvait tre en aucun cas nulle. Il y avait bien
une incohrence avec la manipulation des types rfrences :
string s = null ;
object os = s ;
int?
i = null ;
object oi = i ;

// 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

Chapitre 10 : Le systme de types


}
}

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 ;

// boxing dun nullable nul


// unboxing -> NullReferenceException lev
ee !

}
}

Les structures et les numrations nullables


La notion de type nullable peut sutiliser aussi sur vos propres structures et numrations. Cela
peut mener des erreurs de compilations rdhibitoires illustres par lexemple suivant o la
structure Nullable<MyStruct> ne supporte pas les membres de MyStruct.
Exemple 10-47 :
struct Struct {
public Struct(int i) { m_i = i ; }
public int m_i ;
public void Fct(){}
}
class Program {
static void Main(){
Struct? ns1 = null ; // OK
Struct? ns2 = new Struct?(3) ; //
//
Struct? ns3 = new Struct?() ; //
//
Struct? ns4 = new Struct(3) ; //

KO: Cannot implicitly convert


type int to Struct.
OK Struct.ctor() par d
efaut
est appel
e.
OK

Les types nullables

391

Struct? ns5 = new Struct() ;


ns4.m_i = 8 ; //
//
ns4.Fct() ; //
//

// 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

Chapitre 10 : Le systme de types

Dfinir un type sur plusieurs fichiers sources


C  2 prsente la possibilit dtaler la dclaration dune classe, dune structure ou dune interface
sur plusieurs fichiers sources. Les documentations anglaises ont recours lexpression partial
type pour nommer cette possibilit. Nous aurons recours dans la suite aux expressions type dfini
sur plusieurs fichiers sources et dfinition partielle dun type qui sont plus parlantes que type partiel.
Notez que vous ne pouvez dclarer une dlgation ou une numration sur plusieurs fichiers
sources.
Pour dclarer un type sur plusieurs fichiers sources, il faut faire prcder un des mots cls class,
struct ou interface par le mot cl partial dans chaque dclaration partielle. Les dfinitions partielles doivent imprativement appartenir au mme espace de nom. De plus, le compilateur
autorise quun type nadmettre quune seule dfinition partielle prcde du mot cl partial.
Les dirents fichiers sources contenant les direntes dfinitions partielles dun mme type
doivent tous tre compils en une seule fois. Une consquence est que cette possibilit ne peut
tre utilise pour taler la dfinition dun mme type sur plusieurs modules dun mme assemblage ou sur plusieurs assemblages. Cette limitation est une consquence du fait que la
possibilit de dfinir un type sur plusieurs fichiers sources nest que du sucre syntaxique. Elle ne
concerne que le compilateur C  2 csc.exe et na aucune incidence ni sur la faon dont les types
sont contenus dans les assemblages, ni par leurs traitements par le CLR lexcution.
Le fait de dfinir un type sur plusieurs fichiers sources a tendance complexifier la lecture du
code. Aussi, nous vous conseillons davoir recours cette possibilit que lorsque vous souhaitez
gnrer du code. Dans ce cas, il est apprciable de ne gnrer que partiellement le code dun type
afin de faire interagir aisment le code gnr avec le code fait la main . Cela vite davoir
recours des artifices pour rendre possible cette interaction. Visual Studio 2005 inclut plusieurs
gnrateurs de code qui exploite cette notion de dfinition partielle : gnrateur de formulaires
Windows Forms (page 669), le gnrateur de DataSet typs (page 731), le gnrateur de pages
web (page 869) etc.
Enfin, prcisons quun type encapsul dans un autre type peut tre dfini sur plusieurs fichiers
sources. Dans ce cas chaque dfinition partielle dun type encapsul doit imprativement tre
rdige dans une des dfinitions partielles du type encapsulant.

Les modificateurs qui doivent tre rpts


Les direntes dfinitions partielles dun mme type doivent toutes tre prcdes du mot cl
partial. En outre, si le type est gnrique, la dfinition des types paramtres doit tre rptes
pour chaque dfinition partielle. Les noms des types paramtres ainsi que leurs positions dans
la liste doivent tre identiques pour chaque dfinition partielle.

Les modificateurs qui peuvent tre rpts sans rpercussion sur


leurs eets
Les modificateurs de type suivants peuvent tre rpts ou non dans les direntes dfinitions
partielles dun type. Il sut quils soient prsents sur une seule dfinition partielle pour que
leurs eets se rpercutent sur le type entier. Bien entendu, le compilateur C  2 dtecte et sanctionne dune erreur toutes les incohrences telle quune dclaration partielle ayant une visibilit
publique et une autre ayant une visibilit interne.

Dfinir un type sur plusieurs fichiers sources

393

Les mots-cls abstract et sealed. Prcisons quun mme type ne peut tre la fois abstract
et sealed .

La visibilit dun type.

La classe de base dun type.

Lensemble des contraintes pour un type paramtre.

Les modificateurs dont les eets se cumulent


Les modificateurs de type suivant peuvent tre rpts dans les direntes dfinitions partielles
dun type. Dans ce cas leurs eets se cumulent. Ici aussi le compilateur C  dtecte et sanctionne
dune erreur toutes les incohrences telle que la dfinition rpte dune mme mthode dans
plusieurs dclarations partielles dun mme type.

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.

Les modificateurs dont les eets sont locaux


Seul le mot cl unsafe rentre dans cette catgorie. Il peut tre appliqu sur une ou plusieurs dfinitions partielles dun mme type. Dans ce cas, son eet nest limit quaux membres dclars
dans les dfinitions partielles concernes.

11
Notions de classe et dobjet

Remarques sur la programmation objet


C++ C  C  peut tre considr plus orient objet que C++. En eet la notion de fonction
globale ou de variable globale nexiste pas en C  . Seules les mthodes (statiques ou non) de
classes existent.
C  Le concept de la programmation procdurale est construit autour de la notion de fonction.
Tout programme est un ensemble de fonctions sappelant entre elles.
Le concept de la programmation objet est construit autour des notions dobjet et de classe. Une
classe est limplmentation dun type de donnes (au mme titre que int ou une structure).
Un objet est une instance dune classe.
Dans la programmation objet, tout programme peut tre vu comme un ensemble dobjets qui
interagissent entre eux. Nous allons dtailler comment C  traite la programmation oriente
objet (POO). Cependant cet ouvrage nest pas un ouvrage sur la POO. En eet, la plupart des
concepts sont abords mais cela ne sut pas pour savoir les utiliser bon escient.

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