Pourquoi .NET Core ?

Afin de bien comprendre pourquoi .NET Core a été créé, il est important d'analyser et de comprendre chaque morceau dans ce Framework. En effet, chacun à une place bien précise dans l'architecture de ce dernier : runtime, librairie de classe, outils … Comprendre les dépendances entre eux permet de comprendre en quoi ce Framework est si modulable et puissant.

La modularité prend une place prépondérante avec .NET Core car ce doit être un Framework qui fonctionne sur un très grand nombre de plateforme : mobile, desktop et tablette avec UWP, Web  et Cloud avec ASP.NET, IoT avec Windows 10 IoT Core. Selon le runtime, le Framework doit d'adapter afin d'utiliser uniquement les librairies qui l'intéressent, c'est pourquoi la grande majorité de ces dernières sont déployés via des paquets NuGet, optimisant la gestion des versions, la centralisation des paquets et la diffusion du code.

Ce chapitre est l'occasion de faire le tour des différentes briques logicielles de .NET Core : le runtime, les librairies, les outils … et voir comment ces composants font de .NET Core un Framework aussi puissant et multiplateforme.

Les librairies de CoreFX

L'appellation de CoreFX fait principalement référence à l'ensemble des classes de bases disponible sous le namespace System.* et certaines extensions de Microsoft.*. Nous les connaissons plutôt sous le nom de Base Class Library, ainsi nous pouvons dire que CoreFX est le BCL de .NET Core. L'ensemble de ces classes sert de socle solidaire et bas-niveau afin que d'autres composants, tels que ASP.NET, puisse construire le Framework par-dessus. La caractéristique importante de CoreFX est que la majorité des APIs disponible sous .NET Core le sont également avec le .NET Framework classique. On peut facilement rapprocher les 2 en déclarant que CoreFX est un fork de la BCL du .NET Framework.

Le rôle principal de cette BCL est de faire en sorte que les APIs disponibles soit exécutables sur le plus grand nombre de plateforme possible. Prenons l'exemple de la cryptographie qui utilise des librairies du système pour fonctionner. Ainsi, l'implémentation de la cryptographie en .NET Core est assez délicate, puisque selon le système sous-jacent, il est possible qu'une même fonctionnalité soit disponible sur Windows mais pas sur macOS. L'énorme travail ici des équipes de Microsoft est de faire en sorte qu'un maximum d'algorithme soit compatible sur les plateformes, cependant ce n'est pas toujours le cas. Nous retrouvons ci-dessous un tableau récapitulatif des algorithmes RSA disponibles :

 

 

Lorsqu'une API n'est pas supportée par la plateforme, le code lève une exception de type PlatformNotSupportedException. De ce fait il faut bien comprendre qu'en fonction des APIs utilisés, si ces dernières sont étroitement dépendantes du système, une exception peut être levé car non supporté par CoreFX. Heureusement, les APIs les plus communes (collection, sérialisation, async, console, système de fichier …) sont entièrement supportés sur toutes les plateformes.

Le runtime historique via CoreCLR

Le runtime de .NET Core est CoreCLR, et est basé sur le même code de base du CLR classique et historique que le .NET connait. Initialement, CoreCLR était le runtime de Silverlight, et conçu spécialement pour fonctionner de manière multiplateforme, notamment Windows et MacOS. CoreCLR fait maintenant partie intégralement de .NET Core, et représente une version simplifiée du CLR classique. Son grand avantage est le côté multiplateforme, notamment depuis l'ajout du support de Linux pour répondre aux besoins de .NET Core. Le runtime est également une machine virtuelle incluant le JIT permettant d'exécuter du code à la volée et aussi le Garbage Collector garantissant la bonne gestion de la mémoire.

Rendu Open Source depuis le début de l'année 2015, l'objectif de CoreCLR est de fournir un environnement d'exécution multiplateforme, c'est pourquoi le code du runtime est un mélange de C# et C++. Le projet utilise CMake, un outil également multiplateforme, permettant de builder le projet sur Windows, Linux et Mac. Le diagramme ci-dessous présente la répartition des quelques 2,6 millions de lignes de code contenu dans CoreCLR :

 

 

Le CoreCLR rejoint ainsi CoreFX en tant que grand projet Open Source de Microsoft. La grande différence entre les 2 se trouve au niveau de l'intégration de code C++ dans CoreCLR : ce projet a besoin de plus d'outils de compilation afin de le faire fonctionner.

Le futur via CoreRT

Le projet CoreRT, encore en développement, est un runtime dédié à .NET Core permettant d'accroître ses performances. Tout d'abord, CoreRT permet de faire de la compilation native, évitant ainsi l'étape du code intermédiaire que .NET à connu pendant longtemps. Cela veut dire que les performances de la compilation et de l'exécution d'un projet sont accrues. Ensuite, ce compilateur permet de faire de l'AOT via RyuJIT (nouveau compilateur pour CoreRT), c’est-à-dire qu'il est capable, via un système de métadonnées, de parcourir le code source et de détecter ce qui est utilisé et ce qui ne l'est pas. Il va ensuite générer un seul exécutable ne contenant que ce qu'il a besoin, et va épurer au maximum les dépendances du projet lors de la compilation. Le déploiement d'un tel livrable est également bien allégé.

En parallèle, on parle également de .NET Native pour le workflow général de compilation optimisé, utilisant CoreRT de manière sous-jacente et totalement transparente. Les avantages de CoreRT sont les suivants :

  • ● Une compilation native générant qu'un seul binaire final, incluant les dépendances, le code managé et le runtime ;
  • ● Démarrage plus rapide des applications puisqu'elles sont directement compilés en natif ;
  • ● Nouveaux scénarios de déploiement puisque ce dernier devient plus simple (via un seul ou via un conteneur Docker).
  •  

Mono savait déjà faire de la compilation AoT native, et UWP via .NET Native. Cependant, afin de faire tourner des applications .NET à la fois sur Windows, Linux et Mac, Microsoft avait besoin d'une nouveau CLR, c'est pour cela que CoreRT a été créé. Le code étant Open Source, on peut identifier les composants principaux du code :

 

 

On peut voir que le composant le plus utilisé est System.Private.CoreLib, écrit en C#. L'intérêt ici de ce langage est qu'il est managé, évitant ainsi au maximum les fuites mémoires. Les composants natifs sont écrits en C++. Nous pouvons constater le runtime intègre également un compilateur (via les composants ILCompiler.XXX et ILVerification). Son rôle est le suivant :

  • Scan des modules et des classes afin de déterminer l'arbre de dépendance entre eux ;
  • ● Génération des métadonnées via Reflection ;
  • ● Compilation du code IL (via RyuJit, Web Assembly ou C++) ;
  • ● Génération des méthodes compilés.
  •  

Le runtime CoreRT s'appuie sur plusieurs services permettant de faire fonctionner le moteur d'exécution, qu'ils soient écrits en C# ou en code natif C/C++. On retrouve :

  • ● Le Garbage Collector (37k lignes de code) ;
  • ● La gestion d'exception ;
  • ● La gestion de la stack.
  • ● L'allocation mémoire ;
  • ● Le débogage et le profiling ;
  • ● L'interfaçage avec l'OS ;
  • ● La gestion des processus et des tâches.
  •  

Si nous prenons un projet un projet Hello World simple, le découpage du binaire ressemble à ceci :

 

 

On constate qu'un programme simple (4 MB au final) embarque beaucoup de fonctionnalités provenant à la fois de CoreFX et CoreRT. Cette analyse met en lumière également les parties de l'application fonctionnant via du code managé et celles où c'est le code natif qui prend le relais (typiquement le GC).

Au final, nous avons identifier les différents composants de .NET Core lui permettant de fonctionner et de proposer autant de fonctionnalités riches pour le développeur. La plupart des couches basses ont été réécrites et modularisé, ce qui permet aujourd'hui d'avoir une plateforme performante tant au niveau de la rapidité d'exécution que de l'architecture adopté.

Christophe Gigax

Technical Leader Microsoft et Angular  - MVP Visual Studio & Development Technologies