调试Windows服务的简单方法

是否有比通过Windows服务控制管理器启动服务,然后将调试器附加到线程更简单的方法来逐步遍历代码?这有点麻烦,我想知道是否有更直接的方法。

271853 次浏览

您也可以通过命令提示符(sc.exe)启动服务。

就我个人而言,我会在调试阶段将代码作为独立程序运行,当大多数错误被解决后,再改为作为服务运行。

我过去所做的是有一个命令行开关,它可以作为服务或常规应用程序启动程序。然后,在我的IDE中,我将设置开关,以便我可以分步执行我的代码。

对于某些语言,您实际上可以检测它是否运行在IDE中,并自动执行此切换。

你在用什么语言?

我通常做的是将服务的逻辑封装在一个单独的类中,并从一个“runner”类开始。这个运行器类可以是实际的服务,也可以只是一个控制台应用程序。所以你的解决方案至少有3个项目:

/ConsoleRunner
/....
/ServiceRunner
/....
/ApplicationLogic
/....

我认为这取决于你使用的操作系统,Vista很难附加到服务,因为会话之间的分离。

我过去使用的两个选项是:

  • 使用GFlags(在Windows调试工具中)为进程设置一个永久调试器。这存在于“镜像文件执行选项”注册表项中,非常有用。我认为你需要调整服务设置以启用“与桌面交互”。我将它用于所有类型的调试,而不仅仅是服务。
  • 另一种选择是将代码分开一点,这样服务部分就可以与正常的应用程序启动互换。这样,您就可以使用一个简单的命令行标志,并作为一个进程(而不是一个服务)启动,这使得调试更加容易。

希望这能有所帮助。

对于常规的小程序,我做了一个非常简单的技巧来轻松调试我的服务:

在服务启动时,我检查命令行参数“/debug”。如果使用此参数调用服务,我不会执行通常的服务启动,而是启动所有侦听器,并只显示一个消息框“调试正在进行中,按ok结束”。

因此,如果我的服务以通常的方式启动,它将作为服务启动,如果它以命令行参数/调试启动,它将像正常程序一样工作。

在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 pragmas的替代,你也可以使用Conditional("DEBUG_SERVICE")属性。

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

在你的OnStart中,只需调用这个方法:

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

在那里,代码将只在调试构建期间启用。在此过程中,为服务调试创建一个单独的Build Configuration可能会很有用。

更新

这种方法是目前为止最简单的:

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

我把最初的答案留给后人。


我的服务倾向于有一个封装定时器的类,因为我想让服务定期检查它是否有任何工作要做。

我们在服务启动期间更新类并调用StartEventLoop()。(这个类也可以很容易地从控制台应用程序中使用。)

这种设计的一个很好的副作用是,可以使用用来设置Timer的参数在服务实际开始工作之前有一个延迟,这样就有时间手动附加调试器。

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]标志来帮助避免它在发布构建中被触发)。

我不再这样做了,因为有时我们会忘记在发布中构建,并在客户端演示上运行的应用程序中出现调试器中断(尴尬!)

#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文件。

  1. 运行GFlags,指定ex -name和vsjitdebugger
  2. 运行regedit并转到GFlags设置选项的位置
  3. 从文件菜单中选择“导出密钥”
  4. 将该文件保存在扩展名为.reg的某处
  5. 任何时候你想调试服务:双击.reg文件
  6. 如果要停止调试,请双击第二个.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();
}
...
}

我喜欢能够调试我的服务的每个方面,包括在OnStart()中的任何初始化,同时仍然在SCM框架内执行完整的服务行为…没有“控制台”或“应用程序”模式。

为此,我在同一个项目中创建了第二个用于调试的服务。调试服务在正常启动时(即在服务MMC插件中),创建服务主机进程。这为您提供了一个可以附加调试器的进程,即使您还没有启动真正的服务。在将调试器附加到进程之后,启动真正的服务,您可以在服务生命周期中的任何位置进入它,包括OnStart()。

因为调试服务只需要极少的代码侵入,所以可以很容易地将其包含在服务设置项目中,并且通过注释掉一行代码和删除单个项目安装程序就可以很容易地从产品版本中删除调试服务。

细节:

1)假设你正在实现MyService,也创建MyServiceDebug。将两者都添加到Program.cs中的ServiceBase数组中,如下所示:

    /// <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)将真实服务和调试服务添加到服务项目的项目安装程序中:

enter image description here

在将服务项目输出添加到服务的设置项目时,将包括两个服务(真实的和调试的)。安装后,这两个服务都将出现在服务中。MMC插件。

3)启动MMC中的调试服务。

4)在Visual Studio中,将调试器附加到调试服务启动的进程。

5)启动真正的服务,享受调试。

当我几周前建立一个新的服务项目时,我发现了这个帖子。虽然有很多很好的建议,但我仍然没有找到我想要的解决方案:在不修改服务类的情况下调用服务类的OnStartOnStop方法的可能性。

我提出的解决方案使用Environment.Interactive选择运行模式,正如这篇文章的其他答案所建议的那样。

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

RunInteractive helper使用反射来调用受保护的OnStartOnStop方法:

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 7 x64Visual Studio 2010团队版,但也应该对其他(更新的)环境有效。


重要的是:“manual"模式中部署服务(使用VS命令提示符中的InstallUtil实用程序或运行你准备好的服务安装程序)。打开Visual Studio 之前,启动服务并加载包含服务源代码的解决方案——在Visual Studio中根据需要设置额外的断点——然后通过业务控制面板。启动服务

由于Debugger.Launch代码,这将导致对话框“一个未处理的Microsoft .NET框架异常发生在Servicename.exe."出现。点击Elevate是,调试Servicename.exe如截图所示:
FrameworkException

.

之后,Windows UAC可能会提示您输入管理凭据。输入它们并继续< kbd >对< / kbd >:

UACPrompt

在此之后,出现了众所周知的Visual Studio即时调试器窗口。它询问您是否要使用已删除的调试器进行调试。在单击之前, select that you 不想打开一个新实例(第二个选项)-一个新的实例在这里没有帮助,因为源代码将不会显示。因此,您选择前面打开的Visual Studio实例:
VSDebuggerPrompt

. 一段时间后,Visual Studio将在Debugger.Launch语句所在的行中显示黄色箭头,你可以调试你的代码(方法MyInitOnStart,其中包含你的初始化)。 VSDebuggerBreakpoint

按下F5立即继续执行,直到到达你准备的下一个断点。

提示:要保持服务运行,选择调试→分离所有。这允许您在服务正确启动并完成调试启动代码之后运行与服务通信的客户机。如果你按< kbd > < /转移kbd > + < kbd > F5 < / kbd >(停止调试),这将终止服务。而不是这样做,你应该使用业务控制面板来停止它。



请注意,

  • 如果你构建了一个释放,,那么调试代码将自动删除和服务正常运行。

  • 我正在使用Debugger.Launch(),其中启动并附加调试器。我也测试了Debugger.Break(),其中没有工作,因为在服务启动时还没有附加调试器(导致错误1067:进程意外终止。)。

  • RequestAdditionalTime设置一个更长的服务启动超时(它是延迟代码本身,但将立即继续Debugger.Launch语句)。否则,启动服务的默认超时时间太短,如果从调试器中调用base.Onstart(args)不够快,则启动服务会失败。实际上,10分钟的超时可以避免在调试器启动后立即看到消息"服务部门没有回应……”

  • 一旦你习惯了它,这个方法是非常简单的,因为它只需要你增加4行到一个现有的服务代码,允许你快速获得控制和调试。

使用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服务管理器中启动和停止服务。

YouTube视频由Fabio Scopel制作解释了如何很好地调试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()程序使用#DEBUG添加了#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

您有两个选项来进行调试。

  1. 创建一个日志文件:我个人更喜欢一个单独的日志文件,比如文本文件,而不是使用应用程序日志或事件日志。但是这将花费你大量的时间,因为它仍然很难找出准确的错误位置
  2. 将应用程序转换为控制台应用程序:这将使你能够在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
}
}

最好的选择是使用'系统。诊断'命名空间。

将代码包含在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

我能够轻松地调试一个windows服务遵循微软官方文档 - 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服务作为控制台应用程序进行调试。