调试Windows服务的简便方法
有没有比通过Windows Service Control Manager启动服务然后将调试器添加到线程更简单的方法来遍历代码?这有点麻烦,我想知道是否有更简单的方法。
解决方案
我们也可以通过命令提示符(sc.exe)启动服务。
就个人而言,我将在调试阶段将代码作为独立程序运行,并且当大多数错误被消除后,更改为作为服务运行。
我曾经做过的事情是拥有一个命令行开关,它将以服务或者常规应用程序的形式启动程序。然后,在我的IDE中设置开关,以便逐步执行代码。
使用某些语言,我们实际上可以检测它是否在IDE中运行,并自动执行此切换。
我们使用什么语言?
我通常要做的是将服务的逻辑封装在一个单独的类中,然后从"运行器"类开始。该运行器类可以是实际的服务,也可以只是控制台应用程序。因此,解决方案有(至少)3个项目:
/ConsoleRunner /.... /ServiceRunner /.... /ApplicationLogic /....
我认为这取决于我们所使用的操作系统,由于会话之间的分隔,Vista很难添加到服务上。
我过去使用的两个选项是:
- 使用GFlags(在Windows调试工具中)为进程设置永久调试器。它存在于"图像文件执行选项"注册表项中,并且非常有用。我认为我们需要调整服务设置以启用"与桌面交互"。我将其用于所有类型的调试,而不仅仅是服务。
- 另一种选择是稍微分离代码,以便服务部分可与正常的应用启动互换。这样,我们可以使用简单的命令行标志,并将其作为进程(而不是服务)启动,这使得调试变得更加容易。
希望这可以帮助。
对于常规的小型编程,我做了一个非常简单的技巧来轻松调试我的服务:
服务启动时,我检查命令行参数" / debug"。如果使用此参数调用该服务,则不会执行通常的服务启动,而是启动所有侦听器,仅显示一个消息框"调试正在进行中,按ok结束"。
因此,如果我的服务以通常的方式启动,它将作为服务启动,如果使用命令行参数/ 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()怎么样?
如果我想快速调试该服务,只需在其中放入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 */ }
在那里,仅在Debug构建期间启用代码。当我们在使用它时,为服务调试创建一个单独的构建配置可能会很有用。
更新
到目前为止,这种方法是最简单的:
http://www.codeproject.com/KB/dotnet/DebugWinServices.aspx
为了后代,我在下面保留原始答案。
我的服务倾向于有一个封装Timer的类,因为我希望该服务定期检查是否有任何工作要做。
在服务启动过程中,我们将新建类并调用StartEventLoop()。 (该类也可以很容易地从控制台应用程序中使用。)
这种设计的一个很好的副作用是,用来设置Timer的参数可以在服务真正开始工作之前有一个延迟,这样我们就有时间手动添加调试器。
p.s. How to attach the debugger manually to a running process...?
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
我还认为,为正常执行和作为服务使用单独的"版本"是可行的方法,但是是否真的需要为此目的专门使用单独的命令行开关?
你不能只是做:
public static int Main(string[] args) { if (!Environment.UserInteractive) { // Startup as service. } else { // Startup as application } }
那将具有"好处",我们可以通过双击来启动应用程序(如果确实需要,可以单击确定),并且只需在Visual Studio中按" F5"即可(无需修改项目设置以包括该功能) / console选项)。
从技术上讲," Environment.UserInteractive"会检查是否为当前窗口站设置了" WSF_VISIBLE"标志,但是除了作为非交互服务运行之外,还有其他原因会返回" false"吗?
为了调试Windows服务,我将GFlags和regedit创建的.reg文件结合在一起。
- 运行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"
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 }
#if DEBUG System.Diagnostics.Debugger.Break(); #endif
我在JOP的答案上使用了变体。使用命令行参数,可以在IDE中使用项目属性或者通过Windows服务管理器来设置调试模式。
protected override void OnStart(string[] args) { if (args.Contains<string>("DEBUG_SERVICE")) { Debugger.Break(); } ... }