65.9K
CodeProject 正在变化。 阅读更多。
Home

可调试的 Windows 服务模板项目(C#)

starIconstarIconstarIconstarIconstarIcon

5.00/5 (10投票s)

2019 年 7 月 18 日

CPOL

6分钟阅读

viewsIcon

20358

用 C# 编写的易于调试的 Windows 服务项目模板。

引言

Windows 服务是非常强大的应用程序,可用于在后台执行许多不同类型的任务。它们无需任何用户登录即可启动,并且可以使用与已登录用户不同的用户帐户运行。但是,如果 Windows 服务应用程序是按照常规服务开发步骤开发的,即使在开发环境中也很难调试。

本文提出了一种在开发阶段无需使用任何服务开发库(如 Topshelf)即可轻松监控和调试 Windows 服务的新开发方式。

特点

示例项目具有以下功能

  • 它在调试模式下作为控制台应用程序运行,在发布模式下作为常规 Windows 服务运行。
  • 根据日志类型使用不同的文本颜色在控制台上显示日志消息。
  • Windows 服务相关的操作和实际后台任务处理操作是分开的,因此实际服务操作可以轻松进行单元测试。
  • 可以使用 InstallUtil.exe 安装和卸载服务。但您不需要安装它即可进行测试和调试。

准备此应用程序的步骤

示例项目是使用 Visual Studio 2017 准备的。

1. 创建 Windows 服务项目

在 Visual Studio 中,单击 文件\新建\项目。选择“Visual C#\Windows 桌面\Windows 服务”项目类型。

2. 将项目输出类型从 Windows 应用程序更改为控制台应用程序

右键单击项目名称并选择“属性”以打开项目属性。

从“输出类型”选择列表中选择控制台应用程序。

3. 安装 log4net 包

从包管理器控制台或从管理 Nuget 包菜单选项安装 log4net nuget 包。

4. 配置 log4net

log4net 是一个非常强大的日志记录库,可以将日志写入许多目标,例如文本文件、控制台、数据库、Windows 事件日志,甚至以电子邮件形式发送。这些日志写入器(或目标)称为“追加器”。log4net 配置必须至少有一个追加器,但它可以有多个。每个追加器都有自己的设置。

log4net 配置可以添加到 app.config 文件中,也可以是一个单独的文件。我更喜欢单独文件的方法,因此为 log4net 添加两个配置文件,它们将在调试和发布模式下使用。名称无关紧要,您可以将它们命名为“log4net.debug.config”和“log4net.prod.config”。调试配置文件有两个追加器:RollingFileAppenderColoredConsoleAppender。生产配置文件也有两个追加器:RollingFileAppenderEventLog。但是 EventLog 追加器已注释掉,如果您想写入 Windows 事件日志,可以取消注释。

最小日志级别在 <root> 元素下定义为 <level> 配置元素。对于调试配置,级别是 DEBUG,对于生产,它是 INFO。有效级别是:DEBUGINFOWARNERROR。有关更多信息,请参阅 log4net 文档

下一个配置步骤是告诉 log4net 库在调试和发布模式下使用哪个文件。为此,打开 AssemblyInfo.cs 文件并为 log4net 添加程序集级别属性。添加这些行以在调试和发布模式下切换两个文件。

#if DEBUG
[assembly: log4net.Config.XmlConfigurator(ConfigFile = "log4net.debug.config", Watch = true)]
#else
[assembly: log4net.Config.XmlConfigurator(ConfigFile = "log4net.prod.config", Watch = true)]
#endif

5. 添加 SampleBackgroundService 类,其中包含我们的 Windows 服务将执行的实际后台操作

这是一个非常简单的类,包含用于启动和停止后台任务处理线程的 StartStop 方法以及一个持续写入日志的线程方法。

class SampleBackgroundService
{
    //Get a logger for this class from log4net LogManager
    private static ILog logger = LogManager.GetLogger(typeof(SampleBackgroundService));

    //Starts the thread
    public void Start() { ... }

    //Stops the thread
    public void Stop() { ... }

    //Service thread that performs background tasks and writes logs
    private void serviceThread()
    {
       while (!stopRequested)
       {
         //Write different types of logs...
       }
    }
}

6. 更新自动生成的 Windows 服务类中的代码

我已将自动添加的 Windows Services 类重命名为 SampleWindowsService。此类继承自 ServiceBase,并且在 Windows 服务启动或停止时会调用其 OnStartOnStop 方法。此类只创建 SampleBackgroundService 类的一个实例并调用其 StartStop 方法。

public partial class SampleWindowsService : ServiceBase
{
   SampleBackgroundService sampleBackgroundService;

   public SampleWindowsService()
   {
      InitializeComponent();
   }

   //Called when the Windows service is started
   protected override void OnStart(string[] args)
   {
      sampleBackgroundService = new SampleBackgroundService();
      sampleBackgroundService.Start();
   }

   //Called when the Windows service is stopped
   protected override void OnStop()
   {
      sampleBackgroundService.Stop();
   }
}

7. 更新 Program.cs 文件中的 Main 方法,使其在调试和发布模式下行为不同

创建新的 Windows 服务项目时,自动生成的 Main 方法包含用于创建和启动自动生成的 Windows 服务的代码。但是 Windows 服务不能作为常规应用程序启动和托管。因此,当您尝试运行应用程序时,会出现错误消息。

为了运行和测试我们的应用程序,我们不需要创建真实的 Windows 服务实例,因为它除了创建和启动 SampleBackgroundService 类的一个实例之外不包含任何代码。Main 方法中更新的代码在调试模式下创建并启动 SampleBackgroundService 类的一个实例,并作为控制台应用程序运行。但在发布模式下创建并运行真实的 Windows 服务。

static void Main()
{
    ILog logger = LogManager.GetLogger(typeof(Program));

#if DEBUG //Run as a regular console application in Debug mode
    //Manually create an instance of SampleBackgroundService class and call its start method

    logger.Info("Starting services...");

    SampleBackgroundService _backgroundService = new SampleBackgroundService();
    _backgroundService.Start();

    logger.Info("Services started. Press enter to stop...");
    Console.ReadLine();

    logger.Info("Stopping service...");
    _backgroundService.Stop();
    logger.Info("Stopped.");

#else //Create and run the real Windows service instance in Release mode
    ServiceBase[] ServicesToRun;
    ServicesToRun = new ServiceBase[]
    {
        new SampleWindowsService()
    };
    ServiceBase.Run(ServicesToRun);
#endif
}

8. 添加服务安装程序组件,以便能够使用 InstallUtil.exe 安装此服务

要添加安装程序组件,请双击解决方案资源管理器中的 SampleWindowsService.cs。它将显示服务的设计视图。

右键单击设计区域并在上下文菜单中单击“添加安装程序”。

这将向项目添加 ProjectInstaller.cs 和一个设计器文件。删除自动生成的代码 ProjectInstaller.InitializeComponent() 方法和自动生成的变量 (serviceProcessInstaller1, serviceInstaller1)。

将以下代码添加到 ProjectInstaller.cs 文件中

public partial class ProjectInstaller : Installer
{
    public const string SERVICE_NAME = "Sample Background Service";

    private readonly ServiceProcessInstaller m_ServiceProcessInstaller;
    private readonly ServiceInstaller m_ServiceInstaller;

    public ProjectInstaller()
    {
        //Installer that installs the process (in this case 'DebuggableWindowsService.exe')
        //There can be only one ServiceProcessInstaller
        m_ServiceProcessInstaller = new ServiceProcessInstaller();
        m_ServiceProcessInstaller.Account = ServiceAccount.LocalSystem;

       //Installer that registers actual Windows Service implementations in the application
       //There may be one or more ServiceInstaller
        m_ServiceInstaller = new ServiceInstaller();
        m_ServiceInstaller.ServiceName = SERVICE_NAME;
        m_ServiceInstaller.Description = "";
        m_ServiceInstaller.StartType = ServiceStartMode.Automatic;
        m_ServiceInstaller.DelayedAutoStart = true;            

        Installers.Add(m_ServiceProcessInstaller);
        Installers.Add(m_ServiceInstaller);

        InitializeComponent();
    }

//...

}

如果您想在服务安装前后执行任何任务,您可以重写适当的基类方法,例如 OnBeforeInstallOnBeforeUninstall 等。

您不需要安装(或将服务注册到 Windows 服务注册表)您的服务即可运行和调试。但如果您愿意,可以使用 InstallUtil.exe 安装和卸载服务。InstallUtil.exe 位于 .NET Framework 安装文件夹下。例如,“C:\Windows\Microsoft.NET\Framework\v4.0.30319”。

要注册服务,请打开命令行窗口并使用可执行文件的完整路径运行 InstallUtil.exe。您可能需要“以管理员身份”运行命令行窗口才能注册服务。

C:\Windows\Microsoft.NET\Framework\v4.0.30319>InstallUtil.exe 
"D:\DebuggableWindowsService\src\bin\Release\DebuggableWindowsService.exe"

要卸载服务,请使用 /u 选项运行相同的命令。

C:\Windows\Microsoft.NET\Framework\v4.0.30319>InstallUtil.exe 
/u "D:\DebuggableWindowsService\src\bin\Release\DebuggableWindowsService.exe"

9. 在调试模式下运行应用程序以查看日志输出和调试

以下是应用程序在调试模式下运行时显示的示例输出。

关于 Windows 服务的重要注意事项

Windows 服务应用程序在某些方面与其他常规应用程序不同。因此,应用程序在调试(控制台应用程序模式)和发布(Windows 服务模式)模式下可能表现不同。

首先,当您在调试模式下运行应用程序时,其工作目录将是可执行文件所在的路径,例如,“D:\DebuggableWindowsService\src\bin\Release\DebuggableWindowsService.exe”。

但是,当您使用 InstallUtil.exe 或安装应用程序安装它并从 Windows 服务管理应用程序运行它时,其工作目录将是“C:\Windows\System32”或“C:\Windows\SysWOW64”,具体取决于您的服务是 64 位还是 32 位,以及它是在 32 位还是 64 位 Windows 上运行。

如果您想在安装目录而不是系统目录中读取或写入文件,您可以在启动时更改工作目录。例如

Environment.CurrentDirectory = 
       Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);

其次,Windows 服务可以使用与已登录用户不同的用户帐户运行。在调试模式下运行的一些操作在应用程序作为真实的 Windows 服务运行时可能无法运行。这些操作的示例包括访问目录或网络路径、打开端口...

第三,Windows 服务没有用户界面,通常无法显示用户界面。它们对显卡的访问被 Windows 操作系统阻止。不幸的是,不可能从 Windows 服务中使用强大的 GPU。要了解有关此限制的更多信息,请搜索“会话 0 隔离”。

要从 Windows 服务显示用户界面,请查看这篇文章:Windows 服务可以有 GUI 吗?Windows 服务应该有 GUI 吗? 感谢 Burak-Ozdiken 分享此链接。

历史

  • 2019 年 7 月 18 日 初始版本。
© . All rights reserved.