Visual c++ .net 2005: des points obscurs expliques

COURS VISUAL C++ 2005 .NET

Ce tutoriel a pour objectif de vous expliquer des points obscurs qui ne sont pas (ou peu) expliqués ailleurs. Je voudrais vous aider sur des questions qui m'ont posé problème. Ce tutoriel sera complété au fur et à mesure.

GENERALITES

Ce cours n'est pas complet. Je préciserai des points utiles à savoir du langage VISUAL C++ 2005. Je vous conseille, avant d'aborder le langage C++ 2005, de commencer par le Visual Basic 2005, ou encore par le C# 2005. Ainsi, vous pourrez comprendre toutes les notions de la programmation orientée objet, sans vous encombrer trop avec des préoccupations de bas niveau.

Les applications Windows Form

Le Visual C++ 2005 est capable de créer des applications Windows. Il utilise pour cela les Windows Forms. C'est un des moyens, le dernier, pour parvenir à créer une application Windows. Les applications Windows Forms sont des applications managées. Elles le sont par le Framework 2.0 . Les objets créés dans une application WinForm sont managés eux aussi par le Framework. Ce qui a pour avantage énorme d'être détruits automatiquement par le garbage collector, quand ils ne sont plus référencés. Et tout en gardant la possibilité de les détruire par nous-même, avec la fonction delete.

Les applications MFC

Cependant, il existe d'autres moyens, plus anciens, comme les MFC (Microsoft Foundation Classes). Ce sont des classes fournies par Microsoft, qui permettent de créer des applications Windows. L'application ainsi produite sera appelée application MFC. Les MFC étaient fournies avec Visual C++ 6.0. Elles sont encore disponibles dans Visual studio 2005, mais pas dans la version express. La personne qui ne dispose que de Visual studio express, ne peut donc pas utiliser les MFC, ce qui est un gros problème des MFC.
Les applications MFC sont intégrables simultanément dans des applications windows forms.
Les applications MFC ne sont pas managées par le Framework .Net, au contraire des Winforms. Les objets créés ne seront par conséquent pas managés. Le garbage collector ne les détruira donc pas. Et vous devrez donc les éliminer vous-même, avec le risque bien connu de remplissage de la mémoire( si vous oubliez de les libérer).

Objets managés et objets non managés - généralités

Les objets créés dynamiquement en Visual c++ 2005 .net sont des objets managés par le .Net Framework. Il est possible toutefois de créer des objets non managés, comme les allocations classiques en C. Dans ce cas, on ne fait plus du .Net!

L'avantage des objets managés, c'est qu'ils sont détruits par le garbage collector, sans que l'on ait à s'en préoccuper. Par contre, les objets non managés doivent être détruit "manuellement".

Les objets non managés

Rappelons d'abord que les objets non managés ne sont plus du .Net. Pour créer des objets non managés, on utilise le new classique:

 personne* Jean = new personne( );

Un objet de la classe personne est ici alloué dans la mémoire. Jean contient l'adresse en mémoire de cet objet. L'objet lui-même, c'est la valeur pointée par Jean. L'objet, c'est *Jean . Le champs age de Jean, c'est *Jean.age, ou encore Jean->age

Les objets managés - Les handles

Voici le cas qui nous intéresse.
Un objet managé est créé dans le tas (heap) du Framework. Alors que les objets non managés sont créés dans la mémoire. Bien entendu, le tas est situé dans une partie de la mémoire. Mais c'est une partie de la mémoire, appelée tas (heap en anglais), qui est réservée pour le .Net Framework, au même titre que la pile pour l'exécution.
Pour désigner où se trouve un objet managé, on n'utilise non pas son adresse en mémoire, mais un handle. Le handle est un identifiant qui permet de désigner l'objet de manière unique. C'est juste une autre manière de référencer l'objet. On ne parlera plus d'adresses pour les références d'objets managés.

L'opérateur top-level: ^

Pour créer des objets managés, on utilise gcnew, et non pas new. Exemple:

personne^ Jean = gcnew personne( );

Jean est une référence sur un objet de la classe personne. C'est le handle de l'objet managé.

Pour les objets .Net, on utilise donc l'opérateur ^, et non pas * . Pour accéder aux membres de l'objet, on écrit: Jean->age. Car Jean étant une référence sur un objet personne, on ne peut pas écrire Jean.personne. Jean->age signifie: l'attribut age de l'objet pointé par Jean.

Créer ses propres événements en C++ .Net

Il est bon de savoir créer ses propres événements, afin de pouvoir faire sa propre programmation événementielle.
Il est possible de réaliser cela en C++ .Net, mais aussi en C# et en Visual Basic .Net . Voici les différentes étapes à suivre.
Prenons l'exemple d'une classe voiture, qui crée des objets voiture. On veut pouvoir disposer d'un événement FREINE, qu'on déclenchera au freinage d'une voiture.

Rappel sur les procédures liées( aux événements)

On peut observer très facilement, par rapport aux lignes générées automatiquement par Visual studio en cas d'événement Windows, que la procédure liée est de la forme suivante:

private: System::Void button1_Click(System::Object^ sender, System::EventArgs^ e) { }

De plus, le code suivant est auto-généré lui-aussi, dans la fonction initializecomponent( ) :

this->button1->Click += gcnew System::EventHandler(this, &Form1::button1_Click);

Cette ligne permet de lier la fonction button1_Click à l'événement Click du bouton 1. Nous l'expliquerons plus en détail bientôt.

Revenons à la ligne précédente:

private: System::Void button1_Click(System::Object^ sender, System::EventArgs^ e) { }

Ceci est donc une fonction qui sera appelée en cas de click sur le bouton1. Cette fonction possède deux paramètres.
- sender, qui est un objet (en fait une référence sur l'objet, bien sûr, mais je dis que c'est un objet pour simplifier; en réalité on devrait l'appeler handle_sender). sender est l'objet qui a émis l'événement. L'émetteur de l'événement peut y mettre ce qu'il veut. C'est juste une information pour la fonction liée, pour la renseigner. Ici, le sender est l'objet button1, donc on n'a pas l'impression que ce paramètre est utile. Mais parfois, il est nécessaire. Par exemple, dans notre cas, on pourrait y mettre la référence de la voiture qui freine.
- L'argument e est un objet, qui encapsule toutes les informations intéressantes concernant l'événement. Par exemple, dans le cas d'un appui sur une touche, le code de la touche pressée.
Dans notre cas, cela peut être les circonstances au moment du freinage( pluie, soleil, etc), la vitesse au moment de freiner, etc.
Cet argument doit forcément être d'une classe dérivée de la classe System::EventArgs . Par exemple, dans le cas de l'évènement KeyDown:

private: System::Void Form1_KeyDown(System::Object^ sender, System::Windows::Forms::KeyEventArgs^ e) { } 

Le e ici est de la classe KeyEventArgs, qui est une classe qui hérite de la classe EventArgs. La classe KeyEventArgs est spécialement adaptée pour cet événement. Elle possède notamment un attribut e->KeyCode, qui est le code de la touche pressée. Ce KeyCode ne figure pas, par contre, dans la classe EventArgs. Cet objet e est là pour aider la fonction liée. Pour lui donner des informations que événement KeyDown seul, ne suffit pas à donner (il nous indique juste qu'une touche a été enfoncée). Le e est donc nécessaire.

La procédure à suivre pour créer ses événements

Pour créer ses propres événements, la façon de procéder est composée de plusieurs parties:
- Créer sa propre classe EventArgs
- Se créer une classe EmetEvent, qui permettra de créer un objet émetteur d'événements.
- Procéder à quelques déclarations et initialisations, à l'extérieur, par exemple dans la classe Form1 (votre classe de formulaire principal). Par exemple se créer un objet émetteur d'événements.
Nous étudierons un exemple complet, celui des objets voiture et de l'événement FREINE.

Créer sa propre classe EventArgs

Nous devons tout d'abord nous créer notre propre classe EventArgs, qui tient compte des particularités de notre évènement.

namespace CreerEventCpp {

   using namespace System;

   public ref class VoitEventArgs : public System::EventArgs {

      public: String^ type_freinage; //pluie, etc
      public: int vitesse_fr; //vitesse au moment de freiner
      public: VoitEventArgs( ) { }
      public: VoitEventArgs( String^ type_fr ) {
         this->type_freinage = type_fr;
      }

   }; //fin classe
} //fin namespace

Notre classe VoitEventArgs hérite de la classe System::EventArgs, bien sûr.

Puis nous avons les 2 propriétés type_freinage et vitesse_fr, qui vont donner au développeur des renseignements sur l'événement FREINE. Enfin nous avons 2 constructeurs de la classe. L'objet e nous concernant sera donc une instanciation de la classe VoitEventArgs.

Créer une classe EmetEvent

Cette classe servira à créer un objet émetteur d'événement. Cet objet sera utilisé pour émettre l'événement FREINE. Et un de ses membres sera un event FREINE.
On ne peut émettre des événements qu'à partir de la classe qui contient le membre event. C'est pour cela que pour émettre des événements FREINE, nous prévoirons des méthodes à l'intérieur de l'objet émetteur d'événements. Ainsi la méthode emet_freine_pluie émet un événement FREINE, avec un e->type_freinage à "pluie".
On pourra appeler cet méthode de n'importe où, donc émettre des événements de n'importe où.

Le constructeur de EmetEvent commence par créer un objet e_voit (une des propriétés de EmetEvent).

VoitEventHandler est en réalité une classe qui hérite de la classe System::EventHandler . Les objets EventHandler contiennent les adresses des fonctions liées aux évènements. Ainsi le Framework saura quelles méthodes appeler dans le cas du déclenchement de l'événement.
Les fonctions liées mises dans les objets de la classe EventHandler doivent avoir obligatoirement 2 paramètres: un sender de type object, et un objet e d'une classe dérivée de la classe EventsArgs. Et ces fonctions ne doivent rien retourner (void).

Cependant, le VoitEventHandler est déclaré dans la classe comme une fonction "déléguée". Et on fait une sorte de déclaration de son prototype. Cette notion de déléguée est juste une vue de l'esprit, pour simplifier les choses. VoitEventHandler n'est pas une fonction en réalité, mais un objet (quand la classe sera instanciée) contenant toutes les adresses des fonctions liées. On peut voir VoitEventHandler comme UNE fonction déléguée, représentant toutes les fonctions liées. Et on déclare ainsi le prototype de cette fonction déléguée. Grâce à cet déclaration de prototype, le Framework saura le prototype de toutes les fonctions liées (qui auront toutes ce prototype).
Je vous conseille de voir l'objet de la classe VoitEventHandler comme un objet de la classe System::EventHandler (un 'traiteur' d'événements); et de ne pas voir VoitEventHandler comme une fonction, car ce n'en est pas une. D'ailleurs les membres event, tel FREINE, sont déclarés comme des objets de la classe EventHandler (dans notre cas de la classe VoitEventHandler).

On déclare ensuite un membre event. Event n'est pas un type, c'est un genre de membre. Il y a les attributs, les méthodes, et les events! . On appelle cet event: FREINE. Cet event est un objet de la classe VoitEventHandler. Ainsi, un événement, est juste un objet traiteur d'événements, qui contient les références de toutes les fonctions liées. Ce qui est logique.

Enfin, on se prévoit des méthodes, telles emet_freine_pluie, qui sauront capables d'émettre des événements de n'importe où dans notre programme. Et avec un objet e rempli d'une manière qui nous arrange. Emet_freine_pluie, par exemple, non seulement émet un événement FREINE, mais en plus fournit un e avec le champ e.type_freinage = "pluie". On pourra appeler par exemple emet_freine_pluie à partir de la Form1!

#include"VoitEventArgs.h"

namespace CreerEventCpp {

  ref class EmetEvent {

    private: VoitEventArgs^ e_voit;

    public: EmetEvent( ) {

      this->e_voit = gcnew VoitEventArgs( ); }

    public: delegate void VoitEventHandler System::Object^ sender, VoitEventArgs^ e );

    public: event VoitEventHandler^ FREINE;

    public: void emet_freine_pluie( System::Object^ sender){

     this->e_voit->type_freinage = L"pluie"; this->FREINE( sender, this->e_voit ); }

  }; //Fin classe
}  //Fin namespace

Les déclarations à l'extérieur

- Se créer un objet émetteur d'événement, de la classe EmetEvent, par exemple comme attribut de la classe Form1. Ne pas oublier d'ajouter #include "EmetEvent.h" dans Form1.h (ou dans votre fichier si ce c'est pas Form1.h).

public EmetEvent^ Emetteur_Voit

Puis, dans le Form1_load(ou autre):

this->Emetteur_voit = gcnew EmetEvent();

- Dans le Form1_load, par exemple, ajouter notre événement à notre objet de la classe VoitEventHandler

this->Emetteur_voit->FREINE += gcnew EmetEvent::VoitEventHandler( this, &Form1::ma_fct_liee);

Ceci se fait avec une syntaxe spécial( "+=" ), qui a un sens particulier ici. On "ajoute" à notre membre FREINE, qui est un objet VoitEventHandler, un nouvel objet VoitEventHandler. Ce nouvel objet est initialisé avec un constructeur à 2 paramètres: le premier est l'objet contenant la fonction liée. Mettez, par exemple, "this", si vous êtes dans la classe Form1, et que votre fonction liée s'y trouve.
Le second paramètre est l'adresse de la fonction liée, c'est donc un pointeur de fonction.
Tout ceci est une syntaxe spéciale, que Visual Studio comprendra, et il comprendra que vous voulez ajouter cet fonction à votre objet VoitEventHandler. Vous pouvez ajouter, de cette manière, autant de fonctions liées que vous le souhaitez. Et vous pouvez aussi utiliser le "-=" pour en retirer.
Remarque: cette syntaxe est utilisée dans les lignes auto-générées par Visual Studio dans la classe Form1, dans la méthode initializecomponent, pour lier vos fonctions aux événements Windows( par exemple votre fonction button1_click).

- Ensuite, on peut déclencher les événements comme bon nous semble, en appelant les méthodes de la classe EmetEvent

this->Emetteur_voit->emet_freine_pluie(this);

le this en paramètre correspond au formulaire principal de la classe Form1, dans mon exemple. On aurait pu mettre la référence de la voiture qui freine.

- Et bien sûr, il faut se faire ses procédures liées, exactement de la même manière que pour les événements Windows.

 void ma_fct_liee( System::Object^ sender, VoitEventArgs^ e ) {

  this->textBox1->Text = L"1 event FREINE emis. e.text:=' " + e->type_freinage + L" ' . sender: " + sender->ToString();
}
Ce document intitulé « Visual c++ .net 2005: des points obscurs expliques » issu de CodeS SourceS (codes-sources.commentcamarche.net) est mis à disposition sous les termes de la licence Creative Commons. Vous pouvez copier, modifier des copies de cette page, dans les conditions fixées par la licence, tant que cette note apparaît clairement.
Rejoignez-nous