1.Introduction

Avec l'émergence de la technologie .Net, on peut observer beaucoup de flou, d'incompréhension et d'abus de langage autour du concept de "managé".
On entend parler d'objet managé, non managé, de CLR, de MSIL ... Mais que représentent vraiment tous ces mots ? Quelle est la différence entre une classe managée et une classe non managée ? Comment fonctionnent les différents modes de compilation d'un projet managé ?

Je vais essayer d'éclaircir ces concepts à travers cet article afin que vous puissiez avoir une vision plus précise et que vous puissiez tirer parti au mieux de la puissance du langage C++ dans sa cohabitation avec le framework .net grâce au C++/CLI.

Les exemples présentés dans cet article utiliseront le logiciel Visual Studio 2005 de Microsoft et plus particulièrement Visual C++ 2005 et le framework dotnet 2.0.

2.Définitions

2.1.Managé (objet)

Un objet est managé (managed) lorsque son allocation et sa désallocation sont prises en charge par le CLR. Ce mot est utilisé un peu à toutes les sauces, à défaut d'en avoir des plus explicites. Par exemple, il faudrait plutôt parler de C++/CLI plutôt que de C++ managé.

2.2.Non managé (objet)

Un objet est non managé (unmanaged) lorsque son allocation et sa désallocation sont prises en charge par le CRT (malloc).

2.3.CLS : Common Language Specification

Pour interagir entièrement avec des objets managés quel que soit le langage dans lequel ils ont été implémentés, les objets managés ne doivent exposer aux appelants que les fonctionnalités qui sont communes à tous les langages avec lesquels ils doivent fonctionner.
Pour cette raison, un ensemble de fonctionnalités de langage appelé spécification CLS (Common Language Specification), qui comprend les fonctionnalités de langage de base nécessaires à de nombreuses applications, a été défini.
La spécification CLS permet d'optimiser et d'assurer l'interopérabilité des langages en définissant un ensemble de fonctionnalités sur lequel les développeurs peuvent compter dans de nombreux langages. La spécification CLS établit également des exigences de conformité CLS ; elles vous permettent de déterminer si votre code managé est conforme à la spécification CLS et dans quelle mesure un outil donné prend en charge le développement du code managé qui utilise des fonctionnalités CLS.

Si votre composant n'utilise que des fonctionnalités CLS dans des interfaces API qu'il expose à un autre code (y compris des classes dérivées), le composant est assuré d'être accessible à partir d'un langage de programmation prenant en charge la spécification CLS.

Il est important de comprendre que le respect du CLS augmente la sécurité de votre programme.

2.4.CTS : Common Type System

Afin que des classes définies dans plusieurs langages puissent communiquer entres elles, elles ont besoin d'un ensemble de types de données communs. C'est l'objet du CTS, il définit les types de données que le Runtime .NET comprend et que les applications .NET peuvent utiliser.

2.5.CLR : Common Language Runtime

On l'appelle aussi le runtime .NET. Le CLR est le moteur d'exécution du noyau .Net pour exécuter des applications. Il apporte des services tels que la sécurité d'accès de code, la gestion de la vie d'un objet, la gestion des ressources, la sûreté des types, etc ...

2.6.CLI : Common Language Infrastructure

Dans le contexte de C++/CLI, cela correspond à du C++ pouvant interagir avec le CLR.

2.7.MSIL : Microsoft Intermediate Language

C'est un jeu d'instruction indépendant du CPU généré par les compilateurs .NET, à partir de langages comme le J#, C# ou Visual Basic. Le langage MSIL est compilé avant ou pendant l'exécution du programme par le VES (Virtual Execution System), qui fait partie intégrante du CLR.

3.Les différents modes de compilation

Le compilateur nous propose 5 options pour compiler son projet.

  • No Common Language Runtime support
  • Common Language Runtime Support (/clr)
  • Pure MSIL Common Language Runtime Support (/clr:pure)
  • Safe MSIL Common Language Runtime Support (/clr:safe)
  • Common Language Runtime Support, Old Syntax (/clr:oldSyntax)

3.1.Pas de support du CLR

Le premier mode (sans option /clr) est, vous l'aurez compris, le mode classique qui ne permet pas d'utiliser les extensions managées du framework .net. Le compilateur génère uniquement du langage machine dans ce mode là.
Autrement dit, le compilateur va générer un exécutable Win32 classique, indépendant de .Net qui ne pourra donc pas accéder aux fonctions des bibliothèques du framework .Net.

3.2.Support (mixte) du CLR : /clr

Cette option /clr permet de compiler du code C++ dans un mode mixte. C'est à dire que nous pourrons faire cohabiter du code natif avec du code managé. Le compilateur génère alors du MSIL. A partir du moment où on utilise un mode de compilation qui utilise /clr, le compilateur génère uniquement du MSIL.
Plus précisément, il pourra générer du langage machine lorsqu'il ne saura pas générer de MSIL, mais il génère prioritairement du MSIL.
La principale force de ce mode mixte est ce qu'on appelle IJW (It Just Work). Ce concept représente la capacité du compilateur à créer un exécutable "managé" à partir d'une application non managée. Plus de détails sur IJW et sur la capacité de mixer du code managé et du code natif.

3.3.Support pur du CLR : /clr:pure

On utilise /clr:pure lorsque l'on n'utilise aucune classe non managée dans son application. Ce mode empêche la compilation de code natif mais autorise l'écriture de code qui dérogerait aux règles du CLS. Ce mode ne permet pas d'utiliser la technologie IJW.
Un des avantages de /clr:pure est que le code est sûr et possédant des performances meilleures qu'avec /clr uniquement. En effet, les assemblys purs contiennent uniquement du MSIL, il n'y a pas de fonctions natives et, par conséquent, aucune transition managée/non managée n'est nécessaire (hors appels P/Invoke).

3.4.Support vérifiable du CLR : /clr:safe

Le mode /clr:safe génère des assemblys vérifiables, en se conformant aux exigences du CLS qui permettent au Common Language Runtime (CLR) de garantir que le code ne viole aucun paramètre de sécurité actuel.

Sachez dès à présent que les futures versions de Windows exigeront de plus en plus que les composants et les applications soient vérifiables ; donc commencer à écrire ses assemblys en mode /clr:safe est une bonne idée. (Par contre, cela rend impossible l'interopérabilité).

3.5.Support de la syntaxe du framework 1.x : /clr:oldsyntax

Le mode /clr:oldsyntax est utilisé pour avoir une compatibilité avec les extensions managées du framework 1.x. Il est utile pour migrer en douceur son code pour se conformer à la syntaxe du framework 2.0. Cette syntaxe indigeste est à bannir au plus vite et à remplacer avantageusement par l'utilisation du C++/CLI.

4.Créer une classe managée

Que veux dire "classe managée" ?

Comme dit dans les définitions du dessus, une classe managée veut simplement dire que l'objet est pris en charge par le CLR. Son allocation (avec gcnew) et sa désallocation (utilisant soit delete explicitement, soit utilisant le garbage collector) est donc entièrement gérée (managed in english) par le CLR. Microsoft a essayé de rayer le mot managé de son vocabulaire pour éviter l'abus de langage (pour parler par exemple de C++/CLI plutôt que de C++ managé).

Cet objet est alloué sur le tas (heap) managé par l'intermédiaire de gcnew et est référencé grâce à un handle.
On utilise l'opérateur hat (^) pour définir un handle vers un objet managé qui est en fait une référence vers cet objet managé. Attention ce n'est pas un pointeur. Un handle est une référence sur un objet managé sur le tas (heap) managé, alors que les pointeurs pointent vers une zone mémoire.

 
Sélectionnez

String ^ str = gcnew String("Ma chaîne managée est alloué sur le heap managé");			
			

La désallocation est automatique grâce au garbage collector.
Le garbage collector est un mécanisme qui permet à un ordinateur de détecter et de supprimer les objets managés du heap qui ne sont plus référencés par une application.
Le garbage collector du framework .net ajoute la fonctionnalité intéressante de compacter la mémoire après libération des objets managés inutilisés.
Le garbage collector est une révolution de programmation, car il annonce la fin des fuites mémoires, qui font rager tout développeur. Un développeur n'a plus à se soucier d'appeler la destruction de ses objets avec l'opérateur delete.

Evidement, les objets existants du framework .net (comme le String ci-dessus) sont managés.

Le C++/CLI ajoute deux nouveaux types, qui peuvent bénéficier pleinement des avantages du CLI. Ils s'utilisent grâce aux mots clés ref ou value.

4.1.Les types de références

Un type ref est un type CLI qui est alloué sur le heap managé. Il est pris complètement en charge par le garbage collector qui gère sa désallocation quand il n'est plus utile.
Le mot clé ref permet de créer donc des objets managés ou des structures managées (on utilise alors ref class ou bien ref struct).

 
Sélectionnez

ref class CMaClasse
{
	CMaClasse(void);
	~CMyClass(void);
	bool maFonction();	
	String ^ monAttributChaine;
};			
			

4.2.Les types de valeurs

Un type de valeur (value type) est un type CLI qui est alloué sur la pile et qui est détruit lorsqu'il sort de sa portée. Il se comporte essentiellement comme un type POD (Plain Old Data : un type POD est un type C++ qui possède un équivalent en C. Il correspond aux types composés à partir des types primitifs).

On ne peut pas hériter d'un type de valeur. Ils n'ont pas non plus de constructeur par défaut, de destructeur, d'opérateur de copie ou d'affectation.
Lorsque l'on conçoit un objet qui fonctionne comme un type natif (un int par exemple), alors cette classe est une candidate parfaite pour un type de valeur.
Pour créer un type de valeur, on utilise alors value class ou bien value struct.

 
Sélectionnez

value class MonTypeDeValeur { 
	String^ uneChaine; 
	String^ uneAutreChaine; 
};			
			


Remarque :
- Les types de valeurs ont été créés pour permettre une copie rapide et efficace de données, sans utiliser d'indirections de mémoire inutile pour y accéder (pas besoin d'une place mémoire dans le garbage collector et d'une place mémoire pour le handle le référençant). Ainsi, le type de mémoire n'est pas stocké sur le heap managé et permet d'éviter l'encombrement du garbage collector. Ils bénéficient de même des optimisations de registre.
- Sans les types de valeurs, .Net aurait été beaucoup trop lent face à ses concurrents Java et C++, même pour des petits projets.
Cependant, il faut noter que lors de la phase d'optimisation du compilateur, celui-ci peut décider lui même si le type de valeur aurait été un meilleur choix qu'un type de référence et choisir ainsi le meilleur emplacement pour ce type, dans la plupart des cas.
- On utilise l'expression "types managés" ou "objets managés" par abus de langage. Il est plus correct de parler de types CLI.

Il est en général recommandé pour le CLS d'utiliser des structures pour les types de valeurs et des classes pour les types de références.

5.Créer une classe non managée

Pour déclarer une classe non managée par le CLR, on n'utilise aucun autre mot clé que class.

 
Sélectionnez

class CMaClasseNonManagee
{
	CMaClasseNonManagee(void);
	~CMaClasseNonManagee(void);
	bool maFonctionNonManagee();
	CString	maChaineNonManagee;
};			
			

Cette ancienne déclaration fonctionne toujours de la même façon, et est gérée par le CRT, allouée sur le tas (non managé) avec new.

Une classe non managée par le CLR est dite native. Il faut cependant préciser que dans ce cas, natif ne veut pas dire forcément langage machine. Quand le mode de compilation est l'un des /clr, alors le code généré est toujours du MSIL (sauf dans le cas où le compilateur ne sait pas le générer).
Par contre, quand le compilateur ne compile pas en mode /clr, alors le code est bien du langage machine.

En mode /clr, on peut très bien utiliser dans une classe non managée, des objets managés. En effet, comme le langage généré est du MSIL, on peut accéder à ces objets grâce au mot clé gcroot.

 
Sélectionnez

class CObjetNonManage
{
public:
	CObjetNonManage();
	gcroot<String ^> chaineManagee;
};			
			

6.Utiliser du code compilé natif

Lorsque l'on développe une application "managée", le compilateur génère donc du MSIL et du langage machine lorsqu'il ne peut pas générer de MSIL (j'entends par langage machine du code x86/x64/IA64).
Il peut être intéressant de forcer le compilateur à générer du langage machine pour certaines portions de code. Cela est très utilisé lorsque vous souhaitez porter votre application Win32 petit à petit en .Net ou lorsque vous souhaitez profiter de la couche la plus basse possible, pour des portions de code critiques par exemple.
Dans ces portions de code, il sera bien sur impossible d'accéder au CLR, car le compilateur ne génère pas de MSIL.

Cela se fait par l'emploi des pragmas :

 
Sélectionnez

#pragma unmanaged			
			

et

 
Sélectionnez

#pragma managed
			

Il suffit alors d'entourer du code par ces deux pragmas pour qu'il soit généré en langage machine. Cela implique donc bien sur d'utiliser le mode de compilation mixte (/clr).

 
Sélectionnez

#pragma unmanaged

#include <windows.h>
#pragma comment(lib, "User32.lib")

class CNonManagee
{
public:
	CNonManagee(void);
	~CNonManagee(void);
	void Show()
	{
		MessageBoxW(NULL, L"Message depuis le langage machine", L"", 0);
	}
};

#pragma managed			
			
 
Sélectionnez

ref class CManagee
{
public:
	CManagee(void);
	void Show()
	{
		System::Windows::Forms::MessageBox::Show("Message depuis le MSIL");
	}
};			
			
 
Sélectionnez

CManagee ^ objManaged = gcnew CManagee();
CNonManagee * objUnmanaged = new CNonManagee();
objManaged->Show();
objUnmanaged->Show();			
			

Ce petit exemple déclare donc une classe non managée, dont la génération est forcée en langage machine. Il déclare aussi une classe managée. Ces deux objets sont instanciés et cohabitent ensemble dans le mode de compilation mixte. L'exemple affichera donc le message venant du monde managé, puis celui venant du monde non managé.

Il n'apparaît pas toujours clairement l'intérêt de ces pragmas. Il y a cependant plusieurs avantages à ne pas mettre de code dans le CLR :

  • Dans le CLR, il a bien sur besoin du runtime .net pour s'exécuter.
  • L'exécution du code dans le CLR est moins rapide.
  • Cependant, il faut faire attention à ne pas trop souvent passer du monde managé au monde non managé, car cela coûte beaucoup de temps. Il est donc essentiel de bien définir la frontière entre les deux et le C++ est le langage idéal pour cela.
  • Le C++/CLI maintient toute la saveur et les possibilités du développement en C++, même en dehors de l'accès au framework .Net.

7.Conclusion

J'espère que cette article vous aura clarifié les idées sur les différents concepts associés au terme "managé".
Il est très intéressant de bien savoir différencier ces concepts, mais globalement, dans la pratique, il n'est en général pas vraiment utile de savoir comment le code est généré par le compilateur.
Veuillez plutôt avantageusement à être respectueux des normes et du CLS.

Remerciements

Je remercie toute l'équipe C++, notamment Anomaly, pour leur relecture attentive du document.

Contact

Si vous constatez une erreur dans le tutorial, dans le source, dans la programmation ou pour toutes informations, n'hésitez pas à me contacter par mail, ou par le forum.