Более простой способ отладки службы Windows
-
02-07-2019 - |
Вопрос
Есть ли более простой способ пошагового выполнения кода, чем запустить службу через диспетчер управления службами Windows, а затем подключить отладчик к потоку?Это довольно громоздко, и мне интересно, есть ли более простой подход.
Решение
Если я хочу быстро отладить сервис, я просто заскакиваю в 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 */
}
Там код будет включен только во время отладочных сборок.Пока вы этим занимаетесь, может быть полезно создать отдельную конфигурацию сборки для отладки сервиса.
Другие советы
Я также думаю, что наличие отдельной "версии" для обычного выполнения и в качестве сервиса - это правильный путь, но действительно ли требуется выделять отдельный переключатель командной строки для этой цели?
Не могли бы вы просто сделать:
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
, помимо того, что он запускается как (неинтерактивный) сервис?
Когда я несколько недель назад создавал новый сервисный проект, я нашел это сообщение.Хотя есть много замечательных предложений, я все еще не нашел того решения, которое хотел:Возможность вызова классов обслуживания' 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);
}
Это весь требуемый код, но я также написал Прохождение с пояснениями.
Иногда важно проанализировать, что происходит во время запуска сервиса. Подключение к процессу здесь не помогает, потому что вы недостаточно быстры, чтобы подключить отладчик во время запуска службы.
Короткий ответ таков: я использую следующее 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, но должен быть действителен и для других сред.
Важный: Разверните службу в "ручной" режим (используя либо InstallUtil
утилиту из командной строки VS или запустите подготовленный вами проект установки службы).Откройте Visual Studio до того, как вы запускаете службу и загружаете решение, содержащее исходный код службы - устанавливаете дополнительные точки останова по мере необходимости в Visual Studio - затем запускаете службу с помощью Панель управления сервисом.
Из-за Debugger.Launch
кода, это вызовет диалоговое окно "Необработанное исключение Microsoft .NET Framework, возникшее в Servicename.exe." появиться.Щелчок Да, отлаживать Servicename.exe как показано на скриншоте:
После этого, особенно в Windows 7, UAC может предложить вам ввести учетные данные администратора.Введите их и продолжайте ДА:
После этого хорошо известный Окно отладчика Visual Studio "Точно в срок" появляется.Он спрашивает вас, хотите ли вы выполнить отладку с помощью удаленного отладчика. Прежде чем вы нажмете ДА, выберите то, что вам нужно не хотите открывать новый экземпляр (2-й вариант) - новый экземпляр здесь был бы бесполезен, потому что исходный код не отображался бы.Таким образом, вместо этого вы выбираете экземпляр Visual Studio, который вы открыли ранее:
После того, как вы нажмете ДА, через некоторое время Visual Studio покажет желтую стрелку прямо в строке, где Debugger.Launch
оператор есть, и вы можете отлаживать свой код (метод MyInitOnStart
, который содержит вашу инициализацию).
Нажимающий F5 немедленное продолжение выполнения, до тех пор, пока не будет достигнута следующая подготовленная вами точка останова.
Подсказка: Чтобы служба продолжала работать, выберите Отладка -> Отсоединить все.Это позволяет вам запустить клиент, взаимодействующий со службой, после того, как он запустился правильно, и вы закончили отладку кода запуска.Если вы нажмете Сдвиг+F5 (остановить отладку), это приведет к завершению работы службы.Вместо того чтобы делать это, вам следует использовать Панель управления сервисом чтобы остановить это.
Примечание это
Если вы построите Освободить, затем отладочный код автоматически удаляется и служба работает нормально.
Я использую
Debugger.Launch()
, который запускает и подключает отладчик.Я проверилDebugger.Break()
также, который не сработало, поскольку при запуске службы еще не подключен отладчик (что приводит к "Ошибка 1067:Процесс неожиданно завершился ".).RequestAdditionalTime
устанавливает более длительный тайм-аут для запуска сервиса (это так нет задерживает сам код, но немедленно продолжит выполнениеDebugger.Launch
заявление).В противном случае время ожидания по умолчанию для запуска службы слишком короткое, и запуск службы завершится неудачей, если вы не вызоветеbase.Onstart(args)
достаточно быстро из отладчика.Практически, тайм-аут в 10 минут позволяет избежать того, что вы увидите сообщение "служба не ответила ..." сразу после запуска отладчика.Как только вы привыкнете к этому, этот метод станет очень простым, потому что он просто требует от вас добавьте 4 строки к существующему сервисному коду, что позволяет вам быстро получить контроль и выполнить отладку.
Что я обычно делаю, так это инкапсулирую логику сервиса в отдельный класс и запускаю его из класса 'runner'.Этот класс runner может быть фактическим сервисом или просто консольным приложением.Итак, в вашем решении есть (по крайней мере) 3 проекта:
/ConsoleRunner
/....
/ServiceRunner
/....
/ApplicationLogic
/....
Это Видео Фабио Скопеля на YouTube довольно хорошо объясняет, как отладить службу Windows...сам метод выполнения этого начинается в 4:45 на видео...
Вот код, описанный в видео...в вашем файле Program.cs добавьте материал для раздела 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
}
}
}
В вашем файле 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
если проект скомпилирован в режиме отладки.Это позволит разделу отладки (ниже) выполняться при выполнении условия true
Service1 myService = new Service1();
myService.OnDebug();
System.Threading.Thread.Sleep(System.Threading.Timeout.Infinite);
И он будет работать точно так же, как консольное приложение, как только все пойдет нормально, вы сможете сменить режим Release
и обычный else
раздел вызовет логику
Обновить
Такой подход, безусловно, самый простой:
http://www.codeproject.com/KB/dotnet/DebugWinServices.aspx
Я оставляю свой первоначальный ответ ниже для потомков.
Мои сервисы, как правило, имеют класс, который инкапсулирует Таймер, поскольку я хочу, чтобы сервис регулярно проверял, есть ли для него какая-либо работа.
Мы создаем новый класс и вызываем 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 и отладчик прерывался в приложении, запущенном на демо-версии клиента (неловко!).
#if DEBUG
if (!System.Diagnostics.Debugger.IsAttached)
{
System.Diagnostics.Debugger.Break();
}
#endif
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
}
Вы также можете запустить службу через командную строку (sc.exe).
Лично я бы запустил код как автономную программу на этапе отладки, а когда большинство ошибок будет устранено, переключился бы на запуск от имени службы.
То, что я раньше делал, - это имел переключатель командной строки, который запускал программу либо как службу, либо как обычное приложение.Затем в моей IDE я бы установил переключатель таким образом, чтобы я мог пошагово выполнять свой код.
С некоторыми языками вы действительно можете определить, работает ли он в IDE, и выполнить это переключение автоматически.
На каком языке вы говорите?
Используйте Верхняя полка библиотека.
Создайте консольное приложение, затем настройте программу установки в вашем главном
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()
{
}
}
Чтобы отладить свой сервис, просто нажмите клавишу F5 в Visual Studio.
Чтобы установить сервис, введите в cmd команду "console.exe установить".
Затем вы можете запускать и останавливать службу в диспетчере служб Windows.
Я думаю, это зависит от того, какую ОС вы используете, Vista гораздо сложнее подключить к Сервисам из-за разделения между сеансами.
Два варианта, которые я использовал в прошлом, это:
- Используйте GFlags (в инструментах отладки для Windows), чтобы настроить постоянный отладчик для процесса.Это существует в разделе реестра "Параметры выполнения файла изображения" и невероятно полезно.Я думаю, вам нужно будет настроить настройки Сервиса, чтобы включить "Взаимодействовать с рабочим столом".Я использую это для всех типов отладки, а не только для сервисов.
- Другой вариант - немного отделить код, чтобы служебная часть была взаимозаменяема с обычным запуском приложения.Таким образом, вы можете использовать простой флаг командной строки и запускать как процесс (а не как службу), что значительно упрощает отладку.
Надеюсь, это поможет.
Когда я пишу сервис, я помещаю всю логику сервиса в проект dll и создаю два "узла", которые вызывают эту dll, один из которых является службой Windows, а другой - приложением командной строки.
Я использую приложение командной строки для отладки и подключаю отладчик к реальному сервису только для устранения ошибок, которые я не могу воспроизвести в приложении командной строки.
Если вы используете этот подход, просто помните, что вам нужно протестировать весь код во время работы в реальном сервисе, хотя инструмент командной строки является хорошим средством отладки, это другая среда, и он ведет себя не совсем так, как настоящий сервис.
Мне нравится иметь возможность отлаживать каждый аспект моего сервиса, включая любую инициализацию в OnStart(), при этом все еще выполняя его с полным поведением сервиса в рамках SCM...нет режима "консоль" или "приложение".
Я делаю это, создавая второй сервис в том же проекте для использования для отладки.Служба отладки, запущенная как обычно (т. е.в плагине services MMC) создает процесс хоста сервиса.Это дает вам возможность подключить отладчик к процессу, даже если вы еще не запустили свой реальный сервис.После подключения отладчика к процессу запустите свой реальный сервис, и вы сможете взломать его в любом месте жизненного цикла сервиса, включая OnStart().
Поскольку для этого требуется минимальное вмешательство в код, службу отладки можно легко включить в ваш проект установки службы и легко удалить из вашего производственного выпуска, закомментировав одну строку кода и удалив один установщик проекта.
Подробные сведения:
1) Предполагая, что вы реализуете MyService
, также создавать MyServiceDebug
.Добавьте и то, и другое к ServiceBase
массив в Program.cs
вот так:
/// <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) Добавьте реальную службу И службу отладки в установщик проекта для проекта service:
Обе службы (реальная и отладочная) включаются при добавлении выходных данных проекта службы в проект установки для службы.После установки оба сервиса появятся в плагине service.msc MMC.
3) Запустите службу отладки в MMC.
4) В Visual Studio подключите отладчик к процессу, запущенному службой отладки.
5) Запустите настоящий сервис и наслаждайтесь отладкой.
При разработке и отладке службы Windows я обычно запускаю ее как консольное приложение, добавляя параметр запуска /console и проверяя это.Это намного облегчает жизнь.
static void Main(string[] args) {
if (Console.In != StreamReader.Null) {
if (args.Length > 0 && args[0] == "/console") {
// Start your service work.
}
}
}
Как насчет Debugger.Break() в первой строке?
Для отладки служб Windows я объединяю GFlags и reg-файл, созданный regedit.
- Запустите GFlags, указав exe-имя и vsjitdebugger
- Запустите regedit и перейдите в папку, где GFlags устанавливает свои параметры
- Выберите "Экспортировать ключ" в меню файл
- Сохраните этот файл где-нибудь с расширением .reg
- В любое время, когда вы захотите отладить службу:дважды щелкните по файлу .reg
- Если вы хотите остановить отладку, дважды щелкните по второму reg-файлу
Или сохраните следующие фрагменты и замените servicename.exe желаемым именем исполняемого файла.
debugon.reg:
Windows Registry Editor Version 5.00 [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\servicename.exe] "GlobalFlag"="0x00000000" "Debugger"="vsjitdebugger.exe"
debugoff.reg:
Windows Registry Editor Version 5.00 [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\servicename.exe] "GlobalFlag"="0x00000000"
Для рутинного программирования мелких вещей я выполнил очень простой трюк, позволяющий легко отлаживать мой сервис:
При запуске службы я проверяю наличие параметра командной строки "/debug".Если служба вызывается с этим параметром, я не выполняю обычный запуск службы, а вместо этого запускаю все прослушиватели и просто показываю окно сообщения "Выполняется отладка, нажмите ok для завершения".
Таким образом, если моя служба запущена обычным способом, она запустится как service, если она запущена с параметром командной строки / debug, она будет действовать как обычная программа.
В VS я просто добавлю /debug в качестве параметра отладки и напрямую запущу сервисную программу.
Таким образом, я могу легко выполнять отладку для большинства небольших задач.Конечно, некоторые вещи все еще нужно будет отлаживать как сервис, но для 99% этого достаточно.
#if DEBUG
System.Diagnostics.Debugger.Break();
#endif
Я использую вариант ответа ДЖОПА.Используя параметры командной строки, вы можете установить режим отладки в IDE с помощью свойств проекта или через диспетчер служб Windows.
protected override void OnStart(string[] args)
{
if (args.Contains<string>("DEBUG_SERVICE"))
{
Debugger.Break();
}
...
}
Для устранения неполадок в существующей сервисной программе Windows используйте 'Debugger.Break()", как предлагали другие ребята.
Для новой сервисной программы Windows я бы предложил использовать метод Джеймса Майкла Хэйра http://geekswithblogs.net/BlackRabbitCoder/archive/2011/03/01/c-toolbox-debug-able-self-installable-windows-service-template-redux.aspx
Просто поместите свой debugger lunch в любое место и подключите Visualstudio при запуске
#if DEBUG
Debugger.Launch();
#endif
Также вам нужно запустить VS от имени администратора, и вам нужно разрешить, чтобы процесс мог автоматически отлаживаться другим пользователем (как объяснено здесь):
reg add "HKCR\AppID{E62A7A31-6025-408E-87F6-81AEB0DC9347}" /v AppIDFlags /t REG_DWORD /d 8 /f
Используйте шаблон службы Windows C # project для создания нового приложения-службы https://github.com/HarpyWar/windows-service-template
Включены автоматически определяемый консольный / сервисный режим, автоматическая установка / деинсталляция вашего сервиса и несколько наиболее часто используемых функций.
Вот простой метод, который я использовал для тестирования сервиса, без каких-либо дополнительных методов "Отладки" и с интегрированными модульными тестами VS.
[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
}
}
У вас есть два варианта выполнения отладки.
- создайте файл журнала :Лично я предпочитаю отдельный файл журнала, такой как текстовый файл, а не использовать журнал приложений или журнал событий.Но это будет стоить вам много времени, потому что по-прежнему сложно определить, где находится точное местоположение ошибки
- Преобразуйте приложение в консольное приложение :это позволит вам использовать все инструменты отладки, которые мы можем использовать в VS.
Пожалуйста, обратитесь к ЭТО запись в блоге, которую я создал для этой темы.
Просто вставьте
Debugger.Break();
любое место в вашем коде.
Например ,
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);
}
}
Это ударит Debugger.Break();
когда вы запускаете свою программу.
Лучшим вариантом является использование 'Система.Диагностика' пространство имен.
Заключите свой код в блок if else для режима отладки и выпуска, как показано ниже, чтобы переключаться между режимами отладки и выпуска в 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