Existe-t-il un moyen plus simple de parcourir le code que de démarrer le service via le Gestionnaire de contrôle des services Windows, puis d'attacher le débogueur au thread? C'est un peu lourd et je me demande s'il existe une approche plus simple.
Existe-t-il un moyen plus simple de parcourir le code que de démarrer le service via le Gestionnaire de contrôle des services Windows, puis d'attacher le débogueur au thread? C'est un peu lourd et je me demande s'il existe une approche plus simple.
Réponses:
Si je veux déboguer rapidement le service, je tombe juste Debugger.Break()
dedans. Lorsque cette ligne est atteinte, elle me ramènera à VS. N'oubliez pas de supprimer cette ligne lorsque vous avez terminé.
MISE À JOUR: Comme alternative aux #if DEBUG
pragmas, vous pouvez également utiliser l' Conditional("DEBUG_SERVICE")
attribut.
[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 */
}
Là, le code ne sera activé que lors des builds de débogage. Pendant que vous y êtes, il peut être utile de créer une configuration de build distincte pour le débogage du service.
Je pense également que le fait d'avoir une "version" distincte pour une exécution normale et en tant que service est le chemin à parcourir, mais est-il vraiment nécessaire de dédier un commutateur de ligne de commande séparé à cet effet?
Ne pourriez-vous pas simplement faire:
public static int Main(string[] args)
{
if (!Environment.UserInteractive)
{
// Startup as service.
}
else
{
// Startup as 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 cliquer F5dans Visual Studio (sans avoir besoin de modifier les paramètres du projet pour inclure cette /console
option).
Techniquement, il Environment.UserInteractive
vérifie si le WSF_VISIBLE
drapeau est défini pour la station Windows actuelle, mais y a-t-il une autre raison pour laquelle il reviendrait false
, à part d'être exécuté en tant que service (non interactif)?
System.Diagnostics.Debugger.IsAttached
place de Environment.UserInteractive
.
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 beaucoup de bonnes suggestions, je n'ai toujours pas trouvé la solution que je voulais: la possibilité d'appeler les classes de service OnStart
et les OnStop
méthodes sans aucune modification des classes de service.
La solution que j'ai trouvée utilise le Environment.Interactive
mode d'exécution sélectionné, comme suggéré par d'autres réponses à ce post.
static void Main()
{
ServiceBase[] servicesToRun;
servicesToRun = new ServiceBase[]
{
new MyService()
};
if (Environment.UserInteractive)
{
RunInteractive(servicesToRun);
}
else
{
ServiceBase.Run(servicesToRun);
}
}
L' RunInteractive
assistant utilise la réflexion pour appeler les méthodes protégées OnStart
et OnStop
:
static void RunInteractive(ServiceBase[] servicesToRun)
{
Console.WriteLine("Services running in interactive mode.");
Console.WriteLine();
MethodInfo onStartMethod = typeof(ServiceBase).GetMethod("OnStart",
BindingFlags.Instance | BindingFlags.NonPublic);
foreach (ServiceBase service in servicesToRun)
{
Console.Write("Starting {0}...", service.ServiceName);
onStartMethod.Invoke(service, new object[] { new string[] { } });
Console.Write("Started");
}
Console.WriteLine();
Console.WriteLine();
Console.WriteLine(
"Press any key to stop the services and end the process...");
Console.ReadKey();
Console.WriteLine();
MethodInfo onStopMethod = typeof(ServiceBase).GetMethod("OnStop",
BindingFlags.Instance | BindingFlags.NonPublic);
foreach (ServiceBase service in servicesToRun)
{
Console.Write("Stopping {0}...", service.ServiceName);
onStopMethod.Invoke(service, null);
Console.WriteLine("Stopped");
}
Console.WriteLine("All services stopped.");
// Keep the console alive for a second to allow the user to see the message.
Thread.Sleep(1000);
}
C'est tout le code requis, mais j'ai également écrit une procédure pas à pas avec des explications.
walk through
) est de vous assurer d'entrer dans les propriétés du projet et de changer le type de sortie Console Application
avant d'essayer de compiler et d'exécuter. Trouvez-le sur Project Properties -> Application -> Output type -> Console Application
. De plus, pour que cela fonctionne correctement pour moi, j'ai fini par devoir exécuter l'application à l'aide de la start
commande. Ex: C:\"my app name.exe" -service
ne fonctionnerait pas pour moi. Au lieu de cela, j'ai utiliséC:\start /wait "" "my app name.exe" -service
Parfois, il est important d'analyser ce qui se passe lors du démarrage du service. La connexion au processus n'aide pas ici, car vous n'êtes pas assez rapide pour connecter le débogueur pendant le démarrage du service.
La réponse courte est, j'utilise les 4 lignes de code suivantes pour ce faire:
#if DEBUG
base.RequestAdditionalTime(600000); // 600*1000ms = 10 minutes timeout
Debugger.Launch(); // launch and attach debugger
#endif
Ceux-ci sont insérés dans la OnStart
méthode du service comme suit:
protected override void OnStart(string[] args)
{
#if DEBUG
base.RequestAdditionalTime(600000); // 10 minutes timeout for startup
Debugger.Launch(); // launch and attach debugger
#endif
MyInitOnstart(); // my individual initialization code for the service
// allow the base class to perform any work it needs to do
base.OnStart(args);
}
Pour ceux qui ne l'ont pas encore fait, j'ai inclus des conseils détaillés ci - dessous , car vous pouvez facilement rester coincé. Les conseils suivants se réfèrent à Windows 7x64 et Visual Studio 2010 Team Edition , mais devraient également être valables pour d'autres environnements.
Important: déployez le service en mode "manuel" (à l'aide de l' InstallUtil
utilitaire à partir de l'invite de commande VS ou exécutez un projet d'installation du 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 configuration du service.
En raison du Debugger.Launch
code, cela provoquera une boîte de dialogue "Une exception Microsoft .NET Framework non gérée s'est produite dans Servicename.exe ." apparaître. Cliquez comme indiqué sur la capture d'écran: Yes, debug Servicename.exe
Ensuite, surtout dans Windows 7 UAC peut vous inviter à entrer les informations d'identification d'administrateur. Entrez-les et procédez avec Yes:
Après cela, la fenêtre bien connue du débogueur Visual Studio Just-In-Time apparaît. Il vous demande si vous souhaitez déboguer à l'aide du débogueur supprimé. Avant de cliquer Yes, sélectionnez que vous ne voulez 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 à la place l'instance Visual Studio que vous avez ouverte:
Après avoir cliqué Yes, après un certain temps, Visual Studio affiche la flèche jaune à droite de la ligne où se trouve l' Debugger.Launch
instruction et vous pouvez déboguer votre code (méthode MyInitOnStart
, qui contient votre initialisation).
Appuyez sur pour F5continuer l'exécution immédiatement, jusqu'à ce que le prochain point d'arrêt que vous avez préparé soit atteint.
Astuce: pour que le service continue de fonctionner, sélectionnez Déboguer -> Détacher tout . Cela vous permet d'exécuter un client communiquant avec le service une fois qu'il a démarré correctement et que vous avez terminé de déboguer le code de démarrage. Si vous appuyez sur Shift+F5 (arrêtez le débogage), cela mettra fin au service. Au lieu de cela, vous devez utiliser le Panneau de configuration du service pour l'arrêter.
Notez que
Si vous créez une version, le code de débogage est automatiquement supprimé et le service s'exécute normalement.
J'utilise Debugger.Launch()
, qui démarre et attache un débogueur . J'ai également testé Debugger.Break()
, ce qui n'a pas fonctionné , car il n'y a pas encore de débogueur attaché au démarrage du service (provoquant "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 poursuivra immédiatement l' Debugger.Launch
instruction). 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 à partir du débogueur. Pratiquement, un délai de 10 minutes évite que vous voyez le message " le service n'a pas répondu ..." immédiatement après le démarrage du débogueur.
Une fois que vous vous y êtes habitué, cette méthode est très facile car elle vous oblige simplement à ajouter 4 lignes à un code de service existant, vous permettant ainsi de prendre rapidement le contrôle et de déboguer.
base.RequestAdditionalTime(600000)
empêchera le contrôle de service de mettre fin au service pendant 10 minutes s'il n'appelle pas base.OnStart(args)
dans ce délai). En dehors de cela, je me souviens que l'UAC abandonnera également si vous n'entrez pas les informations d'identification d'administrateur après un certain temps (je ne sais pas combien de secondes exactement, mais je pense que vous devez l'entrer dans une minute, sinon UAC abandonne) , qui mettra fin à la session de débogage.
Ce que je fais habituellement, c'est encapsuler la logique du service dans une classe distincte et commencer celle-ci à partir d'une classe 'runner'. Cette classe de runner peut être le service réel ou simplement une application console. Votre solution a donc (au moins) 3 projets:
/ConsoleRunner
/....
/ServiceRunner
/....
/ApplicationLogic
/....
Cette vidéo YouTube de Fabio Scopel explique très bien comment déboguer un service Windows ... la méthode actuelle de le faire commence à 04h45 dans la vidéo ...
Voici le code expliqué dans la vidéo ... dans votre fichier Program.cs, ajoutez les trucs pour la section Debug ...
namespace YourNamespace
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
static void Main()
{
#if DEBUG
Service1 myService = new Service1();
myService.OnDebug();
System.Threading.Thread.Sleep(System.Threading.Timeout.Infinite);
#else
ServiceBase[] ServicesToRun;
ServicesToRun = new ServiceBase[]
{
new Service1()
};
ServiceBase.Run(ServicesToRun);
#endif
}
}
}
Dans votre fichier Service1.cs, ajoutez la méthode OnDebug () ...
public Service1()
{
InitializeComponent();
}
public void OnDebug()
{
OnStart(null);
}
protected override void OnStart(string[] args)
{
// your code to do something
}
protected override void OnStop()
{
}
Comment ça fonctionne
Fondamentalement, vous devez créer un public void OnDebug()
qui appelle le OnStart(string[] args)
car il est protégé et n'est pas accessible à l'extérieur. Le void Main()
programme est ajouté avec un #if
préprocesseur avec #DEBUG
.
Visual Studio définit DEBUG
si le projet est compilé en mode débogage, ce qui permettra à la section de débogage (ci-dessous) de s'exécuter lorsque la condition est vraie
Service1 myService = new Service1();
myService.OnDebug();
System.Threading.Thread.Sleep(System.Threading.Timeout.Infinite);
Et il fonctionnera comme une application console, une fois que les choses vont bien, vous pouvez changer le mode Release
et la else
section régulière déclenchera la logique
METTRE À JOUR
Cette approche est de loin la plus simple:
http://www.codeproject.com/KB/dotnet/DebugWinServices.aspx
Je laisse ma réponse originale ci-dessous pour la postérité.
Mes services ont tendance à avoir une classe qui encapsule une minuterie car je veux que le service vérifie à intervalles réguliers s'il y a du travail à faire.
Nous renouvelons la classe et appelons StartEventLoop () pendant le démarrage du service. (Cette classe peut également être facilement utilisée à partir d'une application console.)
L'effet secondaire agréable de cette conception est que les arguments avec lesquels vous configurez le minuteur peuvent être utilisés pour avoir un délai avant que le service ne commence réellement à fonctionner, de sorte que vous ayez le temps d'attacher un débogueur manuellement.
ps Comment attacher le débogueur manuellement à un processus en cours ...?
using System;
using System.Threading;
using System.Configuration;
public class ServiceEventHandler
{
Timer _timer;
public ServiceEventHandler()
{
// get configuration etc.
_timer = new Timer(
new TimerCallback(EventTimerCallback)
, null
, Timeout.Infinite
, Timeout.Infinite);
}
private void EventTimerCallback(object state)
{
// do something
}
public void StartEventLoop()
{
// wait a minute, then run every 30 minutes
_timer.Change(TimeSpan.Parse("00:01:00"), TimeSpan.Parse("00:30:00");
}
}
J'ai aussi l'habitude de faire ce qui suit (déjà mentionné dans les réponses précédentes mais avec les drapeaux du compilateur conditionnel [#if] pour éviter qu'il ne se déclenche dans une version Release).
J'ai arrêté de le faire de cette façon parce que parfois nous oublions de compiler Release et d'avoir une pause de débogage dans une application fonctionnant sur une démo client (embarrassante!).
#if DEBUG
if (!System.Diagnostics.Debugger.IsAttached)
{
System.Diagnostics.Debugger.Break();
}
#endif
// do something
prend plus de 30 minutes?
static void Main()
{
#if DEBUG
// Run as interactive exe in debug mode to allow easy
// debugging.
var service = new MyService();
service.OnStart(null);
// Sleep the main thread indefinitely while the service code
// runs in .OnStart
Thread.Sleep(Timeout.Infinite);
#else
// Run normally as service in release mode.
ServiceBase[] ServicesToRun;
ServicesToRun = new ServiceBase[]{ new MyService() };
ServiceBase.Run(ServicesToRun);
#endif
}
OnStart
est protected
et vous ne pouvez pas modifier le niveau d'accès :(
Ce que j'avais l'habitude de faire était d'avoir un commutateur de ligne de commande qui démarrerait le programme en tant que service ou en tant qu'application régulière. Ensuite, dans mon IDE, je réglais le commutateur pour pouvoir parcourir mon code.
Avec certaines langues, vous pouvez réellement détecter s'il s'exécute dans un IDE et effectuer ce changement automatiquement.
Quelle langue utilisez-vous?
Utilisez le TopShelf bibliothèque .
Créez une application console puis configurez la configuration dans votre Main
class Program
{
static void Main(string[] args)
{
HostFactory.Run(x =>
{
// setup service start and stop.
x.Service<Controller>(s =>
{
s.ConstructUsing(name => new Controller());
s.WhenStarted(controller => controller.Start());
s.WhenStopped(controller => controller.Stop());
});
// setup recovery here
x.EnableServiceRecovery(rc =>
{
rc.RestartService(delayInMinutes: 0);
rc.SetResetPeriod(days: 0);
});
x.RunAsLocalSystem();
});
}
}
public class Controller
{
public void Start()
{
}
public void Stop()
{
}
}
Pour déboguer votre service, appuyez simplement sur F5 dans Visual Studio.
Pour installer le service, tapez cmd "install console.exe"
Vous pouvez ensuite démarrer et arrêter le service dans le gestionnaire de services Windows.
Je pense que cela dépend du système d'exploitation que vous utilisez, Vista est beaucoup plus difficile à attacher aux services, en raison de la séparation entre les sessions.
Les deux options que j'ai utilisées dans le passé sont:
J'espère que cela t'aides.
J'aime pouvoir déboguer tous les aspects de mon service, y compris toute initialisation dans OnStart (), tout en l'exécutant avec un comportement de service complet dans le cadre du SCM ... pas de mode "console" ou "application".
Je le fais en créant un deuxième service, dans le même projet, à utiliser pour le débogage. Le service de débogage, lorsqu'il est démarré comme d'habitude (c'est-à-dire dans le plugin MMC de services), crée le processus hôte de service. Cela vous donne un processus auquel attacher le débogueur même si vous n'avez pas encore démarré votre véritable service. Après avoir attaché le débogueur au processus, démarrez votre véritable service et vous pouvez le pénétrer n'importe où dans le cycle de vie du service, y compris OnStart ().
Parce qu'il nécessite une intrusion de code très minimale, le service de débogage peut facilement être inclus dans votre projet de configuration de service et est facilement supprimé de votre version de production en commentant une seule ligne de code et en supprimant un seul programme d'installation de projet.
Détails:
1) En supposant que vous implémentez MyService
, créez également MyServiceDebug
. Ajoutez les deux au ServiceBase
tableau Program.cs
comme ceci:
/// <summary>
/// The main entry point for the application.
/// </summary>
static void Main()
{
ServiceBase[] ServicesToRun;
ServicesToRun = new ServiceBase[]
{
new MyService(),
new MyServiceDebug()
};
ServiceBase.Run(ServicesToRun);
}
2) Ajoutez le service réel ET le service de débogage au programme d'installation du projet pour le projet de service:
Les deux services (réel et débogage) sont inclus lorsque vous ajoutez la sortie du projet de service au projet d'installation du service. Après l'installation, les deux services apparaîtront dans le plugin MMC service.msc.
3) Démarrez le service de débogage dans MMC.
4) Dans Visual Studio, attachez le débogueur au processus démarré par le service de débogage.
5) Démarrez le vrai service et profitez du débogage.
Lorsque j'écris un service, je mets toute la logique du service dans un projet dll et crée deux "hôtes" qui appellent dans cette dll, l'un est un service Windows et l'autre est une application en ligne de commande.
J'utilise l'application de ligne de commande pour le débogage et attache le débogueur au service réel uniquement pour les bogues que je ne peux pas reproduire dans l'application de ligne de commande.
Si vous utilisez cette approche, souvenez-vous simplement que vous devez tester tout le code lors de l'exécution dans un service réel, tandis que l'outil de ligne de commande est une bonne aide au débogage, c'est un environnement différent et il ne se comporte pas exactement comme un vrai service.
Lors du développement et du débogage d'un service Windows, je l'exécute généralement en tant qu'application console en ajoutant un paramètre de démarrage / console et en le vérifiant. Rend la vie beaucoup plus facile.
static void Main(string[] args) {
if (Console.In != StreamReader.Null) {
if (args.Length > 0 && args[0] == "/console") {
// Start your service work.
}
}
}
Pour déboguer les services Windows, je combine GFlags et un fichier .reg créé par regedit.
Ou enregistrez les extraits de code suivants et remplacez servicename.exe par le nom de l'exécutable souhaité.
debugon.reg:
Éditeur de registre Windows version 5.00 [HKEY_LOCAL_MACHINE \ SOFTWARE \ Microsoft \ Windows NT \ CurrentVersion \ Image File Execution Options \ servicename.exe] "GlobalFlag" = "0x00000000" "Debugger" = "vsjitdebugger.exe"
debugoff.reg:
Éditeur de registre Windows version 5.00 [HKEY_LOCAL_MACHINE \ SOFTWARE \ Microsoft \ Windows NT \ CurrentVersion \ Image File Execution Options \ servicename.exe] "GlobalFlag" = "0x00000000"
Pour la programmation de petites choses de routine, j'ai fait une astuce très simple pour déboguer facilement mon service:
Au démarrage du service, je vérifie un paramètre de ligne de commande "/ debug". Si le service est appelé avec ce paramètre, je ne fais pas le démarrage habituel du service, mais plutôt démarre tous les écouteurs et affiche simplement une boîte de message "Débogage en cours, appuyez sur ok pour terminer".
Donc, si mon service est démarré de la manière habituelle, il démarrera en tant que service, s'il est démarré avec le paramètre / debug de la ligne de commande, il agira comme un programme normal.
Dans VS, je vais simplement ajouter / debug comme paramètre de débogage et démarrer directement le programme de service.
De cette façon, je peux facilement déboguer pour la plupart des petits problèmes. Bien sûr, certaines choses devront encore être déboguées en tant que service, mais pour 99%, cela suffit.
J'utilise une variante de la réponse de JOP. À l'aide des paramètres de ligne de commande, vous pouvez définir le mode de débogage dans l'EDI avec les propriétés du projet ou via le gestionnaire de services Windows.
protected override void OnStart(string[] args)
{
if (args.Contains<string>("DEBUG_SERVICE"))
{
Debugger.Break();
}
...
}
Pour le dépannage sur le programme de service Windows existant, utilisez 'Debugger.Break ()' comme d'autres gars l'ont suggéré.
Pour le nouveau programme Windows Service, je suggère d'utiliser la méthode de James Michael Hare http://geekswithblogs.net/BlackRabbitCoder/archive/2011/03/01/c-toolbox-debug-able-self-installable-windows-service-template- redux.aspx
Placez votre déjeuner de débogueur n'importe où et attachez Visualstudio au démarrage
#if DEBUG
Debugger.Launch();
#endif
Vous devez également démarrer VS en tant qu'Administrateur et vous devez autoriser qu'un processus puisse être débogué automatiquement par un utilisateur différent (comme expliqué ici ):
reg add "HKCR\AppID{E62A7A31-6025-408E-87F6-81AEB0DC9347}" /v AppIDFlags /t REG_DWORD /d 8 /f
Utilisez le projet de modèle de service Windows C # pour créer une nouvelle application de service https://github.com/HarpyWar/windows-service-template
Le mode console / service est détecté automatiquement, l'installateur / désinstallateur automatique de votre service et plusieurs des fonctionnalités les plus utilisées sont inclus.
Voici la méthode simple que j'ai utilisée pour tester le service, sans aucune méthode supplémentaire de «débogage» et avec des tests unitaires VS intégrés.
[TestMethod]
public void TestMyService()
{
MyService fs = new MyService();
var OnStart = fs.GetType().BaseType.GetMethod("OnStart", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static);
OnStart.Invoke(fs, new object[] { null });
}
// As an extension method
public static void Start(this ServiceBase service, List<string> parameters)
{
string[] par = parameters == null ? null : parameters.ToArray();
var OnStart = service.GetType().GetMethod("OnStart", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static);
OnStart.Invoke(service, new object[] { par });
}
static class Program
{
static void Main()
{
#if DEBUG
// TODO: Add code to start application here
// //If the mode is in debugging
// //create a new service instance
Service1 myService = new Service1();
// //call the start method - this will start the Timer.
myService.Start();
// //Set the Thread to sleep
Thread.Sleep(300000);
// //Call the Stop method-this will stop the Timer.
myService.Stop();
#else
ServiceBase[] ServicesToRun;
ServicesToRun = new ServiceBase[]
{
new Service1()
};
ServiceBase.Run(ServicesToRun);
#endif
}
}
Vous avez deux options pour effectuer le débogage.
Veuillez consulter CET article de blog que j'ai créé pour le sujet.
Il suffit de coller
Debugger.Break();
n'importe où dans votre code.
Par exemple ,
internal static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
private static void Main()
{
Debugger.Break();
ServiceBase[] ServicesToRun;
ServicesToRun = new ServiceBase[]
{
new Service1()
};
ServiceBase.Run(ServicesToRun);
}
}
Il frappera Debugger.Break();
lorsque vous exécuterez votre programme.
La meilleure option est d'utiliser le ' System.Diagnostics espace de noms ».
Insérez votre code dans le bloc if else pour le mode de débogage et le mode de libération comme indiqué ci-dessous pour basculer entre le mode de débogage et le mode de libération dans Visual Studio,
#if DEBUG // for debug mode
**Debugger.Launch();** //debugger will hit here
foreach (var job in JobFactory.GetJobs())
{
//do something
}
#else // for release mode
**Debugger.Launch();** //debugger will hit here
// write code here to do something in Release mode.
#endif