wtorek, 30 października 2012

Debugowanie usług windows w Visual Studio

Jak wiadomo debuger wielkim przyjacielem programisty jest, ale nie zawsze istnieje możliwość odpalenia go od tak sobie wciskając F5. W przypadku usług (serwisów) windows otrzymamy komunikat:
---------------------------
Błąd uruchomienia usługi systemu Windows
---------------------------
Nie można uruchomić usługi z wiersza polecenia lub z debugera.
Usługa systemu Windows musi być najpierw zainstalowana (przy użyciu pliku installutil.exe),
a następnie uruchomiona za pomocą Eksploratora serwera, 
Narzędzi administracyjnych usług systemu Windows lub polecenia NET START.
---------------------------
OK   
---------------------------
Pozostaje pytanie co z tym fantem zrobić? Jak na razie spotkałem się z kilkoma różnymi sposobami, np:
Nie są one jednak wygodne ponieważ w każdym przypadku wymuszają chwilowe dodawanie kodu, kompilację warunkową lub parametry lini komend sterujące przebiegiem uruchomienia serwisu. Jaki jest więc złoty środek? Otóż istnieje możliwość sprawdzenia czy uruchomiony proces umożliwia użytkownikowi interakcję. Służy do tego właściwość Environment.UserInteractive. Dzięki niej wystarczy prosty warunek w punkcie wejścia do aplikacji aby rozdzielić przebieg raz a skutecznie. Czyli:
  • Zmieniamy rodzaj aplikacji na konsolową (wartość Output Type, zakładki Application, w Properties danego projektu) dzięki czemu zyskujemy wgląd w konsolę
  • Dodajemy kod umożliwiający odpalenie implementacji OnStart i OnStop przeciążonych po ServiceBase:
    public partial class Service1 : ServiceBase
    {
        public Service1()
        {
            InitializeComponent();
        }
    
        internal void Start(string[] args)
        {
            OnStart(args);
        }
    
        internal void Stop()
        {
            OnStop();
        }
    
        protected override void OnStart(string[] args)
        {
            //...
        }
    
        protected override void OnStop()
        {
            //...
        }
    }
    
  • Podmieniamy standardową metodę Main na:
    static void Main(string[] args)
    {
        Service1 svc = new Service1();
    
        if (Environment.UserInteractive)
        {
            Console.WriteLine("Starting...");
            svc.Start(args);
            Console.WriteLine("Started");
            Console.ReadLine();
            Console.WriteLine("Stopping...");
            svc.Stop();
            Console.WriteLine("Stopped");
        }
        else
        {
            ServiceBase[] ServicesToRun;
            ServicesToRun = new ServiceBase[] 
            { 
                svc
            };
            ServiceBase.Run(ServicesToRun);
        }
    }
    
W ten sposób, kiedy wciśniemy F5 uruchomiony zostanie debugger z całym jego dobrodziejstwem. Natomiast po zainstalowaniu i uruchomieniu serwisu z poziomu services.msc odpali się druga klasyczna dla usług nitka kodu. Wilk syty i owca cała ;)

PS. Równie dobrze, zamiast konsoli można przygotować aplikację okienkową, nie ma tutaj ograniczeń, idea pozostaje niezmieniona.

3 komentarze:

  1. WOW! You are genious !!! Tego mi bardzo brakowało przez co robiłem bardziej upierdliwe obejścia

    OdpowiedzUsuń
  2. Moim zdaniem ZDECYDOWANIE lepsze jest System.Diagnostics.Debugger.IsAttached. Nie trzeba przy tym rozwiązaniu zmieniać typu projektu i tym podobnych sztuczek. Wystarczy np:

    if (!System.Diagnostics.Debugger.IsAttached)
    {
    ServiceBase[] ServicesToRun = new ServiceBase[]
    {
    new TestService()
    };
    ServiceBase.Run(ServicesToRun);
    }
    else
    {
    new QueueProcessor().Process();
    }


    Nie bardzo rozumiem dlaczego wspominasz o chwilowym kodzie, dziwnej kompilacji czy parametrach z linii komend..

    OdpowiedzUsuń
  3. @Łukasz
    No widzisz... Co osoba to inne rozwiązanie.
    Dodatkowy kod przy IsAttached wiązał się ze sposobem podpinania debugera. Tzn przygotowaniem pętli:

    while(!Debugger.IsAttached) Thread.Sleep(500);

    I podpięciu się do już uruchomionego procesu w systemie. Stąd taki kod wymagał usunięcia lub dodatkowych parametrów przed uruchomieniem produkcyjnym. Sposób mało elegancki, ale widziałem już takie pomysły.

    PS. Tak na prawdę w tym kontekście IsAttached i UserInteractive można traktować jako synonimy. Konsola nie jest niezbędna.

    OdpowiedzUsuń