343 votes

Manière plus facile de déboguer un service Windows

Y a-t-il un moyen plus facile de parcourir le code que de démarrer le service via le Gestionnaire de services de Windows, puis d'attacher le débogueur au thread? C'est un peu lourd et je me demande s'il existe une approche plus directe.

0 votes

J'ai créé ce ticket User Voice. Considérez voter pour cela : visualstudio.uservoice.com/forums/121579-visual-studio-ide/…

278voto

jop Points 31978

Si je veux déboguer rapidement le service, je place simplement un Debugger.Break() là-dedans. Lorsque cette ligne est atteinte, cela me ramènera à VS. N'oubliez pas de supprimer cette ligne lorsque vous avez fini.

MISE À JOUR: En alternative aux pragmas #if DEBUG, vous pouvez également utiliser l'attribut Conditional("DEBUG_SERVICE").

[Conditional("DEBUG_SERVICE")]
private static void DebugMode()
{
    Debugger.Break();
}

Sur votre OnStart, appelez simplement cette méthode:

public override void OnStart()
{
     DebugMode();
     /* ... do the rest */
}

De cette façon, le code ne sera activé que pendant les builds de débogage. Pendant que vous y êtes, il pourrait être utile de créer une configuration de build séparée pour le débogage du service.

48 votes

Ou vous pourriez utiliser Debugger.Launch() vous devrez inclure une instruction using pour le namespace Systems.Diagnostics.

1 votes

Votre article de blog a très bien fonctionné et a sauvé ma journée :) cependant, Debugger.Break() n'a pas fonctionné pour moi. Il semble que .Net saute la fonction DebugMode pour des raisons liées à l'optimisation.

3 votes

Debugger.Launch() fonctionne pour moi lorsque Debugger.Break() ne fonctionne pas. (Le processus se termine avec le code 255.)

219voto

Christian.K Points 18883

Je pense également que le fait d'avoir une "version" séparée pour l'exécution normale et en tant que service est la meilleure solution, mais est-il vraiment nécessaire de dédier un commutateur de ligne de commande séparé à cette fin ?

Ne pourriez-vous pas simplement faire :

public static int Main(string[] args)
{
  if (!Environment.UserInteractive)
  {
    // Démarrage en tant que service.
  }
  else
  {
    // Démarrage en tant qu'application
  }
}

Cela aurait l ' "avantage", que vous pouvez simplement démarrer votre application via un double clic (OK, si vous en avez vraiment besoin) et que vous pouvez simplement appuyer sur F5 dans Visual Studio (sans avoir besoin de modifier les paramètres du projet pour inclure cette option /console).

Techniquement, le Environment.UserInteractive vérifie si le drapeau WSF_VISIBLE est défini pour la station de travail actuelle, mais y a-t-il une autre raison pour laquelle il pourrait retourner false, en dehors d'être exécuté en tant que service (non interactif) ?

0 votes

Génial! J'ai précédemment utilisé une méthode "if #debug" pour démarrer l'application en mode débogage, sinon en tant que service. Cela fait que l'application ne peut pas être exécutée en tant que service si vous voulez la déboguer, mais votre solution résout ce problème et permet de l'exécuter dans les quatre combinaisons possibles de service/application et de release/débogage.

31 votes

Si vous ne souhaitez pas que le programme s'exécute lorsqu'il est double-cliqué (les utilisateurs pourraient être confus et lancer plusieurs instances, etc.), vous pouvez utiliser System.Diagnostics.Debugger.IsAttached au lieu de Environment.UserInteractive.

5 votes

mais y a-t-il une autre raison pour laquelle cela renverrait false, à part s'il est exécuté en tant que service (non interactif) ? Je peux en penser une : une tâche planifiée qui ne se connecte pas à une console.

134voto

Anders Abel Points 36203

Lorsque j'ai mis en place un nouveau projet de service il y a quelques semaines, j'ai trouvé ce message. Bien qu'il y ait de nombreuses suggestions géniales, je n'ai toujours pas trouvé la solution que je voulais : la possibilité d'appeler les méthodes OnStart et OnStop des classes de service sans aucune modification des classes de service.

La solution que j'ai trouvée utilise Environment.Interactive pour sélectionner le mode d'exécution, comme suggéré par d'autres réponses à ce message.

static void Main()
{
    ServiceBase[] servicesToRun;
    servicesToRun = new ServiceBase[] 
    {
        new MyService()
    };
    if (Environment.UserInteractive)
    {
        RunInteractive(servicesToRun);
    }
    else
    {
        ServiceBase.Run(servicesToRun);
    }
}

L'assistant RunInteractive utilise la réflexion pour appeler les méthodes protégées OnStart et OnStop :

static void RunInteractive(ServiceBase[] servicesToRun)
{
    Console.WriteLine("Services fonctionnant en mode interactif.");
    Console.WriteLine();

    MethodInfo onStartMethod = typeof(ServiceBase).GetMethod("OnStart", 
        BindingFlags.Instance | BindingFlags.NonPublic);
    foreach (ServiceBase service in servicesToRun)
    {
        Console.Write("Démarrage de {0}...", service.ServiceName);
        onStartMethod.Invoke(service, new object[] { new string[] { } });
        Console.Write("Démarré");
    }

    Console.WriteLine();
    Console.WriteLine();
    Console.WriteLine(
        "Appuyez sur une touche pour arrêter les services et terminer le processus...");
    Console.ReadKey();
    Console.WriteLine();

    MethodInfo onStopMethod = typeof(ServiceBase).GetMethod("OnStop", 
        BindingFlags.Instance | BindingFlags.NonPublic);
    foreach (ServiceBase service in servicesToRun)
    {
        Console.Write("Arrêt de {0}...", service.ServiceName);
        onStopMethod.Invoke(service, null);
        Console.WriteLine("Arrêté");
    }

    Console.WriteLine("Tous les services ont été arrêtés.");
    // Garder la console active pendant une seconde pour permettre à l'utilisateur de voir le message.
    Thread.Sleep(1000);
}

C'est tout le code requis, mais j'ai aussi écrit un tutoriel avec des explications.

0 votes

Cela fait une bonne méthode d'extension pour ServiceBase[]. J'ai de multiples services dans ma solution donc au lieu d'avoir une classe de base commune pour Program.cs, j'appelle juste servicesToRun.RunInteractive(args). Belle solution @Anders!

4 votes

Grande solution en effet. J'ai créé une extension simple pour ServiceBase[] comme David l'a suggéré, ce qui permet d'exécuter des services en une seule ligne de code : pastebin.com/F0fhhG2R

0 votes

@Funbit C'est vraiment génial - cela vous dérangerait-il si je le publiais sur mon blog comme une mise à jour du guide ?

55voto

Matt Points 3445

Parfois, il est important d'analyser ce qui se passe pendant le démarrage du service. S'attacher au processus ne vous aide pas ici, car vous n'êtes pas assez rapide pour attacher le débogueur pendant que le service démarre.


La réponse courte est que j'utilise les 4 lignes de code suivantes pour faire ceci :

#if DEBUG
    base.RequestAdditionalTime(600000); // délai de 10 minutes
    Debugger.Launch(); // lancer et attacher le débogueur
#endif

Cela définit un délai plus long pour le démarrage du service, notez que cela permet simplement au service de prendre plus de temps pour démarrer (il n'attend pas réellement, mais il donne au service un délai de grâce avant d'être considéré comme non réactif par le système).


Ces lignes sont insérées dans la méthode OnStart du service comme suit :

protected override void OnStart(string[] args)
{
    #if DEBUG
       base.RequestAdditionalTime(600000); // délai de 10 minutes pour le démarrage
       Debugger.Launch(); // lancer et attacher le débogueur
    #endif
    MyInitOnstart(); // mon code d'initialisation individuel pour le service
    // permettre à la classe de base d'effectuer tout le travail nécessaire
    base.OnStart(args);
}

Pour ceux qui ne l'ont jamais fait auparavant, j'ai inclus des indications détaillées ci-dessous, car vous pouvez facilement rester bloqué. Les indications suivantes se réfèrent à Windows 7x64 et à Visual Studio 2010 Team Edition, mais elles devraient également être valables pour d'autres environnements (plus récents).


Important : Déployez le service en mode "manuel" (en utilisant soit l'utilitaire InstallUtil depuis l'invite de commande de VS, soit en exécutant un projet d'installation de service que vous avez préparé). Ouvrez Visual Studio avant de démarrer le service et chargez la solution contenant le code source du service - configurez des points d'arrêt supplémentaires selon vos besoins dans Visual Studio - puis démarrez le service via le Panneau de contrôle des services.

En raison du code Debugger.Launch, une boîte de dialogue "Une exception non gérée du framework .NET Framework a eu lieu dans Nomservice.exe." s'affiche. Cliquez sur <a href="https://i.stack.imgur.com/4j4ua.jpg" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/4j4ua.jpg" alt="Elevate"></a> Oui, déboguer <em>Nomservice.exe</em> comme indiqué dans la capture d'écran :
FrameworkException


Ensuite, l'UAC de Windows peut vous demander d'entrer des informations d'identification administratives. Entrez-les et continuez avec Oui :

UACPrompt

Après cela, la bien connue fenêtre Visual Studio Just-In-Time Debugger apparaît. Elle vous demande si vous voulez déboguer en utilisant le débogueur sélectionné. Avant de cliquer sur Oui, sélectionnez que vous ne souhaitez pas ouvrir une nouvelle instance (2e option) - une nouvelle instance ne serait pas utile ici, car le code source ne serait pas affiché. Vous sélectionnez donc l'instance de Visual Studio que vous avez ouverte précédemment :
VSDebuggerPrompt

Après avoir cliqué sur Oui, au bout d'un moment Visual Studio affichera la flèche jaune exactement à la ligne où se trouve l'instruction Debugger.Launch et vous pourrez déboguer votre code (méthode MyInitOnStart, qui contient votre initialisation). VSDebuggerBreakpoint

Appuyer sur F5 continue l'exécution immédiatement, jusqu'au prochain point d'arrêt que vous avez préparé.

Astuce : Pour maintenir le service en cours d'exécution, sélectionnez Déboguer -> Détacher tout. Cela vous permet de faire fonctionner un client communiquant avec le service après son démarrage correct et après avoir terminé le débogage du code de démarrage. Si vous appuyez sur Maj+F5 (arrêter le débogage), cela mettra fin au service. Au lieu de faire cela, vous devriez utiliser le Panneau de contrôle des services pour l'arrêter.



Remarquez que

  • Si vous créez une version Release, alors le code de débogage est automatiquement supprimé et le service fonctionne normalement.

  • J'utilise Debugger.Launch(), qui démarre et attache un débogueur. J'ai également testé Debugger.Break(), qui n'a pas fonctionné, car aucun débogueur n'est attaché au démarrage du service (provoquant l'erreur "1067 : Le processus s'est terminé de manière inattendue").

  • RequestAdditionalTime définit un délai plus long pour le démarrage du service (il ne retarde pas le code lui-même, mais continuera immédiatement avec l'instruction Debugger.Launch). Sinon, le délai par défaut pour démarrer le service est trop court et le démarrage du service échoue si vous n'appelez pas base.Onstart(args) assez rapidement depuis le débogueur. Pratiquement, un délai de 10 minutes évite que vous voyiez le message "le service n'a pas répondu..." immédiatement après le démarrage du débogueur.

  • Une fois que vous avez pris l'habitude, cette méthode est très facile car elle vous demande simplement d'ajouter 4 lignes à un code de service existant, vous permettant de rapidement reprendre le contrôle et déboguer.

1 votes

Par curiosité, savez-vous s'il y a une limite de temps pour l'interaction de l'utilisateur avec le message de mise en marche Debugger.Launch() ?

1 votes

Comme décrit, base.RequestAdditionalTime(600000) empêchera le contrôle du service de terminer le service pendant 10 minutes s'il n'appelle pas base.OnStart(args) dans ce laps de temps). En dehors de cela, je me souviens que UAC abandonnera également si vous n'entrez pas les informations d'administration après un certain temps (je ne sais pas exactement combien de secondes, mais je pense que vous devez les entrer dans un délai d'une minute, sinon UAC abandonnera), ce qui mettra fin à la session de débogage.

2 votes

J'ai trouvé que c'était la meilleure méthode pour déboguer les messages CustomCommand. +1.

40voto

pb. Points 4609

Ce que je fais habituellement est d'encapsuler la logique du service dans une classe séparée et de la démarrer à partir d'une classe 'runner'. Cette classe runner peut être le service réel ou simplement une application console. Donc votre solution a (au moins) 3 projets :

/ConsoleRunner
   /....
/ServiceRunner
   /....
/ApplicationLogic
   /....

1 votes

J'avais l'habitude d'utiliser cette approche aussi, mais je pense qu'une combinaison de cela et de la réponse ci-dessus fonctionne à merveille.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X