001_HS02.indd 1 Le seul magazine écritpar et pourles développeurs Le magazinedesdéveloppeurs Printed inEU-Imprimé enUE-BELGIQUE7€Canada9,80$ CAN -SUISSE13,10FSDOMSurf7,50€ -TOM1020XPFMAROC55DH SPÉCIAL WINDOWS AZURE .NET 5 + +

SPÉCIAL AUTOMNE

12/10/2020 12:28 E M 01642 - 2HF: - 6,50 - RD

3’:HIKLQE=WU[ZU[:?k@a@a@c@p"; 2020 002.qxp_HS02 13/10/2020 11:51 Page2 003.qxp_HS02 13/10/2020 08:50 Page3

EDITO SOMMAIRE # Spécial automne 2020 .Net est mort, vive .Net 5.Net (et tout ce qui va avec) 3 8 - .Net 5 + C# 9.0 Être développeur Windows, Xamarin, .Net, UWP… 3 13 Windows Form, WPF, WinUI… n’a jamais été un long code tranquille. Il faut bien 3 22 Initier un projet .Net 5 avouer que Microsoft n’a pas facilité les choses : multiplication des couches, multiplication des noms, annonces et contre-annonces donnant une impression 5Windows de flottements, abandon de technologies parfois après une très très très très longue agonie alors qu’il 3 27 Pourquoi C++ en 2020 aurait fallu couper dans le vif. 3 36 WinRT Tout cela trouble le développeur : quelle technologie choisir ? Laquelle a le plus de chance de rester en vie 5Multiplateforme dans les 5 ans à venir ? Quel avenir pour telle ou telle couche ? Un jour, on nous annonce la mort pure et 3 48 Les nouveautés de Xamarin simple de Win32, mais finalement, il faut le garder, 3 54 Développer une app desktop pour Windows et macOS car trop d’apps l’utilisent et que les développeurs sont mécontents. Par contre, quand IE6 fut définitivement arrêté : on pouvait entendre des cris de joie ! 5Cloud/IoT

.Net, .Net Core, .Net Standard… Profusion de noms, 3 60 No Ops ?! Yes, Back2Bash… incompréhension sur les noms, les équipes ont eu 3 65 Azure FarmBeats parfois du mal à préciser les choses. Il était temps de redonner un peu de cohérence et de logique à toutes 3 70 Azure IoT Cental ces briques techniques et surtout, fournir une vision d’ensemble à des développeurs parfois un peu perdus. 5Web 3 75 Piloter votre chaîne Youtube… .Net 5 n’est finalement qu’une première étape mineure, mais importante. Mineure dans le sens où il 3 80 Edge sauce Chromium s’agit d’une évolution très douce. Importante, dans le sens, que .Net 5 fournit la fondation nécessaire pour les grosses évolutions annoncées pour 2021 : .Net 6, 5Divers intégration et dilution de Xamarin, projet Reunion, 3 4 Agenda .Net MAUI, etc. 3 6 Roadmap / Brèves Finalement, la véritable rupture n’est pas .Net 5 et 2020, mais 2021. La dernière BUILD a dévoilé les 3 7 Conférence DevCon#9 grandes lignes. Aux équipes Microsoft de mener la mue. 5Magazine Au-delà, on peut même se poser la question sur 3 42 Abonnement l’utilité, en 2020, du développeur desktop et donc du développeur .Net et Windows. C’est un peu comme si 3 43 Boutique Programmez! Apple rayait purement et simplement le développeur macOS. Même si l’OS desktop a perdu de son aura au profit du web, du mobile et du cloud, il reste primordial pour des millions d’applications, Prochain numéro d’utilisateurs et de développeurs. Rebootons la matrice. Programmez! 243

François Tonic - [email protected] Boss de niveau 42 disponible dès le 30 octobre

programmez.com// 3 004.qxp_HS02 12/10/2020 12:31 Page4

AGENDA

MEETUPS PROGRAMMEZ! DEVCON#9 : SPÉCIALE .NET 5 +

10 novembre : parlons serverless. Avec Aymeric Weinbach AZURE + WINDOWS +10 sessions & ateliers 22 décembre : C++20, avec Christophe Pichaud 19 novembre, à partir de 13h30 19 janvier 2021 : être développeur en 2021, en partenariat avec Arolla TOUS NOS ÉVÉNEMENTS 23 février : Java, en partenariat avec Arolla SONT, POUR LE MOMENT, VIRTUELS. Informations & inscription : programmez.com

NOVEMBRE 1 2 3 4 5 6 7 8 9 10 11 12 13 14 Meetup Programmez!/ 15 16 17 18 19 20 21 DevDay Sophi.A Sophi.A Sophi.A (Belgique) / DevCon en ligne spéciale .Net 5 / en ligne Cloudnord 2020 / en ligne 22 23 24 25 26 27 28 Voice Tech Voice Tech Paris Paris 29 30

DÉCEMBRE 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 Meetup Programmez! 29 30 31 30 30 30 30

JANVIER 2021 1 2 3 4 5 6 7 8 9 10 11 12 13 14

15 16 17 18 19 20 21 Meetup Programmez! 22 23 24 25 26 27 28

29 30 31 30 30 30 30

Merci à Aurélie Vache pour la liste 2020/2021, consultable sur son GitHub : https://github.com/scraly/developers-conferences-agenda/blob/master/README.md 4 //programmez.com 005_promo techno4-243.qxp_4 13/10/2020 08:51 Page68

NUMÉRO EXCEPTIONNEL 100 % APPLE LISA

2 ÉDITIONS : - STANDARD 52 PAGES - DELUXE 84 PAGES ÉDITIONS LIMITÉES

Commandez directement sur www.programmez.com Standard édition : 8,99 * €* Deluxe édition : 13,99 * Frais de port : 1,01 € € TECHNOSAURES le magazine du rétro-computing 006.qxp_HS02 12/10/2020 12:31 Page6

ROADMAP / BRÈVES

ARRÊTONS DE PARLER DE DÉVELOPPEUR .NET ! A partir de 2021, il y aura des changements majeurs. Xamarin se PARLONS DÉVELOPPEUR C# fondra dans .Net MAUI et disparaîtra comme technologie indépendante. Avec .Net 5, Microsoft adopte une mise à jour e développeur .Net, ou plus largement Des entreprises, particulièrement les ESN, majeure annuelle. Lsur les technologies Microsoft, n’est peuvent être prêtes à proposer des bonus pas un profil rare. La popularité de C# élevés, des conditions de travail Les principales annonces suite à la BUILD 2020 : prouve une certaine vivacité du langage. aménagés, voire un niveau salarial sortant Mais parler de développeur .Net est pour de la norme, pour pouvoir recruter des Fin 2020 2021 2022+ nous une aberration, on devrait parler de profils, même s’ils ne correspondent pas Août : Uno Platform 3.0 WinUI 3.0 WinUI 3.x développeur C# au sens large. Car si totalement au besoin. Cette situation n’est Septembre / octobre : Project Reunion .Net 7, 8 vous savez coder en C#, potentiellement pas nouvelle, ce qui change c’est son Xamarin.Forms 5.0 Visual Studio 16.x (?) vous savez coder avec toutes les ampleur. La pénurie n’est pas générale, il Novembre : .Net 5, WinUI 3 Automne (?) : .Net MAUI technologies incluant C# (.Net, ASP.Net, faut le rappeler. Certains profils sont plus Preview 3, C# 9 Novembre : .Net 6 Xamarin, Windows, etc.). recherchés que d’autres. Visual Studio 16.8 Malheureusement les cabinets de C#/WinRT recrutement et les annonces, ainsi que les Attention Project Reunion Preview études sur les salaires, gardent cette aux certifications terminologie obsolète. Dans de nombreuses technologies, il .Net MAUI Preview Quel salaire peut-on espérer ? Comme existe des certifications. Le monde WebView2 toujours, les écarts moyens sont Microsoft en propose de nombreuses pour importants. La base serait : tous les profils : développeur, DevOps, Support : LTS et current • 28-34 k€ en profil junior donc -3 ans sécurité, data, admin, etc. Microsoft a Soyez vigilant(e) sur les cycles de support de .Net. Officiellement : d’expérience décidé de supprimer plusieurs .Net Core 2.1.x : LTS .Net 6.0 : LTS • 35-42 k€ en profil confirmé certifications : MCSA, MCSD, MCSE. .Net Core 3.1.x : LTS .Net 7.0 : current • 42-53 k€ en profil senior hors Ile de Certaines le sont depuis cet été, d’autres .Net 5.0 : current .Net 8.0 : LTS France. 52-62 k€ en région Paris. prendront fin en janvier 2021. Comme toujours les fourchettes sont Liste complète des certifications et Le fait que .Net 5 ne soit pas LTS montre bien qu’il s’agit d’une version larges et nous retrouvons l’écart région examens retirés : parisienne et province. Nous sommes https://techcommunity.microsoft.com/t5/microsoft- de transition. On bénéficiera d’un LTS une version sur deux. Rappelons dans les grandes moyennes salariales. learn-blog/mcsa-mcsd-mcse-certifications-retire- que le LTS garantit un support actif sur 3 ans (après la disponibilité de Nous ne tenons pas compte ici du with-continued-investment/ba- la version finale). Les versions dites courantes (current) sont supportées variable ou des primes. Selon indeed, le p/1489670?BlogId=8&Id=375282 durant 3 mois ou jusqu’à la prochaine version LTS. salaire pivot serait 39 955 € bruts. Certaines ESN ou entreprises demandent Le constat est grosso modo le même chez aux nouveaux développeurs recrutés de chooseyourboss : passer rapidement les certifications pour Historique des versions • 34-40 k€ en junior valider les acquis. C’est particulièrement Année Version Principales nouveautés • 40-49 k€ en expérimenté le cas pour les nouveaux diplômés et les 2002 .Net 1.0 Version initiale • 50-60 k en senior développeurs juniors. Mais comme nous € 2003 .Net 1.1 Support IPv6, exécution side by side, Attention tout de même : la dynamique l’ont précisé plusieurs développeurs, la contrôle mobile ASP.Net dans les régions et métropoles n’est pas qualité et le niveau des certifications homogène. La demande C# est en effet varient. Attention : ces certifications sont 2005 .Net 2.0 Génériques, ClickOnce, ASP.Net 2.2, support 64 bits très diverse. Et il ne sera pas étonnant de payantes. Et certaines nécessitent 2006 .Net 3.0 WPF, WCF, WWF, CardSpace devoir déménager ou faire plus de plusieurs examens pour être validées. 2007 .Net 3.5 Linq, collections, réseau p2p transport selon votre région / ville. Avec l’évolution des outils et des 2010 .Net 4.0 Parallélisme, nouveautés sur le réseau, WWF, WCF, Comme nous l’a confié un responsable technologies, il faut aussi repasser les réseau, déploiement d’ESN dans le monde Microsoft, « la examens de temps en temps. 2012 .Net 4.5 Démarrage plus rapide, tableaux + 2 Go, rémunération est assez variable selon le La réelle reconnaissance vient quand un JIT en arrière-plan, UTF-16, amélioration ZIP, IDNA, candidat et le recruteur. En gros, quand développeur devient MVP. Cela indique 2015- .Net 4.6.x JIT 64 bits, diverses améliorations, types SIMD, on sort de reconversion, on démarre entre une implication dans la communauté et 2016 .Net Core 1.x certificat X509 avec algo ECDSA 30 et 34 k . Quand on sort de l’école, on une connaissance de son domaine (C#, € 2017- .Net 4.7.x Améliorations sur l’ensemble des couches voit de tout mais globalement ce sera Visual Studio, etc.). Un MVP peut 2018 .Net Core 2.x entre 36 et 42. J’ai un profil sorti apporter une vrai valeur à une ESN, une 2019 .Net 4.8 Nouveautés dans WCF, WPF, CLR d’alternative embauché à 51 k€. Pour des équipe. L’entreprise doit aussi jouer le jeu. profils très précis ou un lead dev, on peut Et c’est aussi un argument pour négocier .Net Core 3.x 2020 .Net 5.0 Version finale vite monter à 60 k€ ou plus. » le salaire.

6 //programmez.com 007.qxp_HS02 14/10/2020 08:49 Page7 DevCon#9 Conférence développeur 100 % .Net+Azure+Window

19 NOVEMBRE 2020 A PARTIR DE 13H30

10 sessions & quickies .Net 5 - C++20 - Grafana+Azure - Projet Orleans Blazor - .Net Core - etc.

Agenda complet et inscription : https://www.programmez.com

Evenement virtuel Conférence du magazine Programmez! En partenariat avec SoftFluent, ZDNet et CACD2 programmez.com// 7 008_021.qxp_HS02 13/10/2020 09:25 Page8

Christophe Anthony Giretti PICHAUD MVP, MCSD Miguel Bernard Leader de Domaine NET Developer, Blogger, Speaker MVP, Blogger, Speaker chez Infeeny .NET [email protected] http://blog.miguelbernard.co [email protected] http://anthonygiretti.com m@miguelbernard88 www.windowscpp.com

.NET 5 arrive (enfin) ! .NET 5 arrive à grands pas. Depuis son annonce le 6 mai 2019, beaucoup de ses nouveautés ont été présentées et sont déjà quasiment prêtes. A l’heure où nous écrivons ces lignes .NET 5 se trouve en preview 8 et pas mal de fonctionnalités sont déjà plutôt stables, notamment ASP.NET Core 5 et C# 9 que nous avons eu l’occasion de tester.

un CLI efficace, l’aspect open source et communautaire sur GitHub et les outils tels que Visual Studio et Visual Studio Code, il sera possible de profiter : • De l’interopérabilité avec Java sur toutes les plateformes ; • Idem pour Objective-C et Swift ; • CoreFX sera amélioré pour prendre en charge la compilation sta- tique de .NET (à l’avance – ahead of time), des empreintes plus petites et la prise en charge d’un plus grand nombre de systèmes d’exploitation. La sortie de .NET 5 est planifiée pour Novembre 2020. A noter que Microsoft a volontairement nommé la nouvelle version de .NET « .NET.5 » pour éviter toute confusion avec .NET Framework 4.x. En passant, la dernière version de .NET Framework est et sera la version 4.8. Microsoft a également mentionné que .NET 5 ne sera epuis la création du projet .NET Core 1 en novembre 2016, pas supporté à long terme et que seulement les versions numéro- Microsoft a ajouté environ cinquante mille API .NET tées paires seront supportées à long terme. Enfin, Microsoft prévoit DFramework à la plateforme. .NET Core 3 comble une gran- de ne pas sortir (ou peu) de versions mineures, l’objectif étant de de partie de l’écart entre .NET Core et .NET Framework 4.8 en sortir une fois par an une version majeure de .NET et de s’y tenir. termes de capacité. .NET 5 continue à combler ce retard et même Voici un diagramme des versions actuelles et planifiées de le rattraper en s’appuyant sur les efforts réalisés avec .NET Core. Il .NET :ᖥ reprend également le meilleur de Mono pour créer une plateforme unique que vous pourrez utiliser pour tous vos programmes .NET. Mono et CoreCLR Différences et points communs .NET 5 une nouvelle étape après .NET Core Mono est l’implémentation interplateforme de .NET. Il a commen- .NET 5 est la prochaine grande révolution de .NET après .NET cé par être une alternative open-source à .NET Framework et a fait Core. Il sera en effet possible de créer des applications diverses la transition vers le ciblage des appareils mobiles tels que les appa- avec le même runtime, donc une uniformité dans les comporte- reils iOS et Android. Mono est le runtime utilisé pour exécuter ments d’exécution de vos applications .NET ainsi qu’une expérience Xamarin. de développement homogène (base de code unique). Cela signifie CoreCLR est le runtime utilisé dans le cadre de .NET Core. Il a été donc : le code de vos applications et vos fichiers de projets vont très principalement destiné à la prise en charge des applications cloud, fortement se ressembler; peu importe le type de projet que vous il est utilisé pour les applications de bureau, l’IoT et le machine développerez. Pour que tout cela soit possible .NET 5 va reprendre learning. .NET Core et Mono ont beaucoup de similitudes mais comme mentionné précédemment le meilleur de .NET Core, aussi des différences qui les caractérisent. Il est donc logique de Mono, .NET Framework, mais aussi Xamarin. permettre au développeur de choisir l’expérience qu’il souhaite en En plus des habitudes appréciées prises avec .NET Core telles que faisant en sorte que le passage de l’un à l’autre se fasse de la l’implémentation d’application cross-platform, la haute performance, manière la plus simple possible.

8 //programmez.com 008_021.qxp_HS02 13/10/2020 09:25 Page9

.NET

JIT (just in time compiler) également fonctionner sur des mobiles et des applications de Dès le début, .NET s’est appuyé sur un compilateur juste à temps bureau et pas seulement dans un navigateur. Les applications (JIT) pour traduire le code de langage intermédiaire (IL) en code Cloud natives seront plus performantes et prendront moins de optimisé. Depuis ce temps, Microsoft a construit un runtime géré place, enfin .NET 5 va supporter HTTP3. Voyons désormais un peu basé sur un JIT efficace, performant et permettant une expérience plus en détail les améliorations de .NET 5. de développement rendant la programmation rapide et facile. L’expérience par défaut pour la plupart des application .NET 5 uti- Réorganisation des repository Github lisera le runtime CoreCLR basé sur JIT. Les deux exceptions Avec ASP.NET, .NET Core, Core, il y avait environ notables seront iOS et Blazor (WebAssembly), puisque les deux une centaine de repository à gérer, c’était devenu difficile, nécessitent une compilation native à l’avance (AOT). Microsoft a nettement simplifié ceci en proposant trois repository consolidés : AOT (Ahead of time) https://github.com/dotnet/runtime Le compilateur Mono quant à lui, pour .NET, est un compilateur https://github.com/dotnet/aspnetcore AOT qui permet de compiler du code en code natif qui peut s’exé- https://github.com/dotnet/sdk cuter sur une machine, tout comme le code C++. Le projet Blazor, comme mentionné précédemment utilise déjà Mono AOT et est le Amélioration des performances du code premier projet à passer à .NET 5. La compilation AOT restera généré par le JIT nécessaire également pour iOS et certaines consoles de jeux car Le code machine généré par le JIT est beaucoup plus performant, Microsoft fera de la compilation AOT une option pour les applica- les améliorations sont tellement nombreuses qu’il est impossible de tions qui nécessitent démarrage rapide et / ou faible empreinte. toutes les décrire ici, mais voici le repository Github si vous voulez en savoir plus : https://github.com/dotnet/runtime Conclusion Microsoft investira dans le débit et la fiabilité dans CoreCLR tout en Les diagnostics du chargement des travaillant davantage pour améliorer le démarrage et la réduction assembly avec Event Pipe de consommation mémoire avec le compilateur Mono AOT. Microsoft propose un outil nommé dotnet-trace qui a été introduit L’investissement en termes d’effort n’étant pas identique sur ces avec le SDK .NET 3. Il est toujours disponible avec .NET 5 et ce aspects, cela ne veut pas dire que l’investissement sur d’autres sera dernier propose en plus de ce qui existait déjà les diagnostics de différent également. Par exemple les capacités de diagnostic doi- chargement des assembly. Le tutoriel complet peut être trouvé ici : vent être les mêmes sur .NET 5, tant pour les diagnostics fonction- https://github.com/richlander/testapps/tree/master/trace-assembly-loading nels que pour les diagnostics de performance. Toutes les applications .NET 5 seront également constructibles Améliorations du Garbage Collector avec le .NET CLI, en assurant aux développeurs de pouvoir disposer Dans cette mouture de .NET 5, le Garbage collector bénéficie aussi des mêmes outils en ligne de commande. Enfin C# évoluera sur de quelques améliorations et notamment : tous les projets .NET 5 en se présentant sous la neuvième version • Les pauses du GC éphémères sont plus courtes pour les scénarios majeure : C# 9. où certains threads GC ont pris beaucoup plus de temps à mar- quer que d’autres ; AMÉLIORATIONS • Introduction du Pinned Object Heap, qui permettra au GC de CONCRÈTES DANS .NET 5 gérer les objets épinglés (Pinned Objects) séparément, et par conséquent d’éviter les effets négatifs des objets épinglés sur les A l’heure où nous écrivons ces lignes .NET 5 en est à sa preview 8. heap générationnels ; Nous sommes tout proche de la version Release candidate puisque • Autorisation de l’allocation d’objets volumineux à partir de la liste la preview 8 contient toutes les nouveautés de .NET 5. Il est possible libre pendant le balayage de l’arrière-plan SOH (Start of de télécharger le SDK ici : https://dotnet.microsoft.com/download/dotnet/5.0 Heading) ; Pour migrer les projets existants, il suffit de mettre à jour le target • Des corrections pour réduire le temps de suspension des threads framework comme suit : Garbage Collector en tâches de fond et utilisateurs. Cela réduit le temps total qu’il faut pour suspendre les threads gérés avant netcoreapp5.0 qu’un Garbage Collector puisse se produire. Concrètement l’unification de l’expérience de développement avec le .NET SDK va permettre d’utiliser des librairies de classes de base Améliorations de l’assembly (BCL) dans Xamarin ; actuellement Xamarin utilise la librairie des System.Text.Json classes de base de mono (Mono BCL). .NET 5 va également sup- Microsoft a introduit dans .NET 3.0 une nouvelle assembly, desti- porter le développement mobile car son SDK va permettre de créer née à remplacer (essentiellement pour des raisons de performan- des projets Xamarin Forms, et le CLI pour également en être ce) NewtonSoft.Json. Si l’arrivée de cette nouvelle assembly a lar- capable (dotnet new XamarinForms). Les projets d’applications gement été bien accueillie, elle a en revanche souffert de quelques natives seront compatibles sur différents supports tels que Windows manques que la version de .NET 5 vient combler : Desktop, Microsoft Duo (Android), et iOS. Un projet Blazor pourra • Préservation des références d’objet : par défaut, Newtonsoft.Json

programmez.com// 9 008_021.qxp_HS02 13/10/2020 09:25 Page10

.NET

sérialise par valeur. Par exemple, si un objet contient deux pro- rage. Quant à .NET 3, il a bénéficié de quelques optimisations priétés qui contiennent une référence à un même objet, les grâce à l’introduction de Span pour améliorer l’utilisation de valeurs des propriétés de cet objet sont dupliquées dans le JSON. la mémoire dans certains scénarios. Newtonsoft.Json a un paramètre PreserveReferencesHandling Dans .NET 5 le moteur de Regex a bénéficié d’améliorations qui sur JsonSerializerSettings qui vous permet de sérialiser par réfé- ont, dans la plupart des cas, permis d’améliorer les performances rence. System.Text.Json quant à lui prend en charge uniquement par 3 voir 6. la sérialisation par valeur et lance une exception pour les réfé- Voici ci-dessous grâce à l’outil de Visual Studio .NET Object rences circulaires. Allocation Tracking, on peut voir l’allocation mémoire des Regex • Création d’un nouveau namespace System.Net.Http.Json conte- comparé entre .NET Core 3.1 à gauche et .NET 5 à droite. Je vous nant des méthodes d’extension pour HttpClient (serialisation / laisse faire vos propres conclusions : ᖦ désérialisation) : • - HttpClientJsonExtensions : contient les méthodes d’extension Les applications à fichier unique pour envoyer/recevoir du contenu HTTP en tant que JSON, Les applications de fichier unique sont publiées et déployées en exemple : tant que fichier unique. L’application et ses dépendances sont toutes incluses dans ce fichier. Lorsque l’application est en cours var result = await Http.GetJsonAsync("api/users/profiles") d’exécution, les dépendances sont chargées directement à partir • - HttpContentJsonExtensions : contient les méthodes d’exten- de ce fichier en mémoire. Il n’y a pas de pénalité de rendement sion pour lire, puis analyser le HttpContent de JSON, exemple : avec cette approche. Lorsqu’elles sont combinées avec la coupe d’assemblage et la compilation à l’avance (AOT), les applications var response = await_httpClient.GetAsync("api/users/profiles"); de fichiers uniques sont plus petites et rapidement en démarrage. response.EnsureSuccessStatusCode(); Dans .NET 5.0, les applications de fichiers uniques sont principale- return await response.Content.ReadFromJsonAsync(); ment axées sur Linux. • - JsonContent : fournit du contenu HTTP basé sur JSON. Pour déployer une application à fichier unique, voici les com- Exemple : mandes à exécuter : • Application dépendante du framework: var result = await httpContent.ReadAsByteArrayAsync(); Amélioration des performances des dotnet publish -r linux-x64 --self-contained false /p:PublishSingleFile=true expressions régulières • Application à fichier unique prête à être activée : Le namespace System.Text.RegularExpressions existe dans le fra- dotnet publish -r linux-x64 --self-contained true /p:PublishSingleFile=true /p:PublishTrimmed mework .NET depuis des années, depuis .NET Framework 1.1. Il =true /p:PublishReadyToRun=true est utilisé par des milliers d’applications. Dans tout cela, il repré- sente une source importante de consommation de temps proces- Il est possible d’effectuer la même configuration d’application à seur. fichier unique à partir d’un fichier de projet : En termes de performance les Regex n’ont pas reçu beaucoup d’attention depuis, excepté depuis l’arrivée de .NET Core 2 avec l’introduction d’une option de compilation pour les Regex nommée RegexOptions.Compiled. Cette option permet de spécifier que l’ex- pression régulière est compilée dans une assembly et que cela per- Exe met une exécution plus rapide mais augmente le temps de démar- net5.0

10 //programmez.com 008_021.qxp_HS02 13/10/2020 09:26 Page11

.NET

true }; true linux-x64 o.Number = 2; // Erreur de compilation true true Constructeurs primaires Actuellement, une des choses les plus irritantes avec les construc- teurs est le fait qu’ils sont souvent très redondants et ne contien- nent pas d’informations très utiles, et ce, surtout pour les classes simples. En effet, la plupart du temps vous aurez un paramètre qui Autres améliorations aura le même nom qu’une propriété et le constructeur fera simple- • Amélioration des performances HTTP/1.1 ; ment l’assignation de cette valeur du paramètre vers la propriété. • Amélioration des performances HTTP/2 ; Beaucoup de lignes de code qui ont peu de valeur, mais qui sont • Amélioration des performances des extensions sur les strings, obligatoires afin d’obtenir une bonne encapsulation. comme string.ToLowerInvariant etc. ; Les constructeurs primaires tentent d’adresser et de simplifier cette • Amélioration des performances pour les processeurs de type problématique. L’idée est de laisser le compilateur émettre tout ce ARM64 ; code par défaut. Pour ce faire, vous devrez ajouter des parenthèses • Réduction de la taille des images des containers tels que Docker ; à la déclaration de votre classe en indiquant les paramètres voulus. • Les annotations nullables () ; Derrière, le compilateur se chargera d’ajouter une propriété à votre • Et bien d’autres encore. objet et d’en faire l’assignation lors de la construction. Ainsi, vous pourrez grandement réduire le nombre de lignes requises pour C# 9 déclarer un type, parfois même jusqu’à une seule ligne! L’an dernier, nous avons eu droit à une nouvelle version majeure de Vous trouverez un exemple regroupant les records et un construc- C# contenant beaucoup de nouvelles fonctionnalités et d’amélio- teur primaire dans le prochain exemple. rations du langage. À peine quelques mois plus tard, nous avons déjà droit à C# 9.0 qui risque d’être encore plus prometteur. Les records Microsoft met les bouchées doubles pour faire de C# un des lan- Avec l’arrivée des records, C# a maintenant une façon intégrée gages les plus agréables à utiliser, en enlevant tout de qui est sour- dans le langage pour créer des types immuables. Ne vous mépre- ce d’irritation, et en simplifiant la syntaxe afin que les développeurs nez pas, il est déjà possible aujourd’hui d’atteindre le même résul- puissent gagner en vitesse. tat, cependant ceci vous nécessitera beaucoup de travail. Dans cet article, nous découvrirons quelques-unes des nouvelles Afin de garder une syntaxe simple et légère, Microsoft a dû intro- fonctionnalités. duire un nouveau mot-clé, record. Celui-ci doit être utilisé en rem- placement du mot-clé class ou struct lorsque vous définissez un Attention! Les fonctionnalités et exemples décrits dans cet article type. C’est en combinant avec les constructeurs primaires que vous sont sujets à changement tant que la prochaine version de C# n’est irez chercher le maximum de valeur avec cette fonctionnalité. pas livrée officiellement. Il est aussi possible que certaines fonction- public class Person nalités soient remises à plus tard. { Les propriétés immuables public Person(string firstName, string lastName) { Les propriétés immuables sont déclarées à l’aide du nouveau mot- this.FirstName = firstName; clé init. Ce mot-clé remplace set et permet d’indiquer que l’on sou- this.LastName = lastName; haite pouvoir assigner une valeur à la propriété, mais seulement } lors de l’initialisation de l’objet et ne plus le permettre par la suite. Ainsi, la propriété devient immuable. public string FirstName { get; } public class Order public string LastName { get; } { } public int Number { get; init; } } // Équivalent avec un enregistrement et un constructeur primaire public record Person(string firstName, string lastName); Cette nouvelle fonctionnalité est très pratique lorsqu’on veut utiliser les initialiseurs d’objet sans pour autant permettre la mutation de Vous avec bien vu, une seule ligne pourra remplacer une déclara- la propriété une fois l’objet construit. tion qui autrefois en prenait plus de onze. D’ailleurs, plus la classe est complexe et longue, plus le bénéfice en nombre de lignes sau- var o = new Order vées sera accentué. { L’immutabilité offre bien des avantages, mais peut être inconvé- Number = 1, nient dans certains cas. Par exemple, si vous avez un objet plutôt

programmez.com// 11 008_021.qxp_HS02 13/10/2020 09:26 Page12

.NET

complexe et que vous voulez modifier une seule propriété, eh bien var s = new AsyncService(); ce n’est pas permis, car l’objet est immuable. Vous devez construire var result = await s.MyAsyncMethod(); un nouvel objet en spécifiant toutes les propriétés, dont celle qui Console.WriteLine($"Hello {args[0]}! result: {result}"); aura une valeur différente. Vous comprendrez que si votre objet est return 1; très gros ou complexe, ceci devient vite encombrant. Heureusement, Microsoft a pensé à vous en vous offrant le nou- Il est bon de prendre note ici que cet exemple est le contenu com- veau mot-clé with pour gérer ce cas. Ce mot-clé permet avec une plet du fichier Program.cs. Plus besoin de déclarer un namespace syntaxe toute simple de cloner un objet tout en remplaçant les ni de méthode Main. Pour ce qui est des arguments, on peut y valeurs des propriétés spécifiées. accéder en appelant la variable args qui existe presque par magie dans le contexte de ce fichier. Ce contexte d’exécution est d’ailleurs var p = new Person("Miguel", "Bernard"); asynchrone, ce qui veut dire que vous pouvez faire des appels asyn- var p2 = p with { lastName = "Lapierre" }; chrones avec await sans aucun problème. p2 est maintenant un nouvel objet distinct, qui possède une réfé- rence mémoire différente de p1. Ce nouvel objet contient Amélioration des expressions logiques « Miguel » comme prénom et « Lapierre » comme nom de famille. C# 9.0 supporte désormais les expressions logiques directement Les records ont aussi un autre comportement qui diffère grande- dans les filtres par patron. Cette fonctionnalité apporte aussi une ment de ce auquel vous êtes habitué. Lors de comparaison entre foule de nouveaux mots-clés tels que : and, or et not. deux objets, les enregistrements utilisent l’égalité structurelle au string ÉtatDeSanté(decimal poid) => poid switch lieu de l’égalité référentielle par défaut. Qu’est-ce que cela veut { dire ? Eh bien, au lieu de simplement comparer le pointeur en >= 18.5m and < 25m => "normal", mémoire, les enregistrements vont comparer toutes les valeurs de < 15m or > 30m => "en santé", toutes leurs propriétés afin de déterminer si une égalité existe. Par _ => "inconnu" exemple, ce comportement peut être très utile lorsque vous avez à }; manipuler des objets de transfert de données (DTO/POCO). Petit piège auquel il faut quand même faire attention. Actuellement, ce Dans certains cas, ces mots-clés rendent la lecture beaucoup plus comportement s’applique seulement à la méthode Equals et pas à facile que leur équivalent en symbole : &&, || et !. l’opérateur ==. if (s is not string) var p = new Person("Miguel", "Bernard"); // est plus facile à lire que var p2 = new Person("Miguel", "Bernard"); if (!(s is string)) Détection implicite de type p.Equals(p2); // True Le compilateur étant maintenant plus intelligent, il est désormais p == p2; // False capable de déterminer le type implicitement à partir du contexte Les programmes de niveau supérieur d’exécution. Ainsi, lors de la création d’un objet avec new vous Plusieurs langages offrent une forme très simple pour exécuter un n’aurez plus toujours besoin de répéter le type. programme, souvent en une seule ligne pour un programme de Cat c = new Cat("Chester"); // Avant type « Hello world ». L’équivalent en C# n’en prendra qu’une dizaine Cat c = new("Chester"); // C# 9.0 de lignes. Validation des valeurs nulles simplifiée // Avant Vous pouvez maintenant utiliser une syntaxe simplifiée pour valider namespace csharp les valeurs nulles directement dans la signature d’une méthode à { l’aide de l’opérateur ! Vous devez placer celui-ci en suffixe au para- class Program mètre qui doit être non nul et le tour est joué. { static void Main(string[] args) public void Before(string name) { { Console.WriteLine("Hello World!"); if (name is null) } throw new ArgumentNullException(); } } } public void Now(string name!) // C# 9.0 Avec C# 9.0, le compilateur est capable d’émettre automatique la { grande majorité de ces lignes, il n’est donc plus requis de les spé- } cifier. Tous les cas spéciaux sont supportés, que ce soient les appels asynchrones, les arguments d’entrées ou les valeurs de retour. Voici Le reste… on verra bien un programme qui combine un peu de tout : Il y a encore quelques autres fonctionnalités qui sont proposées, Voir le fichier program.cs mais comme elles sont encore en ébauche, elles risquent fortement

12 //programmez.com 008_021.qxp_HS02 13/10/2020 09:26 Page13

.NET

d’être reportées à une version ultérieure. Comme .NET est main- langage afin d’aborder ce problème. Avec tous ces nouveaux tenant open source, si vous êtes curieux vous pouvez suivre le pro- outils, les développeurs C# n’ont plus rien à envier aux autres lan- grès et toutes les discussions autour de C# 9.0 sur GitHub. gages. https://github.com/dotnet/roslyn/blob/master/docs/Language%20Feature%20Status.md Exemples supplémentaires Pour conclure Vous pouvez retrouver sur le dépôt GitHub ci-dessous le code sour- Un des plus grands reproches que la communauté exprime face à ce des exemples cités, ainsi que des cas d’utilisation plus avancés. C# est le fait qu’il est très verbeux. Comme vous pouvez le consta- https://github.com/mbernard/codeSamples/tree/master/csharp9 ter, cette version met un accent important sur la simplification du

Windows Form, WPF, WinUI et Xamarin avec .NET Core

Windows Forms pour .NET Core .NET Core propose des outils graphiques dans les éditions preview de Visual Studio. Windows Forms ou WinForms est une librairie graphique évène- mentielle qui encapsule le GDI Windows. C’est robuste, rapide et fiable. Cela existe depuis la première version de .NET en 2002. Pourquoi WinForms pour .NET Core ? .NET Core est open-source et multiplateformes (Windows, Linux, Mac). Le support de WinForms pour passer de .NET Framework à .NET Core nécessite des ajustements. WinForms ne fonctionne que sur Windows. ᖧ 10, qui sera désormais entièrement découplée du SDK UWP. WinForms Designer pour Visual Studio Nous nous concentrons sur l'activation de trois cas d'utilisation prin- en Preview cipaux : Le Designer est en preview dans Visual Studio depuis le mois de • Moderniser les applications Win32 existantes : cela vous permet mai 2020 dans la Preview v 16.6. Le Designer a complètement été d'étendre les applications Win32 (WPF, WinForms, MFC ..) exis- réécrit. Ce module semble être complexe car il met du temps à pas- tantes avec l'interface utilisateur moderne de à votre ser en RTM. propre rythme en utilisant la dernière version à venir de Xaml Islands ; WPF et .NET Core • Créer de nouvelles applications Windows : cela vous permet de WPF fournit aussi un Designer réécrit from scratch. Comme pour créer facilement de nouvelles applications Windows modernes "à WinForms, ce module est complexe et prend du temps. A l’heure la carte" avec votre choix de modèle d'application (Win32 ou de l’écriture de cet article, il n’est toujours pas disponible. WPF UWP) et de langage (.NET ou C ++) ; n’est disponible que pour Windows Desktop. Pas pour Linux ni Mac. • Activation d'autres frameworks : cela fournit l'implémentation native d'autres frameworks tels que React Native lors de l'exécu- Que choisir ? tion sous Windows WPF et ses composants XAML ont de nouveau le vent en poupe L'équipe a récemment publié WinUI 3 Preview 1, qui est une pré- car les composants XAML de Windows 10 sont maintenant en version précoce qui inclut la prise en charge des applications open-source (WinUI XAML sur GitHub). WinUI c’est l’avenir car Win32 et UWP. L'aperçu 1 est disponible pour tout le monde, mais WinUI permet l’inclusion de contrôles XAML en WinForms, WPF et notez qu'il présente des limitations et des problèmes connus, il n'est Win32. Microsoft ne fait pas ses produits avec WPF. Le modèle donc pas équipé pour les applications de production. Commencez MVVM est difficile à debugger. Préférez WinForms ! Plus rapide, ou apprenez-en plus sur l'aperçu 1 ici. La roadmap WinUI res- plus léger, plus facile à comprendre. De plus, c’est du Windows à semble à ceci : ᖧ l’état brut. Les API UWP Xaml existantes qui sont livrées dans le cadre du systè- me d'exploitation ne recevront plus de nouvelles mises à jour de fonc- WinUI 3 tionnalités. Elles recevront toujours des mises à jour de sécurité et des WinUI 3 élargira considérablement la portée de WinUI pour inclure correctifs critiques selon le cycle de vie du support Windows 10. la plateforme d'interface utilisateur native complète de Windows La plateforme Windows universelle contient plus que le cadre Xaml

programmez.com// 13 008_021.qxp_HS02 13/10/2020 09:26 Page14

.NET

(par exemple, modèle d'application et de sécurité, pipeline multi- 4° Rétrocompatibilité pour les nouvelles fonctionnalités média, intégrations de shell et Windows 10, prise en charge Les nouvelles fonctionnalités de WinUI continueront d'être rétro- étendue des appareils) et continuera d'évoluer. Toutes les nouvelles compatibles avec un large éventail de versions de Windows 10 : fonctionnalités Xaml seront simplement développées et livrées vous pouvez commencer à créer et à publier des applications avec dans le cadre de WinUI à la place. de nouvelles fonctionnalités dès leur sortie, sans avoir à attendre que vos utilisateurs mettent à jour Windows. Avantages de WinUI 3 WinUI 3 offrira un certain nombre d'avantages par rapport au fra- 5° Prise en charge du développement natif mework UWP Xaml actuel, WPF, WinForms et MFC, ce qui fera de WinUI peut être utilisé avec .NET, mais ne dépend pas de .NET : WinUI le meilleur moyen de créer des interfaces utilisateur pour les WinUI est 100% C++ et peut être utilisé dans des applications applications Windows : Windows non gérées, par exemple en utilisant C++ 17 standard via C++ / WinRT. 1° La plate-forme d'interface utilisateur native de Windows WinUI est la plateforme d'interface utilisateur native hautement 6° Mises à jour plus fréquentes optimisée utilisée pour créer Windows lui-même. Elle est désormais WinUI continuera à livrer de nouvelles versions stables 3x par an, plus largement disponible en utilisation pour tous les développeurs avec des versions mensuelles en avant-première. qui veulent atteindre des applications pleinement Windows. Il s'agit d'une plateforme d'interface utilisateur entièrement testée et éprou- 7° Développement open source et engagement communautaire vée qui alimente l'environnement du système d'exploitation et les WinUI continuera à être développé en tant que projet open source expériences essentielles de plus de 800 millions de PC Windows 10, sur GitHub. WinUI 2 est déjà open source dans ce dépôt, et il est , HoloLens, Surface Hub et d'autres appareils. prévu d'ajouter le framework WinUI 3 Xaml complet dans le dépôt dans les mois à venir. 2° Le dernier design Fluent Vous pouvez vous engager directement avec l'équipe d'ingénierie WinUI est le principal objectif de Microsoft pour l'interface utilisa- principale de Microsoft et contribuer aux rapports de bogue, aux teur et les contrôles Windows natifs et accessibles. Il est la source idées de fonctionnalités et même au code : consultez le Guide de définitive du système de conception Fluent sur Windows. contribution pour plus d'informations. Il prendra également en charge les dernières innovations de com- Vous pouvez également essayer les versions mensuelles des ver- position et de rendu de niveau inférieur telles que les animations sions préliminaires pour voir les nouvelles fonctionnalités en déve- vectorielles, les effets, les ombres et l'éclairage. loppement et aider à façonner leur forme finale.

3° Développement de bureau "à la carte" plus facile 8° Une cible Windows native pour les frameworks web et WinUI 3 vous permettra de mélanger et assortir plus facilement la multiplateformes bonne combinaison de : WinUI 3 est mieux optimisé pour les bibliothèques et les frame- • Langage: C # (.NET), C++ ; works sur lesquels s'appuyer. • Modèle d'application: UWP, Win32 ; Par exemple, la nouvelle implémentation Windows native C++ • Emballage: MSIX, AppX pour le , non emballé ; React haute performance travaille actuellement sur l'utilisation de • Interopérabilité: utilisez WinUI 3 pour étendre les applications WinUI 3 comme base. ᖨ WPF, WinForms et MFC existantes avec l'interface utilisateur Pour le moment, nous sommes en WinUI 3 Preview 2. Fluent moderne. WinUI c’est du C++ Comme indiqué dans le point n°5 précédent : « WinUI peut être uti- ᖨ lisé avec .NET, mais ne dépend pas de .NET : WinUI est 100% C++ et peut être utilisé dans des Application Windows » , n’impor- te quelle application Windows. L’envers du décor, c’est que les équipes Windows ne veulent pas de .NET dans Windows. Vous vous rappelez le traumatisme de Windows Vista ? Relisez ceci : https://arstechnica.com/information-technolo- gy/2011/06/windows-8-for--developers-the-longhorn-dream-reborn/ , ils expliquent le trauma de Windows Longhorn et Microsoft est passé à deux doigts de la catastrophe industrielle. Les équipes Windows ont une liste exhaustive de problèmes et de questions sur .NET aux- quels les équipes DevDiv (qui font .NET) n’arrivent pas à répondre ni résoudre. Donc, les équipes Windows ignorent .NET. Le XAML de Windows 10 (depuis Windows 8) est écrit en C++. Vous pour- riez me dire : et pourquoi pas WPF ? Parce que c’est du .NET… Les équipes Windows fournissent Windows XML qui se nomme

14 //programmez.com 008_021.qxp_HS02 13/10/2020 09:26 Page15

.NET

maintenant WinUI : la bataille est gagnée (une fois de plus) pour C++. Le système d’exploitation Windows fournit une extension de COM qui se nomme (WinRT) et c’est une com- binaison de nouvelles fonctionnalités MIDL et le support du C++ Moderne dans Windows. Le code Windows ne change pas d’une année sur l’autre et il est fait pour rester très longtemps. Windows est (very) LTS (Long Time Support). Il est important de savoir qu’il existe des divergences en interne chez Microsoft sur l’adoption de .NET et Windows. Cela permet de comprendre beaucoup de choses. WinUI est le meilleur exemple ! Avec .NET Core, on a le support WinForms et WPF mais le vrai hot stuff NEW, c’est WinUI et Microsoft ne fait pas la promotion de WPF. Vous avez des appli- cations WPF ? C’est certainement couplé avec du WCF, donc non supporté par .NET Core… Laissez-les en .NET Framework, c’est du legacy. ᖩ Le mobile avec Xamarin et le projet MAUI Pour ce qui concerne Blazor c’est un peu différent. Deux nouvelles Les produits Xamarin pour iOS et Xamarin pour Android existent propriétés s’ajoutent : toujours. La nouveauté concerne Xamarin.Forms qui passe en ver- sion 4.8 depuis Août 2020. La communauté Xamarin est en ébul- lition car le projet MAUI (Multi Application User Interface) arrive. C’est l’unification de Xamarin.Forms avec .NET Core sous .NET 5 net5.0 ou .NET 6. Il n’y a pas beaucoup d’informations existantes sur browser-wasm MAUI. Le projet n’est pas en preview publique pour le moment true mais nous avons un slide : ᖩ ASP.NET CORE 5 Et voilà la nouvelle version tant attendue d’ASP.NET Core ! Tout comme comme .NET 5, la version est encore en preview au Concernant Blazor encore, la référence au package moment où nous écrivons ces lignes, et comme pour .NET 5 nous Microsoft.AspNetCore.Components.WebAssembly.Build n’est tout en sommes à la preview 8 qui contient toute les caractéristiques de simplement plus nécessaire. la sortie finale en novembre. Enfin pour tout ce qui est relatif à ASP.NET Core en général, les La plupart des évolutions concernent Blazor, mais pas que, heureu- packages suivants doivent être mis à jour : sement ! Mais tout d’abord regardons comment migrer d’ASP.NET • Microsoft.AspNetCore.* Core 3.1 à ASP.NET Core 5.0 ainsi que les améliorations les plus • Microsoft.Extensions.* marquantes. • System.Net.Http.Json Pour pouvoir migrer, il va bien entendu falloir télécharger le dernier Exemple : SDK de .NET 5 ici : https://dotnet.microsoft.com/download/dotnet/5.0 mais aussi télécharger Visual Studio 2019, il faut savoir que la version minimale requise est la version 16.8 que vous pouvez télécharger ici : https://visualstudio.microsoft.com/fr/downloads/?utm_medium=microsoft&utm _source=docs.microsoft.com&utm_campaign=inline+link&utm_content=down- load+vs2019 Pour finir, les images docker subissent elles aussi un tout petit chan- D’ASP.NET Core 3.1 à ASP.NET Core 5.0 gement : Migrer d’une version à l’autre est assez simple, cependant il y a • Avant : quelques breaking changes que nous verrons un peu plus bas. docker pull mcr.microsoft.com/dotnet/core/aspnet:3.1 Premièrement il faut mettre à jour le target framework, comme suit : • Après : docker pull mcr.microsoft.com/dotnet/aspnet:5.0 Concernant maintenant les breaking changes, il ne sont pas si net5.0 nombreux que cela, Blazor, Kestrel, SignalR et les assembly de localisation subissent la plupart des modifications de rupture. Voici l’Url décrivant les modifications à effectuer : programmez.com// 15 008_021.qxp_HS02 13/10/2020 09:26 Page16

.NET

await context.Response.WriteAsJsonAsync(countries); }).AllowAnonymous(); }); La gestion customisée des échecs d’autorisations La gestion personnalisée des échecs d’autorisation est désormais plus facile avec la nouvelle interface IAuthorizationMiddleware ResultHandler qui est invoquée par AuthorizationMiddleware. L’implémentation par défaut reste la même, mais un gestionnaire personnalisé peut être enregistré dans le système d’injection de dépendance qui permet des choses comme les réponses HTTP per- sonnalisées en fonction des raisons de l’échec de l’autorisation. Un exemple qui démontre l’utilisation de l’IAuthorizationMiddleware ᖪ ResultHandler : https://docs.microsoft.com/en-us/dotnet/core/compatibility/3.1-5.0. using System; Ci-dessous le résumé des breaking changes : ᖪ namespace CustomAuthorizationFailureResponse.Authorization { Les extensions JSON pour HttpRequest public class SampleAuthorizationMiddlewareResultHandler : IAuthorizationMiddlewareResultHandler et HttpResponse { Vous pouvez maintenant lire et écrire facilement les données JSON private readonly IAuthorizationMiddlewareResultHandler _handler; à partir d’un HttpRequest et HttpResponse à l’aide des nouvelles méthodes d’extension ReadFromJsonAsync et WriteAsJsonAsync. public SampleAuthorizationMiddlewareResultHandler(IAuthorizationMiddlewareResultHandler Ces méthodes d’extension utilisent l’assembly System.Text.Json. handler) Exemple d’un endpoint lightweight : { _handler = handler ?? throw new ArgumentNullException(nameof(handler)); app.UseEndpoints(endpoints => } { endpoints.MapGet("/countries", async context => public async Task HandleAsync( { RequestDelegate requestDelegate, var countryService = context.Request.HttpContext.RequestServices.GetRequiredService< HttpContext httpContext, CountryService>(); AuthorizationPolicy authorizationPolicy, PolicyAuthorizationResult policyAuthorizationResult) var countries = await countryService.Get(); { // if the authorization was forbidden, let's use custom logic to handle that. await context.Response.WriteAsJsonAsync(countries); if (policyAuthorizationResult.Forbidden && policyAuthorizationResult.AuthorizationFailure ! }).RequireAuthorization(); = null) }); { Méthode d’extension autorisant l’accès // as an example, let's return 404 if specific requirement has failed anonyme if (policyAuthorizationResult.AuthorizationFailure.FailedRequirements.Any(requirement Sur l’exemple précédent, nous avions ajouté une méthode d’exten- => requirement is SampleRequirement)) sion permettant de forcer une authentification. Avec ASP.NET Core { 5, il est désormais possible, et ce manière explicite, d’autoriser httpContext.Response.StatusCode = (int)HttpStatusCode.NotFound; l’accès à un endpoint de façon anonyme. Si nous reprenons await httpContext.Response.WriteAsync(Startup.CustomForbiddenMessage); l’exemple précédent, cela donne ceci : // return right away as the default implementation would overwrite the status code app.UseEndpoints(endpoints => return; { } endpoints.MapGet("/countries", async context => else if (policyAuthorizationResult.AuthorizationFailure.FailedRequirements.Any(requirement { => requirement is SampleWithCustomMessageRequirement)) var countryService = context.Request.HttpContext.RequestServices.GetRequiredService< { CountryService>(); // if other requirements failed, let's just use a custom message // but we have to use OnStarting callback because the default handlers will want to var countries = await countryService.Get(); modify i.e. status code of the response

16 //programmez.com 008_021.qxp_HS02 13/10/2020 09:26 Page17

.NET

// and modifications of the response are not allowed once the writing has started exemple, les streams gRPC). Dans ASP.NET Core 5, Microsoft ajou- var message = Startup.CustomForbiddenMessage; té la possibilité d’envoyer des frames PING périodiques dans Kestrel en fixant des limites sur KestrelServerOptions. Exemple : httpContext.Response.OnStarting(() => httpContext.Response.BodyWriter.WriteAsync public static IHostBuilder CreateHostBuilder(string[] args) => (Encoding.UTF8.GetBytes(message)).AsTask()); Host.CreateDefaultBuilder(args) } .ConfigureWebHostDefaults(webBuilder => } { webBuilder.ConfigureKestrel(options => await _handler.HandleAsync(requestDelegate, httpContext, authorizationPolicy, policy { AuthorizationResult); options.Limits.Http2.KeepAlivePingInterval = TimeSpan.FromSeconds(10); } options.Limits.Http2.KeepAlivePingTimeout = TimeSpan.FromSeconds(1); } }); } webBuilder.UseStartup(); Les filtres Hub de SignalR }); Les filtres Hub, appelés pipelines Hub dans ASP.NET SignalR, est Le décodage de header personnalisé une fonctionnalité qui vous permet d’exécuter du code avant et Microsoft a ajouté la possibilité de spécifier le codage après l’appel des méthodes Hub, similaire à la façon dont vous System.Text.Encoding à utiliser pour interpréter les headers pouvez exécuter du code avant et après une demande http (ou l’en- entrants en fonction du nom du header. Ceci au lieu d’UTF-8 par trée dans un contrôleur comme les filtres d’action dans MVC ou défaut. Nous pouvons désormais définir la propriété une WebAPI). Les utilisations courantes incluent la journalisation, RequestHeaderEncodingSelector sur KestrelServerOptions pour la gestion des erreurs et la validation des arguments. Exemple : spécifier l’encodage à utiliser. public class CustomFilter : IHubFilter public static IHostBuilder CreateHostBuilder(string[] args) => { Host.CreateDefaultBuilder(args) public async ValueTask InvokeMethodAsync( .ConfigureWebHostDefaults(webBuilder => HubInvocationContext invocationContext, Func> next) { { webBuilder.ConfigureKestrel(options => Console.WriteLine($"Calling hub method '{invocationContext.HubMethodName}'"); { try options.RequestHeaderEncodingSelector = encoding => { { return await next(invocationContext); switch (encoding) } { catch (Exception ex) case "Host": { return System.Text.Encoding.Latin1; Console.WriteLine($"Exception calling '{invocationContext.HubMethodName}'"); default: throw ex; return System.Text.Encoding.UTF8; } } } }; } }); webBuilder.UseStartup(); La configuration est ensuite simple. Exemple : }); public void ConfigureServices(IServiceCollection services) La console de logging JSON { Microsoft a apporté des améliorations au provider de logging services.AddSignalR(options => ILogger (Microsoft.Extensions.Logging). Les développeurs peu- { vent désormais implémenter un ConsoleFormatter pour exercer un options.AddFilter(); contrôle complet sur la mise en forme et la colorisation de la sortie }); de la console. Parallèlement à cela, Microsoft a également ajouté un formateur services.AddSingleton(); JSON intégré qui émet des logs JSON structurés à la console. Pour } remplacer le logger par défaut par le logger JSON, voici comment L’envoi de frames PING HTTP/2 procéder : HTTP/2 dispose d’un mécanisme permettant d’envoyer des frames PING afin de s’assurer qu’une connexion inactive est toujours fonc- public static IHostBuilder CreateHostBuilder(string[] args) => tionnelle. Ceci est particulièrement utile lorsque vous travaillez avec Host.CreateDefaultBuilder(args) des flux à longue durée de vie qui sont souvent inactifs (par .ConfigureLogging(logging =>

programmez.com// 17 008_021.qxp_HS02 13/10/2020 09:26 Page18

.NET

{ h1 { logging.AddJsonConsole(options => font-family: 'Comic Sans MS' { } options.JsonWriterOptions = new JsonWriterOptions() { Indented = true }; }); .cool-list li { }) color: red; .ConfigureWebHostDefaults(webBuilder => } { webBuilder.UseStartup(); Le lazy loading dans Blazor WebAssembly }); Le lazy loading vous permet d’améliorer le temps de chargement de votre application Blazor WebAssembly en reportant le téléchar- Exemple d’un log structuré produit : gement de dépendances d’applications spécifiques jusqu’à ce qu’elles soient requises. Le lazy loading peut être utile si votre appli- { cation Blazor WebAssembly présente de grandes dépendances qui "EventId": 0, ne sont utilisées que pour des parties spécifiques de l’application. "LogLevel": "Information", Pour retarder le chargement d’une assembly, il suffit d’ajouter "Category": "Microsoft.Hosting.Lifetime", BlazorWebAssemblyLazyLoad au groupe d’éléments dans votre "Message": "Now listening on: https://localhost:5001", fichier de projet : les assemblys marqués pour le lazy loading doi- "State": { vent être explicitement chargés par l’application avant d’être utili- "Message": "Now listening on: https://localhost:5001", sés. Pour charger les assembly en période d’exécution, il faut utili- "address": "https://localhost:5001", ser le service LazyAssemblyLoader comme suit : "{OriginalFormat}": "Now listening on: {address}" } @inject LazyAssemblyLoader LazyAssemblyLoader } Le rafraîchissement automatique avec @code { dotnet watch var assemblies = await LazyAssemblyLoader.LoadAssembliesAsync(new string[] { "Lib1.dll" }); } Dans .NET 5, l’exécution de dotnet watch sur un projet ASP.NET Core lancera désormais le navigateur par défaut et actualisera L’exemple ci-dessous illustre le lazy loading d’une assembly lors de automatiquement le navigateur au fur et à mesure que le dévelop- la navigation vers la page 1 : peur ajoutera des modifications à son code. Cela signifie que vous @using System.Reflection pourrez ouvrir un projet ASP.NET Core dans votre éditeur de texte @using Microsoft.AspNetCore.Components.Routing préféré, exécuter ensuite une fois dotnet watch, puis l’outillage @using Microsoft.AspNetCore.Components.WebAssembly.Services associé à ce dernier gèrera la reconstruction, le redémarrage et le @inject LazyAssemblyLoader LazyAssemblyLoader rechargement de votre application. Microsoft apportera la fonc- tionnalité de rafraîchissement automatique à Visual Studio à l’ave- L’isolation CSS des composants Blazor

Blazor prend maintenant en charge la définition des styles CSS qui

Loading the requested page...

sont étendus à un composant donné. Les styles CSS spécifiques
aux composants facilitent l’intégration des styles de votre applica- tion et évitent les effets secondaires involontaires des styles glo- baux. Les développeurs pourront utiliser des styles spécifiques aux composants dans un fichier .razor.css qui correspond au nom du fichier .razor pour le composant. Par exemple, prenons un compo- sant nommé MyComponent.razor qui ressemble à ceci :

My Component

Sorry, there is nothing at this address.

  • Item1
  • Item2
@code { private List lazyLoadedAssemblies = Il est possible de définir un fichier MyComponent.razor.css avec les new List(); styles de MyComponent :

18 //programmez.com 008_021.qxp_HS02 13/10/2020 09:26 Page19

.NET

private async Task OnNavigateAsync(NavigationContext args) Protection du sessionStorage { et localStorage dans Blazor if (args.Path.EndsWith("/page1")) Dans les applications Blazor , il est possible de persister l’état { de l’application dans le stockage local ou de session afin que l’ap- var assemblies = await LazyAssemblyLoader.LoadAssembliesAsync(new string[] { "Lib1.dll" }); plication puisse la réhydrater ultérieurement si nécessaire. Lorsque lazyLoadedAssemblies.AddRange(assemblies); le développeur stocke l’état de l’application dans le navigateur de } l’utilisateur, il faut également s’assurer qu’elle n’a pas été altérée. } Blazor dans .NET 5 aide à résoudre ce problème en fournissant } deux nouveaux services : ProtectedLocalStorage et Protected SessionStorage. Ces services aident à stocker l’état dans le stocka- L’ajout du composant InputRadio dans ge local et de session respectivement. Ils prennent aussi soin de Blazor protéger les données stockées à l’aide des API de protection des Blazor dans .NET 5 inclut désormais les composants InputRadio et données ASP.NET Core. Pour utiliser les nouveaux services de stoc- InputRadioGroup intégrés. Ces composants simplifient la liaison de kage de navigateur protégés il faut : données aux groupes de boutons radio avec validation intégrée aux • Ajouter une référence de package à Microsoft.AspNet côtés des autres composants d’entrée de formulaire Blazor. Core.Components.Web.Extensions ;- • Configurer les services en appelant services.AddProtected Opinion about blazor: BrowserStorage() (configuré dans le Startup.cs) ; • Injecter ProtectedLocalStorage et ProtectedSessionStorage @foreach (var opinion in opinions) dans de le / les composants désirés : {

@inject ProtectedLocalStorage ProtectedLocalStorage @inject ProtectedSessionStorage ProtectedSessionStorage Enfin utiliser le service souhaité pour obtenir, définir et supprimer
l’état de façon asynchrone : } private async Task IncrementCount() L’ajout de l’autofocus sur les inputs dans { Blazor await ProtectedLocalStorage.SetAsync("count", ++currentCount); } Blazor dispose maintenant d’une méthode de commodité FocusAsync sur ElementReference pour permettre à l’utilisateur courant de se focaliser automatiquement sur l’input HTML désiré. ENTITY FRAMEWORK CORE 5 Entity Framework Core 5 est actuellement en développement au moment où nous écrivons ces lignes. Par conséquent cet article contient seulement un aperçu des modifications les plus perti- Influencer les balises HTML head dans nentes de cette mouture numéro 5 d’Entity Framework 5. Blazor L’ajout des nouveaux composants Title, Link et Meta permettent Le mapping table par type (TPT mapping) définir par programmation le titre d’une page et d’ajouter dynami- Par défaut, Entity Framework Core 5 mappe une hiérarchie d’héri- quement des balises de lien et de méta HTML d’une application tage des types .NET à une seule table de base de données. C’est ce Blazor. Pour utiliser les nouveaux composants Title, Link et Meta : qu’on appelle le mapping de table par hiérarchie (TPH). Entity • Ajouter une référence de package au package Microsoft.Asp Framework Core 5 permet également de mapper chaque type .NET NetCore.Components.Web.Extensions ; dans une hiérarchie d’héritage à une table de base de données dif- • Inclure une référence de script à _content/Microsoft.Asp férente, appelée mapping de table par type (TPT). Exemple : NetCore.Components.Web.Extensions/headManager.js ; public class Animal • Ajouter une directive @using pour Microsoft.AspNetCore. { Components.Web.Extensions.Head. public int Id { get; set; } Exemple : public string Species { get; set; } } @if (unreadNotificationsCount > 0) { public class Pet : Animal var title = $"Notifications ({unreadNotificationsCount})"; { public string Name { get; set; } } }

programmez.com// 19 008_021.qxp_HS02 13/10/2020 09:26 Page20

.NET

Pour que cela se produise, il faut utiliser annotations habituelles et public class Cat : Pet créer une classe par table comme suit : { public string EducationLevel { get; set; } [Table("Animals")] } public class Animal { public class Dog : Pet public int Id { get; set; } { public string Species { get; set; } public string FavoriteToy { get; set; } } } [Table("Pets")] Par défaut la table suivante est générée si le modèle précédent est public class Pet : Animal utilisé pour générer la base de données : { CREATE TABLE [Animals] ( public string Name { get; set; } [Id] INT NOT NULL IDENTITY, } [Species] NVARCHAR(MAX) NULL, [Discriminator] NVARCHAR(MAX) NOT NULL, [Table("Cats")] [Name] NVARCHAR(MAX) NULL, public class Cat : Pet [EducationLevel] NVARCHAR(MAX) NULL, { [FavoriteToy] NVARCHAR(MAX) NULL, public string EducationLevel { get; set; } CONSTRAINT [PK_Animals] PRIMARY KEY ([Id])); }

A la place, Entity Framework Core 5 va générer les 4 tables suivantes : [Table("Dogs")] public class Dog : Pet CREATE TABLE [Animals] ( { [Id] int NOT NULL IDENTITY, public string FavoriteToy { get; set; } [Species] NVARCHAR(MAX) NULL, } CONSTRAINT [PK_Animals] PRIMARY KEY ([Id]) ); Ou utiliser le ModelBuilder comme suit : protected override void OnModelCreating(ModelBuilder modelBuilder) CREATE TABLE [Pets] ( { [Id] int NOT NULL, modelBuilder.Entity().ToTable("Animals"); [Name] NVARCHAR(MAX) NULL, modelBuilder.Entity().ToTable("Pets"); CONSTRAINT [PK_Pets] PRIMARY KEY ([Id]), modelBuilder.Entity().ToTable("Cats"); CONSTRAINT [FK_Pets_Animals_Id] FOREIGN KEY ([Id]) REFERENCES [Animals] ([Id]) ON DELETE modelBuilder.Entity().ToTable("Dogs"); NO ACTION } ); Reconstruction des tables SQLite CREATE TABLE [Cats] ( Par rapport à d’autres bases de données, SQLite est relativement [Id] int NOT NULL, limitée dans ses capacités de manipulation de schéma. Par [EducationLevel] NVARCHAR(MAX) NULL, exemple, la suppression d’une colonne d’une table existante néces- CONSTRAINT [PK_Cats] PRIMARY KEY ([Id]), site que la table entière soit supprimée et recréée. Entity Framework CONSTRAINT [FK_Cats_Animals_Id] FOREIGN KEY ([Id]) REFERENCES [Animals] ([Id]) ON DELETE Core 5 Migrations prend désormais en charge la reconstruction NO ACTION, automatique de la table pour les modifications de schéma qui l’exi- CONSTRAINT [FK_Cats_Pets_Id] FOREIGN KEY ([Id]) REFERENCES [Pets] ([Id]) ON DELETE NO ACTION gent. Voici les étapes réalisées par Entity Framework Core 5 : ); • Une table temporaire est créée avec le schéma souhaité pour la nouvelle table ; CREATE TABLE [Dogs] ( • Les données sont copiées à partir de la table actuelle dans la [Id] int NOT NULL, table temporaire ; [FavoriteToy] NVARCHAR(MAX) NULL, • L’application des clés étrangères est désactivée ; CONSTRAINT [PK_Dogs] PRIMARY KEY ([Id]), • La table actuelle est supprimée ; CONSTRAINT [FK_Dogs_Animals_Id] FOREIGN KEY ([Id]) REFERENCES [Animals] ([Id]) ON DELETE • La table temporaire est renommée pour être la nouvelle table. NO ACTION, CONSTRAINT [FK_Dogs_Pets_Id] FOREIGN KEY ([Id]) REFERENCES [Pets] ([Id]) ON DELETE NO Les fonctions table évaluées ACTION (Table-Valued functions) ); Entity Framework Core 5 inclut la prise en charge des fonctions table évaluées. Ces fonctions peuvent ensuite être utilisées dans les

20 //programmez.com 008_021.qxp_HS02 13/10/2020 09:26 Page21

.NET

requêtes LINQ où la composition supplémentaire sur les résultats On peut ensuite l’utiliser dans une Requête Linq, Exemple : de la fonction sera également traduite en SQL. Exemple : var query = from employee in context.Employees CREATE FUNCTION GetReports(@employeeId INT) from report in context.GetReports(e.Id) RETURNS @reports TABLE where report.IsDeveloper == true ( select new Name NVARCHAR (50) NOT NULL, { IsDeveloper BIT NOT NULL ManagerName = employee.Name, ) EmployeeName = report.Name, AS }); BEGIN WITH cteEmployees AS Le DbContextFactory ( Vous souvenez vous des services Singleton qui vous empêchaient SELECT Id, Name, ManagerId, IsDeveloper d’utiliser des Repository dont la durée de vie était prévue pour ne FROM Employees durer que le temps d’une requête Http (Scoped dans ASP.NET WHERE Id = @employeeId Core) ? Eh bien il fallait créer une factory (ou utiliser UNION ALL DbContextFactory qui existait déja)pour contextualiser votre repo- SELECT e.Id, e.Name, e.ManagerId, e.IsDeveloper sitory (et donc sa connexion à la base de données). Eh bien Entity FROM Employees e Framework Core 5 introduit le AddDbContextFactory et INNER JOIN cteEmployees cteEmp ON cteEmp.Id = e.ManagerId AddPooledDbContextFactory qui va permettre de manière native ) d’enregistrer une DbContextFactory dans le système d’injection de INSERT INTO @reports dépendance, exemple : SELECT Name, IsDeveloper FROM cteEmployees services.AddDbContextFactory(b => WHERE Id != @employeeId b.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=MyDatabase")); RETURN Exemple à l’utilisation : END public class MyRepository Ensuite, il faut créer les modèles associés pour pouvoir utiliser cette { fonction table évaluée et les enregistrer dans le ModelBuilder : private readonly IDbContextFactory _contextFactory; public class Employee public MyRepository(IDbContextFactory contextFactory) { { public int Id { get; set; } _contextFactory = contextFactory; public string Name { get; set; } } public bool IsDeveloper { get; set; } } public int? ManagerId { get; set; } Conclusion public virtual Employee Manager { get; set; } .NET 5 est une nouvelle direction importante et passionnante pour } .NET..NET va devenir plus simple, mais va également proposer une capacité plus large et utilitaire. Toutes les nouvelles fonctionnalités public class Report de développement feront partie de .NET 5, y compris les nouvelles { versions C# et F#. Microsoft va nous proposer un avenir promet- public string Name { get; set; } teur dans lequel les développeurs pourront utiliser les mêmes API public bool IsDeveloper { get; set; } .NET et langages pour cibler un large éventail de types d’applica- } tions, de systèmes d’exploitation et d’architectures de puces. Il sera facile d’apporter des modifications aux configurations de build pour modelBuilder.Entity(); créer des applications différemment, dans Visual Studio, Visual modelBuilder.Entity(typeof(Report)).HasNoKey(); Studio pour Mac, Visual Studio Code, Azure DevOps ou à la ligne de commande. Enfin en déclarant la fonction comme suit dans le ModelBuilder : Cet article vous a proposé de découvrir dans les grandes lignes les nouvelles fonctionnalités de .NET 5 mais .NET 5 est bien plus large public IQueryable GetReports(int managerId) que cela, ASP.NET Core 5 ou Entity Framework Core 5 , pour ne => FromExpression(() => GetReports(managerId)); citer qu’eux, proposent des tonnes de nouveautés que nous n’avons pas pu décrire ici. Soyez cependant assurés que le passage de .NET modelBuilder.HasDbFunction(() => GetReports(default)); Core 3.1 à 5 sera le plus simple possible, Microsoft l’a promis !

programmez.com// 21 022_026.qxp_HS02 13/10/2020 14:14 Page22

.NET Jérémy Jeanson Développeur MVP Initier un projet d'application Windows avec.net 5 sans se louper

Ces dernières années, Microsoft a multiplié les solutions pour créer des applications pour Windows : .Net MAUI, WinUi 3, Projet Reunion, .Net core 3.1, UWP, WinRT, XAML Island, etc. Mais aujourd’hui avec l’arrivée de .Net 5, où en est-on ? Quelles solutions avons-nous à disposition et comment se lancer dans de bonnes conditions ? Un développeur .Net peut choi- sir entre trois solutions stables : WinForm, WPF et UWP.

i vous êtes déjà habitué à .Net Framework et .net core, vous Anticiper WinUI 3 et Project Reunion vous demandez peut-être où se cachent les nouveautés pour UWP permet déjà d’utiliser WinUI 2. Si vous commencez dès Sle desktop ? Ne cherchez pas du côté des modèles de projets, aujourd’hui avec WinUI et UWP, il n’y a donc rien à faire pour anti- il n’y a rien de nouveau de ce côté. Tout se cache sous le capot. ciper WinUI 3. .Net 5 est une version axée sur l’unification des API, la stabilité, Côté WPF, le développeur qui souhaite adapter une interface l’amélioration des performances et de l’accessibilité. moderne avec WPF devra passer par XAML Island. Ceci lui permet- tra d’embarquer les éléments graphiques d’UWP dans son applica- Faire un choix parmi les technologies tion (dont WinUI 2). WPF est donc une option intéressante si l’on actuelles souhaite adopter le Fluent Design et se préparer à WinUI 3 tout en Au moment de faire son choix, il faut se faire une raison. Toutes les restant sur Win32. Cela fait de WPF la solution la plus malléable et stacks techniques annoncées par Microsoft exploitent XAML ou la plus progressive. une solution à base de Markup. Même si WinForm a de nombreux Il reste à traiter la grande inconnue : le Project Reunion. avantages, il ne trouve pas sa place dans ce futur. On réservera Heureusement, depuis son annonce Microsoft a clarifié la chose. donc WinForm à la migration d’application .net vers .net 5. Les Reunion n’est pas un nouveau modèle d’application. Il faut l’entre- projets ainsi migrés pourront utiliser progressivement WPF. voir comme un ensemble de composants qui faciliteront l’accès Concernant UWP, le discours de Microsoft n’est pas forcément très aux API modernes. Une application UWP ou WPF pourra en profi- limpide. Son terrain s’est réduit. Sa mort a été annoncée. Cette ter. Pour WPF, l’adoption de C#/WinRT est un premier pas vers incertitude a eu un impact négatif sur l’engouement des dévelop- Reunion. Aujourd’hui, UWP ne profite pas de nouveauté liée à peurs. Pour compliquer la chose, on parle beaucoup de WinRT Reunion. (Windows Runtime) en parallèle d’UWP ou à la place de UWP. À la sortie de Windows 8, la dénomination WinRT a été mise en oppo- La problématique du déploiement sition à WinJS. D’où aujourd’hui une plus grande confusion. Pour Si votre choix entre WPF et UWP n’est pas encore défini, celui-ci se être clair : non, Microsoft ne ressuscite pas Metro. Microsoft fera certainement au moment d’établir les contraintes de déploie- désigne par WinRT, l’ensemble des API modernes utilisables par ment : une application desktop. • Une application WPF peut être déployée de toutes les manières La technologie a évolué. Les nouveaux contrôles ont été extraits possibles et imaginables (xCopy, MSI, MSIX, Microsoft Store, …) ; d’UWP pour devenir WinUI et permettre aux développeurs d’utiliser • Une application UWP ne peut être déployée que via MSIX ou le plus rapidement de nouvelles fonctionnalités avec Windows 10 Microsoft Store. sans attendre après les SDK ou . Ceci a facilité • Pour une application Desktop, WPF est évidemment le choix le l’adoption du Fluent Design. Aujourd’hui, il faut entrevoir UWP plus souple. comme un paradigme pour la création d’application moderne. Ce choix reste donc viable. Comment organiser sa logique applicative ? La version de WPF fournie avec .net 5 inclut l’ensemble des possi- Le choix d’utiliser XAML pousse à s’intéresser au pattern MVVM. bilités initialement offertes par le .net Framework. Les développeurs Vous pouvez bien évidemment coder vos propres outils pour exploi- déjà coutumiers de WPF ne seront donc pas perdus. En comparai- ter MVVM, .net 5 n’impose rien. Le plus simple étant d’utiliser une son avec .net Framework, .net 5 utilise davantage d’API fournies solution du marché telle que MVVM Light ou Prism. Je ne cite que par l’OS. L’expérience utilisateur n’en est que meilleure (boîtes de ces deux exemples, car ils reflètent bien les tendances, complica- dialogue, écran HDPI…). S’ajoute à cela un gain de performance tions et avantages liés à MVVM. non négligeable. Pour autant, on reste sur un socle Win32 et non D’un côté, MVVM Light est à entrevoir comme une boîte à outils. WinRT. Pour utiliser WinRT, il faudra utiliser le package NuGet On peut y piocher ce que l’on souhaite. Notre développement n’est C#/WinRT. pas conditionné par cette librairie.

22 //programmez.com 022_026.qxp_HS02 13/10/2020 14:14 Page23

.NET

En ce qui concerne Prism, la situation est totalement différente. Il fonctionnelles de vos utilisateurs, donnez des noms à vos s’agit d’un véritable framework qui va cadrer notre manière de ViewModel qui respectent le vocabulaire des utilisateurs. coder, structurer, et naviguer dans l’application. Considérez le ViewModel comme étant le garant de la logique Attention donc à bien choisir vos librairies en fonction de votre fonctionnelle. Cela vous évitera d’éparpiller les règles fonction- situation : nelles entre des domaines qui n’ont rien à voir. Un ViewModel = • Si vous n’avez pas encore fait de choix techniques ou devez partir un domaine fonctionnel. de zéro, Prism est une bonne solution ; • Si vous avez un projet existant, ou avez déjà cadré votre architec- Comment garantir la stabilité ture, MVVM Light vous facilitera la tâche. et l’évolutivité ? Concernant MVVM, je ne vais pas faire une énième redite de ses La stabilité d’une application dans le temps n’est possible que si principes, mais en extraire quelques règles qui vous éviteront les l’on évite les deux problèmes les plus courants : écueils les plus courants : • Le code spaghetti : tout est tellement lié que l’on ne peut plus • View First : progressivement, toutes les technologies qui exploitent prendre de recul. Si les classes ne sont pas directement liées, il y XAML ont évolué vers un modèle de navigation qui impose l’ins- a souvent plusieurs classes ou méthodes statiques qui sont appe- tanciation de la View (Page, Window). Ne cherchez donc pas à lées d’un peu partout. Une modification d’une portion de code instancier vos ViewModel avant les Views. De plus, c’est à la View peut avoir un impact non prévisible sur une autre. Ce qui peut de référencer le ViewModel (ceci vous évitera les références circu- aboutir à des effets de bords et des régressions. laires et les problèmes de libération de la mémoire associés). • L’application non testable : il n’y a pas de tests unitaires codés, • Navigation : créez une classe qui se chargera de la centralisation ou l’application n’est testable que manuellement et dans un envi- de toutes les méthodes de navigation (ouverture de Window ou ronnement bien défini. Reproduire un cas qui produit une erreur navigation vers une Page). Ceci facilitera la migration si un jour est difficile. il vous faut quitter WPF ou UWP pour un autre type de projet. Pour éviter cela, les solutions sont simples : Cette approche facilite aussi l’utilisation du pattern d’IOC. • Utiliser l’Inversion Of Control (IOC) ; • View Code : contrairement à ce que l’on peut souvent entendre, • Coder systématiquement des tests unitaires (en profitant du il n’est pas interdit d’avoir du code derrière une View. Il n’est pas découpage offert par l’IOC). rare que cela soit l’approche la plus performante et facile pour L’IOC va avoir un impact très positif sur votre application : manipuler l’interface graphique. • Diminution du couplage entre les différents modules fonctionnels • Attached properties : codez des Attached Properties dès que vous d’une application. Ceci facilite les évolutions futures (change- constatez qu’un comportement doit être reproduit sur plusieurs ment d’API système par exemple). Views. Ceci est préférable à l’héritage de Views. • Empêche la création de classes à tout faire. • Views : ne jamais instancier un élément graphique via le • Simplification du travail d’équipe (une personne peut travailler ViewModel. Laisser cette tâche à votre View principale. Si l’ins- sur une classe qui dépend d’une autre qui est codée par un col- tanciation dépend de l’état du ViewModel, il suffit d’utiliser un lègue avant que celui-ci n'ait fini son implémentation). Binding (sur la ou les propriétés à observer). • Pour faciliter les tests (utilisation d’un Framework de Mocks pour • Communication entre ViewModels : si vos ViewModels doivent ne pas avoir besoin de coder plusieurs implémentations unique- communiquer entre eux (échange de données, évènements), il ment pour les tests). Les Mocks vont aussi permettre de simuler ne faut pas céder à la tentation d’utiliser des Events (pour éviter des comportements qui n’ont lieu que dans des situations de pro- le couplage trop fort entre les classes et les références circu- blèmes système (perte d’accès réseau, serveur non accessible, laires). Il faut impérativement passer par le pattern Publisher – refus de droits). Subscriber (Pub-Sub). Celui-ci vous permet de faciliter les tests, la • Documentation et définition des attentes (un développeur qui lit gestion de votre mémoire et l’évolutivité de l’application. Prism et les tests unitaires aura une compréhension plus rapide du rôle de MVVM Light ont des classes pour cela (respectivement : chaque classe). EventAggregator et Messenger) • Une grande modularité qui permet d’accélérer les migrations. • Utilisation du Pub-Sub : quand un ViewModel n’est plus utile, Vous pouvez identifier rapidement les points de blocage d’une pensez à le désabonner de votre EventAggregator ou Messenger migration et adopter rapidement les nouveautés en ne touchant (afin de vous assurer de ne pas bloquer inutilement des res- qu’aux classes qui contiennent l’implémentation concrète du sources en mémoire). code. Les classes qui en dépendent ne sont pas impactées direc- • Faciliter l’usage du designer XAML : souvent, le test d’une inter- tement (ce qui réduit les effets de bords et régressions). face XAML passe par d’interminables phases de compilation et Un projet .net 5 est en mesure d’utiliser nombre de solutions exécution. Pour éviter cela, il suffit de créer un VieModel dédié au d’IOC. Si vous avez opté pour l’utilisation de Prism pour votre design. Celui-ci doit respecter la même interface que le MVVM, un container est déjà disponible pour gérer l’IOC. ViewModel réel. Il est associé à la View via l’attribut Dans le cas contraire, la plus simple consiste à utiliser "d:DataContext". À noter qu’il est aussi possible de simulateur "Microsoft.Extensions.Hosting". Les développeurs utilisant déjà ASP l’état des contrôles en mode design en utilisant "d:" devant l’at- .net core ne seront pas dépaysés. Il s’agit de la librairie qui est uti- tribut que l’on veut remplacer en mode design. lisée pour la construction du WebHost. • VieModel fonctionnel : afin de respecter au mieux les exigences De la même manière que pour un projet web, vous aurez à produire

programmez.com// 23 022_026.qxp_HS02 13/10/2020 14:14 Page24

.NET

une classe qui fournira un Host. Pour un projet à ASP.net core, la private Host() détermination des Controller à instancier est établie par des { Routes. Le WebHost sait quel Controller il doit instancier. Vous _host = new HostBuilder() n’avez donc jamais besoin de manipuler votre Host. .ConfigureServices(ConfigureServices) Pour une application desktop (UWP ou WPF), vous aurez besoin de .Build(); passer par votre Host pour demander l’instanciation de vos } ViewModels. Cet Host doit donc être unique et accessible à tous. L’implémentation d’un Singleton s’impose d’elle-même. ///

Avant de coder votre Host, il faudra installer le package nuget /// Configure services "Microsoft.Extensions.Hosting". Ce package n’est utile que pour le /// projet qui doit accueillir votre Host. Si vous avez prévu de découper /// votre projet entre plusieurs assemblies, il faudra ajouter le package /// Nuget "Microsoft.Extensions.Hosting.Abstractions" à chaque projet private void ConfigureServices(HostBuilderContext context, IServiceCollection services) qui doit accéder à votre Host ou qui doit lui fournir des services (il { n’est pas rare qu’un assembly externe fasse office de plug-in qu’il // Add ViewsModels, Views, services faudra injecter dynamiquement). Ce package contient les inter- services.AddTransient(); faces et classes génériques utiles à l’injection et la configuration services.AddTransient(); des services. services.AddTransient(); Attaquons maintenant le code d’une classe Host type : services.AddTransient(); services.AddTransient(); using Microsoft.Extensions.DependencyInjection; } using Microsoft.Extensions.Hosting; using System.Threading.Tasks; #endregion

namespace MyDemo #region Methodes { public sealed class Host ///

{ /// Start the host #region Singleton /// /// /// public async Task StartAsync() /// Static constructor { /// await _host.StartAsync(); static Host() } { Current = new Host(); /// } /// Stop the host /// /// /// /// current instance public async Task StopAsync() /// { public static Host Current { get; } await _host.StopAsync(); _host.Dispose(); #endregion }

#region Declarations ///

/// Get an instance of service of type T private readonly IHost _host; /// /// #endregion /// public T GetService() #region Constructor / Services { return _host.Services.GetService(); /// } /// Constructor /// #endregion

24 //programmez.com 022_026.qxp_HS02 13/10/2020 14:14 Page25

.NET

} un comportement nouveau à un contrôle existant (de plus, cela } est testable). En plus de garantir de meilleures performances, cela permet d’inclure plus rapidement de nouveaux développeurs On a les bases d’un Singleton. Le constructeur fait un appel à sa en cours de projet. Ils n’ont pas à apprendre la palette de méthode interne ConfigureServices pour enregistrer les classes qui contrôles que vous avez créée avant de se lancer. Leur intégration seront à instancier. Les méthodes StarAsync et StopAsync sont à est bien plus rapide. S’ils oublient un élément, il suffit de l’ajouter. appeler respectivement lors du lancement et de la fermeture de Leur code n’est pas à supprimer ou à réécrire entièrement. l’application. Pour UWP, StarAsync sera à appeler via la classe App • Éviter de dupliquer les Styles similaires. Un Style peut être basé sur sa méthode OnLaunched. sur un Style existant et ne comprendre que les propriétés qui Exemple : changent. De plus un Style peut affecter un Template à un Code complet sur programmez.com & github Control et lui changer quelques propriétés. Un Style peut donc Le StopAsync ne peut pas être utilisé avec UWP. aussi vous éviter de dupliquer les Templates. Ceci facilitera les Pour une application WPF, il faudra modifier le fichier App.xaml et futures évolutions et réduit le temps passé à chercher les codes supprimer l’attribut MainWindow. On profite ensuite des méthodes XAML concernés. De plus cela pallie l’incapacité de Visual Application_Startup et Application_Exit pour définir la fenêtre prin- Studio à identifier les codes XAML dupliqués. cipale de l’application et manipuler la classe Host. • Ne pas mettre tous les Templates et Styles dans un seul diction- Exemple : naire de ressources. Le fait de charger très tôt des Templates qui Code complet sur programmez.com & github ne sont pas utiles au lancement de l’application va ralentir celui- Par la suite, vos Views n’auront qu’à appeler la méthode GetService ci. Ces éléments doivent être au plus près des contrôles qui les pour obtenir une instance de la classe désirée. utilisent. S’ils ne sont utiles que sur une seule View, ils doivent Exemple, le MainView peut demander son ViewModel comme ceci : être dans le nœud Resources de la Window ou Page. S’ils sont utiles à plusieurs Views, ils doivent être dans un dictionnaire qui var vm = Host.Current.GetService(); n’est référencé que par les Views qui en ont l’usage. Outre le La classe Host présentée ici n’est fournie qu’à titre d’exemple, mais gain de performance non négligeable, cela permet au dévelop- elle prend en compte les principaux usages. Elle doit être adaptée peur d’avoir moins de code à parcourir avant d’identifier une por- dans le cas où votre application doit prendre en compte des plug- tion de XAML problématique ou devant évoluer. Cette pratique ins. Ceux-ci devront être chargés au moment de l’appel à la métho- rend aussi votre contrôle de sources plus pratique. Que vous uti- de ConfigureServices. lisiez Git, TFVC ou autre, vous serez en mesure d’identifier rapi- Concernant l’usage de ce Singleton pour obtenir des instances de dement les changements survenus dans le temps sur une inter- View, ViewModel, Service. La mauvaise pratique consiste à appeler face. Chose quasiment impossible si toutes les modifications de la méthode GetService d’un peu partout. La meilleure solution toutes les interfaces ont lieu sur un seul et unique fichier pour pour l’éviter consiste à coder une classe dédiée à la navigation toute l’application. (comme déjà évoqué avec MVVM). Cette classe devra être la seule • Vérifier régulièrement la complexité des Views. Il n’est pas rare à faire appel à Host. Ceci reproduit le comportement que l’on peut que l’on imbrique des contrôles de même type les uns dans des déjà avoir avec MVC (chaque Controller définit les Services utiles autres, sans pour autant que ce soit utile. Je pense entre autres via son constructeur et n’a pas conscience d’utiliser l’IOC). La clas- aux Grid qui finissent souvent dans des Grids qui elles-mêmes se de navigation sera alors responsable de demander la création sont dans d’autres Grids. L’imbrication de trop de contrôles à un des Views. L’ensemble des classes utiles aux Views étant fourni via impact très négatif sur les performances et la compréhension de IOC, les Views n’auront pas besoin d’appeler Host. La classe de l’interface par les développeurs. Vous-même, vous ne pouvez pas navigation pourra aussi être introduite dans les View et ViewModel être certain que vous comprendrez ce même code un an plus via leurs constructeurs et l’IOC. La boucle vertueuse est bouclée. tard. Il ne faut pas hésiter à inclure des commentaires dans le L’IOC est présente, mais très discrète. XAML plutôt que de regrouper les contrôles. • Dans les listes, tableaux et grilles : résistez à l’envie de redéfinir Comment garantir la qualité de son UI ? les conteneurs qui servent à la réplication des éléments (que ce Construire une interface avec XAML n’a rien de véritablement sor- soit pour la disposition des Items, la création de marges ou bor- cier. La modularité et la simplicité de XAML peuvent cependant dures). De base ceux-ci sont optimisés pour garantir de bonnes aboutir à de mauvaises pratiques qui peuvent rapidement mettre performances, et une cohérence entre l’expérience de l’utilisa- en péril votre application. teur qui utilise la souris et le clavier ou l’écran tactile. Ce type de Le site Docs de Microsoft fournit déjà nombre de conseils modification implique une bonne synchronisation avec son équi- (https://docs.microsoft.com/en-us/windows/apps/desktop/). La plupart sont pe. Si l'on est seul, une bonne organisation est nécessaire afin de axés performances et gestion des ressources. Voici une liste com- ne pas oublier de l’appliquer partout. plémentaire qui est davantage orientée sur la maintenabilité du Un mot au sujet des Bindings compilés : dans un contexte du desk- code : top, ceci est une fonctionnalité propre à UWP. La même fonction- • Éviter de créer des contrôles personnalisés. Il est préférable d’uti- nalité existe pour mobiles avec Xamarin et le tout nouveau MAUI. liser les contrôles de base en leur associant des Templates per- Contrairement à UWP, elle ne nécessite pas l’utilisation de la syn- sonnalisés. Via les Attached Properties, on peut très bien ajouter taxe x:Bind. Quand les Bindings compilés seront supportés par

programmez.com// 25 022_026.qxp_HS02 13/10/2020 14:14 Page26

.NET

WPF et WinUI, il est fort probable que leur syntaxe suive la même Thread et UI logique que MAUI et reste telle qu’elle est actuellement. Après avoir suivi l’ensemble des conseils évoqués jusqu’ici, votre Si vous entrevoyez le portage d’une application UWP vers WPF, évi- application ne court plus qu’un seul et unique danger : le gel de l’in- tez donc x:Bind. Si vous restez sur UWP, x:Bind est une solution très terface. Il s’agit d’un problème assez fréquent avec les applications puissante qui permet de détecter nombre de problèmes à la com- .net Framework qui tend à disparaître avec .net 5. La raison est très pilation. Il est donc conseillé de l’utiliser. simple : .net 5 et son prédécesseur .net core 3.1 nous contraignent de plus en plus à utiliser async / await pour toutes les opérations qui Paramètres et préférences potentiellement peuvent conduire à un gel de l’interface si on les de l’utilisateur exécute sur son thread principal (lecture de fichiers, accès réseau, Sur UWP, la gestion des préférences de l’utilisateur n’est pas un API…). Ceci permet de garder une interface réactive. sujet à problématique. La classe ApplicationData.LocalSettings De manière générale, à partir du moment où votre application a fournit tout ce dont on peut avoir besoin. Concernant les para- besoin d’accéder à une ressource externe, utiliser async / await. Si mètres, gérés par l’IT lors du déploiement ou par les développeurs, vous utilisez un code ou une librairie synchrone que vous avez pris il n’existe pas de solution intégrée. Il ne s’agit clairement pas d’un l’habitude de réutiliser d’un projet à l’autre, prenez le temps de les scénario ciblé par UWP. Rien ne vous interdit cependant d’ajouter convertir vers async/await. Vos utilisateurs vous remercieront. à votre application des écrans dédiés à l’administrateur et d’utiliser Dans le même ordre d’idée, il vous faut résister à la tentation de ApplicationData.LocalSettings pour cela. lancer des threads manuellement. Que ce soit via la classe Task ou Comme bien souvent, WPF est plus souple. On sent bien l’héritage toute autre solution. N’utiliser ces méthodes que pour lancer des de Win32. On peut accéder à l’ensemble du stockage du PC, et au tâches véritablement lourdes et/ou parallélisables. Et même dans réseau. On peut donc envisager tout type de solution à base de ce cas, il faut privilégier l’async/await pour attendre la fin d’exécu- fichiers, de base de données locale ou distante. Contrairement à tion de ces méthodes. UWP, on peut y accéder directement. Une gestion manuelle des threads implique l’usage du Dispatcher Il y a cependant deux options intégrées à WPF et .net 5 qui sont pour mettre à jour l’interface. Ceci peut compliquer le débogage. fort pratiques : • Properties..Default : il s’agit de la même solution qui exis- Localisation te avec .net Framework depuis .net 2. Le designer a été conservé Que l’on soit avec UWP ou WPF, la localisation de ressources est et n’a pas changé. On peut toujours y définir des paramètres uti- possible et peut être unifiée. Dans les deux cas, il est possible d’uti- lisateur et applicatifs (pour l’IT). Le fichier XML résultant peut liser les fichiers RESX. Ces fichiers sont acceptés par WPF et .net être modifié lors du déploiement par l’IT. Standard. UWP ne supportant pas ces fichiers, la mort du RESX a • ConfigurationBuilder : on retrouve là les mêmes possibilités que été annoncée à tort. Il s’agit du seul format qui peut être partagé celles qui sont offertes par ASP.net core. La souplesse de la solution avec le .net Framework. Il ne faut donc pas s’en priver. Pour l’utiliser n’est plus à démontrer. On peut utiliser des fichiers, des variables avec UWP, il suffit de créer un projet .net Standard, d’y ajouter les d’environnement, et mixer le tout de manière transparente. fichiers RESX, et de rendre publiques les classes générées. Il reste Properties.Settings.Default ne nécessite pas la mise en place d’une alors à référencer ce projet dans l’application UWP. logique compliquée, car il s’agit d’un objet intrinsèque accessible Pour faciliter les traductions, il est conseillé d’utiliser le Multilingual de tous. Cependant, cela va à contrario de l’IOC et des tests uni- App Toolkit. Cette extension pour Visual Studio va se charger de taires. Pour garantir un usage propre et testable, il est donc suivre l’évolution des fichiers RESX de votre langue à défaut (même conseillé de créer une classe qui va encapsuler tous les appels à si vous avez plusieurs fichiers). Elle va permettre la création de Properties.Settings.Default. Cette classe sera utilisée par l’applica- fichiers XLIFF à destination des traducteurs (un par langue à sup- tion et substituée par des Mock qui exploiteront la même Interface porter, quel que soit le nombre de fichiers RESX). Quand ces fichiers pour les tests unitaires. Si un jour vous souhaitez ne plus utiliser sont traduits, l’extension va générer les fichiers RESX des langues Properties.Settings.Default, le changement pourra se faire en dou- supportées (ceci se fait automatiquement lors de la compilation). ceur sans avoir d’impact sur le reste de l’application. Le format XLIFF est un format couramment utilisé par les traduc- L’utilisation de ConfigurationBuilder peut se faire avec une teurs professionnels. Microsoft fournit un outil gratuit via lequel il approche très simple. Il suffit de modifier sa classe Host en lui ajou- est possible d’identifier rapidement les textes en attente de traduc- tant une méthode générique telle que celle-ci : tion et de les traduire. Celui-ci facilite grandement le travail. Code complet sur programmez.com & github Même une application qui n’est disponible que dans une langue Cette méthode peut être utilisée dans la méthode peut tirer profit de la localisation. Le fait de centraliser les textes a ConfigureServices pour définir un Singleton qui représentera vos de nombreux avantages. Notamment pour corriger rapidement une paramètres. Elle va lire le contenu d’un fichier appsettings.json se faute d’orthographe ou pour s’adapter si le vocabulaire change. trouvant à la racine de votre application, tenter de le désérialiser et retourner votre objet. Si certaines propriétés du fichier json ne sont Conclusion pas présentes sur votre classe, cela ne pose pas de problèmes. Il est .Net 5 offre un contexte de développement agréable et performant même possible d’utiliser plusieurs classes différentes et utiliser la dans lequel on peut rapidement se perdre. J’espère que les conseils même méthode GetSettings pour les alimenter à partir d’un seul évoqués ici vous permettront de vous lancer en toute tranquillité fichier. tout en restant prêts pour les évolutions à venir.

26 //programmez.com 027_035.qxp_HS02 13/10/2020 09:50 Page27

WINDOWS Christophe PICHAUD Leader de Domaine NET chez Infeeny [email protected] | www.windowscpp.com Pourquoi C++ en 2020 ? Le C++ est omniprésent dans l’industrie, les jeux vidéo, le médical, l’internet, les applications de bureau, le mobile, l’IA et le Cloud. L’avantage de C++ est qu’il bénéficie de toute la panoplie du C et de ses librairies. Ainsi, toutes les libs C/C++ de Linux sont facile- ment réutilisables. Dans d’autres langages, il faut des bindings (des ponts) sinon il est impossible de faire appel à ces libs. L’avantage de C/C++ est son côté multiplateforme supérieur à tous ses concurrents. Le C++ existe depuis 40 ans et il a été bâti après de mures réflexions au fil des ans. Il surpasse les langages comme Java et C#, Python et Rust de par sa STL, ses templates, sa capacité d’abs- traction et son niveau de génération de code optimisé. C’est pour cela que tous les grands logiciels sont faits en C/C++.

Pourquoi utiliser C++ en 2020 ? La question est posée… C++ est Les classes un descendant de C, regardez : ᖥ Les classes regroupent des membres (fonctions, variables) qui for- Sur l’index TIOBE, C est repassé devant Java. Java est à 14%. Avec ment un groupe cohérent et unique. On identifie une classe C et C++ on est à 24%. C++ est 4ème. Comme vous pouvez le comme un élément autonome qui propose des méthodes (autre constater, on est loin de ces sondages foireux qui classent JS en nom pour méthode) et des variables pour répondre à un besoin. premier. Les vrais logiciels sont faits en C/C++, pas en JS dans des Exemple de classe : ᖧ ᖥ browsers. Il faut dire les choses. Cette classe est autonome. Elle hérite de CObject, possède des membres variables et fonctions simples et virtuelles. Pour l’utiliser, C++, un meilleur C il suffit de déclarer un objet et de l’utiliser directement. C++ est un langage riche qui a été mis au point au fur et à mesure L’exemple de base est celui-ci : du temps, calmement et avec soin. Les rumeurs prétendent que C++ est difficile d’accès, un peu élitiste. Ce n’est pas vrai. ᖦ A a ; C++ apporte : If( a.membre == value) • La notion de classes ; … • Les hiérarchies de classes ou classes dérivées ; a.fonction() ; • Les fonctions virtuelles ; • La surcharge des opérateurs ; Comment définir une classe ? • Les pointeurs intelligents ; Ce n’est pas très compliqué. Prenons l’exemple d’un logiciel de • Les templates ; dessin. On va modéliser les concepts d’un logiciel de dessin. Au • La librairie standard STL. départ, de quoi a-t-on besoin ? • D’une fenêtre de dessin ; • De gabarits ; • De propriétés externes ajustables ; • Des options pour charger et sauvegarder les dessins. Prenons un papier et un crayon et notons les classes au fur et à mesure : C’est la première phase ! Voici les différents éléments identifiés : • CWindow : la fenêtre

ᖧ ᖦ

programmez.com// 27 027_035.qxp_HS02 13/10/2020 09:50 Page28

WINDOWS

• CShape : l’objet de dessin, le gabarit double emploi. Je m’explique. Quand l’utilisateur fait un clic gauche, • CProperties : les propriétés cela peut vouloir signifier : • CFileManager : le gestionnaire de fichiers • Dessin d’un gabarit ; Ensuite, on essaie de « remplir les carrés ». Chaque classe doit avoir • Redimensionnement d’un gabarit ; des membres qui lui sont propres. Au fur et à mesure de la • Sélection d’un gabarit. réflexion, on s’aperçoit que l’on réinvente peut-être des choses exis- On s’aperçoit qu’il n’y a pas que l’action de la souris qui rentre en tantes : exemple, la fenêtre qui existe déjà sous Gnome ou ligne de compte, mais aussi des états. Cela peut se manifester par Windows… Il faut donc affiner le design et le rendre appliqué au une sélection dans une barre d’outils : système d’exploitation. • Dessiner ; C’est là que les classes peuvent avoir des membres purement tech- • Sélectionner. niques, issues de l’OS, qui sont un peu du domaine de la plombe- Voici la barre de sélection : rie. On fera attention de bien isoler la plomberie du code « métier ». Ce n’est pas évident. Si le code de plomberie est mêlé au code métier, la classe va vite devenir un fourre-tout difficile à gérer. Il ne faut pas hésiter à isoler certaines choses.

La classe CShape La sélection d’un objet peut aussi être orientée. En effet, si un objet Cette classe représente le gabarit à dessiner. L’utilisateur utilise la est sélectionné, son contour se voit ajouter des carrés de sélection souris et décide de créer un gabarit d’une taille particulière à une (un tracker) qui ressemble à cela : position particulière. On peut donc en déduire certaines propriétés comme position et taille. Lorsque la souris bouge (évènement int x ; MouseMove) et passe au-dessus int y ; d’un petit carré, le curseur de la sou- CRect size ; ris se voit modifié pour indiquer que CRect est une classe qui représente un rectangle avec les membres le redimensionnement est possible. left, top, right, bottom, TopLeft, BottomRight, etc. Il y a donc deux états : Lorsque l’utilisateur va dessiner un gabarit, il va utiliser sa souris • L’état du type d’opération (select, draw) sur LeftMouseClick ; pour dimensionner l’objet, il faut donc gérer les évènements : • L’état du mode de l’opération (move, sizing) sur MouseMove. • LeftMouseDown • LeftMouseUp • MouseMove Et maintenant que l’on a cerné la C’est qu’il ne faut pas lâcher en se disant c’est trop compliqué. cinématique, il ne reste plus qu’à Dans la construction logicielle, on détaille tout à l’unité de fabrica- faire l’algorithme. tion la plus petite. Chaque opération d’un logiciel est codée, rien Examinons le code de la fonction n’est le fruit du hasard ou du « ben, ça marche tout seul ! », non. LeftMouseDown :

Les évènements LeftMouseDown/Up et MouseMove void CElementManager::OnLButtonDown(CModeler1View* pView, UINT nFlags, const Les évènements doivent être gérés dans la classe CWindow. Ça va CPoint& cpoint) ressembler à cela : { if( m_type == ElementType::type_unknown ) class CModeler1View : public CScrollView { { //FIXME : do we need to handle not implemented objects ? … return; public: } afx_msg void OnLButtonDown(UINT nFlags, CPoint point); afx_msg void OnLButtonUp(UINT nFlags, CPoint point); CPoint point = cpoint; afx_msg void OnMouseMove(UINT nFlags, CPoint point); ViewToManager(pView, point); … m_clickPoint = point; } m_lastPoint = point; Comme vous pouvez le constater, les méthodes utilisent un objet CPoint. m_selectPoint = point;

Un peu d’algorithmes if( m_type == ElementType::type_select ) Pour avoir une mécanique de fonctionnement d’un logiciel de des- { sin, il va falloir un peu d’algorithmes. En effet, la souris peut faire

28 //programmez.com 027_035.qxp_HS02 13/10/2020 09:50 Page29

WINDOWS

m_selectMode = SelectMode::none; pElement->m_bMoving = true; // Check for resizing (only allowed on single selections) } if( HasSelection() && m_selection.GetCount() == 1) else { { std::shared_ptr pElement = m_selection.GetHead(); // if shift or control i spressed, unselect the element //Change cursor look because mouse click is over an object for sizing if ((nFlags & MK_SHIFT) || (nFlags & MK_CONTROL)) m_nDragHandle = pElement->HitTest(point, pView, TRUE); { if (m_nDragHandle != 0) Deselect(pElement); { } m_selectMode = SelectMode::size; }

if (m_nDragHandle == 2) m_selectMode = SelectMode::move; { m_connectorInUse = ConnectorType::connector2; // Update UI } UpdateUI(pView, pElement); else // Redraw { Invalidate(pView, pElement); m_connectorInUse = ConnectorType::connector1; } } else } { else // See if the click was on an object { // TRUE -> select and start move if so m_connectorInUse = ConnectorType::connector2; // FALSE -> Click on background, start a net-selection } // m_selectMode = netSelect; } if( HasSelection() ) if( m_selectMode == SelectMode::none ) { { SelectNone(); // See if the click was on an object Invalidate(pView, pElement); std::shared_ptr pElement = m_objects.ObjectAt(point, m_selectType); } if( pElement != NULL ) { m_selectMode = SelectMode::netselect; if( IsSelected(pElement) == false ) } { } if( (nFlags & MK_SHIFT) || (nFlags & MK_CONTROL)) } { // We are not in a select operation } // -> this is a drawing operation else // We have to create... SelectNone(); // Create a Drawable Object... else if (pElement->m_bGrouping == false) { { m_objectId = pElement->m_objectId; #ifdef VERSION_COMMUNITY Select(pElement); if (CFactory::g_counter > MAX_SHAPES && IsMyLocalDev() == false) } { else AfxMessageBox(_T("Maximum number or shapes reached !\nFor more, please buy the A { rchitect Edition.")); for (vector>::const_iterator itSel = pElement-> return; m_pElementGroup->m_Groups.begin(); itSel != pElement->m_pElementGroup->m_Groups. } end(); itSel++) #endif { std::shared_ptr pObj = *itSel; SelectNone(); Select(pObj); } std::shared_ptr pNewElement = CFactory::CreateElementOfType(m_type, } m_shapeType);

programmez.com// 29 027_035.qxp_HS02 13/10/2020 09:50 Page30

WINDOWS

if( m_type == ElementType::type_unknown ) _objectId); { if( pElement == NULL ) return; { } return; } if (m_shapeType == ShapeType::selection) { if( m_type == ElementType::type_select ) m_bSelectionHasStarted = true; { pSelectionElement = pNewElement; if( HasSelection() ) } { // Change cursor look temporary just because mouse could be over a shape pNewElement->m_point = point; int nHandle = pElement->HitTest(point, pView, true); // For plumbing purpose... if (nHandle != 0) pNewElement->m_pManager = this; { pNewElement->m_pView = pView; SetCursor(pElement->GetHandleCursor(nHandle)); } // Add an object m_objects.AddTail(pNewElement); if( m_selectMode == SelectMode::move ) { // Store last created object m_objectId = pNewElement->m_objectId; for( vector>::const_iterator itSel = m_selection.m_objects .begin() ; itSel!=m_selection.m_objects.end() ; itSel++ ) // Select the new element { Select(pNewElement); std::shared_ptr pObj = *itSel;

m_selectMode = SelectMode::size; CPointdelta = (CPoint)(point - m_lastPoint); InvalObj(pView, pObj); m_nDragHandle = 1; pObj->m_rect += delta; m_connectorInUse = ConnectorType::connector2; pObj->m_point = pObj->m_rect.TopLeft(); FindAConnectionFor(pNewElement, point, pView, ConnectorType::connector1); InvalObj(pView, pObj); } pView->GetDocument()->SetModifiedFlag(); pView->GetDocument()->SetModifiedFlag(); // Update ClassView & FileView } UpdateClassView();//pNewElement); UpdateFileView();//pNewElement); if( m_selectMode == SelectMode::size ) { // Update UI if( m_nDragHandle != 0) UpdateUI(pView, pNewElement); { } std::shared_ptr pObj = m_selection.GetHead(); } pObj->MoveHandleTo(m_nDragHandle, point, pView); Examinons maintenant la fonction OnMouseMove : FindAConnectionFor(pElement, point, pView, m_connectorInUse); void CElementManager::OnMouseMove(CModeler1View* pView, UINT nFlags, const CPoint& cpoint) InvalObj(pView, pObj); { // a SELECT operation is started pView->GetDocument()->SetModifiedFlag(); if( m_selectMode == SelectMode::none ) } { } } } CPoint point = cpoint; } CPoint m_movePoint = point; else ViewToManager(pView, point); { CPoint lastPoint = point; if( m_selectMode == SelectMode::size ) { std::shared_ptr pElement = m_selection.GetHead(); //m_objects.FindElement(m pElement->m_last = point;

30 //programmez.com 027_035.qxp_HS02 13/10/2020 09:50 Page31

WINDOWS

pElement->InvalidateObj(); pElement->CheckForKeepingAMinimumSize(); FindAConnectionFor(pElement, point, pView, m_connectorInUse); // Switch the view in Select Mode InvalObj(pView, pElement); m_type = ElementType::type_select; } pView->GetDocument()->SetModifiedFlag(); } // } // We are gonna end a slection rect... // m_lastPoint = point; if (m_bSelectionHasStarted == true) // Check for mouse cursor -> sizing/moving { if( m_selectMode == SelectMode::size ) CRect rect = pSelectionElement->m_rect; { //if (m_nDragHandle != 0) // remove the selection element from the objects list { vector>::iterator pos; SetCursor(m_selection.GetHead()->GetHandleCursor(m_nDragHandle)); pos = find(m_objects.m_objects.begin(), m_objects.m_objects.end(), pSelectionElement); } if (pos != m_objects.m_objects.end()) } { m_objects.m_objects.erase(pos); if( m_type == ElementType::type_select && m_selection.GetCount() == 0) } { SetCursor(AfxGetApp()->LoadStandardCursor(IDC_ARROW)); // remove the selection element from the selection list } vector>::iterator pos2; } pos2 = find(m_selection.m_objects.begin(), m_selection.m_objects.end(), pSelectionElement); if (pos2 != m_selection.m_objects.end()) Examinons maintenant la fonction LeftMouseUp : { void CElementManager::OnLButtonUp(CModeler1View* pView, UINT nFlags, const CPoint& cpoint) m_selection.m_objects.erase(pos2); { } CPoint point = cpoint; ViewToManager(pView, point); //ViewToManager(pView, rect); SelectNone();

//if( m_selectMode == SelectMode::move || m_selectMode == SelectMode::size ) shared_ptr pLastSelected = nullptr; { vector> v = m_objects.ObjectsInRectEx(rect, m_selectType); // Selection Moving or Sizing is finished. // version Ex : do not select lines with full connector // Nothing to do. if (v.size() != 0) m_selectMode = SelectMode::none; { } for (std::shared_ptr pElement : v) { //m_bDrawing = FALSE; if (IsSelected(pElement) == false) { std::shared_ptr pElement = m_objects.FindElement(m_objectId); if (pElement->m_bGrouping == false) if (pElement == NULL) { return; Select(pElement); pLastSelected = pElement; if (m_type == ElementType::type_select) } { } if (HasSelection() && m_selection.GetCount() == 1) } { } // Nothing to do... } if (pLastSelected != nullptr) else { { // Update UI // Finish a drawing operation UpdateUI(pView, pLastSelected); pElement->m_last = point; } pElement->InvalidateObj();

programmez.com// 31 027_035.qxp_HS02 13/10/2020 09:50 Page32

WINDOWS

pSelectionElement = nullptr; • Ajout de l’élément à liste des objets à afficher ; m_bSelectionHasStarted = false; • Sélection de l’élément ; } • Passage en état mode sizing.

m_bSizingALine = false; Accès aux données Dessiner des gabarits, c’est bien, mais il faut aussi penser aux opé- // Set selectType to default rations Load & Save et là il faut un support. Via le Framework des m_selectType = SelectType::intuitive; MFC je peux faire un support fichiers très facilement via la méthode m_connectorInUse = ConnectorType::connector2; Serialize du Document. Cela est très facile. Moi ce que je veux, m_bDrawRectForConnectionPoint = false; c’est un support de bases de données comme SQLite par exemple. C’est portable et très rapide et surtout très pratique. Procédons par pElement->m_bMoving = FALSE; étape : // Redraw • Download de SQLite ; InvalObj(pView, pElement); • Recompilation ; • Création du wrapper C++ ; m_selectMode = SelectMode::none; • Création de la couche d’accès aux données. pView->GetDocument()->SetModifiedFlag(); pView->GetDocument()->UpdateAllViews(pView); Download de SQLite } Il faut aller sur https://sqlite.org/index.html et télécharger le fichier Maintenant que les évènements sont codés, il faut coder les com- https://sqlite.org/2020/sqlite-src-3330000.zip mandes de la barre d’outils. Pour ceux qui veulent une solution simple, avec un seul fichier (sqli- te3.c) on préférera https://sqlite.org/2020/sqlite-amalgamation-3330000.zip Création d’un gabarit Une fois que l’on a les sources, on les extrait dans un répertoire Pour créer un gabarit, le Ribbon met à disposition un ensemble de sqlite et on crée un projet Visual C++ de type DLL. gabarits. Chaque gabarit possède un index et il existe une enum Dans le header sqlite3.h, on ajoute les extensions SQLITE_API qui contient le mapping entre l’index et le nom de l’élément : devant les fonctions que l’on veut exporter. Vous allez me dire, les- Code complet sur programmez.com & github quelles ? Toutes ! Par défaut, sqlite est une librairie statique donc il Voici le Ribbon : ᖨ faut exporter les fonctions si l’on veut utiliser une DLL. Examinons l’évènement associé à cet élément du Ribbon : Contenu du fichier sqlite.h : void CModeler1View::OnModelingShapes() #ifdef SQLITE_EXPORTS { #define SQLITE_API __declspec(dllexport) // TODO: Add your command handler code here #else GetManager()->m_type = ElementType::type_shapes_simple; #define SQLITE_API __declspec(dllimport) int shapeId = CMFCRibbonGallery::GetLastSelectedItem(ID_DESIGN_SHAPES); #endif GetManager()->m_shapeType = CShapeType::ToShapeType(OffsetShapes_Simple + shapeId); Contenu du fichier sqlite3.h : } ../.. Le positionnement des états permet de passer dans le mode sui- SQLITE_API int sqlite3_open( vant sur l’évènement LeftMouseBtnDown : const char *filename, /* Database filename (UTF-8) */ std::shared_ptr pNewElement = sqlite3 **ppDb /* OUT: SQLite db handle */ CFactory::CreateElementOfType(m_type, m_shapeType); ); pNewElement->m_point = point; SQLITE_API int sqlite3_open16( // For plumbing purpose... const void *filename, /* Database filename (UTF-16) */ pNewElement->m_pManager = this; sqlite3 **ppDb /* OUT: SQLite db handle */ pNewElement->m_pView = pView; ); // Add an object SQLITE_API int sqlite3_open_v2( m_objects.AddTail(pNewElement); const char *filename, /* Database filename (UTF-8) */ // Store last created object sqlite3 **ppDb, /* OUT: SQLite db handle */ m_objectId = pNewElement->m_objectId; int flags, /* Flags */ // Select the new element const char *zVfs /* Name of VFS module to use */ Select(pNewElement); ); m_selectMode = SelectMode::size; ../… Voici le déroulé des opérations : Gestion des utilisateurs et mot de passe • Création de l’élément ; Pour avoir le support des user/pwd, il faut ajouter un bout de code • Association des éléments technique (manager, view) ; à sqlite3.c. Nous allons piocher dans les extensions de sqlite et

32 //programmez.com 027_035.qxp_HS02 13/10/2020 09:50 Page33

WINDOWS

récupérer un bout de code depuis sqlite-src-3330000.zip\sqlite-src- int ExecuteCommand(std::string command, int& rows); 3330000\ext\userauth\. Query ExecuteQuery(std::string query, int& rows); Ajoutez le bout de code au bout du fichier sqlite3.c. PreparedStmt CreatePreparedStmt(std::string statement); Cela permet d’avoir une fonction qui prend user/pwd : int ExecuteScalar(std::string query); sqlite_int64 GetLastRowID(); int sqlite3_user_authenticate( void SetBusyTimeout(int timeout); sqlite3 *db, /* The database connection */ sqlite_int64 InsertAndReturnID(std::string sqlinsert); const char *zUsername, /* Username */ const char *aPW, /* Password or credentials */ protected: int nPW /* Number of bytes in aPW[] */ std::string m_DatabaseFileName; ); bool m_bOpen; Compilation de la DLL sqlite3* m_instance; Voici le projet de DLL C++ dans Visual Studio : ᖩ };

Création du wrapper } Pour créer le wrapper, on va faire des classes simples, mais pra- #endif //__Database_H__ tiques à utiliser : ᖨ • Database ; Contenu de PreparedStmt.h : • DatabaseException ; • ColumnType ; #ifndef __PreparedStmt_H__ • PreparedStmt ; #define __PreparedStmt_H__ • Query. ᖪ #include Contenu du fichier Database.h : #include #include "Query.h" #ifndef __Database_H__ #define __Database_H__ namespace SQLite { #include class SQLITEWRAPPER_API PreparedStmt namespace SQLite { { public: PreparedStmt(void); class Query; PreparedStmt(sqlite3* instance, sqlite3_stmt* stmt); class PreparedStmt; virtual ~PreparedStmt(void);

class SQLITEWRAPPER_API Database public: { PreparedStmt& operator=(const PreparedStmt& right); public: void SetStringParameter(int index, std::string value); Database(void); void SetIntegerParameter(int index, int value); Database(std::string filename); void SetDoubleParameter(int index, double value); virtual ~Database(void); void SetInt64Parameter(int index, sqlite_int64 value); void SetNullParameter(int index); public: void Reset(); void SetDatabaseName(std::string fileName); void Close(); void AddUser(std::string user, std::string password); int Execute(int& rozs); bool AuthenticateUser(std::string user, std::string password); sqlite_int64 ExecuteAndGetID(); bool IsOpen(); Query ExecuteAndGetQuery(); sqlite3_stmt* PrepareQuery(std::string query); void SetFileName(std::string filename); protected: bool CreateAdminUser(); sqlite3_stmt* m_statement; bool Open(); sqlite3* m_instance; bool OpenEx(std::string user, std::string password); int m_NumberOfParameters; bool Close(); void ReportError(std::string context); public: int ExecuteSQL(std::string sql, int& rows); inline bool IsOpen() { return m_instance != NULL; }

programmez.com// 33 027_035.qxp_HS02 13/10/2020 09:50 Page34

WINDOWS

inline bool IsStmtOpen() { return m_statement != NULL; } protected: inline void SetNull() { m_statement = NULL; } inline bool IsOpen() { return m_instance != NULL; } inline bool CheckParameter(int index) { return (index <= m_NumberOfParameters); }; inline bool IsStmtOpen() { return m_statement != NULL; } inline int GetNumberOfParams() { return m_NumberOfParameters; } inline void SetNull() { m_statement = NULL; } }; public: } inline bool IsEOF() { return m_bEOF; } inline bool IsColumnIndexValid(int index) { return (((index >= 0) && (index <= (m_Nb #endif //__PreparedStmt_H__ Cols - 1)))); } inline int GetNumberOfColumns() { return m_NbCols; } Contenu de Query.h : void Close(); std::string GetColumnName(int index); #ifndef __Query_H__ ColumnType GetColumnType(int index); #define __Query_H__ double GetDoubleValue(int index); int GetIntValue(int index); #include long GetLongValue(int index); #include std::string GetStringValue(int index); #include "common/ColumnType.h" void MoveNext(); #include "common/DatabaseException.h" };

namespace SQLite } { #endif //__Query_H__ class SQLITEWRAPPER_API Query { Ces classes encapsulent l’accès à la librairie C SQLite en C++. public: Pourquoi ? Le code C++ est bien plus lisible quand on utilise des Query(void); objets plutôt que des fonction C en vrac. Voici le contenu de la virtual ~Query(void); fonction Database ::OpenEx qui prend un user/pwd : Query(sqlite3_stmt* stmt, sqlite3* instance, bool norows, bool ownstatement = true); bool Database::OpenEx(std::string user, std::string password) Query(const Query& query); { Query& operator=(const Query& query); int retValue = sqlite3_open_v2(m_DatabaseFileName.c_str(), &m_instance, SQLITE_ OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL); protected: m_bOpen = (retValue == SQLITE_OK); sqlite3_stmt* m_statement; if (!m_bOpen) sqlite3* m_instance; { int m_NbCols; ReportError(" opening database file " + m_DatabaseFileName + " "); bool m_bEOF; } bool m_bDestroyStatement;

retValue = sqlite3_user_authenticate(m_instance, user.c_str(), password.c_str(), ᖩ ᖪ password.size()); if (retValue == SQLITE_OK) return true;

return false; }

Son utilisation est très simple : // open database SQLite::Database db; string dbName = UFM_SQLITE_DATABASE; db.SetDatabaseName(dbName); if (!db.OpenEx(UFM_SQLITE_USER, UFM_SQLITE_PASSWORD)) return;

Pour la gestion des requêtes, le principe est le même. Exemple de stockage de diagramme :

34 //programmez.com 027_035.qxp_HS02 13/10/2020 09:50 Page35

WINDOWS

// store to db vector> SQLiteDiagramEntity::SelectAll() SQLiteDiagramEntity diagramEntity(&db); { diagramEntity.FileName = diagramName; diagramEntity.Json = strJson; vector> vDiagrams; diagramEntity.InsertOrUpdate(m_diagramId); string sql; db.Close(); Voici le code de la méthode InsertOrUpdate : try { bool SQLiteDiagramEntity::InsertOrUpdate(int& id) // "SELECT DiagramPK, LastUpdate, FileName, Json FROM Diagram;" { sql = SQLiteQueryString::GetSQLQuery(SQLiteQueryString::SelectAll_Diagram); string sql; int rows = 0;

if (id == 0) //SQLite::Query q; { //q = m_pDatabase->ExecuteQuery("select count(*) from Diagram", rows); // Insert //int count = q.GetLongValue(0); // "INSERT INTO Diagram VALUES (NULL, datetime('now'), ?, ?);" //q.Close(); sql = SQLiteQueryString::GetSQLQuery(SQLiteQueryString::Insert_Diagram); rows = 0; SQLite::PreparedStmt stmt = m_pDatabase->CreatePreparedStmt(sql); SQLite::Query query; stmt.SetStringParameter(1, this->FileName); query = m_pDatabase->ExecuteQuery(sql, rows); stmt.SetStringParameter(2, this->Json); //stmt.SetNullParameter(6); while (query.IsEOF() == false) { int rows; shared_ptr sde = make_shared(); if (!stmt.Execute(rows)) return false; sde->DiagramPK = query.GetLongValue(0); sde->LastUpdate = query.GetStringValue(1); stmt.Close(); sde->FileName = query.GetStringValue(2); sde->Json = query.GetStringValue(3); id = m_pDatabase->GetLastRowID(); } vDiagrams.push_back(sde); else { query.MoveNext(); // Update } // "UPDATE Diagram SET Json=? WHERE DiagramPK=?;" sql = SQLiteQueryString::GetSQLQuery(SQLiteQueryString::Update_Diagram); query.Close(); } SQLite::PreparedStmt stmt = m_pDatabase->CreatePreparedStmt(sql); catch (SQLite::DatabaseException ex) stmt.SetStringParameter(1, this->Json); { stmt.SetIntegerParameter(2, id); std::cerr << ex.ToString() << std::endl; //stmt.SetNullParameter(6); }

int rows; return vDiagrams; if (!stmt.Execute(rows)) } return false; Conclusion stmt.Close(); Le C++ Moderne, c’est l’utilisation accrue de la STL, des } smart pointers, des containers et des applications sophis- tiquées. On tire parti du système d’exploitation et de ses return true; librairies et aussi des librairies open source. L’accès aux } données avec SQLite est très pratique. Le code présenté Le mécanisme est simple : on déclare un statement avec une est disponible sur : requête SQL, on lui affecte les différents paramètres, et on exécute https://github.com/ChristophePichaud/UltraFluidModelerNET le tout. Cela c’est pour l’insertion ou la modification de données. Avec une syntaxe proche à 95% de Java et C#, C++ est facile à Pour la récupération, le code est différent. Voici la méthode qui apprendre. Pour vous initier, n’oubliez pas mon livre « Aide- charge tous les diagrammes de la base : Mémoire C++ » chez DUNOD.

programmez.com// 35 036_047.qxp_HS02 13/10/2020 09:53 Page36

WINDOWS Alain Zanchetta Principal Software Developer dans l’équipe Windows de Microsoft. Introduction à C++/WinRT

Windows 8 avait introduit un nouveau modèle d’application – Universal Windows Application – qui était destiné à remplacer le modèle existant depuis toujours. Ce basculement ne s’est pas effectué et les applications classiques – souvent appelées Applications Win32 – continuent à dominer sur nos bureaux.

éanmoins, ce nouveau modèle a de nombreux avantages appels de méthodes. Chaque langage de programmation – C++, techniques et est en particulier associé à une redéfinition C#, JavaScript, etc. – utilise ensuite une projection spécifique de Ncomplète des API de Windows, totalement orientée objet et ces API pour exposer au développeur ou à la développeuse une cohérente, ce qui rend accessible aux développeurs et dévelop- intégration naturelle et consistante avec reste du langage. peuses l’ensemble des domaines couverts par ces API. Au plus bas niveau, l’ABI du Windows Runtime emprunte les élé- Les adeptes du C++ et de Win32 font généralement face à des ments fondamentaux de COM comme la notion d’interface déri- collections de fonctions sans aucune cohérence d’un domaine à un vant de IUnknown ainsi que l’utilisation de HRESULT pour signaler autre : parfois elles retournent des entiers pour indiquer leur succès les erreurs, l’immuabilité des interfaces une fois définies. Les princi- ou la raison de leur échec, parfois elles retournent des booléens et pales différences avec le COM « standard » résident dans les modes un appel à GetLastError() est alors nécessaire pour savoir ce qui d’activation, les modèles de threading, les métadonnées (rempla- s’est réellement passé. Les API UWP apportent à ces développeurs cement des .TLB par des .WINMD) ainsi que dans l’utilisation d’un et développeuses le confort auquel sont habitué∙e∙s depuis de nom- certain nombre d’interfaces prédéfinies pour exposer de manière breuses années leurs homologues adeptes du C# et de .NET. standard quelques mécanismes comme les événements, les itéra- Malheureusement, migrer une application en bloc d’un modèle de tions ou encore les appels asynchrones. programmation et son ensemble d’API à un autre n’est en pratique Un langage comme C# masque toute cette complexité en expo- que rarement faisable, car cela prend énormément de temps et le sant au niveau de la classe elle-même l’union des méthodes appar- retour sur investissement est très long, comparé au simple ajout de tenant aux différentes interfaces implémentées par cette classe (et nouvelles fonctions au code existant. en transformant au passage les HRESULT en exceptions). Est-ce à dire que les développeurs et développeuses C++ sont Dans le cas du C++, la correspondance entre classe C++ et classe condamné∙e∙s à écrire du code obsolète ? Non, car lors de la Build WinRT est tout simplement impossible à réaliser directement : par 2020, Microsoft a enterré définitivement la séparation entre les exemple, les opérateurs new et delete donnent toute la charge de applications modernes et les applications classiques en formalisant l’allocation et de la libération de la mémoire au code client d’un son initiative appelée « Project Reunion » : un des aspects majeurs objet, alors que ces opérations sont de la responsabilité de l’implé- de cette initiative est de rendre les nouvelles API de Windows mentation de cet objet. accessibles à toutes les applications. La projection de l’ABI en C++ peut alors suivre plusieurs voies, qui Cette possibilité n’est pas totalement nouvelle – un bon nombre des ont été explorées et utilisées par Microsoft : nouvelles classes étaient déjà accessibles aux applications Win32 – mais elle devient une priorité et se généralise. Par ailleurs, pour les Exposer ou implémenter directement WRL (Windows développements en C++, ce rapprochement entre les applications les interfaces COM de l’ABI Runtime Libraries) classiques et les UWP est facilité par l’adoption d’une autre Étendre le langage C++ pour se C++/CX technologie – C++/WinRT – qui donne accès à ces API via du code rapprocher du confort de C# C++ standard, alors que le développement UWP s’appuyait aupa- S’appuyer sur des classes C++ C++/WinRT ravant sur un dialecte appelé C++/CX qui rebutait les puristes. La générées par les outils. suite de cet article explique pourquoi ce C++/WinRT était néces- saire et comment le mettre en œuvre. Avant de plonger dans le vif du sujet, c’est-à-dire la mise en œuvre C++/WinRT, comparons ces différentes approches dans un petit Pourquoi une nouvelle technologie C++ ? exemple affichant l’identifiant, l’intervalle de publication et la laten- Pour être universelle, l’interface de programmation d’un système ce de l’accéléromètre par défaut. d’exploitation doit être indépendante du langage de programma- tion, mais aussi supporter les évolutions des classes qu’elle contient WRL = efficace, mais lourd et celles des outils de développement (il est généralement décon- À chaque fois qu’une classe évolue (ajouts de méthodes dans une seillé de fusionner dans le même binaire des composants écrits nouvelle version), on doit définir une nouvelle interface, voire deux avec différentes versions d’un compilateur). Pour cela, elle s’appuie si certaines méthodes ajoutées sont statiques. Prenons l’exemple de sur une ABI – Application Binary Interface – qui définit comment Windows.Devices.Sensors.Accelerometer qui masque… 9 inter- s’effectuent au plus bas niveau les instanciations d’objets et les faces COM différentes !

36 //programmez.com 036_047.qxp_HS02 13/10/2020 09:53 Page37

WINDOWS

Membre Interface C++/WinRT = standard… et facile ? MinimumReportInterval (Propriété) IAccelerometer Le C++/WinRT est du pur C++ (version 17) et s’appuie sur la ReportInterval (Propriété) IAccelerometer génération de classes : GetCurrentReading (Méthode) IAccelerometer • Les classes sont générées par le compilateur à partir des fichiers ReadingChanged (Evénement) IAccelerometer de métadonnées .WINMD. Ces classes sont simples à utiliser et Shaken (Evénement) IAccelerometer s’intègrent parfaitement dans un code C++ totalement standard. ReadingTransform (Propriété) IAccelerometer2 • L’implémentation de composants WinRT passe par l’utilisation de MaxBatchSize (Propriété) IAccelerometer3 fichiers IDL et l’héritage des classes générées. Ceci est moins aisé ReportLatency (Propriété) IAccelerometer3 que la consommation des API, mais reste néanmoins lui aussi ReadingType (Propriété) IAccelerometer4 basé sur du code C++ standard. ReportThreshold (Propriété) IAccelerometer5 Notre exemple devient : DeviceId (Propriété) IAccelerometerDeviceId GetDefault() (Méthode statique) IAccelerometerStatics Accelerometer defaultAccelerometer = Accelerometer::GetDefault(); GetDefault(AccelerometerReadingType) (Méthode statique) IAccelerometerStatics2 hstring deviceId = defaultAccelerometer.DeviceId(); FromIdAsync (Méthode statique) IAccelerometerStatics3 unsigned int reportInterval = defaultAccelerometer.ReportInterval(); GetDeviceSelector (Méthode statique) IAccelerometerStatics3 unsigned int latency = defaultAccelerometer.ReportLatency();

ComPtr spAccelerometerStatics; wcout << L"Device #" << deviceId.c_str() << L":\n Report Interval: " << reportInterval << L"ms\n Latency: " << latency << L"ms"; RETURN_IF_FAILED(GetActivationFactory(HStringReference(RuntimeClass_Windows_Devices _Sensors_Accelerometer).Get(), &spAccelerometerStatics)); Nous allons maintenant détailler un certain nombre de méca- ComPtr spAccelerometer; nismes de mise en œuvre de C++/WinRT. RETURN_IF_FAILED(spAccelerometerStatics->GetDefault(&spAccelerometer)); ComPtr spAccelerometerDeviceId; Intégrer C++/WinRT dans une RETURN_IF_FAILED(spAccelerometer.As(&spAccelerometerDeviceId)); application HString deviceId; Même les applications les plus archaïques avec leur Winmain et RETURN_IF_FAILED(spAccelerometerDeviceId->get_DeviceId(deviceId.GetAddressOf())); leur boucle GetMessage() / TranslateMessage() / DispatchMessa- UINT32 reportInterval; ge() peuvent utiliser C++/WinRT sans devoir être restructurées. RETURN_IF_FAILED(spAccelerometer->get_ReportInterval(&reportInterval)); Imaginons une telle application qu’on aimerait moderniser un petit ComPtr spAccelerometer3; peu en adaptant ses couleurs à la luminosité ambiante. RETURN_IF_FAILED(spAccelerometer.As(&spAccelerometer3)); Les API du Windows Runtime contiennent la classe Windows. unsigned int latency; Devices.Sensors.LightSensor qui permet d’accéder au capteur de RETURN_IF_FAILED(spAccelerometer3->get_ReportLatency(&latency)); luminosité éventuellement présent sur la machine, ce qui est assez cout << "Device #" << deviceId.GetRawBuffer(nullptr) << ":\n Report Interval: " fréquent sur les portables récents. << reportInterval << "ms\n Latency: "<< latency << "ms"; Moderniser notre vénérable application ne demande que quelques étapes : C++/CX = facile… • Activer le support de C++/WinRT dans le projet ; mais pas du « vrai » C++ • Inclure les en-têtes nécessaires à l’utilisation de la classe Light- L’exemple précédent s’écrit en C++/CX aussi simplement qu’en C# : Sensor ; • Accéder à cette classe comme à une classe C++ normale : Accelerometer^ defaultAccelerometer = Accelerometer::GetDefault(); construction puis appels de méthodes. String^ deviceId = defaultAccelerometer->DeviceId; Pour activer le support de C++/WinRT, le plus simple est d’ajouter unsigned int reportInterval = defaultAccelerometer->ReportInterval; au projet le package Nuget correspondant : unsigned int latency = defaultAccelerometer->ReportLatency; • Cliquer avec le bouton droit sur le projet et sélectionner de « Manage Nuget Packages… ». Chercher « C++ winrt » sur nuget.org et instal- cout << "Device #" << deviceId->Data() << ":\n Report Interval: " ler la version la plus récente de Microsoft.Windows.CppWinRT. ᖥ << reportInterval << "ms\n Latency: " << latency << "ms"; • L’affichage des propriétés du projet permet de vérifier que le lan- Mais malheureusement, ce n’est pas du C++ standard et c’est gage de programmation est C++ 17. totalement rédhibitoire pour un langage qui devient aujourd’hui de • La même fenêtre permet de préciser quelle version du SDK de plus en plus portable avec sa standardisation respectée par quasi- Windows 10 utiliser : le MSDN décrit quand les différents ment tous les compilateurs. membres de chaque classe ont été ajoutés : si une méthode ou Au passage, l’utilisation d’une syntaxe proche du C++ CLI de .NET une propriété semble manquer dans Intellisense ou lors de la avec son ^ pour les références a souvent semé la confusion parmi compilation, c’est généralement dû au référencement d’une ver- les développeurs et développeuses en leur faisant penser que le code sion trop ancienne du SDK de Windows 10 ; on peut commencer généré n’était pas du code natif, mais un code intermédiaire. par laisser l’option par défaut – « 10.0 (latest installed version) » –

programmez.com// 37 036_047.qxp_HS02 13/10/2020 09:53 Page38

WINDOWS

et ne préciser un SDK particulier que si cela s’avère nécessaire // Windows Header Files (généralement, quand le projet est partagé entre différentes #include machines). ᖦ // C RunTime Header Files • En termes d’API Windows, LightSensor appartient à l’espace de #include noms « Windows.Devices.Sensors » : le fichier d’en-tête corres- #include pondant sera donc « winrt/Windows.Devices.Sensors.h ». Ces en- #include têtes sont générés à partir des fichiers .WINMD et respectent #include strictement cette correspondance entre l’espace de nom et le nom de l’en-tête. #include On peut inclure ce fichier dans le .H des headers précompilés du projet – framework.h dans cet exemple – sans avoir besoin d’inclure On peut maintenant déclarer un objet de type LightSensor. Les des dépendances additionnelles. Néanmoins, si on utilise des opé- classes générées dans winrt/Windows.Device.Sensors.h appartien- rations asynchrones, il faudra aussi inclure winrt/Windows.Founda- nent à l’espace de nom C++ winrt::Windows::Devices::Sensors : on tion.h : peut utiliser le nom complet de la classe ou utiliser l’instruction « using namespace ». Dans cet article, nous utiliserons une option #pragma once intermédiaire pour mettre en évidence les objets et fonctions de #include "targetver.h" C++/WinRT : #define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers namespace winrt { using namespace Windows::Devices::Sensors; }

winrt::LightSensor g_lightSensor{ nullptr };

À présent, on peut initialiser le support de WinRT et obtenir le cap- teur par défaut de la machine dans la fonction wWinmain. On note- ra au passage que l’absence de capteur de luminosité n’étant pas une erreur de programmation, LightSensor::GetDefault() ne déclenche pas d’exception et se contente de retourner une référen- ce nulle.

winrt::init_apartment();

g_lightSensor = winrt::LightSensor::GetDefault(); ᖥ if (!g_lightSensor) { MessageBox(nullptr, L"Impossible d’adapter les couleurs à la luminosité ambiante", L"Pas de capteur de luminosité." MB_OK | MB_ICONERROR); } Exécuter ce code sous le débogueur permet de voir que celui-ci a été mis à jour pour être en mesure de découvrir et de lire les pro- priétés des références C++/WinRT : ᖧ Pour finir, il reste à appeler la méthode GetCurrentReading : ᖦ auto lightReading = g_lightSensor.GetCurrentReading(); if (lightReading != nullptr) { float illuminance = lightReading.IlluminanceInLux(); UpdateColorTheme(illuminance); }

Et voilà une application classique qui a maintenant accès aux der- nières API de Windows !

ᖧ 38 //programmez.com 036_047.qxp_HS02 13/10/2020 09:53 Page39

WINDOWS

Super-smart pointers celle-ci appartient à un autre espace de noms, il faut penser à inclu- Les classes C++ WinRT sont des « super-smart » pointers : un re manuellement le fichier d’en-tête correspondant sous peine de smart pointer habituel ne sait en réalité pas grand-chose de l’objet voir apparaître une erreur cryptique à la compilation : qu’il gère. Il ne s’intéresse qu’à la durée de vie de celui-ci (et c’est déjà bien pratique) et se contente de transmettre sans valeur ajou- auto buffer = winrt::CryptographicBuffer::DecodeFromHexString(L"30310AFF"); // OK tée les appels effectués via l’opérateur ->. Les classes C++ WinRT auto length = buffer.Length(); // Erreur sont générées à partir des métadonnées des classes qu’elles repré- sentent. Elles peuvent de ce fait offrir bien plus de services : ras- 1>… foo.cpp(52,29): error C3779: 'winrt::impl::consume_Windows_Storage_Streams_ sembler les méthodes de toutes les interfaces implémentées par IBuffer::Length': a function that returns 'auto' l’objet WinRT, en gérer l’instanciation, etc. Examinons comment les cannot be used before it is defined différents types de membres des classes WinRT – ce qui est visible 1>… \Generated Files\winrt\impl\Windows.Storage.Streams.0.h(392): message : see declaration dans la documentation en ligne – se traduisent en C++. of 'winrt::impl::consume_Windows_Storage_Streams_IBuffer::Length' Constructeur Si la classe WinRT possède un constructeur, celui-ci se traduit auto- Propriétés matiquement par un constructeur de même signature pour la classe Contrairement à l’API WinRT ou C++/CX, le C++ standard n’a C++ correspondante. Par exemple le Windows.UI.Popups. Messa- pas la notion de propriété. Les propriétés des objets WinRT se tra- geDialog qui en possède deux (message ou message + titre) se duisent donc par des méthodes en C++. C++ WinRT ne s’en- construit naturellement dans le code : combre pas avec des préfixes get/set bavards et exploite la surcharge des méthodes C++: MessageDialog dialog(L"Hello world !"); MessageDialog dialog2(L"Hello world !", L"C++/WinRT Rocks !"); • Type NomDeLaPropriete() pour lire • void NomDeLaPropriete(Type nouvelleValeur) pour écrire Dans le cas où il y a un constructeur par défaut, il faut bien com- prendre la différence entre ces classes et de simples smart poin- Par exemple : teurs : ici, la simple déclaration d’une variable peut suffire à déclencher une instanciation : // Ecrit la propriété DefaultCommandIndex dialog.DefaultCommandIndex(0); Microsoft::WRL::ComPtr spFoo; // Pointeur nul vers une interface IFoo std::shared_ptr bar ; // Pointeur nul vers une instance de Bar // Lit la propriété DefaultCommandIndex winrt::Windows::Data::Json::JsonArray js; // Instancie une JsonArray uint32_t index = dialog.DefaultCommandIndex(); winrt::Windows::Data::Json::JsonArray js{nullptr}; // Référence nulle vers JsonArray Événements Le dernier exemple – initialisation à nullptr en utilisant un construc- Ici encore, C++ WinRT va à l’essentiel et définit les constructeurs teur acceptant un unique paramètre de type std::nullptr_t – rappelle et opérateurs de conversion permettant un code à la fois lisible et que la classe C++ WinRT reste une référence même si elle est suf- efficace ; les méthodes pour enregistrer et désenregistrer un hand- fisamment intelligente pour créer des instances. Les constructeurs ler d’événement portent le nom de l’événement et sont différen- et opérateurs de copie sont définis de manière à partager les réfé- ciées par leurs paramètres, par exemple pour traiter l’événement rences à la manière des smart pointers, ce qui autorise par exemple Added du DeviceWatcher à l’aide d’une expression lambda : le stockage de ces objets dans les conteneurs C++ sans duplica- tion coûteuse d’objet : auto watcher = winrt::DeviceInformation::CreateWatcher( winrt::DisplayMonitor::GetDeviceSelector()); namespace winrt { // Enregistrement du handler using namespace Windows::Foundation; auto addedToken = watcher.Added( } [](const winrt::DeviceWatcher&, const winrt::DeviceInformation& device) { … … }); std::vector trustedAddresses; trustedAddresses.push_back(winrt::Uri(L"http://www.microsoft.com")); // Désenregistrement trustedAddresses.push_back(winrt::Uri(L"http://www.programmez.com")); Watcher.Added(addedToken)

Méthodes L’enregistrement de méthodes de classes pour traiter les événe- Elles sont traduites naturellement en méthodes C++, y compris les ments est lui aussi très concis grâce à la définition des constructeurs méthodes statiques. Si une méthode retourne une autre classe appropriés pour les objets de type EventHandler : WinRT, sa projection retournera la classe C++ correspondante. Si

programmez.com// 39 036_047.qxp_HS02 13/10/2020 09:53 Page40

WINDOWS

void MainPage::Initialize() inline void check_sta_blocking_wait() noexcept { { auto watcher = winrt::DeviceInformation::CreateWatcher( // Note: A blocking wait on the UI thread for an asynchronous operation can cause a deadlock. winrt::DisplayMonitor::GetDeviceSelector()); // See https://docs.microsoft.com/windows/uwp/cpp-and-winrt-apis/concurrency#block- the-calling-thread auto addedToken = watcher.Added({ this, &MainPage::OnDisplay WINRT_ASSERT(!is_sta_thread()); Added }); } … } L’appel de get() est valide sur un thread d’arrière-plan. Pour attendre le résultat d’un appel asynchrone déclenché sur le thread void MainPage::OnDisplayAdded(const winrt::DeviceWatcher&, de l’interface graphique, la solution recommandée consiste à s’ap- const winrt::DeviceInformation& device) puyer sur les coroutines de C++ qui permettent d’interrompre le { déroulement d’une méthode puis de le reprendre sur un autre thread, à la manière des async / await de C# : } • Déclarer la méthode comme retournant une winrt::Windows:: Foundation::IAsyncAction (ou une interface similaire comme Enfin, il existe une troisième version de la méthode correspondant à IAsyncOperation) ; un événement, qui prend comme premier paramètre • Utiliser co_await pour récupérer les résultats des appels asyn- winrt::auto_revoke et retourne un objet dont le destructeur effec- chrones ; tuera le désenregistrement automatiquement : • Si on doit mettre à jour l’interface utilisateur après un ou plusieurs appels asynchrones, il est nécessaire de revenir sur le thread de // Mainpage.h l’interface. Ceci s’effectue ainsi : class MainPage { • - capturer le contexte d’exécution : … winrt::apartment_context ui_thread;

winrt::Windows::Devices::Enumeration::DeviceWatcher::Added_revoker m_addedRevoker; revenir sur ce thread : co_await ui_thread; void OnDisplayAdded( const winrt::Windows::Devices::Enumeration::DeviceWatcher&, Par exemple : const winrt::Windows::Devices::Enumeration::DeviceInformation& device); }; winrt::IAsyncAction MainPage::LoadImage() { // MainPage.cpp // Capturer le contexte du thread de l’IHM void MainPage::Initialize() winrt::apartment_context ui_thread; { m_addedRevoker = watcher.Added( // Charger l’image de manière asynchrone winrt::auto_revoke, { this, &MainPage::OnDisplayAdded }); winrt::Uri image(L"ms-appx:///Assets/sample_image.jpg"); … winrt::StorageFile file = } co_await winrt::StorageFile::GetFileFromApplicationUriAsync(image);

Appels asynchrones // Revenir sur le thread de l'IHM L’API WinRT contient de nombreuses méthodes asynchrones aux- co_await ui_thread; quelles il est difficile d’échapper (la norme était que toutes les méthodes effectuant un traitement potentiellement long comme un // Faire quelque chose avec le fichier obtenu accès disque ou accès réseau devaient être définies ainsi afin d’évi- MyTB().Text(file.Path()); ter de bloquer l’interface graphique). Ces méthodes retournent des } instances de IAsyncAction, IASyncOperation, etc. La méthode la plus simple pour attendre le résultat d’une opération Implémenter un composant WinRT asynchrone consiste à appeler get() sur celle-ci : Après avoir regardé les principaux mécanismes de consommation de WinRT en C++, intéressons-nous maintenant à l’autre côté : winrt::Uri image(L"ms-appx:///Assets/sample_image.jpg"); l’écriture de tels composants. Pourquoi en avons-nous besoin ? La auto file = winrt::StorageFile::GetFileFromApplicationUriAsync(image).get(); première utilisation sera l’implémentation d’un modèle MVVM pour une application XAML : à condition d’éviter les délires des charge- Cependant, le code ci-dessus lève une exception s’il est exécuté sur ments dynamiques et inversions de dépendances, le MVVM est une le thread de l’IHM à cause du contrôle ci-dessous (dans architecture saine qui permet réellement de finaliser une interface Windows.Foundation.h) : sans avoir à retoucher sans cesse au code métier. L’article « XAML

40 //programmez.com 036_047.qxp_HS02 13/10/2020 09:53 Page41

WINDOWS

et C++/WinRT » montrera aussi que les pages et contrôles compo- sés XAML sont aussi de tels composants. Une deuxième raison d’implémenter des composants WinRT est la réutilisabilité via des packages Nuget, grâce à leur interface « ABI-stable » indépendante du langage et de la version du compilateur utilisé. L’équipe de C++ WinRT a donné la priorité à la consommation des composants plutôt qu’à leur implémentation : celle-ci demande ᖨ quelques étapes supplémentaires, et en particulier l’utilisation du langage IDL pour la définition de l’interface des composants. namespace winrt::BTCube::implementation Pour faire une DLL réutilisable, le type de projet Visual Studio à { choisir est « Windows Runtime Component » : ᖨ struct ConnectedCube : ConnectedCubeT Le projet ainsi créé contient une classe dont la définition est basée { sur trois fichiers source (ainsi que plusieurs autres fichiers générés) : ConnectedCube(const winrt::hstring& bluetoothAddress); class.idl contient la définition de l’interface publique du composant ; • class.h et class.cpp en contiennent l’implémentation. • L’IDL utilisé ici correspond à une nouvelle version spécifique à winrt::Windows::Foundation::IAsyncOperation TryConnectAsync(); C++/WinRT. Il n’est plus nécessaire de définir séparément les inter- void Disconnect(); faces et les classes comme avec le COM classique : la définition de la classe est suffisante. Après un renommage de bon aloi, on peut bool IsSolved(); ajouter quelques propriétés et méthodes à notre composant dans le LedState Leds(); fichier .IDL : void Leds(LedState value); namespace BTCube { static winrt::com_array FindCubes() enum LedState { Off, Blinking, On }; private: [default_interface] … runtimeclass ConnectedCube }; { }

ConnectedCube(String bluetoothAddress); namespace winrt::BTCube::factory_implementation { Windows.Foundation.IAsyncOperation TryConnectAsync(); struct ConnectedCube : ConnectedCubeT void Disconnect(); { }; Boolean IsSolved{ get; }; } LedState Leds{ get; set; }; Notre classe appartient à l’espace de noms “winrt::BTCube::imple- mentation” alors que les types publics appartiennent simplement à static String[] FindCubes(); “winrt::BTCube”. On retrouve les correspondances entre les } concepts de l’API et leur implémentation présentée précédemment } (par exemple propriété = une ou deux méthodes surchargées). À partir de ces déclarations, Visual C++ génère plusieurs fichiers. Il faut bien saisir la différence entre l’interface publique du compo- La grosse différence entre C++ WinRT et l’implémentation clas- sant – définie dans le fichier .IDL – et la partie publique de cette sique d’un composant COM avec ATL ou WinRT avec WRL, c’est classe C++, celle-ci correspond un peu au concept “Internal” de que la classe C++ que l’on écrit n’est pas celle qui est directement C# : elle peut contenir tout ce dont la DLL – qui contient générale- accédée depuis l’extérieur de la DLL (oubliées les méthodes retour- ment plusieurs classes – a besoin pour réaliser ses tâches. Par nant des HRESULTS) : C++ WinRT utilise abondamment les tem- exemple, on a vu que certains objets de l’API WinRT n’avaient pas plates et les optimisations du compilateur pour autoriser une de constructeur public : on peut implémenter de tels composants implémentation (presque) totalement libérée des tracasseries de en supprimant tout constructeur du fichier .IDL tout en conservant bas niveau. Voici le .H correspondant à notre classe de Cube des constructeurs dans la classe d’implémentation. connecté : Bien que ce soit du C++ standard, on voit apparaître dans la déclaration de ConnectedCube certains types de données qui sont #pragma once propres à C++ WinRT comme les classes com_array ou hstring. C’est en fait totalement inévitable, car c’est l’ABI évoquée en début #include "ConnectedCube.g.h" de cet article qui garantit la compatibilité entre des composants écrits dans différents langages (ou avec le même langage, mais dif-

programmez.com// 41 042.qxp_HS02 12/10/202012:47Page42 Abonnez-vous à Abonnez-vous Oui, E- : E-mail : postal Code : Adresse Prénom: n n n n n n Le magazinedesdéveloppeurs

Je joins mon règlement par chèque à l’ordre de Programmez ! ! Programmez de l’ordre à chèque par règlement mon joins Je Mme

Option : Option : Etudiant an 1 Abonnement : ans 2 Abonnement : an 1 Abonnement

Photocopie de la carte d'étudiant à joindre à d'étudiant carte la de Photocopie Abonnement numérique 1 an 1 * Tarifs* France métropolitaine : Option (6 numéros + 4 hors séries) hors 4 + numéros (6 an 1 PDF séries) hors 4 + numéros (6 séries) hors 8 + numéros (12 séries) hors 4 + numéros (6 an 1 Etudiant 2 ans 2 Nos classiques I___I___I___I___I___I___I___I___I___I___I___I___I___I___I I___I___I___I___I___I___I___I___I___I___I___I___I___I I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I n www.programmez.com D

D I___I___I___I___I___I M. Entreprise : Entreprise M. Souscription uniquement sur uniquement Souscription 10 numéros numéros 10 D 10 numéros 10 ...... cè u rhvs19 archives aux accès D je m’abonne je 10 numéros 10 accès aux archives aux accès 20 numéros 20

Adresse email indispensable pour la gestion de votre abonnement votre de gestion la pour indispensable email Adresse I___I___I___I___I___I___I___I___I___I___I Touteswww.programmez.com sur offres nos 49 79 Programmez! € Ville : Ville € 79 49 39 € I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I 39 39 € € € 19 € € * * * €

@ Nom : Nom I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I Abonnez-vous à Abonnez-vous n I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I

57 Rue de Gisors, 95300 Pontoise95300 Gisors, de Rue 57 Abonnements Service PROGRAMMEZ, : à règlement votre avec retourner à ABONNEMENT Pour bienfinirl’annéeetdémarrer2021,profitezdès Je souhaite régler à réception de facture de réception à régler souhaite Je n n n Programmez! + carte PybStick carte + Programmez! PybStick carte + Technosaures + Programmez! archives aux accès + PybStick carte + Magazine Pharaon + Technosaures + Programmez! aujourd’hui denosnouvellesoffresd’abonnements. Fonction: 1 an soit 18 numéros en tout en numéros 18 soit an 1 Abonnement 1 an : an 1 Abonnement : an 1 Abonnement : an 1 Abonnement Programmez! + Technosaures + Pharaon Magazine Pharaon + Technosaures + Programmez! Programmez! + Technosaures + carte PybStick : PybStick carte + Technosaures + Programmez! 1 an soit 10 numéros 10 soit an 1 numéros 14 soit an 1 Offres 2021 I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I + carte PybStick + accès aux archives : archives aux accès + PybStick carte +

Programmez! + carte PybStick : PybStick carte + Programmez!

Ces offres peuvent s’arrêter à tout moment. Sans préavis. préavis. Sans moment. tout à s’arrêter peuvent offres Ces (*) Tarifs France. Dans la limite des stocks disponibles de la PybStick. PybStick. la de Tarifs disponibles (*) stocks des limite la France. Dans Programmez! 55 75 89 € € € * Tarifs* Francemétropolitaine au lieu de 137 de lieu au au lieu de 93 de lieu au au lieu de 63 de lieu au 75 55 89 € € € bne- Abonnez-v * * * € € €

Offres pouvant s'arrêter à tout moment, sans préavis PROG spécial 2 043.qxp_HS02 14/10/2020 09:54 Page43

Boutique Programmez! Les anciens numéros de Technosaures

Le magazine des développeurs Le magazine N°1 à remonter le temps ! ÉPUISÉ N°4 10 Standard € N°2 N°4 15 Deluxe € 236 231 226

Prix unitaire : 7,66 N°3 (frais postaux€ inclus) 240 239 238 Histoire de la micro-informatique 1973-2007

241 HS 01 été 2020 242 12,99 € Tarif unitaire 6,5 € (frais postaux inclus) (frais postaux inclus)

n 226 : I___I ex n 240 : I___I ex Technosaures n N°1 n N°2 n N°3 Commande à envoyer à : n 236 : I___I ex n 241 : I___I ex soit I___I___I exemplaires x 7,66 € = I___I___I___I___I € Programmez! n N°4 Deluxe...... 15 n 238 : I___I ex n HS 01 : I___I ex € 57 rue de Gisors n N°4 Standard ...... 10 239 : I___I ex 242 : I___I ex € 95300 Pontoise

n n R i l c a 2Gésp n Histoire de la Micro-informatique ...... 12,99 € soit I___I___I exemplaires x 6,50 € = I___I___I___I___I € ...... I___I___I___I___I € soit au TOTAL = I___I___I___I___I___I €

n M. n Mme n Mlle Entreprise : I___I___I___I___I___I___I___I___I Fonction : I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I

Prénom : I___I___I___I___I___I___I___I___I___I___I___I___I___I Nom : I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I

Adresse : I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I

Code postal : I___I___I___I___I___I Ville : I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I___I

Règlement par chèque à l’ordre de Programmez ! I Disponible sur www.programmez.com Offres pouvant s'arrêter à tout moment, sans préavis 036_047.qxp_HS02 12/10/2020 15:46 Page44

WINDOWS

férents compilateurs). La bonne nouvelle est que les classes inter- médiaires de C++ WinRT ont été conçues pour être à la fois effi- XAML et caces et totalement compatibles avec la STL : l’implémentation de ConnectedCube:: FindCubes reste donc naturelle pour un ∙e habi- tué∙e du C++. C++/WinRT winrt::com_array ConnectedCube::FindCubes() { Après avoir exploré les bases de C++ WinRT, creu- std::vector cubes; sons le sujet en examinant comment implémenter

cubes.push_back(L"cube #1"); // ou du vrai code ;-) les éléments courants d’une application XAML … comme le modèle MVVM. return winrt::com_array(cubes); Pour créer un projet XAML en C++/WinRT, il faut commencer par } télécharger les modèles de projets correspondants à l’adresse : L’article « XAML et C++ WinRT » contient d’autres exemples simi- https://docs.microsoft.com/en-us/windows/apps/desktop/visual-studio-templates, laires, en particulier afin de permettre l’utilisation directe du com- puis créer un projet en choisissant le modèle “Blank App posant WinRT dans un contexte de liaison de données (C++/WinRT)”. La page principale du projet ainsi généré permet (databinding). d’ores et déjà de noter quelques points importants : Pour utiliser ce composant dans un autre projet de la même solu- • Toutes les pages XAML sont implémentées sous forme de com- tion (le packaging Nuget est en dehors du périmètre de cet article), posants C++/WinRT avec les trois fichiers vus précédemment: il suffit de l’ajouter aux références du projet consommateur. Dans .IDL, .H et .CPP ; celui-ci, le composant est vu comme n’importe quel autre compo- • Les propriétés qu’on désire rendre accessibles au databinding sant C++ WinRT : doivent être déclarées dans le fichier .IDL ; • Il faut inclure (si le client est une application • Les éléments XAML nommés comme les boutons ne sont pas XAML, il faut l’inclure dans le header précompilé), déclarés dans fichier .IDL, mais seront accessibles comme des • Puis le composant s’utilise comme décrits dans la première partie propriétés dans le XAML et le code C++ : ::winrt::Windows::UI:: de cet article. Seule son interface publique, définie dans le fichier Xaml::Controls::Button myButton() correspond à