提问人: 提问时间:9/24/2008 最后编辑:7 revs, 6 users 50%Mats 更新时间:11/17/2023 访问量:288857
调试 Windows 服务的更简单方法
Easier way to debug a Windows service
问:
有没有比通过 Windows 服务控制管理器启动服务,然后将调试器附加到线程更简单的方法来单步执行代码?这有点麻烦,我想知道是否有更直接的方法。
答:
还可以通过命令提示符 (sc.exe) 启动服务。
就我个人而言,我会在调试阶段将代码作为独立程序运行,当大多数错误得到解决时,更改为作为服务运行。
我曾经做的是有一个命令行开关,它可以将程序作为服务或常规应用程序启动。然后,在我的 IDE 中,我将设置开关,以便我可以单步执行我的代码。
使用某些语言,您可以实际检测它是否在 IDE 中运行,并自动执行此切换。
你用的是什么语言?
我通常做的是将服务的逻辑封装在一个单独的类中,然后从“运行器”类开始。此运行器类可以是实际服务,也可以只是控制台应用程序。因此,您的解决方案(至少)有 3 个项目:
/ConsoleRunner
/....
/ServiceRunner
/....
/ApplicationLogic
/....
评论
我认为这取决于您使用的操作系统,由于会话之间的分离,Vista 更难附加到服务。
我过去使用的两个选项是:
- 使用 GFlags(在 Windows 调试工具中)为进程设置永久调试器。它存在于“图像文件执行选项”注册表项中,非常有用。我认为您需要调整服务设置以启用“与桌面交互”。我将其用于所有类型的调试,而不仅仅是服务。
- 另一种选择是将代码分开一点,以便服务部分可以与正常应用启动互换。这样,您可以使用简单的命令行标志,并作为进程(而不是服务)启动,这使得调试变得更加容易。
希望这会有所帮助。
评论
对于常规的小东西编程,我做了一个非常简单的技巧来轻松调试我的服务:
在服务启动时,我检查命令行参数“/debug”。如果使用此参数调用服务,我不会执行通常的服务启动,而是启动所有侦听器,并仅显示一个消息框“调试正在进行中,按确定结束”。
因此,如果我的服务以通常的方式启动,它将作为服务启动,如果它使用命令行参数 /debug 启动,它将像普通程序一样运行。
在 VS 中,我只需添加 /debug 作为调试参数并直接启动服务程序。
这样我就可以很容易地调试大多数小问题。当然,有些东西仍然需要作为服务进行调试,但对于 99% 来说,这已经足够了。
当我编写服务时,我将所有服务逻辑放在一个 dll 项目中,并创建两个调用此 dll 的“主机”,一个是 Windows 服务,另一个是命令行应用程序。
我使用命令行应用程序进行调试,并将调试器附加到实际服务,仅针对我无法在命令行应用程序中重现的错误。
我使用这种方法,请记住,在实际服务中运行时必须测试所有代码,虽然命令行工具是一个很好的调试辅助工具,但它是一个不同的环境,它的行为与实际服务不完全相同。
在开发和调试 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() 怎么样?
如果我想快速调试服务,我只需插入其中即可。当到达该行时,它会让我回到 VS。完成后不要忘记删除该行。Debugger.Break()
更新:作为编译指示的替代方法,您还可以使用 attribute。#if DEBUG
Conditional("DEBUG_SERVICE")
[Conditional("DEBUG_SERVICE")]
private static void DebugMode()
{
Debugger.Break();
}
在你的 上,只需调用这个方法:OnStart
public override void OnStart()
{
DebugMode();
/* ... do the rest */
}
在那里,代码将仅在调试生成期间启用。在执行此操作时,为服务调试创建单独的生成配置可能很有用。
评论
更新
这种方法是迄今为止最简单的方法:
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
评论
// do something
我还认为有一个单独的“版本”用于正常执行和作为服务是要走的路,但真的需要为此目的专用一个单独的命令行开关吗?
你不能做:
public static int Main(string[] args)
{
if (!Environment.UserInteractive)
{
// Startup as service.
}
else
{
// Startup as application
}
}
这将有“好处”,你可以通过双击启动你的应用程序(好吧,如果你真的需要的话),你可以在Visual Studio中点击(不需要修改项目设置来包含该选项)。F5/console
从技术上讲,检查是否为当前窗口站设置了 Flag,但除了作为(非交互式)服务运行之外,还有其他原因会返回吗?Environment.UserInteractive
WSF_VISIBLE
false
评论
System.Diagnostics.Debugger.IsAttached
Environment.UserInteractive
为了调试 Windows 服务,我将 GFlags 和 regedit 创建的 .reg 文件结合起来。
- 运行 GFlags,指定 exe-name 和 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"
评论
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
}
评论
OnStart
是,则无法修改访问级别:(protected
#if DEBUG
System.Diagnostics.Debugger.Break();
#endif
我使用JOP答案的变体。使用命令行参数,可以在 IDE 中使用项目属性或通过 Windows 服务管理器设置调试模式。
protected override void OnStart(string[] args)
{
if (args.Contains<string>("DEBUG_SERVICE"))
{
Debugger.Break();
}
...
}
我希望能够调试服务的各个方面,包括 OnStart() 中的任何初始化,同时仍然在 SCM 的框架内以完整的服务行为执行它......没有“控制台”或“应用程序”模式。
为此,我在同一项目中创建了第二个服务,用于调试。调试服务在照常启动时(即在服务 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) 将 real service 和 debug service 添加到服务项目的工程安装程序中:
将服务项目输出添加到服务的安装项目时,将包含这两个服务(real 和 debug)。安装后,这两个服务都将出现在 service.msc MMC 插件中。
3) 在 MMC 中启动调试服务。
4) 在 Visual Studio 中,将调试器附加到调试服务启动的进程。
5)启动实际服务,享受调试。
几周前,当我建立一个新的服务项目时,我发现了这篇文章。虽然有很多很好的建议,但我仍然没有找到我想要的解决方案:可以在不对服务类进行任何修改的情况下调用服务类和方法。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);
}
这是所需的所有代码,但我也编写了带有解释的演练。
评论
walk through
Console Application
Project Properties -> Application -> Output type -> Console Application
start
C:\"my app name.exe" -service
C:\start /wait "" "my app name.exe" -service
有时,分析服务启动期间发生的情况很重要。附加到进程在这里没有帮助,因为在服务启动时,你不够快,无法附加调试器。
简短的回答是,我使用以下 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,但也应该适用于其他(较新)环境。
重要:在“手动”模式下部署服务(使用 VS 命令提示符中的实用工具或运行已准备好的服务安装程序项目)。在启动服务之前打开 Visual Studio,并加载包含服务源代码的解决方案 - 根据需要在 Visual Studio 中设置其他断点 - 然后通过服务控制面板启动服务。InstallUtil
由于代码的原因,这将导致出现一个对话框“服务名称 .exe 中发生未经处理的 Microsoft .NET Framework 异常”。单击“是,调试服务名称 .exe”,如屏幕截图所示:Debugger.Launch
之后,Windows UAC 可能会提示您输入管理员凭据。输入它们并继续“是”:
之后,将显示众所周知的 Visual Studio 实时调试器窗口。它会询问你是否要使用已选择的调试器进行调试。在单击“是”之前,请选择不想打开新实例(第二个选项) - 新实例在此处没有帮助,因为不会显示源代码。因此,请改为选择之前打开的 Visual Studio 实例:
单击“是”后,一段时间后,Visual Studio 将在语句所在的行中显示黄色箭头,并且你可以调试代码(方法,其中包含初始化)。Debugger.Launch
MyInitOnStart
按 F5 立即继续执行,直到到达您准备的下一个断点。
提示:若要使服务保持运行,请选择“调试”->“全部分离”。这样,你就可以在客户端正确启动并完成启动代码调试后运行与服务通信的客户端。如果按 Shift+F5(停止调试),这将终止服务。您应该使用服务控制面板来停止它,而不是这样做。
请注意,
如果生成 Release,则会自动删除调试代码,并且服务将正常运行。
我正在使用
Debugger.Launch(),
它启动并附加调试器。我也测试了Debugger.Break(),
它不起作用,因为在服务启动时还没有附加调试器(导致“错误 1067:进程意外终止”)。RequestAdditionalTime
为服务的启动设置更长的超时时间(它不会延迟代码本身,但会立即继续执行语句)。否则,启动服务的默认超时时间太短,如果从调试器调用的速度不够快,则启动服务将失败。实际上,10 分钟的超时可避免在调试器启动后立即看到消息“服务未响应...”。Debugger.Launch
base.Onstart(args)
一旦你习惯了它,这个方法就非常简单了,因为它只需要你在现有的服务代码中添加 4 行,让你快速获得控制和调试。
评论
base.RequestAdditionalTime(600000)
base.OnStart(args)
使用 TopShelf 库。
创建一个控制台应用程序,然后在 Main 中配置设置
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()
{
}
}
若要调试服务,只需在 Visual Studio 中按 F5 即可。
要安装服务,请键入 cmd “console.exe install”
然后,您可以在 Windows 服务管理器中启动和停止服务。
评论
Fabio Scopel 的这个 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
评论
要对现有 Windows 服务程序进行故障排除,请按照其他人的建议使用“Debugger.Break()”。
对于新的 Windows 服务程序,我建议使用 James Michael Hare 的方法 http://geekswithblogs.net/BlackRabbitCoder/archive/2011/03/01/c-toolbox-debug-able-self-installable-windows-service-template-redux.aspx
有两个选项可以进行调试。
- 创建日志文件:就我个人而言,我更喜欢单独的日志文件,如文本文件,而不是使用应用程序日志或事件日志。但这会花费你很多时间,因为仍然很难弄清楚我们的确切错误位置在哪里
- 将应用程序转换为控制台应用程序 :这将使您能够在 VS 中使用的所有调试工具。
请参阅我为该主题创建的这篇博文。
只需将调试器启动放在任何位置,并在启动时附加 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# 项目创建新的服务应用 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 });
}
只需粘贴
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();
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
}
}
评论
最好的选择是使用“System.Diagnostics”命名空间。
将代码包含在调试模式和发布模式的 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
我能够按照 Microsoft - https://learn.microsoft.com/en-us/dotnet/framework/windows-services/how-to-debug-windows-service-applications#how-to-run-a-windows-service-as-a-console-application 的官方文档轻松调试 Windows 服务。
它指示将 Windows 服务作为控制台应用运行以进行调试。
有时,您的服务无法启动,甚至在调用 OnStart 方法之前中断,如果您编写了自定义服务,则可能会发生这种情况。 在这种情况下,我们需要找出导致问题的原因。
在这种情况下,我们需要一些额外的时间将我们的 Visual Studio 附加到进程(应用程序),所以我们可以做的是在 Main 方法中添加延迟,然后附加服务。
您可能知道 Main 方法在 OnStart() 之前被调用。
只需添加
static void Main()
{
// add this as first thing
Task.Delay(10000).Wait()
}
这将在执行任何操作之前增加 10 秒的延迟。
有时,Windows服务在作为控制台应用程序运行时可能工作正常,但是在从服务运行时,它可能存在一些问题,我们可以通过这种方式找到这些问题。
评论