更轻松地调试Windows服务

时间:2008-09-24 08:08:37

标签: c# debugging windows-services

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

28 个答案:

答案 0 :(得分:266)

如果我想快速调试服务,我只需要放入Debugger.Break()。当达到那条线时,它会让我回到VS.完成后别忘了删除该行。

更新:作为#if DEBUG pragma的替代方案,您还可以使用Conditional("DEBUG_SERVICE")属性。

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

OnStart上,只需调用此方法:

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

在那里,代码只会在Debug构建期间启用。在使用它时,为服务调试创建单独的Build Configuration可能很有用。

答案 1 :(得分:208)

我还认为有一个单独的“版本”用于正常执行,并且作为服务是要走的路,但是真的需要为此目的专门设置一个单独的命令行开关吗?

你不能这样做:

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 (非互动)服务?

答案 2 :(得分:117)

几周前,当我找到一个新的服务项目时,我找到了这篇文章。虽然有很多很棒的建议,但我仍然没有找到我想要的解决方案:可以在不对服务类进行任何修改的情况下调用服务类的OnStartOnStop方法。

我提出的解决方案使用Environment.Interactive选择运行模式,正如此帖的其他答案所示。

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

RunInteractive助手使用反射来调用受保护的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);
}

这是所需的所有代码,但我也写了walkthrough并附有解释。

答案 3 :(得分:41)

有时候分析服务启动期间的内容是很重要的。在此处附加到此过程没有帮助,因为您没有足够快的速度来附加调试器服务正在启动。

简短的回答是,我使用以下 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 ,但也应对其他环境有效。


重要提示:在"手册"中部署服务mode (使用VS命令提示符中的InstallUtil实用程序或运行您准备好的服务安装程序项目)。打开Visual Studio ,然后启动服务并加载包含服务源代码的解决方案 - 在Visual Studio中根据需要设置其他断点 - 然后通过启动服务服务控制面板。

由于Debugger.Launch代码,这将导致对话框" Servicename.exe 中出现未处理的Microsoft .NET Framework异常。"出现。点击 Elevate是,调试 Servicename.exe ,如屏幕截图所示:
FrameworkException

之后,特别是在Windows 7中,UAC可能会提示您输入管理员凭据。输入它们并继续

UACPrompt

之后,出现众所周知的 Visual Studio即时调试器窗口。它会询问您是否要使用delected调试器进行调试。 点击之前选择您不想打开新实例(第二个选项) - 新实例不会在这里很有帮助,因为源代码不会被显示出来。因此,您选择之前打开的Visual Studio实例: VSDebuggerPrompt

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

F5 会立即继续执行,直到您准备好下一个断点。

提示要保持服务正常运行,请选择调试 - >分离所有。这允许您在正确启动后运行与服务通信的客户端,并且您已完成调试启动代码。如果按 Shift + F5 (停止调试),将终止该服务。您应该使用服务控制面板来停止它。

,而不是这样做

注意那个

  • 如果您构建版本,则会自动删除调试代码,并且该服务会正常运行。

  • 我正在使用 Debugger.Launch() 启动并附加调试器。我已经测试了 Debugger.Break() ,其中无法正常工作,因为在启动服务时尚未附加调试器(导致& #34;错误1067:流程意外终止。" )。

  • RequestAdditionalTime 为服务启动设置更长的超时延迟代码本身,但会立即继续Debugger.Launch声明。否则,启动服务的默认超时时间太短,如果您没有从调试器中快速调用base.Onstart(args),则启动服务会失败。实际上,10分钟的超时可以避免在调试器启动后立即看到消息" 服务没有响应..."

  • 一旦你习惯了,这种方法非常简单,因为它只需要你添加4行到现有的服务代码,让你快速获得控制和调试。 / p>

答案 4 :(得分:40)

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

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

答案 5 :(得分:24)

这个YouTube video by Fabio Scopel解释了如何很好地调试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部分将触发逻辑

答案 6 :(得分:14)

<强>更新

这种方法是迄今为止最简单的方法:

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

我将下面的原始答案留给后人。


我的服务往往有一个封装了Timer的类,因为我希望服务定期检查是否有任何工作要做。

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

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

  

P.S。 How to attach the debugger manually正在运行的流程......?

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

答案 7 :(得分:13)


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
}

答案 8 :(得分:10)

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

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

您使用的是哪种语言?

答案 9 :(得分:10)

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

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

答案 10 :(得分:9)

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

答案 11 :(得分:8)

我认为这取决于您使用的操作系统,由于会话之间的分离,Vista很难连接到服务。

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

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

希望这有帮助。

答案 12 :(得分:5)

当我编写服务时,我将所有服务逻辑放在一个dll项目中并创建两个调用此dll的“主机”,一个是Windows服务,另一个是命令行应用程序。

我使用命令行应用程序进行调试,并将调试器附加到实际服务,仅用于我无法在命令行应用程序中重现的错误。

我使用这种方法只记得你必须在真实服务中运行时测试所有代码,而命令行工具是一个很好的调试辅助工具,它是一个不同的环境,它的行为不像真正的服务

答案 13 :(得分:5)

我希望能够调试我的服务的每个方面,包括OnStart()中的任何初始化,同时仍然在SCM的框架内以完整的服务行为执行它...没有“控制台”或“app”模式。

我通过在同一个项目中创建第二个服务来进行调试。调试服务,像往常一样启动(即在服务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)将实际服务和调试服务添加到服务项目的项目安装程序:

enter image description here

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

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

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

5)启动真实服务并享受调试。

答案 14 :(得分:4)

在开发和调试Windows服务时,我通常通过添加/ console启动参数并检查它来将其作为控制台应用程序运行。让生活更轻松。

static void Main(string[] args) {
    if (Console.In != StreamReader.Null) {
        if (args.Length > 0 && args[0] == "/console") {
            // Start your service work.
        }
    }
}

答案 15 :(得分:4)

第一行中的Debugger.Break()怎么样?

答案 16 :(得分:2)

要调试Windows服务,我将GFlags和regedit创建的.reg文件组合在一起。

  1. 运行GFlags,指定exe-name和vsjitdebugger
  2. 运行regedit并前往GFlags设置选项的位置
  3. 从文件菜单中选择“导出密钥”
  4. 使用.reg扩展名保存该文件
  5. 您想要调试服务的任何时候:双击.reg文件
  6. 如果要停止调试,请双击第二个.reg文件
  7. 或保存以下代码段并将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"

答案 17 :(得分:1)

这是我用来测试服务的简单方法,没有任何额外的&#34; Debug&#34;方法和集成的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 });
}

答案 18 :(得分:1)

我使用JOP答案的变体。使用命令行参数,您可以在IDE中使用项目属性或通过Windows服务管理器设置调试模式。

protected override void OnStart(string[] args)
{
  if (args.Contains<string>("DEBUG_SERVICE"))
  {
    Debugger.Break();
  }
  ...
}

答案 19 :(得分:1)

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

答案 20 :(得分:1)

使用Windows服务模板C#项目创建新的服务应用https://github.com/HarpyWar/windows-service-template

自动检测到控制台/服务模式,服务的自动安装程序/卸载程序以及几个最常用的功能都包含在内。

答案 21 :(得分:1)

只需将调试器午餐放在任何地方,并在启动时附加Visualstudio

#if DEBUG
    Debugger.Launch();
#endif

此外,你需要启动VS作为Administatrator,你需要允许,一个进程可以由不同的用户自动调试(如here所述):

reg add "HKCR\AppID{E62A7A31-6025-408E-87F6-81AEB0DC9347}" /v AppIDFlags /t REG_DWORD /d 8 /f

答案 22 :(得分:1)

要在现有Windows服务程序上进行故障排除,请使用&#39; Debugger.Break()&#39;正如其他人所说的那样。

对于新的Windows服务计划,我建议使用James Michael Hare的方法http://geekswithblogs.net/BlackRabbitCoder/archive/2011/03/01/c-toolbox-debug-able-self-installable-windows-service-template-redux.aspx

答案 23 :(得分:1)

#if DEBUG
    System.Diagnostics.Debugger.Break();
#endif

答案 24 :(得分:1)

对于常规的小东西编程我已经做了一个非常简单的技巧来轻松调试我的服务:

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

因此,如果我的服务以通常的方式启动,它将作为服务启动,如果它是使用命令行参数/ debug启动它将像普通程序一样。

在VS中我只需添加/ debug作为调试参数并直接启动服务程序。

这样我就可以轻松调试大多数小问题。当然,有些东西仍然需要作为服务进行调试,但99%这样就足够了。

答案 25 :(得分:0)

您有两个选项可以进行调试。

  1. 创建一个日志文件:我个人更喜欢单独的日志文件,比如文本文件,而不是使用应用程序日志或事件日志。但这会花费你很多代表时间,因为它仍然很难确定我们在哪里确切的错误位置
  2. 将应用程序转换为控制台应用程序:这将启用我们可以在VS中使用的所有调试工具。
  3. 请参阅我为该主题创建的THIS博文。

答案 26 :(得分:0)

只需粘贴

即可
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();

答案 27 :(得分:0)

最佳选择是使用“ 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