如何使用托管扩展的 C++ 创建 NT(Win) 服务





3.00/5 (5投票s)
2002 年 7 月 26 日
13分钟阅读

140477

1726
本文解释了如何使用 C++ 和托管扩展创建 NT 服务,也称为 Windows 服务。
引言
本文解释了如何使用 C++ 和托管扩展创建 NT 服务,也称为 Windows 服务。在 .NET Framework 之前,创建 NT 服务是一个繁琐且详细的过程。它需要创建一个连接到服务控制管理器 (SCM) 的特定 Main 函数,并实现具有特定签名的多个方法,以便 SCM 可以调用您服务的函数。总而言之,这对新手来说一点也不简单,一个中级人士在完成 1 个 NT 服务项目后,经历了所有的陷阱、错误处理、设计决策以及纯粹的 C++“语法”,也可能变成高级水平。我写了很多服务,深知 .NET Framework 完全简化了这个过程;本文也将证明这一点。
Tefulataat 服务是一个 Windows 服务,它在早上 9:00 播放晨祷,下午 3:00 播放午祷,晚上 9:00 播放晚祷。这些祷告是硬盘上录制的 MP3 文件。Tefulataat 利用 System::Threading 命名空间中的 Timer 对象,就像闹钟一样工作,驱动祷告的播放。MP3 文件使用 DirectX 库的 DirectShow 对象播放,这是 Win2k 及以上系统中的标准。没有太多错误处理,因为本文只是解释如何使用 C++ 和托管扩展创建 Windows 服务。现在,让我们开始深入研究吧?我将使用一种两步法。创建 Windows 服务时,您必须记住,服务可能一直运行,用户不能按其说法来启动和停止它,并且服务本身没有用户界面。这意味着您不能在发生错误时简单地显示消息框。您可以写入事件日志,写入单独的文件,或使用其他方法,以便在事件发生后可以访问日志。它也意味着在安装后调试 Windows 服务不像在代码中放置断点并单步执行那样简单。那么我们如何处理这些概念呢?第一步。第一步是先创建一个具有所有后台功能的简单 Windows 应用程序。单步执行代码,进行调试,然后在所有程序、语法和逻辑错误都已修复或处理后,您可以将代码移到第二步。我在 windows::form 对象上做了一点“作弊”,我使用了 VB.NET 来创建用户界面,然后将 VB 代码中的 Form 属性翻译成了使用托管扩展的 C++(使用 C# 会容易得多,但我想用 VB)。
使用 C++ 托管的 Windows 应用,包含用于第一步的服务方法。
第二步是将您的代码放入 Windows 服务代码中,并添加事件日志以帮助调试。(另一种生成模板服务代码的简单方法是在 C# 中生成,然后将其翻译成 C++)。让我们在这里更深入地探讨这一步:Windows 服务 C++ 源代码以此方式开始。您必须首先在 Header 或源文件的顶部包含您的标准 C 库和 .NET 库或程序集,就像下面的代码片段所做的那样
实际上,#include 语句会在编译时打开 "" 中的文件,并编译这些文件。#using 语句告诉编译器包含这些程序集。这些程序集被引用,并且是您的源文件编译所必需的。对于所有 C++ 托管扩展,您必须引用 mscorlib.dll;对于 Windows 服务,您还必须引用 System.ServiceProcess.dll。其他程序集根据您在应用程序中使用哪些功能而需要。接下来,为了节省您的输入,您可以包含命名空间。命名空间提供了一种使用诸如以下命名约定来访问逻辑上分组的成员的方法
例如,如果我想在 C++ 中访问 Xml Document 对象,语法将是:XmlDocument * pXmlDoc = new XmlDocument(); 带有命名空间。然而,如果没有命名空间,我将不得不按如下方式输入完整的库和命名约定:System::Xml::XmlDocument * pXmlDoc = new System::Xml::XmlDocument(); 命名空间还允许您区分命名冲突,以防您有两个存在于不同命名空间中的对象类型,您可以通过包含其顶层命名约定来区分两者。接下来,我为我的应用程序创建一个命名空间,并基于我继承的接口设置其类
.NET Windows 服务应用程序都继承自 ServiceBase 类。这个类拥有所有为 .NET CLR 设计的功能,以便在您的应用程序运行时与 SCM 进行通信。您只需要重写 ServiceBase 类中定义为可重写或抽象的方法,并添加您的功能。注意“__gc”关键字。这个关键字告诉编译器该类是托管 C++ 类,编译时必须包含“/clr”。实际上,这意味着该类将参与所有 .NET Clr 功能,包括垃圾回收过程、内存管理等。之前的所有代码都放在我们的 Header 文件中。现在我们来处理源文件。在我们的源文件中,我们确保包含我们的 Header 文件,以便源代码拥有编译所需的所有库。
注意:我还包含了“vcclr.h”文件。这个文件包含了将 System::String * 对象转换为 Native char * 对象以及其他用于将 .NET 托管类型转换为 Native C++ 类型的有用函数的必要函数。接下来,在构造函数中,您需要初始化您的组件
在构造函数中,我们调用 InitializeComponent() 方法。此方法创建一个 Eventlog 指针,并将 EventLog 类型设置为 Application,EventLog 名称设置为“Tefulaat”。下一行代码创建了一个 EventLogTraceListener 指针。此指针指向一种 TraceListener 对象。静态 Trace 对象的 TraceListener 对象现在可以添加另一个 TraceListener,以便跟踪程序中的所有 Trace。Trace 是一种在应用程序执行时输出消息的方式。在 System::Diagnostics 命名空间中,有一个静态 Trace 对象,它与每个应用程序一起执行,可以将消息写入 Listener。Listener 可以是文本文件、事件日志,或您可以定义实现的 ITraceListener 的任何对象。在 TraceListener 之后,我们创建一个 TraceSwitch,允许我们获取/设置 Trace 级别。开发人员可以根据代码或配置文件设置的特定 Trace 级别来确定哪些消息写入 Listener。下一行代码获取 Configuration file Reader 指针。到目前为止的所有代码行只是设置了我们的 EventLog 环境,用于将错误消息写入其中,以及一个指向我们的配置文件读取器的指针。这些代码行对服务来说不是必需的,但它们有助于更好地管理应用程序。
事件查看器,Tefulaat 跟踪消息已写入事件日志
接下来我希望您注意的代码行是:this->ServiceName = S”Tefulaat”; 这行非常重要。这个名称是注册表在“CurrentControlSet”键下的“Services”键中创建的名称。如果您使用“Net Start”或“SC”来运行服务,您将使用相同的名称(字母大小写也很重要)。当您稍后在此示例中创建“ServiceInstaller”和“ProcessInstaller”时,您也将使用相同的名称。如果这些不同对象中的名称有任何差异,您将花一周的时间来研究这篇文章!一点也不好玩!一旦您设置了服务名称,而这是服务正常工作所需在构造函数中做的唯一事情,我们就可以看一下下一个编码部分。OnStart 方法
OnStart 方法由 SCM 调用。在这里,您可以接收字符串参数,或者只是默认的无参数。在此过程中,通过将其分配给此类并将其回调方法指定为“TefulaatTimer_Tick”方法(在 Header 文件中声明)来创建 TimerCallback 指针(基本上是一个委托)。接下来,我们创建 Timer 对象的指针,并将其初始属性设置为 timerdelegate、此类、无限启动时间(意味着不要立即启动计时器)以及等待方法委托再次被调用的间隔(以毫秒为单位)。接下来,我们调用 Change 方法,该方法读取配置文件,并将 Timer Interval 设置为配置文件中的 key=value“TefulaatTimer.Interval”,然后启动 Timer。OnPause 和 OnContinue 方法在此未实现,但它们也会由 SCM 调用。您可以想象,我们可以在此处停止计时器并根据各自的过程重新启动它。OnStop 方法定义如下
我们只需停止计时器。在我们的 TefulaatTimer_Tick 方法中,在
这就是代码的所有精华所在。在此方法中,我们获取当前的日期和时间,并根据配置文件设置检查时间。如果时间与我们配置文件中的指定时间匹配,我们就读取 XML 元素列表,并解析 XmlDocument 以获取在该特定时间播放的所有祷告。mp3 文件在 DirectShow 对象中加载并相应播放。其余代码位于源文件中。
PlayIntro 方法(部分)
最后两件事我们需要做的是为我们的应用程序创建一个主入口点并创建一个安装程序。(实际上,我们不需要创建安装程序,因为我们可以随时手动安装——不知何故,在 Win XP Pro 中,您必须这样做。)主入口函数如下所示
请记住,Windows 服务文件是一个普通的 Exe 应用程序,它有一个继承自 ServiceBase 类的类,因此我们需要为我们的应用程序提供一个入口点。实际上,我只需要添加一次 eventlog,添加的次数越多,它就会在您的事件日志中写入的次数越多。您唯一应该密切关注的是这两行代码:System::ServiceProcess::ServiceBase * ServicesToRun[] = { new TefulaatService::Tefulaat() }; ServiceProcess::ServiceBase::Run(ServicesToRun); 这两行分别调用您类或类的构造函数(如果您想在应用程序中拥有多个服务),并注册您的类作为服务运行。当您的服务作为 LocalSystem 对象运行时,当 Win2K 或 Win XP 运行服务时,这个主入口运行,它初始化您的服务。现在,使用“/clr”选项编译应用程序,并链接所有 obj 文件,您就拥有了一个准备运行的 Windows 服务。安装:好的,我有一个服务,但我该如何安装呢???安装 Windows 服务有两种基本方法:自动使用某种脚本方法或 ServiceInstaller,或者手动安装。从一开始,我就无法让托管 C++ 服务应用在 Win XP Pro 上正确安装,因此我手动安装了它(某种程度上…)使用 ServiceInstaller 和 ProcessInstaller。对于您在应用程序中创建的每个服务,您都需要一个 ServiceInstaller 对象和一个 ProcessInstaller 对象。这两个指针存在于继承自 System::Configuration::Install::Installer 类的类中。这是 Header 信息
这里要注意的一件事是放在类上的 [RunInstaller(true)] 属性。这会标记 installutil.exe 程序运行此 Installer 类以安装服务。否则,installutil.exe 程序将完全忽略服务的安装;这意味着服务不会被安装。现在是源文件
需要注意的代码是 InitializeComponent() 方法中的代码。创建了一个新的 serviceProcessInstaller。然后创建了一个新的 ServiceInstaller。Process Installer 创建一个事务性进程来安装服务。如果一个服务失败,那么当尝试安装多个服务时,整个过程将回滚其安装。Service Process 可以作为特定的用户、localSystem 或其他服务帐户运行,就像任何其他 Windows 服务一样。使用 Service Process Installer 来设置此信息。Service installer 实际上安装了服务。会创建一个注册表以及显示名称和服务名称。请记住,服务名称必须与您在实际服务中创建的服务名称相同,否则此安装将无法正常工作。要安装 Windows 服务,请运行 .NET Framework 附带的命令行实用程序:installutil.exe “MyService.exe” 您可以通过键入“Net Start Tefulaat”来启动服务,或者进入控制面板 -> 管理 -> 服务,找到 Tefulaat 服务,单击高亮显示,然后按启动。手动安装服务(非常危险的技巧):出于某种原因,托管 C++ Win 服务应用程序无法使用 installutil.exe 程序安装。因此,我必须手动安装它。一种方法是创建一个 C# InstallerClass 和一个 C# Dummy Service。编译它并设置您想要的所有属性,例如它是 LocalSystem 对象,其 Service Name =“MyC++ServiceName”,显示名称等。使用上面概述的过程签名及其安装程序类来编译和构建此 C# Dummy 服务。然后运行 installutil.exe “MyC#DummyService.exe”命令。接下来运行“Regedit 或 Regedt32”,然后转到 currentcontrolset hive,然后是 system->services hive。接下来搜索您为服务指定的“ServiceName”hive,并将可执行文件的路径更改为您的托管 C++ 应用程序二进制文件。编译:从命令行编译时,请编译您的 InstallClass.cpp 和您的 ServiceClass.cpp,确保您在链接 ServiceClass.obj 之前链接 InstallerClass.obj,我发现反过来链接时存在不一致。信不信由你,就是这样了!作者注:要使 MP3 文件播放,请确保 3PM.xml、9PM.xml、9AM.xml 和 Intro.xml 文件在其内容中指向硬盘上的有效 mp3 文件。另外,请确保配置文件在其内容中指向 xml 文件的正确硬盘路径。配置文件应命名为“Filename.exe.config”,并应位于 exe 文件所在的目录中。如果您使用的是 Visual Studio.Net,您还需要 DirectX include 文件及其库。如果您使用的是文本编辑器,您也需要相同的。即将推出…如何使用服务控制管理器客户端应用程序控制服务 -Dwight Goins, MCSD, MCT, MCP .NET DNGoins@aol.com 2002/26/07