FAQ C++/CLI et VC++.NetConsultez toutes les FAQ

Nombre d'auteurs : 29, nombre de questions : 248, création le 22 février 2013 

 
OuvrirSommaireLe langage C++/CLISyntaxe

On utilise l'opérateur carret (ou 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.
Pour allouer un objet managé et disposer d'un handle sur cet objet, on utilise gcnew.

Créé le 9 mai 2006  par nico-pyright(c)

Lien : Comment allouer un objet managé avec gcnew ?

Une tracking reference est similaire à une référence C++ dans la mesure où elle représente un alias pour un objet / type définit sur le heap CLR.
Bien que les tracking references soient créés sur la pile, elles peuvent référencer un type sur la pile ou un handle dans le heap CLR.
On utilise l'opérateur % pour définir une telle référence. On s'en sert souvent pour pouvoir modifier un paramètre dans une fonction.

 
Sélectionnez

int entier = 1;
int% reference = entier;
reference = 2;
				

Après ces lignes, entier vaut 2.

Créé le 9 mai 2006  par nico-pyright(c)

Il s'avère utile de comprendre les handles et les références, comme en C++.
Voyons cet exemple :

 
Sélectionnez

int main(array<System::String ^> ^args)
{
	int ^x = 5;
	x = *x + 2;
	int y = 6;
	int %z = y;
	inc(*x);
	inc(y);
	inc(z);
	Console::WriteLine("x : {0} / y : {1}", x,y);
	Console::WriteLine("Somme : {0} ", add(x,y));
}				
void inc(int %a)
{
	a++;
}
 
int add(int ^a, int ^b)
{
	return *a + *b;
}
 
				
  • On crée un handle d'entier x. La valeur de l'entier x vaut 5.
  • On ajoute 2 à x, il vaut donc 7. Notez le déréférencement de x dans la partie droite qui permet d'accéder à sa valeur. Le déréférencement de x à gauche est facultatif, le compilateur le faisant pour nous.
  • On déclare un entier y dont la valeur est 6.
  • On créé une référence CLI z qui est un alias de y. Notez qu'on ne peut pas créer une référence CLI sur un handle, dans le cas de x, il faudrait évidement le déréférencer (int %z = *x).
  • La fonction inc attend une référence afin de pouvoir modifier la valeur passée en paramètre. On déréférence x pour le passer à la fonction. x vaut maintenant 8.
  • On incrémente y maintenant. y vaut 7.
  • Puis on incrémente z. z étant un alias de y, c'est évidement y qui est incrémenté. y vaut maintenant 8.
  • On affiche x et y, à savoir 8 et 8.
  • Puis la fonction add qui accepte deux handles en paramètre, affiche donc 16.

Remarque : il est inutile de vouloir connaitre la valeur d'un handle, le garbage collector pouvant la changer à son aise.

Créé le 9 mai 2006  par nico-pyright(c)

Comme en C++, on utilise une référence sur le handle, cela donne une syntaxe particulière mais le principe est le même :

 
Sélectionnez
void change(String ^%chaine)
{
	chaine = "Nouvelle";
}
 
int main()
{
	String ^chaine = gcnew String("Ancienne");
	Console::WriteLine(chaine);
	change(chaine);
	Console::WriteLine(chaine);
}
Créé le 20 novembre 2007  par nico-pyright(c)

Un pointeur interne peut être assimilé au pointeur classique du C. Il "pointe" vers un objet managé, donc soumis au garbage collector. Il représente une adresse qui peut évoluer en fonction des opérations du GC (compactage, allocation, etc ...). On récupère un pointeur interne avec le mot clé interior_ptr.

 
Sélectionnez

array<double>^ tableau = {1, 2, 3, 4, 5};
interior_ptr<double> p_tableau = &tableau[0];
				

Si le pointeur interne n'est pas initialisé, il contient par défaut nullptr.
Voici un exemple de ce qu'on peut faire avec un pointeur interne :

 
Sélectionnez

array<String^>^ chaine = { L"Ne",L"touchez",L"pas",L"trop",L"aux",L"pointeurs"};
for(interior_ptr<String^> p_chaine = &chaine[0]; p_chaine - &chaine[0] < chaine->Length ; ++p_chaine)
	Console::WriteLine(*p_chaine);
				

Cet exemple produit en sortie :

 
Sélectionnez

Ne
touchez
pas
trop
aux
pointeurs
				

Je conseille bien évidement de se passer des pointeurs internes, sauf quand cela est indispensable.

Mis à jour le 27 mars 2007  par nico-pyright(c)

On utilise le mot clé pin_ptr pour déclarer un pinning pointer, ce que j'ai vu traduire par un pointeur épingle.
Il s'agit en fait d'un pointeur interne (Voir Qu'est-ce qu'un pointeur interne ?) dont l'objet pointé ne sera pas déplacé en mémoire par le mécanisme du garbage collector. Concrètement, ce pointeur ne changera pas de valeur.
Ceci est indispensable lorsqu'on travaille directement sur le pointeur dans des fonctions non managées, par exemple lors de conversion de chaînes.
Voir l'exemple de conversion

Créé le 12 juillet 2006  par nico-pyright(c)

Lien : Qu'est-ce qu'un pointeur interne ?

On utilise gcnew pour obtenir un handle sur un objet managé par le CLR.

 
Sélectionnez

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

Le garbage collector s'occupe de la destruction de l'objet.

Créé le 9 mai 2006  par nico-pyright(c)

Un finalizer est une fonction particulière de la classe, au même titre que le constructeur ou le destructeur, qui est appelée automatiquement par le garbage collector quand un objet est détruit.
Il diffère du destructeur selon les éléments suivants :

  • Le finalizer n'est pas appelé si le destructeur a été appelé explicitement lors de la destruction de l'objet.
  • Le finalizer est appelé lorsque l'objet meurt naturellement en sortant du scope.

On définit le finalizer avec l'opérateur !.

 
Sélectionnez

ref class MaClasse
{
public:
	MaClasse(int v) : valeur(v){} // constructeur
	~MaClasse() // destructeur 
	{
		Console::WriteLine("Destructeur de l'objet ({0})", valeur);
	}
protected:
	!MaClasse() // finalizer
	{
		Console::WriteLine("Finalizer de l'objet ({0})", valeur);
	}
private:
	int valeur;
};
 
int main(array<System::String ^> ^args)
{
	MaClasse^ obj1 = gcnew MaClasse(1);
	MaClasse^ obj2 = gcnew MaClasse(2);
	delete obj1;
	Console::WriteLine("Fin");
	return 0;
}				
				

Cet exemple produit le résultat suivant :

 
Sélectionnez

Destructeur de l'objet (1)	
Fin
Finalizer de l'objet (2)			
				

Notez que le finalizer de l'objet 2 est appelé après la fin du programme, lorsqu'il sort du scope.
Il est important de comprendre qu'il y a à chaque fois seulement le finalizer ou seulement le destructeur qui est appelé, suivant la méthode de destruction. Mais jamais les deux.

On peut conclure de cet exemple que si l'on veut être sur que des éléments (non managés par exemple) utilisés par un objet sont bien détruit, tout en ne tenant pas compte de la façon dont un objet est détruit (explicitement ou par le GC), alors il faut implémenter à la fois un destructeur et un finalizer.

Remarque : Le destructeur est déclaré en public, le finalizer doit l'être en protected.

Mis à jour le 12 juillet 2006  par nico-pyright(c)

Les classes (objets) .Net sont regroupées sémantiquement en catégories, sous la forme de bibliothèques. Elles forment un espace de noms : namespace.
Exemple : le namespace System::Collections contient les différents objets de collections du framework.net.
Ils sont accessibles directement en indiquant le chemin et en utilisant les :: ou bien en important le namespace directement avec using namespace.

 
Sélectionnez

System::Collections::ArrayList ^ tableau = gcnew System::Collections::ArrayList();
				

ou bien :

 
Sélectionnez

using namespace System::Collections;				
ArrayList ^ tableau = gcnew ArrayList();
				
Créé le 9 mai 2006  par nico-pyright(c)

Le C++/CLI nous autorise une telle chose gràce aux ... et à un array CLI :

 
Sélectionnez

int somme(... array<int>^ args)
{
	int result = 0;
	for each(int arg in args)
		result += arg;
	return result;
}
 
int main(array<System::String ^> ^args)
{
	Console::WriteLine(somme(1,2,3,4,5,6));
	return 0;
}				
				
Créé le 9 mai 2006  par nico-pyright(c)

En plus des classiques private, public et protected, le C++/CLI ajoute trois nouvelles visibilités :

  • internal : Les membres sont accessibles depuis la classe, à l'intérieur de l'assembly parent.
  • public protected : Les membres sont accessibles dans des classes dérivées de notre classe à l'extérieur de l'assembly parent et dans n'importe quelle classe à l'intérieur de l'assembly parent.
  • private protected : Les membres sont accessibles dans les classes dérivées de notre classe uniquement à l'intérieur de l'assembly parent.
Créé le 9 mai 2006  par nico-pyright(c)

Un delegate est une classe managée (ref class) qui permet d'appeler une méthode qui partage la même signature qu'une fonction globale ou d'une classe possédant une méthode avec cette même signature. Le framework supporte deux formes de delegates :
un delegate qui accepte d'appeler uniquement une méthode :

 
Sélectionnez

System::Delegate

et un delegate qui accepte d'appeler une chaine de méthodes :

 
Sélectionnez

System::MulticastDelegate

Les méthodes utilisées pour le delegate peuvent être :
- Globale (comme en C) - une méthode statique à une classe - une méthode d'une instance.

Exemple :
Déclaration du delegate :

 
Sélectionnez

delegate void FuncMessDelegate(String ^mess);

Maintenant les trois formes d'utilisation :

 
Sélectionnez

void GlobalMess(String ^mess)
{
	Console::Write("Fonction Globale: ");
	Console::WriteLine(mess);
}
ref class OneClass
{
public:
	static void staticMethodeMess(String ^mess)
	{
		Console::Write("Fonction statique: ");
		Console::WriteLine(mess);
	}
};
ref class AnotherClass
{
public:
	void MethodeMess(String ^mess)
	{
		Console::Write("Fonction d'une instance: ");
		Console::WriteLine(mess);
	}
};

L'appel de la fonction :

 
Sélectionnez

int main(array<System::String ^> ^args)
{
	// declaration du delegate
	FuncMessDelegate ^Global= gcnew FuncMessDelegate(&GlobalMess);
	// ajouter une fonction au delegate
	FuncMessDelegate ^statique= gcnew FuncMessDelegate(&OneClass::staticMethodeMess);
 
	AnotherClass ^pAnotherClass= gcnew AnotherClass;
 
	FuncMessDelegate ^instance= gcnew FuncMessDelegate(pAnotherClass,&AnotherClass::MethodeMess);
 
	// l'appel de la fonction&#8230;
	Global->Invoke("Hello");
 
	statique->Invoke("Hello");
	instance->Invoke("Hello");
 
	return 0;
}      

Les delagates peuvent être combinés sous forme de chaine et un élément peut être supprimé (Multicast Chain).
On utilisera la méthode Combine() ou l'operateur + pour l'ajout.
Et la méthode Remove() ou l'opérateur - pour la suppression.
Exemple :

 
Sélectionnez

 
FuncMessDelegate ^ChaineMess= gcnew FuncMessDelegate(&GlobalMess);
 
ChaineMess += gcnew FuncMessDelegate(&OneClass::staticMethodeMess);
 
AnotherClass ^pAnotherClass= gcnew AnotherClass;
ChaineMess += gcnew FuncMessDelegate(pAnotherClass,&AnotherClass::MethodeMess);
 
ChaineMess->Invoke("Hello");
 
ChaineMess -= gcnew FuncMessDelegate(&OneClass::staticMethodeMess);
ChaineMess->Invoke("Hello2");

Note :
J'ai volontairement utilisé les operateurs à la place des méthodes car ceux-ci imposent un cast vers le delegate déclaré ce qui alourdi grandement l'écriture.

Créé le 27 mars 2007  par farscape

Un Event est une implémentation spécifique du delegate ou plutôt du multicast delegate.
Un event permet à partir d'une classe d'appeler des méthodes situées dans d'autres classes sans rien connaitre de ces classes.
Il est ainsi possible pour une classe d'appeler une chaine de méthodes issues de différentes classes.
Exemple :

 
Sélectionnez

using namespace System;
 
delegate void AnalyseHandler(int %n); // la fonction de traitement recoit une reference sur un entier.
 
// classe declencheur de traitment
ref class AnalyseTrigger
{
public:
    event AnalyseHandler ^OnWork;
    void RunWork(int %n)
    {
        OnWork(n);
    }
};
 
// classe effectuant un traitement
ref class Analyse
{
    public:
    AnalyseTrigger ^ m_AnalyseTrigger;
    Analyse(AnalyseTrigger ^src)
    {
        if(src==nullptr)
            throw gcnew ArgumentNullException("erreur argument non specifié");
        m_AnalyseTrigger=src;
        m_AnalyseTrigger->OnWork+= gcnew AnalyseHandler(this,&Analyse::Treatment);
        m_AnalyseTrigger->OnWork+= gcnew AnalyseHandler(this,&Analyse::TreatmentTwo);
    }
    void RemoveTreatmentTwo()
    {
        m_AnalyseTrigger->OnWork-=gcnew AnalyseHandler(this,&Analyse::TreatmentTwo);
    }
    // traitement declenché
    void Treatment(int %n)
    {
        Console::Write("Class Analyse Treatment =+10 n:= ");
        Console::Write(n);
        n+=10;
        Console::Write(" -> n=");
        Console::WriteLine(n);
    }
    // traitement declenché
    void TreatmentTwo(int %n)
    {
        Console::Write("Class Analyse TreatmentTwo =*2 n:= ");
        Console::Write(n);
        n*=2;
        Console::Write(" -> n=");
        Console::WriteLine(n);
    }
};
 
// classe effectuant un traitement
ref class AnalyseTwo
{
    public:
    AnalyseTrigger ^m_AnalyseTrigger;
 
    AnalyseTwo(AnalyseTrigger ^src)
    {
        if(src==nullptr)
            throw gcnew ArgumentNullException("erreur argument non specifié");
        m_AnalyseTrigger=src;
        m_AnalyseTrigger->OnWork+= gcnew AnalyseHandler(this,&AnalyseTwo::Treatment);
 
    }
    // traitement declenché
    void Treatment(int %n)
    {
        Console::Write("Class AnalyseTwo Treatment =+2 n:= ");
        Console::Write(n);
        n+=2;
        Console::Write(" -> n=");
        Console::WriteLine(n);
    }
};
int main(array<System::String ^> ^args)
{
    //element declecheur de l'action fixée par le delegate AnalyseHandler
    AnalyseTrigger ^Trigger = gcnew AnalyseTrigger();
 
    // classe traitements
    Analyse ^analyse = gcnew Analyse(Trigger);
 
    // classe traitements
    AnalyseTwo ^analyseTwo = gcnew AnalyseTwo(Trigger);
 
    int n=2;
 
    Trigger->RunWork(n);
    analyse->RemoveTreatmentTwo();
 
    Console::WriteLine("-----------------------");
    n=2;
    Trigger->RunWork(n);
 
    return 0;
}
Créé le 27 mars 2007  par farscape

Le C++/CLI dispose du template auto_handle pour forcer la destruction d'un objet quand il sort de portée (il fonctionne comme le template auto_ptr du C++).
Pour utiliser le template, on doit inclure :

 
Sélectionnez
#include <msclr\auto_handle.h>

Exemple de code :

 
Sélectionnez
#include <msclr\auto_handle.h> 
 
using namespace System; 
 
ref class CManagee 
{ 
private: 
  String ^s; 
public: 
  CManagee(String ^val) {s = val;}; 
  ~CManagee() { Console::WriteLine("Destructeur : " + s);  } 
protected: 
  !CManagee() { Console::WriteLine("Finalizer :" + s); } 
}; 
 
int main(array<System::String ^> ^args) 
{ 
  { 
    msclr::auto_handle<CManagee> cAutoHandle = gcnew CManagee("auto_handle"); 
    CManagee ^cNormal = gcnew CManagee("normal"); 
    Console::WriteLine("Après la construction"); 
  } 
  Console::WriteLine("fin"); 
  return 0; 
}

On aura en sortie :

 
Sélectionnez

Après la construction
Destructeur : auto_handle
fin
Finalizer : normal

J'ai rajouté des { } pour forcer un bloc et montrer alors la destruction par l'appel au destructeur quand le auto_handle sort de portée. Tandis que le handle normal, lui, est détruit par le garbage collector en fin de programme.
Le template surcharge les opérateurs ->, ==, etc ... ainsi il peut être utilisé comme un handle classique.
Notez que si on souhaite pouvoir continuer à utiliser le auto_handle une fois qu'il est sorti de portée, il faut au préalable l'avoir libéré avec release :

 
Sélectionnez
{ 
  msclr::auto_handle<CManagee> cAutoHandle = gcnew CManagee("auto_handle"); 
  cAutoHandle.release(); 
  Console::WriteLine("Après la construction"); 
} 
Console::WriteLine("fin");

Cette fois-ci en sortie on aura :

 
Sélectionnez

Après la construction
fin
Finalizer : auto_handle
Créé le 20 novembre 2007  par nico-pyright(c)

Il est possible d'utiliser des mots clés réservés du C++/CLI comme nom de variable. On utilise le mot clé __identifier :

 
Sélectionnez
	String ^ __identifier(gcnew) = gcnew String("Abc");
	Console::WriteLine(__identifier(gcnew));
	__identifier(gcnew) = "DEF";
	Console::WriteLine(__identifier(gcnew));

Je trouve personnellement que ça alourdit le code pour un intérêt limité. Cela pourrait s'avérer utile dans le cadre d'une migration. Auparavant, on utilisait une variable nommée gcnew. Une fois passé à Visual C++ 2005, pour continuer à utiliser cette variable on doit l'entourer de __identifier. Mon conseil est de changer de nom de variable.

Créé le 20 novembre 2007  par nico-pyright(c)

Non, cet opérateur n'est pas disponible en C++/CLI.
Pour rappel, en C# on peut utiliser cet opérateur comme suit :

 
Sélectionnez
Console.WriteLine(i ?? 0);

Cela permet d'utiliser l'entier 0 si jamais l'entier passé est nul.
Pour faire l'équivalent en C++/CLI, on fera

 
Sélectionnez
Console::WriteLine(i.HasValue ? i : 0);

Notez que l'on teste la propriété HasValue du type nullable i pour vérifier s'il contient une valeur.

Créé le 9 octobre 2008  par nico-pyright(c)

Non, il n'y a pas d'équivalent en C++/CLI.

Créé le 9 octobre 2008  par nico-pyright(c)
  

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2006-2007 Developpez Developpez LLC. Tous droits réservés Developpez LLC. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez LLC. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.