[Visual Studio] Aller plus loin avec le débogueur de Visual Studio

Dans mon précédent article consacré à la prise en main d’un débogueur, nous avons vu comment utiliser de façon très générale un débogueur et en particulier celui de Visual Studio.

Cependant, il existe des situations où il est très difficile de mettre au point son application, et donc difficile (voir impossible) d’utiliser le débogueur. Hors c’est dans ces situations que l’on souhaite réellement avoir besoin d’utiliser le débogueur pour trouver ce satané bogue ! Vous savez, le bogue qui vous force à poser des questions sur les forums, à vous arracher les cheveux, à consulter la Knowledge Base de Microsoft pour savoir si une mise à jour ne pose pas problème,…etc.

Je vais donc vous montrer maintenant quelques fonctionnalités avancées du débogage, afin que vous puissiez tirer au maximum des possibilités du débogueur, qui est rappelons-le un outil fort utile.

 

Il faut cependant savoir que contrairement à ce que l’on a vu en première partie, certains débogueurs (ou langage de programmation) ne proposent pas ces fonctionnalités avancées.

Faire appel à l’agent 007 durant le débogage !

Nous l’avons vu dans la première partie de manière très rapide que l’on pouvait consulter les variables via l’utilisation « d’espion ».
Les espions sont des expressions évaluées à chaque avancement pas-à-pas de votre application. Beaucoup de développeurs utilisent les espions uniquement pour évaluer une expression contenant une seule variable :

Affichage d'une valeur dans un espion de Visual Studio

Affichage d'une valeur dans un espion de Visual Studio

Mais les espions peuvent être utilisés pour évaluer des expressions beaucoup plus complexes ! On peut par exemple évaluer des expressions booléennes :

Evaluation d'une expression à l'aide des espions de Visual Studio

Evaluation d'une expression à l'aide des espions de Visual Studio

Et cela est évalué à chaque pas d’avancement de votre programme :

Evaluation à chaque avancement du programme

Evaluation à chaque avancement du programme

On peut aussi utiliser les espions pour récupérer un objet (qui n’est pas forcement attaché à votre code) et consulter son contenu. Par exemple dans le tout premier programme, on souhaite voir l’objet System.DateTime de la date courante :

Récupération de la date/heure courante

Récupération de la date/heure courante

ATTENTION : Etant donné que pour chaque avancement pas-à-pas de votre application, les espions sont réévalués (et donc exécute du code), il se peut que vous obtenez des effets secondaires…
Windev fait partie de cet exemple de débogueur. D’autres débogueurs beaucoup plus évolués comme Visual Studio, vous demandera de confirmer l’évaluation d’une expression. C’est le cas par exemple des méthodes. Ajoutons une méthode simple qui renvoi un entier :

public static int UneMéthode()
{
 return 1664;
}
Function UneMéthode() As Integer
 Return  1664
End Function

Si l’on évalue la méthode en ajoutant dans l’espion l’expression « UneMéthode() », Visual Studio affichera « 1664 » dans la fenêtre de l’espion.

Evaluation d'une méthode durant le débogage

Evaluation d'une méthode durant le débogage

Maintenant si l’on avance d’un pas, la ligne devient grisé et un petit bouton d’actualisation apparait :

La méthode n'est plus automatiquement évaluée durant le débogage du programme

La méthode n'est plus automatiquement évaluée durant le débogage du programme

Ce bouton permet d’évaluer l’expression présente sur la ligne. Un tel système permet lorsque vous faites du pas-à-pas d’éviter de provoquer des effets secondaires sur l’exécution de votre code.

Pour informations, il existe 4 fenêtres d’espions sous Visual Studio (Accessible via le menu Déboguer -> Fenêtres -> Espions -> Espion x). Cela vous permet d’organiser vos différentes expressions dans diverses fenêtres dans des situations où vous commencez à perdre vos cheveux…

Espions disponibles dans Visual Studio

Espions disponibles dans Visual Studio

Arrêter le débogueur selon une condition (Par programmation).

Lorsque vous déboguer votre application et en particulier un bout de code contenant une boucle, il devient difficile de parcourir pas-à-pas celle-ci sur beaucoup d’itérations…
Pour cela, le .NET nous propose une fonctionnalité qui permet d’arrêt le débogueur par programmation en appelant une simple méthode. Cela permet d’avoir un point d’arrêt dynamique qui peut être appelé en fonction d’une certaine condition (plus ou moins complexe !). Voici un exemple de code source à déboguer :

using System;

namespace Tourreau.Gilles.VisualStudio.Debogage
{
  class ProgrammeBoucleLa
  {
    static void Main(string[] args)
    {
      BoucleLa();
    }

    static void BoucleLa()
    {
      for (int i = 0; i < 100000; i++)
      {
        if (i == 50000)
          throw new  Exception("Bug");
      }
    }
  }
}
Namespace Tourreau.Gilles.VisualStudio.Debogage

  Module ProgrammeBoucleLa

    Sub Main(ByVal args() As  String)
      BoucleLa()
    End Sub

    Sub  BoucleLa()
      Dim i As Integer

      For i = 0 To  1000000
        If i = 50000 Then
          Throw New  Exception("Bug")
        End If
      Next
    End  Sub

  End Module

End Namespace

Vous constatez avec facilité (du moins je l’espère) qu’un bogue sera déclenché à l’itération 50 000. Il devient donc difficile (et impensable) de parcourir 49 999 fois cette boucle pour s’apercevoir qu’à l’itération 50 000, une exception sera levée… De plus il faudra sans doute relancer plusieurs fois le débogueur pour re-déboguer le code !
Pour éviter cela, on va introduire du code afin qu’arrivé à une certaine condition on puisse mettre en pause le programme pour l’afficher dans le débogueur. On ajoute pour cela en début de boucle la ligne suivante :

if (i == 49999)
  System.Diagnostics.Debugger.Break();

Voici le code la méthode :

static void BoucleLa()
{
  for (int i = 0; i <  100000; i++)
  {
    if (i == 49999)
      System.Diagnostics.Debugger.Break();

    if (i ==  50000)
      throw new Exception("Bug");
  }
}
Sub BoucleLa()
  Dim i As Integer

  For i = 0 To  1000000

    If i = 49999 Then
      System.Diagnostics.Debugger.Break()
    End If

    If i =  50000 Then
      Throw New Exception("Bug")
    End If
  Next
End Sub

Pour les utilisateurs de Visual Basic : Les VB-iens (de base fainéant 😉 ) sont avantagés par rapport aux utilisateurs C#, car ils disposent d’une instruction raccourcie « Stop ». Ce mot-clé produit exactement le même effet que l’appel à la méthode Debugger.Break(). (Le compilateur remplace le mot clé « Stop » par « Debugger.Break() »)

Déboguez cette application, vous constatez que Visual Studio s’est arrêté sur la méthode Break() de la classe Debugger. Consultez au passage la valeur de la variable « i » pour vous assurez que je ne vous mens pas !

Arrêt du débogueur sur la méthode Debugger.Break()

Arrêt du débogueur sur la méthode Debugger.Break()

Cette méthode agit donc comme un point d’arrêt, vous pouvez continuer à déboguer votre application de façon très classique.

ATTENTION : La méthode System.Diagnostics.Debugger.Break() – comme la classe System.Diagnostics.Debugger – ne dispose pas d’attribut ConditionnalAttribute(« DEBUG »). Ce qui signifie que lorsque vous compilez en Release du code contenant un appel à la méthode Break(), Windows affichera sur les systèmes en production (où ne disposant pas de Visual Studio) un message indiquant qu’un point d’arrêt a été trouvé :

Déclenchement de la méthode Debugger.Break() en Release

Déclenchement de la méthode Debugger.Break() en Release

Pensez donc avant la livraison de votre application de bien désactiver tous les points d’arrêts !
La solution idéal pour ne plus ce soucier de ce problème est de créer une méthode statique comme-ceci :

using System;
using System.Diagnostics;

namespace Tourreau.Gilles.VisualStudio.Deboguer
{
  [ConditionalAttribute("DEBUG")]
  public static class Debogueur
  {
    public static void Stop()
    {
      Debugger.Break();
    }
  }
}
Imports System.Diagnostics

Namespace Tourreau.Gilles.VisualStudio.Deboguer

  <ConditionalAttribute("DEBUG")> _
  Public Class Debogueur
    Public Shared Sub [Stop]()
      Debugger.Break()
    End  Sub
  End Class

End Namespace

Il suffit d’appeler la méthode statique Debogueur.Stop() pour arrêter le débogueur. En utilisant l’attribut System.Diagnostics.ConditionalAttribute, toutes les méthodes présentes dans cette classe ne seront pas incluses dans la compilation de votre application si vous ne spécifiez pas la définition « DEBUG » (Ce qui est le cas par défaut en Release).

Arrêter le débogueur selon une condition en utilisant Visual Studio.

ATTENTION : Cette fonctionnalité n’est pas disponible dans les versions Express de Visual Studio !

Arrêter le débogueur selon une condition vraie

La méthode vue précédemment, nécessitait de modifier votre code source. Cependant elle fonctionne avec n’importe quel type de débogueur .NET « écoutant » l’appel de cette méthode.
Nous allons voir qu’il est possible de spécifier des conditions via Visual Studio sans changer son code, pour cela reprenez l’exemple précédent en changeant les valeurs de la boucle et de la condition :

using System;

namespace  Tourreau.Gilles.VisualStudio.Debogage
{
  class ProgrammeBoucleLa
  {
    static void Main(string[] args)
    {
      BoucleLa();
    }

    static void  BoucleLa()
    {
      for (int i = 0; i < 1000;  i++)
      {
        if (i == 500)
          throw new Exception("Bug");
      }
    }
  }
}

Placez un point d’arrêt au niveau de la condition « if (i == 500) » :

Ajout d'un point d'arrêt

Ajout d'un point d'arrêt

Sur le rond rouge du point d’arrêt à gauche des numéros de ligne, faites un clic droit, et choisissez « Condition… ».

Ajout d'une condition sur le point d'arrêt

Ajout d'une condition sur le point d'arrêt

Une fenêtre s’ouvre vous invitant à saisir la condition d’arrêt. Nous allons dans un premier temps spécifier une condition sur la variable « i » quand celle-ci arrive à 499. Pour ce faire saisissez (sans guillemets) « i == 499 » :

Définition de la condition d'arrêt

Définition de la condition d'arrêt

La condition ci-dessus est évaluée à chaque passage sur le point d’arrêt. A noter qu’il est possible de saisir toute expression .NET renvoyant un booléen. Lancez le débogueur et observez le résultat en vérifiant la valeur de la variable « i ».

Le débogueur s'est arrêté à la 500ème itération

Le débogueur s'est arrêté à la 500ème itération

Le débogueur s’arrête bien à la condition spécifiée. Je tiens à préciser cependant les points suivants concernant cette méthode de point d’arrêt par rapport à celle utilisée par programmation :

  • La condition d’arrêt n’est pas spécifiée dans votre fichier code source ! Un simple copier/coller de votre code (ou du fichier source) à un collègue n’inclus en aucun cas la condition d’arrêt, il faudra alors la remettre dans un autre environnement !
  • L’évaluation de la condition est beaucoup plus lente ! En effet, contrairement à la méthode par programmation qui évalue directement la condition dans le code, la condition d’arrêt définie via Visual Studio est évaluée par Visual Studio à chaque itération. Cette évaluation traversant divers mécanismes complexes liés à Visual Studio entraine un temps d’évaluation relativement important…
  • Prêtez une grande attention aux erreurs de syntaxe ! Visual Studio ne contrôle en aucun cas la validité de l’expression que vous saisissez. Si celle-ci est incorrecte à l’exécution vous recevrez un message d’insulte de la part de Visual Studio !
  • L’évaluation exécute votre code en cours d’exécution, prêtez attention aux effets secondaires si vous appelez des méthodes dans votre expression !
Une évaluation incorrecte dans notre exemple à l'exécution ("j == 499")

Une évaluation incorrecte dans notre exemple à l'exécution ("j == 499")

Arrêter le débogueur selon le changement d’une expression

Dans la fenêtre de condition de point d’arrêt, il est possible de définir un point d’arrêt lorsqu’une expression change de valeur. Modifier l’exemple précédent comme ceci :

static void BoucleLa()
{
  bool b;

  for(int i = 0; i < 1000; i++)
  {
    if (i == 500)
      throw new Exception("Bug");

    if (i ==  10)
     b = true;
  }
}
Sub BoucleLa()
  Dim b As Boolean
  Dim i As  Integer

  For i = 0 To 1000

  If i = 500  Then
    Throw New Exception("Bug")
  End If

  If i = 10 Then
    b = True
  End If

  Next
End Sub

Ajouter un point d’arrêt au niveau de la condition « if (i == 500) » et spécifiez une condition comme précédemment sur celui-ci. Saisissez « b » dans la condition et sélectionnez « a changé » :

Définition d'un arrêt du débogueur lors du changement d'une valeur

Définition d'un arrêt du débogueur lors du changement d'une valeur

Lancez le débogueur, vous constatez que Visual Studio s’arrête à la première itération (i = 0), ce comportement est tout à fait normal.
Visual Studio considère la création de la variable comme un changement de la valeur, si le code que vous exécutez passe sur votre point d’arrêt, Visual Studio s’arrêtera dessus car la valeur de « b » à changé. Cliquez ensuite sur la flèche verte classique (« Continuer (F5) ») pour continuer l’exécution de votre programme. Visual Studio s’est arrêté à l’itération (i = 11) car à l’itération précédente on affecte true à « b » et lors du prochain passage sur le point d’arrêt, Visual Studio a remarqué un changement de valeur de « b ».

Comme expliqué dans la partie précédente, l’évaluation d’expression ralentie considérablement l’exécution de votre programme… Préférez donc dans certaines situations, la méthode des conditions par programmation.

Arrêter le débogueur après un certains nombre de passage

Il est possible sous Visual Studio d’arrêter le débogueur après un nombre de passage « égal », « supérieur ou égal » ou « multiple ». Pour ce faire, reprenez l’exemple précédent, supprimez le point d’arrêt crée précédemment, ajoutez un point d’arrêt et faites clic-droit sur celui-ci, choisissez alors « Nombre d’accès » :

Définition d'une condition d'arrêt du débogueur sur le nombre d'accès

Définition d'une condition d'arrêt du débogueur sur le nombre d'accès

La fenêtre « Nombre d’accès à un point d’arrêt » apparait. Vous pouvez choisir le type d’arrêt que vous souhaitez après plusieurs passage, dans notre exemple nous allons choisir l’option « s’arrêter lorsque le nombre d’accès est un multiple de ».

Définition d'un arrêt du débogueur lors que la valeur est un multiple=

Saisissez alors « 100 » dans la boite de saisie qui vient d’apparaitre juste à gauche :

Définition d'un arrêt du débogueur lors que la valeur est un multiple=

En saisissant, « 100 » vous arrêterez l’exécution de votre programme tous les 100 passages sur le point d’arrêt, c’est à dire dans notre exemple toutes les 100 itérations. Lancez le débogage de notre exemple, Visual Studio s’est arrêté à l’itération i = 99 (qui correspond à la centième). Maintenant continuez l’exécution de votre programme via la flèche verte, Visual Studio s’est arrêté à l’itération i = 199.

Pour savoir le nombre de passage déjà effectué sur votre point d’arrêt, accédez à la fenêtre du nombre d’accès :

Affichage du nombre d'accès du point d'arrêt

Affichage du nombre d'accès du point d'arrêt

Le « nombre d’accès actuel » correspond au nombre de passage de votre code sur le point d’arrêt. Il est possible de réinitialiser le compteur en cliquant sur le bouton « Réinitialiser ».
A noter que le « nombre d’accès actuel » est disponible en standard même si vous n’avez pas spécifié aucune option dans la fenêtre « Lorsque le point d’arrêt est atteint ».

Arrêter le débogueur « à la volée ».

Il arrive parfois que le code s’exécute à l’infini. Cela est dû le plus souvent à une boucle dont la condition est toujours vraie.
Traquer ce genre de bogue est parfois difficile, surtout si l’on n’a pas une idée précise qu’elle est la partie du code qui boucle à l’infini.

Visual Studio dispose d’un bouton permettant de stopper immédiatement les machines durant l’exécution d’un programme, si le débogueur est bien entendu lancé :

Bouton permettant de mettre en pause le débogueur

Bouton permettant de mettre en pause le débogueur

Pour mettre en avant cette fonctionnalité utiliser le code suivant :

using System;
using System.Threading;

namespace Tourreau.Gilles.VisualStudio.Debogage
{
  class  ProgramBoucleLaInfini
  {
    static void Main(string[]  args)
    {
      bool b;
      int  i;

      i = 0;
      b = false;

      while  (b == false)
      {
        Thread.Sleep(200);
        Console.WriteLine("Boucle là  !");

        if (i >= 10)
          b =  true;
      }
    }
  }
}
Imports System
Imports System.Threading

Namespace Tourreau.Gilles.VisualStudio.Debogage

  Class ProgramBoucleLaInfini
    Shared Sub Main(ByVal args As  String())

      Dim b As Boolean
      Dim i As  Integer

      i = 0
      b = False

      While b = False
        Thread.Sleep(200)
        Console.WriteLine("Boucle là !")

        If i >= 10  Then
          b = True
        End  If

     End While
   End Sub
 End Class

End Namespace

Vous constatez dans ce programme que l’on a « oublié » d’incrémenter la variable « i ». Celle-ci prend donc toujours la valeur « 0 » et la condition en fin de boucle sera donc toujours fausse. Rendant ainsi l’exécution du programme infinie.
Exécutez le programme avec la tite flèche verte, observez que votre programme boucle à l’infini. A ce moment là cliquez le bouton « Interrompre tout (Ctrl + Alt + Break) ». Vous devez vous trouver dans la situation suivante :

Mise en pause du débogueur sur l'instruction en cours d'exécution

Mise en pause du débogueur sur l'instruction en cours d'exécution

A ce moment là vous pouvez avancer pas-à-pas et comprendre plus précisément pourquoi cette boucle tourne à l’infinie…

Code « utilisateur » et « non-utilisateur »

Sous .NET et plus exactement sous Visual Studio, Microsoft a mis en place un concept pour différencier 2 types de code :

  • Le code utilisateur : C’est le code que vous êtes en train de coder et mettre au point.
  • Le code non-utilisateur : C’est le code externe que vous appelez (la plupart du temps présent dans d’autres bibliothèques externe).

On part du principe que lors du débogage, vous n’avez pas à rentrer dans le code non-utilisateur. Ce code n’est pas le vôtre, et vous faites confiance à ce code qui a déjà été testé et fonctionne « à la perfection ».
Il est cependant pratique à certains endroits de votre code, de le considérer comme « non-utilisateur » car vous savez que :

  • ce code est de « bas niveau » est fonctionne sans problème
  • ou n’apporte rien de bien fonctionnel lors de l’avancement du programme pas-à-pas

Pour illustrer l’un de ces concepts (le deuxième) nous allons utiliser l’exemple suivant :

using System;
using System.Diagnostics;

namespace  Tourreau.Gilles.VisualStudio.Debogage
{
  class  ProgrammeUtilisateur
  {
    private static int  membre;

    static void Main(string[] args)
    {
      int entier;

      membre =  1664;

      for (int i = 0; i < 1000; i++)
      {
        entier = Propriété;
      }
    }

    public static int Propriété
    {
      get {  return membre; }
    }
  }
}
Imports System.Diagnostics

Namespace  Tourreau.Gilles.VisualStudio.Deboguer
  Module  ProgrammeUtilisateur
    Dim membre As String

    Sub  Main(ByVal args() As String)
      Dim entier As Integer
      Dim i As Integer

      membre = 1664

      For i = 1  To 1000
        entier = Propriété
      Next

    End Sub

    ReadOnly Property Propriété() As Integer
      Get
        Return membre
      End Get
    End  Property
  End Module
End Namespace

Déboguer ce programme pas-à-pas, vous constatez que vous entrez dans la propriété « Propriété » à chaque itération.

Arrêt du débogueur dans une propriété "simple".

Arrêt du débogueur dans une propriété "simple".

Vous trouvez qu’il est complètement inutile de parcourir la propriété « Propriété » car elle n’apporte rien de fonctionnel ! (Elle se contente de retourner une simple variable membre).
Vous me direz : « On devrait dans ce cas faire un pas-à-pas principal pour zapper la méthode Propriété » ! C’est une solution, mais qui oblige le développeur à jongler entre les deux boutons « Pas-à-pas principal » et « Pas-à-pas détaillé ». Sous .NET on peut spécifier un attribut indiquant que ce code est non-utilisateur et que le débogueur n’a pas à passer dessus.
Cet attribut s’appelle : System.Diagnostics.DebuggerNonUserCodeAttribute.

Il faut placer cet attribut sur une méthode comme ceci :

public static int Propriété
{
  [DebuggerNonUserCode()]
  get { return membre; }
}
ReadOnly Property Propriété() As Integer
<DebuggerNonUserCode()> _
  Get
    Return membre
  End  Get
End Property

NOTE : Attention cet attribut ne fonctionne pas sur les propriétés ! Il faut placer cet attribut sur une méthode. Dans le cas des propriétés il faut le placer devant « les méthodes » get/set de celle-ci.

Relancez le débogueur, avancez le programme pas-à-pas en détaillé, vous constatez que Visual Studio ne rentre plus dans le code de la propriété « Propriété ».
Encore plus fort : Placez un point d’arrêt dans le getter de la propriété « Propriété », Visual Studio ne s’y arrête pas !

NOTE : Il est possible de forcer le débogage de code non-utilisateur en désactivant l’option : Outils -> Options -> Débogage -> Général -> « Activer Uniquement mon code »

Options de Visual Studio permettant d'activer ou non le code "non-utilisateur"

Options de Visual Studio permettant d'activer ou non le code "non-utilisateur"

Déboguer le code source du .NET Framework avec Visual Studio 2008 !

Une des nouveautés de Visual Studio 2008 est le débogage du code source du .NET Framework !
Pour utiliser cette nouvelle fonctionnalité il faut :

  • Disposer de la version Standard ou Pro de Visual Studio !
  • Avoir une connexion internet solide (ADSL minimum) pour télécharger les sources à la demande.

Au moment où je rédige cet article, Visual Studio 2008 n’existe pas encore en version française… Les screen-shots et les commandes seront donc en Anglais !

Certains screens-shot ont été tiré sur une publication du blog de Shwan Burke qui contient une FAQ sur les problèmes que vous pouvez rencontrer lors de l’utilisation de cette fonctionnalité.

Configuration de Visual Studio 2008

La première chose à faire avant de continuer est de télécharger et d’installer le Service Pack 1 de Visual Studio.

Une fois installé allez dans les options de Visual Studio 2008 via le menu : Tools / Options / Debugging / General :

  • Désactivez l’option « Enable Just My Code ». Cela s’explique tout simplement que le code source du .NET Framework, n’est pas considéré comme du code utilisateur.
  • Activez l’option « Enable source server support ». Afin que Visual Studio puisse télécharger le code source à la demande.
Activation de l'option permettant de déboguer le code source du .NET Framework

Activation de l'option permettant de déboguer le code source du .NET Framework

  • Toujours dans la fenêtre des options, allez dans la rubrique : « Symbols ».
  • Ajoutez l’adresse du serveur de Microsoft qui contient les symboles de débogage : http://referencesource.microsoft.com/symbols
  • Spécifiez un emplacement qui contiendra les symboles téléchargés dans « Cache symbols from symbol servers to this directory ».
    ATTENTION :
    Ce dossier doit disposer des droits de lecture et écriture !
  • Activez l’option « Search the above locations only when symbols are loaded manually ».
  • Cliquez ensuite sur « OK » pour valider et fermer les options.
Définition de l'adresse où se trouve les symbôles de débogage du code source du .NET Framework

Définition de l'adresse où se trouve les symbôles de débogage du code source du .NET Framework

Utilisation du débogage de code source du .NET Framework.

Nous allons tester le débogage sur cet exemple simple :

using System;

namespace  ConsoleApplication1
{
  class Program
  {
    static void  Main(string[] args)
    {
      Console.WriteLine("Résolution  2008 : J'utiliserais toujours le débogueur !");
    }
  }
}
Module Module1

  Sub Main()
    Console.WriteLine("Résolution 2008 : J'utiliserais toujours le débogueur  !")
  End Sub

End Module

Pour utiliser le débogage du code source, lancez le débogage de votre application. Affichez la fenêtre des « Modules » via la commande Debug / Windows / Modules.
Cette fenêtre représente les assemblys chargés par votre application, leur version mais surtout si les symboles sont chargés pour le débogage.

Affichage des modules chargés

Affichage des modules chargés

Par défaut, vos symboles sont chargés (car générés automatiquement dans le répertoire de compilation en mode DEBUG), mais pas ceux du .NET Framework.
Pour charger les symboles, sélectionnez l’assembly « mscorlib » dans la liste (Assembly où est contenue la classe Console). Faites un clic droit, et sélectionnez « Load Symbols ».

Chargement des symbôles associés au module

Chargement des symbôles associés au module

A ce moment là, Visual Studio télécharge automatiquement le .pdb de l’assembly « mscorlib ». Visual Studio est gelé durant le téléchargement, cela est tout à fait normal…
Une fois les symboles chargés, vous pouvez revenir sur votre code à déboguer, et entrer dans la méthode Console.WriteLine du .NET Framework, comme si vous aviez les sources du .NET Framework (Le téléchargement des sources se fait à la demande…) :

Arrêt du débogueur sur une ligne

Arrêt du débogueur sur une ligne

On clic alors dans pas-à-pas détaillé (F11) et on rentre dans le code source du .NET Framework :

Affichage du code source du .NET Framework

Affichage du code source du .NET Framework

Utilisateurs de VB .NET : Les assembly du .NET Framework ont été développés en langage C# par Microsoft, vous n’aurez donc pas durant le débogage du .NET Framework du code VB .NET…

Quand déboguer le code source du .NET Framework ?

Comme je l’ai expliqué dans la précédente partie, déboguer le code source .NET Framework c’est débogueur du code non utilisateur. C’est-à-dire du code source qui ne vous appartient pas, que vous ne devez pas modifier (les conditions d’utilisation de Microsoft vous l’interdise) et que vous devez faire entièrement confiance sur fiabilité de celui-ci.
Microsoft a mis en place ce procédé, afin que les utilisateurs puissent mieux comprendre l’exécution de leur code dans des cas très extrêmes qui posent problèmes.

Par exemple, le DataBinding dans les Windows Forms. Dans certains cas « tordus », il serait intéressant de savoir pourquoi le DataBinding ne fonctionne pas (le plus souvent à cause d’un problème de conversion qui déclenche une exception)…

Bibliothèques disponibles pour le débogage du .NET Framework

  • Le Framework de base
    • mscorlib.dll
    • System.dll
    • System.Xml.dll
  • ADO .NET
    • System.Data.dll
  • Gdi+
    • System.Drawing.dll
  • ASP .NET
    • System.Web
    • System.Web.Extensions
  • Windows Forms
    • System.Windows.Forms
  • Windows Presentation Foundation
  • Visual Basic pour les fainéants !
    • Microsoft.VisualBasic.dll

Déboguer une application multi-thread.

ATTENTION : Cette fonctionnalité n’est pas disponible dans les versions Express de Visual Studio !

Depuis 2004-2005, les constructeurs de micro-processeurs intègrent plusieurs cœurs dans leur architecture sur les ordinateurs de bureau afin de pouvoir exécuter du code en parallèle. Cela implique aux programmeurs pour tirer au maximum les performances de ces architectures de réaliser des applications « multi-threadés ».

Je ne vais pas détailler qu’est ce qu’un thread et une application multi-threadé, car cela sort du cadre de cet article.

Le débogage de ce genre d’application est très difficile, car pendant que vous déboguez pas-à-pas un thread, un autre s’exécute à la vitesse normale…
Si l’on ne dispose pas d’un débogueur évolué comme Visual Studio 2005. Il devient alors très difficile voir impossible de déboguer votre application.

Dans le cas de Visual Studio 2005, tous les threads de votre application sont mis en pause. Le débogage est assez aisé dans ces conditions et l’on peut voir ainsi du code parallèle qui s’exécute pas-à-pas.
Voici un exemple de code à tester :

using System;
 using System.Threading;

namespace  Tourreau.Gilles.VisualStudio.Debogage
{
  class ProgrammeThread
  {
    static void Main(string[] args)
    {
      Thread[] tab;

      tab = new Thread[10];

      for  (int i = 0; i < tab.Length; i++)
      {
        tab[i] =  new Thread(new ThreadStart(ThreadBoucle));
        tab[i].Name =  "Gilles's Thread " + i.ToString();
      }

      for (int  i = 0; i < tab.Length; i++)
        tab[i].Start();

      for (int i = 0; i < tab.Length;  i++)
        tab[i].Join();
    }

    static void  ThreadBoucle()
    {
      Random r;
      int nbItérations;

      r = new Random();
      nbItérations  = r.Next(1, 50);

      for (int i = 0; i  < nbItérations; i++)
      {
        Console.WriteLine("Je suis " + Thread.CurrentThread.Name);
        Thread.Sleep(r.Next(1,1000));
      }
    }
  }
}
Imports System.Threading

Namespace Tourreau.Gilles.VisualStudio.Debogage

  Module ProgrammeThread

    Sub Main(ByVal args As  String())

      Dim i As Integer

      Dim tab As  Thread()
      tab = New Thread(10) {}

      For i = 0 To tab.Length

        tab(i) = New Thread(New ThreadStart(AddressOf  ThreadBoucle))
        tab(i).Name = "Gilles's Thread " +  i.ToString()

      Next

      For i = 0 To  tab.Length
        tab(i).Start()
      Next

      For i = 0 To tab.Length
        tab(i).Join()
      Next

    End Sub

    Sub ThreadBoucle()

      Dim r As Random
      Dim  nbItérations As Integer
      Dim i As Integer

      r =  New Random()
      nbItérations = r.[Next](1, 50)

      For i = 0 To nbItérations
        Console.WriteLine("Je suis " +  Thread.CurrentThread.Name)
        Thread.Sleep(r.[Next](1,  1000))
      Next

    End Sub

  End  Module

End Namespace

Pour ceux qui ne comprendrais pas ce programme, voici son fonctionnement :
Au démarrage, le programme (thread principal) crée 10 threads nommés, les démarre et les attends. Chacun de ces Threads boucle un nombre aléatoire d’itération (entre 1 et 50) et à chaque itération affiche un message sur la console et se met en pause durant un temps aléatoire (de 1 à 1000 millisecondes).

Pour utiliser le débogage d’une application multi-thread il faut affichez une nouvelle fenêtre via le menu Déboguer/Fenêtres/Threads qui représente tous les threads en cours d’exécution. Placez un point d’arrêt au niveau du « Console.WriteLine(… » et lancez le débogage. Effectuez environ une dizaine de clic sur la flèche verte « Continuer (F5) » pour continuer l’exécution du programme. Vous devez vous trouver dans une situation ressemblant à celle-ci :

Affichage de la liste des threads en cours d'exécution

Affichage de la liste des threads en cours d'exécution

La flèche jaune à gauche représente le contexte du thread qui est actuellement chargé et où vous vous êtes arrêté. Comme pour la pile des appels, étant donné que vous mélangez le même code pour les différents threads, il faut faire un double clic sur un thread pour charger son contexte d’exécution (un contexte de thread contient une pile des appels).
Veuillez noter que Visual Studio a mis tous les threads en pause ! Cela permet de déboguer très facilement une application multi-thread complexe !
Observez maintenant pour chaque thread la valeur de la variable « i »… Elle devrait être plus ou moins différente d’un thread à un autre.
Vous pouvez effectuez du pas à pas sur le thread dont vous avez chargé son contexte, les autres threads avanceront au fur et à mesure en parallèle (le parallélisme dépend entièrement de Windows).

Si vous ne disposez pas d’un débogueur évolué comme Visual Studio, il vous faudra tracer l’évolution de votre programme sur une console, dans un fichier…
Sous .NET on peut utiliser la méthode Console.WriteLine() mais aussi System.Diagnostics.Debug.WriteLine()

Déboguer un composant qui fonctionne en mode Design

Lorsque vous développez un composant (ou contrôle) sous .NET, il est possible de programmer du code qu’il s’exécute uniquement en mode Design. C’est à dire lorsque vous utilisez un concepteur de Visual Studio tel que : l’éditeur de fenêtre, l’éditeur de page Web…etc.
Pour savoir si le composant est en cours d’exécution dans une application classique ou sous Visual Studio, on utilise la propriété DesignMode présente dans la classe de base Component.

Créez dans une solution, un nouveau projet de type « Bibliothèques de contrôles Windows »‘ et ajoutez une classe vide contenant le code suivant :

using System;
using System.Drawing;
using  System.Windows.Forms;

namespace  Tourreau.Gilles.VisualStudio.Debogage
{
  public class Class1 :  Control
  {
    protected override void OnPaint(PaintEventArgs  e)
    {
      if (this.DesignMode == true)
        e.Graphics.FillRectangle(null, e.ClipRectangle);
      else
        e.Graphics.FillRectangle(Brushes.Black,  this.ClientRectangle);
    }
  }
}
Imports System.Drawing
Imports  System.Windows.Forms

Namespace  Tourreau.Gilles.VisualStudio.Debogage
  Public Class Class1
    Inherits Control

    Protected Overloads Overrides Sub OnPaint(ByVal  e As PaintEventArgs)
      If Me.DesignMode = True  Then
        e.Graphics.FillRectangle(Nothing,  e.ClipRectangle)
      Else
        e.Graphics.FillRectangle(Brushes.Black, e.ClipRectangle)
      End If
    End Sub

  End Class
End Namespace

Compilez le projet.

Cette classe représente un contrôle affichable sur une fenêtre Windows. Ce contrôle dessine à l’exécution un fond noir sur toute la surface du contrôle.
En mode Design, j’ai volontairement passé un paramètre null à la méthode FillRectangle(). Cela va engendrer bien évidemment un bogue en mode Design.
Pour le mettre en œuvre il suffit de créer un projet de type « Application Windows », d’ouvrir – si ce n’est pas déjà fait – la fenêtre Form1 crée automatiquement et de faire un glisser déplacé du contrôle « Class1 » présent dans boîte à outils vers cette fenêtre.
Un message d’insulte de la part de Visual Studio apparait :

Message d'erreur en mode Design dans Visual Studio

Message d'erreur en mode Design dans Visual Studio

Et ensuite le contrôle n’est pas dessiné sur la fenêtre :

Le contrôle n'est pas dessiné sur la fenêtre

Le contrôle n'est pas dessiné sur la fenêtre en mode Design

Exécutez maintenant le projet contenant la fenêtre (pensez à le définir comme projet par défaut). Vous devez voir apparaître la fenêtre suivante :

Le contrôle dessiné durant l'exécution de l'application

Le contrôle dessiné durant l'exécution de l'application

Vous constatez, contrairement à l’exécution, qu’en mode Design ce contrôle ne fonctionne pas… La question est donc : « Comment débogueur notre contrôle sous Visual Studio avec le débogueur de Visual Studio ? ». La réponse est simple : « Avec Visual Studio ! ». Non ce n’est pas aberrant, nous allons débogueur Visual Studio avec Visual Studio et plus exactement avec une autre instance…

Pour se faire, il faut spécifier dans la bibliothèque contenant le contrôle (qui est à elle seule non exécutable) que l’on veut lancer l’exécution de cette bibliothèque via Visual Studio.
Faites un clic droit sur le projet contenant le contrôle et choisissez « Propriétés », sélectionnez l’onglet « Déboguer ». Les options qui nous intéressent sont celles présentes dans la section « Action de démarrage » :

Changement de l'action de démarrage lors du débogage

Changement de l'action de démarrage lors du débogage

Spécifiez que vous souhaitez lancer votre bibliothèque via un programme externe, choisissez comme exécutable celui de Visual Studio, qui est installé par défaut (pour la version 2005) dans :
C:\Program Files\Microsoft Visual Studio 8\Common7\IDE\devenv.exe

Fermer toutes les fenêtres actuellement ouvertes dans Visual Studio ainsi que celle des propriétés du projet, enregistrez le tout et faites un clic droit toujours sur le même projet. Choisissez alors « Déboguer -> Démarrer une nouvelle instance ».

Débogage d'un projet spécifique

Débogage d'un projet spécifique

Une autre instance de Visual Studio vient d’être lancée… Cette instance représente en fait l’exécution de votre programme en mode Design. Créer une nouvelle solution de test contenant un projet de type « Application Windows ».

Dans la boite à outils de Visual Studio, sélectionnez la section « Général » et faites un clic droit. Choisissez « Choisir les éléments ». Cliquez sur le bouton « Parcourir… » et recherchez votre bibliothèque compilée (dans le répertoire <Répertoire du projet contenant le composant Class1>\bin\Debug).

Ajout du contrôle personnalisé dans la boîte à outils

Ajout du contrôle personnalisé dans la boîte à outils

Faites un glisser/déplacer du contrôle « Class1 » vers la fenêtre « Form1 ». La première instance de Visual Studio prend le focus et vous indique la levée d’une exception :

Le débogueur s'arrête sur l'erreur durant l'utilisation de Visual Studio...

Le débogueur s'arrête sur l'erreur durant l'utilisation de Visual Studio...

Constatez que l’autre instance de Visual Studio est en pause (Le sablier de Windows vous le prouvera). Vous pouvez a ce moment déboguer comme vous avez l’habitude… A noter que dans la première instance de Visual Studio vous pouvez placer des points d’arrêts comme si vous souhaitiez déboguer une application à l’exécution !

ATTENTION : Lorsque vous arrêter le débogage de manière brutale, vous tuez la deuxième instance de Visual Studio ! Les fichiers contenant le projet de test risque d’être endommagés ou non enregistrés !

Je tiens à signaler qu’il ne faut pas hésiter à utiliser ce concept afin de créer et mettre au point ses propres composants et contrôles. Beaucoup de développeurs .NET (plus ou moins débutants) butent sur la création de nouveaux composants car il ne savent pas comment les déboguer en mode Design…

La méthode ToString() utilisée pour afficher le contenu des variables.

Lorsque vous créez une classe, celle-ci dérive forcement de la classe de base du .NET Framework : System.Object. Cette classe contient une méthode appelée ToString() qui renvoi une valeur textuelle de l’instance d’un objet. Par défaut, Microsoft a implémenté la méthode pour retourner le type de d’objet.
Sachez que le résultat de cette méthode est utilisé pour afficher le contenu des variables durant le débogage dans toutes les procédés d’affichages (espions, fenêtres de variables locales,…etc.).

Voici un exemple pour vous le démontrer :

using System;

namespace  Tourreau.Gilles.VisualStudio.Debogage
{
  class ProgrammeToString
  {
    static void Main(string[] args)
    {
      Personne p;

      p = new Personne("LEBRUN",  "Thomas");

      Console.WriteLine(p.Nom + " " +  p.Prénom);
      Console.WriteLine(p.ToString());

      Console.ReadKey();
    }
  }

  class Personne
  {
    private string nom;
    private string  prénom;

    public Personne(string nom, string prénom)
    {
      this.nom = nom;
      this.prénom = prénom;
    }

    public string Nom
    {
      get { return  this.nom; }
    }
    public string Prénom
    {
      get { return this.prénom; }
    }
  }
}
Namespace Tourreau.Gilles.VisualStudio.Debogage
  Public Class ProgrammeToString
    Sub Main(ByVal args As  String())
      Dim p As Personne

      p = New  Personne("LEBRUN", "Thomas")

      Console.WriteLine(p.Nom & "  " & p.Prénom)
      Console.WriteLine(p.ToString())

      Console.ReadKey()

    End Sub
  End Class

  Public  Class Personne
    Dim _nom As String
    Dim _prénom As  String

    Sub New(ByVal nom As String, ByVal prénom As  String)
      MyClass._nom = nom
      MyClass._prénom =  prénom
    End Sub

    ReadOnly Property Nom() As  String
      Get
        Return MyClass._nom
      End Get
    End Property

    ReadOnly Property  Prénom()
      Get
        Return  MyClass._prénom
      End Get
    End Property

  End Class
End Namespace

Déboguer ce programme et arrêtez vous sur le premier « Console.WriteLine(… ». Affichez le contenu de la variable « p » :

Le nom de l'instance n'est pas très explicite...

Le nom de l'instance n'est pas très explicite...

Vous constatez que par défaut le type de l’objet est marqué en accolade pour l’objet « p ». Ce qui n’est pas très explicite et vous oblige à dérouler le contenu de votre variable pour savoir de qu’elle personne il s’agit.

On est obligé de consultez les propriétés pour obtenir plus d'informations...

On est obligé de consultez les propriétés pour obtenir plus d'informations...

Ajoutons alors la redéfinition suivante de la méthode ToString() dans la classe Personne :

public override string ToString()
{
  return  this.Nom + " " + this.Prénom;
}
Public Overrides Function ToString() As String
  Return  Nom & " " & Prénom
End Function

Relancer le débogage de l’application et arrêtez vous au niveau du premier « Console.WriteLine(… ». Affichez maintenant le contenu de la variable « p » :

Immédiatement, l'instance représentée est plus claire...

Immédiatement, l'instance représentée est plus claire...

Voilà, qui est beaucoup plus clair… C’est surtout utile dans le cas des tableaux d’objets :

Affichage d'un tableau de personne

Affichage d'un tableau de personne

Créer un visualiseur

Il est possible sous Visual Studio d’afficher le contenu d’une variable de manière beaucoup plus « représentative ».
Prenons par exemple la création d’un DataTable que l’on remplit avec des données :

</pre>
using System;
 using System.Data;
<pre>namespace Tourreau.Gilles.VisualStudio.Debogage
{
  class ProgramDataTable
  {
    static void Main(string[] args)
    {
      DataTable dt;

      dt = new DataTable("Développeurs  .NET");
      dt.Columns.Add("Nom");

      dt.Rows.Add("TOURREAU");
      dt.Rows.Add("LEBRUN");
      dt.Rows.Add("CREVECOEUR");
      dt.Rows.Add("PHILIPPOT");

      Console.ReadKey();
    }
  }
}
Namespace Tourreau.Gilles.VisualStudio.Debogage
  Module  ProgramDataTable

    Sub Main()
      Dim dt As  DataTable

      dt = New DataTable("Développeurs  .NET")
      dt.Columns.Add("Nom")

      dt.Rows.Add("TOURREAU")
      dt.Rows.Add("LEBRUN")
      dt.Rows.Add("CREVECOEUR")
      dt.Rows.Add("PHILIPPOT")

      Console.ReadKey()
    End Sub

  End Module
End Namespace

Lorsque l’on débogue un tel code et que l’on affiche le contenu du DataTable, vous remarquez que par défaut Visual Studio vous affiche la liste de toutes les propriétés (avec les valeurs associées).

Affichage du contenu d'un DataTable

Affichage du contenu d'un DataTable

Cela ne représente en rien le contenu du DataTable. On est obligé de dérouler la propriété Rows et pour chaque ligne, dérouler encore des propriétés…
Pour rémédier à cet inconvénient, Visual Studio dispose d’un concept de visualiseur. Un visualiseur est une classe .NET qui permet de représenter une instance de données autrement qu’en parcourant la liste de ces variables et propriétés membres.
Les visualiseurs peuvent être affichées en cliquant sur la loupe présente entre le nom de la variable et sa valeur associée au niveau des espions :

Affichage du contenu du DataTable

Affichage du contenu du DataTable

Lorsque l’on clique dessus on obtient la fenêtre suivante :

Contenu du DataTable

Contenu du DataTable

Avec les visualiseurs on peut affichez de manière beaucoup plus représentative, les instances de certaines variables.

Il est bien sûr possible d’en créer sous .NET pour vos propres objets !
Il faut cependant prendre en considération ces 2 aspects :

  • Vos objets doivent être sérialisable.
  • Pensez à bien tester votre visualiseur, car un visualiseur qui affiche des mauvaises informations peut entrainer une mise au point très difficile du programme.

Pour illustrer tout çà nous allons créer une classe contenant une couleur comme ceci :

using System;
using System.Drawing;

namespace  Tourreau.Gilles.VisualStudio.Debogage
{
  public class UneClasse
  {
    private Color couleur;

    public UneClasse(Color  couleur)
    {
      this.couleur = couleur;
    }

    public Color Couleur
    {
      get { return  this.couleur; }
    }
  }
}
Imports System
Imports System.Drawing

Namespace  Tourreau.Gilles.VisualStudio.Debogage

  Public Class  UneClasse

    Private couleur As Color

    Public Sub  New(ByVal couleur As Color)
      Me.couleur = couleur
    End Sub

    Public ReadOnly Property Couleur() As Color
      Get
        Return Me.couleur
      End Get
    End Property

  End Class

End Namespace

Et le programme de test suivant :

using System;
using System.Drawing;

namespace  Tourreau.Gilles.VisualStudio.Debogage
{
  class ProgramUneClasse
  {
    static void Main(string[] args)
    {
      UneClasse c;

      c = new UneClasse(Color.FromArgb(152, 50,  18));

      Console.ReadKey();
    }
  }
}
Imports System
Imports System.Drawing

Namespace Tourreau.Gilles.VisualStudio.Debogage
  Class ProgramUneClasse
    Shared Sub Main(ByVal args As  String())
      Dim c As UneClasse

      c = New  UneClasse(Color.FromArgb(152, 50, 18))

      Console.ReadKey()

    End Sub
  End Class

End Namespace

Lancez le débogage de cette application, consultez la variable « c » :

Affichage des valeurs des composantes ARGB d'une couleur .NET

Affichage des valeurs des composantes ARGB d'une couleur .NET

A moins d’avoir une palette de couleur complète dans la tête, il est très difficile d’avoir une représentation exacte de la couleur rien qu’en consultant ces composantes.
Nous allons donc, créer un visualiseur simple permettant d’afficher à l’écran la couleur de l’instance d’une classe « UneClasse ».
Ajoutez une référence à l’assembly : Microsoft.VisualStudio.DebuggerVisualizers

Créer ensuite la classe UneClasseVisualizer suivante :

using System;
using System.Windows.Forms;
using  Microsoft.VisualStudio.DebuggerVisualizers;

namespace Tourreau.Gilles.VisualStudio.Debogage
{
  public class UneClasseVisualizer : DialogDebuggerVisualizer
  {
    protected override void Show(IDialogVisualizerService windowService,  IVisualizerObjectProvider objectProvider)
    {
      UneClasse data = (UneClasse)objectProvider.GetObject();

      using (Form f =  new Form())
      {
        f.BackColor =  data.Couleur;
        windowService.ShowDialog(f);
      }
    }
  }
}
Imports System
Imports System.Windows.Forms
Imports Microsoft.VisualStudio.DebuggerVisualizers

Namespace Tourreau.Gilles.VisualStudio.Debogage

  Public Class  UneClasseVisualizer
    Inherits DialogDebuggerVisualizer

    Protected Overloads Overrides Sub Show(ByVal windowService As  IDialogVisualizerService, ByVal objectProvider As  IVisualizerObjectProvider)

      Dim data As UneClasse =  DirectCast(objectProvider.GetObject(), UneClasse)

      Using f As  New Form()
        f.BackColor = data.Couleur
        windowService.ShowDialog(f)
      End Using

    End Sub

  End Class

End Namespace

La méthode Show est appelée automatiquement par Visual Studio durant le débogage, on récupère ensuite une instance et on l’affiche à l’écran. Dans notre cas, on change la propriété BackColor de la fenêtre.
Ajoutez maintenant les attributs suivants à la classe UneClasse (cela permet à Visual Studio de savoir qu’elle est la classe à utiliser pour créer le visualiseur, dans notre cas c’est UneClasseVizualiser) :

[DebuggerVisualizer(typeof(UneClasseVisualizer))]
[Serializable]
public  class UneClasse
...

<DebuggerVisualizer(GetType(UneClasseVisualizer))>  _
<Serializable()> _
Public Class UneClasse
...

Remarquez que l’attribut Serializable a été ajouté… (C’est indispensable)
Déboguer de nouveau l’application, consultez la valeur de la variable « c » et cliquez sur la loupe :

Représentation visuelle de la couleur associée

Représentation visuelle de la couleur associée

Voilà qui est beaucoup plus « parlant » !

A noter que pour débogueur un visualiseur, il faut utiliser une autre instance de Visual Studio (en utilisant exactement le concept de débogage de composant en mode Design vu plus haut).

Les limites du débogueur et comment y remédier.

Même si le débogueur permet d’explorer l’exécution d’une application pas à pas de façon simple, il existe des situations ou le débogage est difficile voir impossible ! Tout simplement parce que le débogueur modifie involontairement l’exécution de votre code.
Pour éviter ce genre de désagrément, voici une liste non exhaustive des différents problèmes que vous pourrez rencontrer durant le débogage de vos applications.

Les erreurs de programmation classiques (NullReferenceException,…etc) ne sont pas interceptées par Visual Studio !

C’est la remarque classique du débutant lorsqu’il a découvert le débogueur, qui sait s’en servir et qui souhaite l’utiliser dans son petit projet. Pour mieux comprendre le problème, voici un exemple de code :

using System;
using System.IO;

namespace  Tourreau.Gilles.VisualStudio.Debogage
{
  class ProgrammeTryCatch
  {
    static void Main(string[] args)
    {
      try
      {
        string s;

        s = null;
        Console.WriteLine("Contenu de s : " +  s.ToString());
        File.Open("C:\\Fichier.txt",  FileMode.Open);
      }
      catch
      {
      }

      Console.WriteLine("...");
      Console.ReadKey();
    }
  }
}
Imports System.IO

Namespace  Tourreau.Gilles.VisualStudio.Debogage
  Public Class  ProgrammeTryCatch
    Sub Main(ByVal args As String())
      Try
        Dim s As String

        s = Nothing
        Console.WriteLine("Contenu de s : " +  s.ToString())
        File.Open("C:\Fichier.txt",  FileMode.Open)

      Catch

      End Try

      Console.WriteLine("...")
      Console.ReadKey()
    End Sub
  End Class

End Namespace

On retrouve ce genre de code chez beaucoup de programmeurs VB .NET… Si si ! Ne faites pas l’innocent !!! (Sans doute à cause de l’éditeur de code de VB qui génère du code automatiquement dès que l’on saisie « Try »…)

On suppose ici que le fichier n’existe pas dans la racine.
Lorsque vous lancez le débogueur, ce code semble fonctionner sans problème car Visual Studio n’a pas arrêté l’exécution de votre programme. Cependant il n’a pas affiché le message « Contenu de s :  » sur la console.
En fait, ce code est incorrect, il lève une exception mais elle est attrapée par le bloc « catch » qui poursuit ensuite une exécution normale…

L’exemple précédent représente l’exemple type du mauvais traitement des exceptions ! On le retrouve chez les débutants qui découvrent la gestion des exceptions et souhaitant traiter une exception provoqué par une méthode (ici File.Open() si le fichier n’existe pas).
Il ne faut jamais traiter une exception de façon général comme précédemment (Au pire à éviter).
Toutes les exceptions du .NET Framework héritent d’une classe de base Exception, il y a parmi ces exceptions des exceptions « non traitable » ou « non gérable ». Ce sont des exceptions qui indique le plus souvent une erreur de programmation (Référence null, dépassement de la capacité d’un tableau,…etc.) ou pour indiquer un état instable du système (Pas assez de mémoire,…etc.).
Ces exceptions étant dit « non traitable », une fois déclenchée il n’y a pas grand chose à faire que fermer l’application et la redémarrer de façon à repartir sur un système stable.

Il ne faut donc jamais traiter les exceptions de façon général, utilisez toujours l’une des classes dérivées de System.Exception. Cela permet de traiter des exceptions spécifiques et laisser les autres se déclencher afin d’empêcher une application instable de fonctionner…
Voici le code correct :

try
{
  string s;

  s = null;
  Console.WriteLine("Contenu de s : " + s.ToString());

  File.Open("C:\\Fichier.txt",  FileMode.Open);
}
catch(FileNotFoundException)
{
}
Imports System.IO

Namespace Tourreau.Gilles.VisualStudio.Debogage

  Public Class  ProgrammeTryCatch
    Sub Main(ByVal args As String())
      Try
        Dim s As String

        s = Nothing
        Console.WriteLine("Contenu de s : " +  s.ToString())
        File.Open("C:\Fichier.txt",  FileMode.Open)

      Catch ex As  FileNotFoundException
      End Try

      Console.WriteLine("...")
      Console.ReadKey()
    End Sub
  End Class

End Namespace

Ici on traite uniquement une exception spécifique à l’utilisation de FileNotFoundException. On laisse ainsi se déclencher les autres types d’exceptions dites « non traitables ».

Dans les applications (ou parti de code) critique, on peut s’autoriser de traiter des exceptions de façon général, mais dans ce cas il faut prévoir un code robuste et s’assurer que le programme tourne toujours de façon correct après la levée d’exception !

Les ressources disposant d’un Time-Out.

Certaines ressources par mesure de sécurité dispose d’un Time-Out, qui une fois dépassée, déclenche un certain événement. (La plus-part du temps la libération de la ressource, suivi d’une levée d’exception).
L’exemple le plus classique est la création d’une transaction d’un SGBD.
Prenons l’exemple suivant :

static void Main(string[] args)
{
  using  (Transaction t = new Transaction())
  {
    ExecuterRequeteInsert1();
    ExecuterRequeteInsert2();
    ExecuterRequeteUpdate();

    t.Complete();
  }
}
Sub Main(ByVal args() As String)

  Using t As  Transaction = New Transaction()
    ExecuterRequeteInsert1()
    ExecuterRequeteInsert2()
    ExecuterRequeteUpdate()

    t.Complete()
  End Using

End Sub

Ce code crée une transaction au niveau du using. La transaction est validée (commit) lorsque l’on appel la méthode Complete() sur l’instance crée dans using, ou elle annulé (rollback) dans le cas contraire (due à une levée d’exception ou un time-out).
Lorsque vous déboguer ce code, étant donné que « vous ralentissez » involontairement l’exécution de votre application en faisant du pas-à-pas, il se peut que vous déclenchiez le time-out de la transaction. Cela produit souvent un effet non voulu pour les tests (la plus-part du temps la levée d’une exception).

Pour remédier à ce problème il suffit d’augmenter la durée du time-out (voir la mettre à l’infini si possible).
Ce que je vous dis peut vous paraitre « stupide » et évident à première vue, mais il faut savoir que pour une bonne gestion de ce problème, il est nécessaire de prévoir la centralisation de la durée des time-out dans une variable « globales », constante, fichier de configuration,…etc., afin qu’il soit possible de la modifier d’un coup, lorsque vous êtes en développement ou en train de livrer votre application.
Prévoyez donc une bonne organisation des durées des Time-out avant de coder votre application !!

Les événements liés au focus

Les événements concernant la prise et la perte de focus posent des fois des problèmes au niveau du débogage.
Visual Studio (et plus généralement votre débogueur) est une fenêtre. Lorsque vous prenez le focus sur cette fenêtre, vous déclenchez un événement de perte de focus sur le fenêtre courante de votre application. Le débogage est donc très difficile dans ce cas, car la fenêtre du débogueur déclenche des événements qui ne le seraient pas dans une utilisation normale… Dans ce cas il est souvent nécessaire de tracer l’exécution de votre code via la méthode System.Diagnostics.Debug.WriteLine().

Il m’est difficile de vous produire un exemple, mais sachez que ce problème existe !

Les événements de dessin des contrôles Windows Forms

L’événement de dessin Control.Paint sont déclenchés quand d’autres applications invalident un contrôle. La fenêtre du débogueur peut être devenir cette application qui invalide votre contrôle et déclenchant des événements qui n’aurait pas du l’être dans une exécution normale.
De plus la fréquence d’appel de ces événements étant très grande, il est alors très difficile de faire avancer son programme. Vous n’avez donc pas le choix que de tracer l’exécution de votre application.

Comme précédemment il m’est difficile de vous produire un exemple de cette situation.

Déboguer une application lancée par un autre processus

Il arrive que vous développiez une application qui est exécutée par un autre processus. Il sera difficile dans ce cas d’utiliser le débogueur de Visual Studio car ce n’est pas lui qui lance votre application et ne peut donc en aucun cas prendre le contrôle de votre application.

Pour mettre en pratique ce genre de situation, nous disposons d’une application (.NET ou non) AppA qui appelle une application AppB (.NET) que vous êtes en train de développer.

Créer une nouvelle solution contenant une Application Windows intitulé « AppB ».
Dans la fenêtre Form1 créer par défaut, ajoutez un bouton :

Création d'une fenêtre avec un bouton

Création d'une fenêtre avec un bouton

Et faites un double clic sur le bouton que vous venez d’ajouter pour saisir le code suivant :

private void button1_Click(object sender, EventArgs  e)
{
  Form f;

  f = null;
  f.Hide();
}
Private Sub Button1_Click(ByVal sender As Object, ByVal e As  EventArgs) Handles Button1.Click
  Dim f As Form

  f = Nothing
  f.Hide()
End Sub

Placez un point d’arrêt au niveau du « f = null » :

Ajout d'un point d'arrêt

Ajout d'un point d'arrêt

Compilez ce programme et allez dans le répertoire où a été généré l’exécutable AppB.exe (Par défaut c’est : <Répertoire du projet AppB>\bin\Debug. Créez un document de type texte nommé « AppA.cmd » contenant les commandes suivantes :

@ECHO OFF
ECHO Lancement de AppB et attente de la fin de  celui-ci...
AppB.exe
ECHO ...Fin de AppB

Les commandes ci-dessous représentent une application (non .NET pour être dans le cas le plus général) qui lance l’application AppB.
Assurez-vous maintenant que ces deux programmes fonctionnent sans problème. C’est à dire que AppA lance AppB et AppB provoque une erreur… Pour ce faire, lancez l’application AppA.cmd.
Vous devez vous trouver dans la situation suivante :

Lancement d'une application depuis un script en ligne de commande

Lancement d'une application depuis un script en ligne de commande

Cliquez sur le bouton, vous devez voir ce message d’erreur :

Exception déclenchée en dehors de l'environnement Visual Studio

Exception déclenchée en dehors de l'environnement Visual Studio

Cliquez maintenant sur « Quitter » pour fermer les deux applications.

Nous allons maintenant essayer de déboguer notre application tout en la lançant à partir de AppA.
Relancez l’application AppA et dans Visual Studio toujours avec votre projet AppB ouvert, allez dans « Outils -> Attacher au processus… ». Vous voyez une liste de processus actuellement en cours d’exécution sur votre système. Recherchez dans la liste « AppB.exe » :

Sélection de l'application à déboguer

Sélection de l'application à déboguer

Une fois le processus « AppB.exe » sélectionné, cliquez sur le bouton Attacher. Visual Studio passe automatiquement en mode débogage et « écoute » dès qu’une exception où un point d’arrêt sera déclenché dans « AppB.exe ».

Cliquez maintenant sur le bouton de la fenêtre, Visual Studio prend automatiquement la main et s’arrête sur le point d’arrêt crée précédemment :

Le débogueur de Visual Studio prend la main...

Le débogueur de Visual Studio prend la main...

Vous pouvez continuer à déboguer votre application AppB, le tout en ayant exécuté l’application AppA !

NOTE : Pour utiliser la fonction « Attacher au processus… », soyez sûr que votre projet contenant le code source de l’application à déboguer est ouvert.

Conclusion

Le débogueur est un outil indispensable pour la mise au point de son application. Il permet de trouver aisément des erreurs de programmation dans son application, contrairement à la bière, il ne faut surtout pas hésiter à en abuser !
Visual Studio offre de nombreuse fonctionnalités très avancées, permettant de déboguer vos applications dans des conditions les plus extrêmes !

Remerciements

Je tiens particulièrement à remercier les personnes suivantes pour leurs commentaires qui m’ont permis d’améliorer la qualité ce tutoriel (du moins je l’espère) :

  • ABRASSART Jacques
  • DE HATTEN Thomas
  • DUTHOO Laurent
  • LEMIERE Valentin
  • STENGER Philippe