Una forma más sencilla de depurar un servicio de Windows

Resuelto Mats asked hace 16 años • 30 respuestas

¿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.

Mats avatar Sep 24 '08 15:09 Mats
Aceptado

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 DEBUGlos 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.

jop avatar Sep 24 '2008 08:09 jop

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 /consoleopción).

Técnicamente, Environment.UserInteractivecomprueba si el WSF_VISIBLEindicador 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)?

Christian.K avatar Sep 24 '2008 09:09 Christian.K

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 OnStarty OnStopa los métodos sin ninguna modificación de las clases de servicio.

La solución que se me ocurrió utiliza el Environment.Interactivemodo 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 RunInteractiveayudante usa la reflexión para llamar a los métodos protegidos OnStarty :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.

 avatar May 31 '2012 17:05

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 OnStartmé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 InstallUtilutilidad 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.Launchcó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:Elevate Yes, debug Servicename.exe
Excepción de marco


Luego, Windows UAC podría solicitarle que ingrese las credenciales de administrador. Ingreselos y proceda con Yes:

Aviso UAC

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:
VSDebuggerPrompt

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.Launchestá la declaración y podrá depurar su código (método MyInitOnStart, que contiene su inicialización). VSDebuggerPunto de interrupció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" ).

  • RequestAdditionalTimeestablece un tiempo de espera más largo para el inicio del servicio (no retrasa el código en sí, pero continuará inmediatamente con la Debugger.Launchdeclaración). De lo contrario, el tiempo de espera predeterminado para iniciar el servicio es demasiado corto y el inicio del servicio falla si no llama base.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.

Matt avatar Oct 08 '2012 14:10 Matt

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
   /....
Paul van Brenk avatar Sep 24 '2008 08:09 Paul van Brenk