Processus d’exécution managée
Le processus d’exécution managé inclut les étapes suivantes,qui sont décrites en détail plus loin dans cette rubrique :
- Choix d'un compilateur Pour bénéficier des avantages qu'apporte le Common Language Runtime, vous devez utiliser un ou plusieurs compilateurs de langage ciblant le runtime.
- Compilation de votre code en langage intermédiaire. La compilation traduit votre code source en langage CIL (Common Intermediate Language) et génère les métadonnées requises.
- Compilation du code CIL en code natif. Au moment de l’exécution, un compilateur juste-à-temps (JIT) transforme le code CIL en code natif. Lors de la compilation, le code est soumis à un processus de vérification qui examine le CIL et les métadonnées pour déterminer si le code peut être considéré comme étant sécurisé du point de vue des types.
- Exécution de code Le Common Language Runtime fournit l'infrastructure qui permet à l'exécution d'avoir lieu et les services pouvant être utilisés pendant l'exécution.
Choisir un compilateur
Pour bénéficier des avantages qu'offre le Common Language Runtime (CLR), vous devez utiliser un ou plusieurs compilateurs de langage ciblant le runtime, tels que les compilateurs Visual Basic, C#, Visual C++, F# ou l'un des nombreux compilateurs tiers tels que les compilateurs Eiffel, Perl ou COBOL.
Dans la mesure où il représente un environnement d'exécution multilangage, le runtime prend en charge une grande variété de types de données et de fonctionnalités de langage. Le compilateur de langage que vous utilisez détermine les fonctionnalités du runtime qui sont disponibles et que vous utilisez pour concevoir votre code. C'est votre compilateur et non le runtime qui établit la syntaxe à laquelle votre code doit se conformer. Si votre composant doit être entièrement utilisable par des composants écrits dans d’autres langages, les types exportés de votre composant doivent exposer uniquement les fonctionnalités de langage qui font partie de la spécification CLS (Common Language Specification). Vous pouvez utiliser l'attribut CLSCompliantAttribute pour vous assurer que votre code est conforme CLS. Pour plus d’informations, consultez Indépendance du langage et composants indépendants du langage.
Compiler en CIL
Lors de la compilation en code managé, le compilateur traduit le code source en langage CIL (Common Intermediate Language), un jeu d’instructions indépendant du processeur qui peut être converti efficacement en code natif. CIL inclut des instructions pour le chargement, le stockage, l’initialisation et l’appel de méthodes sur des objets, ainsi que des instructions pour la réalisation d’opérations arithmétiques et logiques, le flux de contrôle, l’accès direct à la mémoire, la gestion des exceptions et d’autres opérations. Avant de pouvoir exécuter le code, vous devez d’abord convertir le code CIL en un code spécifique au processeur, généralement en utilisant un compilateur juste-à-temps (JIT). Comme le Common Language Runtime fournit un ou plusieurs compilateurs JIT pour chaque architecture d’ordinateur qu’il prend en charge, le même jeu d’instructions CIL peut être traité par un compilateur JIT et exécuté sur les architectures prise en charge.
Quand un compilateur produit du code CIL, il génère aussi des métadonnées. Les métadonnées décrivent les types contenus dans votre code, y compris la définition de chaque type, les signatures des membres de chaque type, les membres référencés par votre code, et d'autres données que le runtime utilise au moment de l'exécution. Le code CIL et les métadonnées sont stockés dans un fichier exécutable portable (PE) qui est basé sur et qui étend le fichier Microsoft PE publié et le format COFF (Common Object File Format) historiquement utilisé pour le contenu exécutable. Ce format de fichier, qui accepte le code CIL ou le code natif ainsi que les métadonnées, permet au système d’exploitation de reconnaître les images du Common Language Runtime. La présence de métadonnées dans le fichier en même temps que le code CIL permet à votre code de se décrire lui-même, ce qui signifie que des bibliothèques de types et IDL (Interface Definition Language) ne sont pas nécessaires. Le runtime recherche les métadonnées dans le fichier et les extrait selon les besoins, au moment de l'exécution.
Compiler du code CIL en code natif
Avant de pouvoir exécuter le code CIL (Common Intermediate Language), vous devez le compiler en code natif pour l’architecture de la machine cible avec le Common Language Runtime. .NET fournit deux méthodes pour effectuer cette conversion :
- Compilateur juste-à-temps (JIT) .NET.
- Ngen.exe (générateur d’images natives).
Compilation par le compilateur JIT
La compilation JIT convertit le code CIL en code natif à la demande au moment de l’exécution de l’application, quand le contenu d’un assembly est chargé et exécuté. Comme le Common Language Runtime fournit un compilateur JIT pour chaque architecture de processeur prise en charge, les développeurs peuvent créer un jeu d’assemblys CIL pouvant être traité par un compilateur JIT et exécuté sur différents ordinateurs ayant des architectures de machine différentes. Cependant, si votre code managé appelle des API natives spécifiques à une plateforme ou une bibliothèque de classes spécifique à une plateforme, il s'exécutera sur un système d'exploitation spécifique uniquement.
La compilation JIT tient compte de la possibilité qu'une partie du code ne soit peut-être jamais appelée au moment de l'exécution. Au lieu de consacrer du temps et des ressources mémoire à la conversion de tout le code CIL d’un fichier PE en code natif, il convertit le code CIL au fur et à mesure des besoins lors de l’exécution, et stocke le code natif obtenu en mémoire afin qu’il soit accessible pour les appels ultérieurs dans le contexte de ce processus. Le chargeur crée et attache un stub à chaque méthode dans un type quand le type est chargé et initialisé. Quand une méthode est appelée pour la première fois, le stub passe le contrôle au compilateur JIT, qui convertit le code CIL de cette méthode en code natif et modifie le stub pour qu’il pointe directement vers le code natif généré. Par conséquent, les appels suivants à la méthode traitée par le compilateur JIT passent directement au code natif.
Génération du code au moment de l’installation en utilisant NGen.exe
Comme le compilateur JIT convertit le code CIL d’un assembly en code natif quand des méthodes individuelles définies dans cet assembly sont appelées, ceci affecte négativement les performances au moment de l’exécution. Dans la plupart des cas, cette baisse de performances est acceptable. Et surtout, le code généré par le compilateur JIT est lié au processus qui a déclenché la compilation. Il ne peut pas être partagé entre plusieurs processus. Pour que le code généré puisse être partagé entre plusieurs appels d'une application ou entre plusieurs processus partageant un jeu d'assemblys, le Common Language Runtime prend en charge un mode de compilation à l'avance. Ce mode de compilation à l’avance utilise Ngen.exe (Native Image Generator) pour convertir les assemblys CIL en code natif, d’une façon similaire à ce que fait le compilateur JIT. Toutefois, le fonctionnement de Ngen.exe diffère de celui du compilateur JIT sur trois points :
- Il effectue la conversion du code CIL en code natif avant l’exécution de l’application et non pas pendant l’exécution de celle-ci.
- Il compile un assembly entier à la fois, au lieu d'une méthode à la fois.
- Il conserve le code généré dans le cache des images natives comme un fichier sur le disque.
Vérification du code
Dans le cadre de sa compilation en code natif, le code CIL est soumis à un processus de vérification, sauf si un administrateur a établi une stratégie de sécurité qui autorise le code à ignorer cette vérification. La vérification examine le code CIL et les métadonnées afin de déterminer si le code sécurisé du point de vue des types, ce qui signifie qu’il accède seulement aux emplacements mémoire auxquels il est autorisé à accéder. La sécurité de type permet d'isoler les objets les uns des autres et de les protéger de toute altération accidentelle ou malveillante. Elle garantit également que les restrictions liées à la sécurité peuvent être appliquées au code de manière fiable.
Le runtime s'appuie sur le fait que les instructions suivantes sont vraies pour le code de type sécurisé vérifié :
- une référence à un type qui est strictement compatible avec le type référencé ;
- seules les opérations définies de façon appropriée sont appelées pour un objet ;
- les identités sont conformes à ce qu'elles prétendent être.
Pendant le processus de vérification, le code CIL est examiné pour essayer de vérifier qu’il peut accéder aux emplacements mémoire et appeler des méthodes seulement via des types correctement définis. Par exemple, le code n'autorise pas l'accès aux champs d'un objet d'une manière qui accepte le débordement de capacité des emplacements de mémoire. Par ailleurs, le processus de vérification inspecte le code pour déterminer s’il a été généré correctement, car du code CIL incorrect peut conduire à une violation des règles de sécurité des types. Le processus de vérification passe un jeu de code de type sécurisé et correctement défini, et ne passe que du code de ce type. Cependant, une partie du code de type sécurisé peut ne pas passer le test de vérification avec succès en raison de certaines limitations du processus de vérification, et certains langages, de par leur design, ne produisent pas un code de type sécurisé vérifié. Si le code de type sécurisé est requis par la stratégie de sécurité, mais qu'il ne passe pas le test de vérification avec succès, une exception est levée quand le code est exécuté.
Exécuter le code
Le Common Language Runtime fournit l'infrastructure qui permet à l'exécution managée d'avoir lieu et les services pouvant être utilisés pendant l'exécution. Pour qu'une méthode puisse être exécutée, elle doit d'abord être compilée en un code spécifique au processeur. Chaque méthode pour laquelle le code CIL a été généré est compilée juste-à-temps quand elle est appelée pour la première fois, puis elle s’exécute. Quand la méthode est exécutée la fois suivante, le code natif existant traité par le compilateur JIT est exécuté. Le processus de compilation JIT puis d'exécution du code est répété jusqu'à ce que l'exécution soit complètement terminée.
Pendant l'exécution, le code managé bénéficie de services tels que le garbage collection, la sécurité, l'interopérabilité avec le code non managé, la prise en charge du débogage interlangage ainsi que la prise en charge améliorée du déploiement et du versioning.
Dans Microsoft Windows Vista, le chargeur du système d’exploitation recherche des modules managés en examinant un bit dans l’en-tête du format COFF (Common Object File Format). Le bit défini indique un module managé. Si le chargeur détecte des modules managés, il charge mscoree.dll. _CorValidateImage
et _CorImageUnloading
informent le chargeur quand les images de modules managés sont chargées et déchargées. _CorValidateImage
effectue les actions suivantes :
- garantit que le code est du code managé valide ;
- change le point d'entrée dans l'image en point d'entrée dans le runtime.
Sous Windows 64 bits, _CorValidateImage
modifie l'image en mémoire en la transformant du format PE32 au format PE32+.