Una forma más sencilla de depurar un servicio de Windows
¿Existe una manera más fácil de recorrer el código que iniciar el servicio a través del Administrador de control de servicios de Windows y luego adjuntar el depurador al hilo? Es un poco engorroso y me pregunto si existe un enfoque más sencillo.
Si quiero depurar rápidamente el servicio, simplemente introduzco un Debugger.Break()
allí. Cuando llegue a esa línea, me llevará de regreso a VS. No olvides eliminar esa línea cuando hayas terminado.
ACTUALIZACIÓN: Como alternativa a #if DEBUG
los pragmas, también puede utilizar Conditional("DEBUG_SERVICE")
atributos.
[Conditional("DEBUG_SERVICE")]
private static void DebugMode()
{
Debugger.Break();
}
En tu OnStart
, simplemente llama a este método:
public override void OnStart()
{
DebugMode();
/* ... do the rest */
}
Allí, el código solo se habilitará durante las compilaciones de depuración. Mientras lo hace, puede resultar útil crear una configuración de compilación independiente para la depuración del servicio.
También creo que tener una "versión" separada para la ejecución normal y como servicio es el camino a seguir, pero ¿es realmente necesario dedicar un interruptor de línea de comando separado para ese propósito?
¿No podrías simplemente hacer:
public static int Main(string[] args)
{
if (!Environment.UserInteractive)
{
// Startup as service.
}
else
{
// Startup as application
}
}
Eso tendría el "beneficio" de que puede iniciar su aplicación haciendo doble clic (OK, si realmente lo necesita) y que puede simplemente presionar F5en Visual Studio (sin la necesidad de modificar la configuración del proyecto para incluir esa /console
opción).
Técnicamente, Environment.UserInteractive
comprueba si el WSF_VISIBLE
indicador está configurado para la estación de ventana actual, pero ¿hay alguna otra razón por la que regresaría false
, además de ejecutarse como un servicio (no interactivo)?
Cuando configuré un nuevo proyecto de servicio hace unas semanas encontré esta publicación. Si bien hay muchas sugerencias excelentes, todavía no encontré la solución que quería: la posibilidad de llamar a las clases de servicio OnStart
y OnStop
a los métodos sin ninguna modificación de las clases de servicio.
La solución que se me ocurrió utiliza el Environment.Interactive
modo de ejecución seleccionado, como lo sugieren otras respuestas a esta publicación.
static void Main()
{
ServiceBase[] servicesToRun;
servicesToRun = new ServiceBase[]
{
new MyService()
};
if (Environment.UserInteractive)
{
RunInteractive(servicesToRun);
}
else
{
ServiceBase.Run(servicesToRun);
}
}
El RunInteractive
ayudante usa la reflexión para llamar a los métodos protegidos OnStart
y :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);
}
Este es todo el código requerido, pero también escribí un tutorial con explicaciones.
En ocasiones es importante analizar qué sucede durante la puesta en marcha del servicio. Adjuntar al proceso no ayuda aquí, porque no es lo suficientemente rápido para adjuntar el depurador mientras se inicia el servicio.
La respuesta corta es que estoy usando las siguientes 4 líneas de código para hacer esto:
#if DEBUG
base.RequestAdditionalTime(600000); // 600*1000ms = 10 minutes timeout
Debugger.Launch(); // launch and attach debugger
#endif
Esto establece un tiempo de espera más largo para el inicio del servicio, tenga en cuenta que solo permite que el servicio tarde más tiempo en iniciarse (en realidad no está esperando, pero le está dando al servicio un tiempo de gracia antes de que el sistema considere que no responde) .
Estos se insertan en el OnStart
método del servicio de la siguiente manera:
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);
}
Para aquellos que no lo han hecho antes, he incluido sugerencias detalladas a continuación , porque es fácil que te quedes atascado. Las siguientes sugerencias se refieren a Windows 7x64 y Visual Studio 2010 Team Edition , pero también deberían ser válidas para otros entornos (más nuevos).
Importante: Implemente el servicio en modo "manual" (usando la InstallUtil
utilidad desde el símbolo del sistema VS o ejecutando un proyecto de instalación de servicio que haya preparado). Abra Visual Studio antes de iniciar el servicio y cargue la solución que contiene el código fuente del servicio (configure puntos de interrupción adicionales según los necesite en Visual Studio) y luego inicie el servicio a través del Panel de control del servicio.
Debido al Debugger.Launch
código, esto generará un cuadro de diálogo "Se produjo una excepción de Microsoft .NET Framework no controlada en Servicename.exe ". a aparecer. Haga clic como se muestra en la captura de pantalla: Yes, debug Servicename.exe
Luego, Windows UAC podría solicitarle que ingrese las credenciales de administrador. Ingreselos y proceda con Yes:
Después de eso, aparece la conocida ventana del depurador Just-In-Time de Visual Studio . Le pregunta si desea depurar utilizando el depurador seleccionado. Antes de hacer clic en Yes, seleccione que no desea abrir una nueva instancia (segunda opción); una nueva instancia no sería útil aquí porque el código fuente no se mostraría. Entonces, en su lugar, selecciona la instancia de Visual Studio que abrió anteriormente:
Después de haber hecho clic en Yes, después de un tiempo Visual Studio mostrará la flecha amarilla justo en la línea donde Debugger.Launch
está la declaración y podrá depurar su código (método MyInitOnStart
, que contiene su inicialización).
Al presionar F5continúa la ejecución inmediatamente, hasta que se alcance el siguiente punto de interrupción que haya preparado.
Sugerencia: Para mantener el servicio en ejecución, seleccione Depurar -> Desasociar todo . Esto le permite ejecutar un cliente que se comunica con el servicio después de que se haya iniciado correctamente y haya terminado de depurar el código de inicio. Si presiona Shift+F5 (detener la depuración), esto finalizará el servicio. En lugar de hacer esto, debería utilizar el Panel de control de servicio para detenerlo.
Tenga en cuenta que
Si crea una versión, el código de depuración se elimina automáticamente y el servicio se ejecuta normalmente.
Estoy usando
Debugger.Launch()
, que inicia y adjunta un depurador .Debugger.Break()
También lo probé y no funcionó porque todavía no hay ningún depurador adjunto al iniciar el servicio (lo que provoca el "Error 1067: el proceso finalizó inesperadamente" ).RequestAdditionalTime
establece un tiempo de espera más largo para el inicio del servicio (no retrasa el código en sí, pero continuará inmediatamente con laDebugger.Launch
declaración). De lo contrario, el tiempo de espera predeterminado para iniciar el servicio es demasiado corto y el inicio del servicio falla si no llamabase.Onstart(args)
lo suficientemente rápido desde el depurador. Prácticamente, un tiempo de espera de 10 minutos evita que veas el mensaje " el servicio no respondió..." inmediatamente después de iniciar el depurador.Una vez que se acostumbre, este método es muy fácil porque solo requiere que agregue 4 líneas a un código de servicio existente, lo que le permitirá obtener control y depurar rápidamente.
Lo que suelo hacer es encapsular la lógica del servicio en una clase separada y comenzar desde una clase 'corredor'. Esta clase de corredor puede ser el servicio real o simplemente una aplicación de consola. Entonces su solución tiene (al menos) 3 proyectos:
/ConsoleRunner
/....
/ServiceRunner
/....
/ApplicationLogic
/....