Более простой способ отладки службы Windows - PullRequest
313 голосов
/ 24 сентября 2008

Есть ли более простой способ пошагового выполнения кода, чем запуск службы через диспетчер управления службами Windows и последующее присоединение отладчика к потоку? Это довольно громоздко, и мне интересно, есть ли более простой подход.

Ответы [ 28 ]

266 голосов
/ 24 сентября 2008

Если я хочу быстро отладить службу, я просто добавляю туда Debugger.Break(). Когда эта линия будет достигнута, я вернусь к VS. Не забудьте удалить эту строку, когда закончите.

ОБНОВЛЕНИЕ: В качестве альтернативы #if DEBUG прагмам, вы также можете использовать атрибут Conditional("DEBUG_SERVICE").

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

На вашем OnStart просто вызовите этот метод:

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

Там код будет включен только во время отладочных сборок. Несмотря на это, может быть полезно создать отдельную конфигурацию сборки для отладки службы.

208 голосов
/ 24 сентября 2008

Я также думаю, что есть отдельная «версия» для нормального выполнения и в качестве службы, но стоит ли выделять для этой цели отдельный переключатель командной строки?

Не могли бы вы просто сделать:

public static int Main(string[] args)
{
  if (!Environment.UserInteractive)
  {
    // Startup as service.
  }
  else
  {
    // Startup as application
  }
}

Это принесло бы «выгоду», что вы можете просто запустить свое приложение с помощью двойного щелчка (ОК, если вам это действительно нужно), и что вы можете просто нажать F5 в Visual Studio (без необходимости измените настройки проекта, чтобы включить эту опцию /console.

Технически, Environment.UserInteractive проверяет, установлен ли флаг WSF_VISIBLE для текущей оконной станции, но есть ли другая причина, по которой он возвращает false, за исключением того, что он запускается как (неинтерактивная) служба

117 голосов
/ 31 мая 2012

Когда я создал новый сервисный проект несколько недель назад, я нашел этот пост. Хотя было много хороших предложений, я так и не нашел решение, которое хотел: возможность вызывать методы классов обслуживания OnStart и OnStop без каких-либо изменений классов обслуживания.

В предложенном мною решении используется Environment.Interactive выбранный режим работы, о чем свидетельствуют другие ответы на этот пост.

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

Помощник RunInteractive использует отражение для вызова защищенных OnStart и 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);
}

Это весь необходимый код, но я также написал Пошаговое руководство с пояснениями.

41 голосов
/ 08 октября 2012

Иногда важно проанализировать, что происходит во время запуска службы. Присоединение к процессу здесь не помогает, потому что вы недостаточно быстры, чтобы подключить отладчик во время запуска службы до.

Краткий ответ: я использую следующие 4 строки кода , чтобы сделать это:

#if DEBUG
    base.RequestAdditionalTime(600000); // 600*1000ms = 10 minutes timeout
    Debugger.Launch(); // launch and attach debugger
#endif

Они вставляются в метод OnStart службы следующим образом:

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);
}

Для тех, кто не делал этого раньше, я включил подробные советы ниже , потому что вы можете легко застрять. Следующие советы относятся к Windows 7x64 и Visual Studio 2010 Team Edition , но должны быть действительными и для других сред.


Важно: Развернуть службу в «ручном» режиме (используя либо утилиту InstallUtil из командной строки VS, либо запустить подготовленный вами проект установщика службы). Откройте Visual Studio до , затем запустите службу и загрузите решение, содержащее исходный код службы - установите дополнительные точки останова в соответствии с вашими требованиями в Visual Studio - затем запустите службу через Панель управления службами.

Из-за кода Debugger.Launch это вызовет диалоговое окно «Необработанное исключение Microsoft .NET Framework произошло в Servicename.exe ». появляться. Нажмите Elevate Да, отладку Servicename.exe , как показано на скриншоте:
FrameworkException

Впоследствии, особенно в Windows 7, UAC может попросить вас ввести учетные данные администратора. Введите их и введите Да :

UACPrompt

После этого появляется известное окно отладчика Visual Studio Just-In-Time . Он спросит вас, хотите ли вы отладить с помощью удаленного отладчика. Прежде чем нажать Да , выберите, что вы не хотите открывать новый экземпляр (2-й вариант) - новый экземпляр здесь не поможет, потому что исходный код не будет отображаться. Таким образом, вы выбираете экземпляр Visual Studio, который вы открыли ранее: VSDebuggerPrompt

После того, как вы нажали Да , , через некоторое время Visual Studio покажет желтую стрелку прямо в строке, где находится оператор Debugger.Launch, и вы сможете отлаживать свой код. (метод MyInitOnStart, который содержит вашу инициализацию). VSDebuggerBreakpoint

Нажатие F5 немедленно продолжает выполнение, до достижения следующей подготовленной вами точки останова.

Подсказка: Чтобы сохранить работу сервиса, выберите Отладка -> Отключить все . Это позволяет запустить клиент, обменивающийся данными со службой, после того, как он правильно запустился и вы закончили отладку кода запуска. Если вы нажмете Shift + F5 (прекратить отладку), это приведет к прекращению обслуживания. Вместо этого вы должны использовать Service Control Panel , чтобы остановить его.

Примечание , что

  • Если вы создаете Release, , тогда отладочный код автоматически удаляется и служба работает нормально.

  • Я использую Debugger.Launch(), который запускается и подключает отладчик . Я также проверил Debugger.Break(), который не работал , поскольку при запуске службы отладчик еще не подключен (что вызывает ошибку "1067: процесс неожиданно завершен. ").

  • RequestAdditionalTime устанавливает более длительное время ожидания для запуска службы (это , а не задерживает сам код, но немедленно продолжит работу с оператором Debugger.Launch). В противном случае тайм-аут по умолчанию для запуска службы слишком короткий, и запуск службы завершится неудачно, если вы не вызовите base.Onstart(args) достаточно быстро из отладчика. Практически, 10-минутный тайм-аут предотвращает появление сообщения « служба не отвечает ...» сразу после запуска отладчика.

  • Как только вы привыкнете к этому, этот метод очень прост, потому что вам просто нужно добавить 4 строки к существующему сервисному коду, что позволяет быстро получить контроль и отладку.

40 голосов
/ 24 сентября 2008

Обычно я инкапсулирую логику службы в отдельном классе и запускаю ее из класса «бегун». Этот класс бегуна может быть реальной службой или просто консольным приложением. Таким образом, ваше решение имеет (по крайней мере) 3 проекта:

/ConsoleRunner
   /....
/ServiceRunner
   /....
/ApplicationLogic
   /....
25 голосов
/ 11 сентября 2013

Это видео на YouTube от Фабио Скопеля объясняет, как довольно хорошо отлаживать службу Windows ... фактический способ сделать это начинается в 4:45 в видео ...

Вот код, описанный в видео ... в файле Program.cs, добавьте материал для раздела отладки ...

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

        }
    }
}

Добавьте в файл Service1.cs метод OnDebug () ...

    public Service1()
    {
        InitializeComponent();
    }

    public void OnDebug()
    {
        OnStart(null);
    }

    protected override void OnStart(string[] args)
    {
        // your code to do something
    }

    protected override void OnStop()
    {
    }

Как это работает

По сути, вам нужно создать public void OnDebug(), который вызывает OnStart(string[] args), поскольку он защищен и недоступен снаружи. Программа void Main() добавлена ​​с препроцессором #if с #DEBUG.

Visual Studio определяет DEBUG, если проект скомпилирован в режиме отладки. Это позволит разделу отладки (ниже) выполняться при выполнении условия

Service1 myService = new Service1();
myService.OnDebug();
System.Threading.Thread.Sleep(System.Threading.Timeout.Infinite);

И он будет работать так же, как консольное приложение, как только все пойдет хорошо, вы можете изменить режим Release, и обычный раздел else вызовет логику

14 голосов
/ 24 сентября 2008

UPDATE

Этот подход является самым простым:

http://www.codeproject.com/KB/dotnet/DebugWinServices.aspx

Я оставляю свой исходный ответ ниже для потомков.


В моих службах, как правило, есть класс, который инкапсулирует Timer, так как я хочу, чтобы служба регулярно проверяла, есть ли какая-либо работа для него.

Мы обновляем класс и вызываем StartEventLoop () во время запуска службы. (Этот класс также можно легко использовать из консольного приложения.)

Приятным побочным эффектом этой схемы является то, что аргументы, с помощью которых вы устанавливаете Таймер, могут использоваться для задержки до фактического начала работы службы, чтобы у вас было время подключить отладчик вручную.

p.s. Как вручную прикрепить отладчик к запущенному процессу ...?

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");
    }
}

Также я делал следующее (уже упоминалось в предыдущих ответах, но с флагами условного компилятора [#if], чтобы избежать его запуска в сборке Release).

Я перестал делать это таким образом, потому что иногда мы забыли встроить Release и сделать перерыв в отладчике в приложении, запущенном на клиентской демонстрации (смущающе!).

#if DEBUG
if (!System.Diagnostics.Debugger.IsAttached)
{
    System.Diagnostics.Debugger.Break();
}
#endif
13 голосов
/ 30 сентября 2008

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
}
10 голосов
/ 24 сентября 2008

Вы также можете запустить службу через командную строку (sc.exe).

Лично я бы запустил код как отдельную программу на этапе отладки, и, когда большинство ошибок устранено, перейдем на запуск в качестве службы.

10 голосов
/ 24 сентября 2008

Я использовал переключатель командной строки, который запускал программу как службу или как обычное приложение. Затем в своей среде IDE я установил переключатель так, чтобы я мог шагать по своему коду.

С некоторыми языками вы действительно можете определить, работает ли он в IDE, и выполнить это переключение автоматически.

Какой язык вы используете?

...