用托管C++编写Windows服务

本文介绍如何使用托管C++来创建Windows服务,包括创建服务工程、编写启动和停止逻辑、安装和卸载服务的过程。同时展示了如何通过配置文件设定服务行为,以及如何利用事件日志记录服务活动。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 
用托管C++编写Windows服务
 
 
         多年以来,只要提到编写Windows服务,就会想到用Visual C++编写,同时,这也是其中一件C++程序员可以做,而VB程序员不可以做的事情。以前,我们只称其为“服务”或“NT服务”,现在,它们被命名为“Windows服务”,而且用VB.NET或C#也可以很容易地编写。
         但是,如果你想用托管C++来编写呢?毕竟,大多数有经验的Visual C++程序员都会写过一两个服务,也会知道怎样完成一个类似的工程。假设你有一个必须要一直运行以提供服务的程序,且连接着一些远程电脑,如果不想编写一本使用手册,告诉客户要记得在每次重启电脑之后重新运行此程序,你就应该使它成为一个服务;又假设你有一个用于删除过期数据库记录的便利维护工具,如果不想让管理员每周都亲手运行它一次,你就应该使它成为一个服务。看起来挺吸引人的,那就让我们开始吧。
 
 
         创建服务工程
         以下要做的事情非常简单:打开Visual Studio.NET,创建一个新的工程,在Visual C++工程下,选择Windows服务(.NET)。接下来,为这个服务取一个方便在电脑的服务列表中查找到的名字,在此为CGNotifier。向导会创建一个继承自System::ServiceProcess::ServiceBase的类并打开设计视图,在此,你可放入一个计时器、一个数据库连接,或其他不可见的组件。
         让我们转到代码视图中看一下生成的代码,在此有一个构造函数与一个Dispose方法,这两个你都可以忽略,还有一对重载的方法:OnStart()和OnStop)。在OnStart()中,可编写服务所需的代码。服务中一个重要的范畴是使用“事件引发对象”,例如System::IO::FileSystemWatcher的一个实例,一般可在OnStart()中创建这些对象,在本例中,你可为类加入事件方法,并处理在服务运行期间,由这些对象引发的事件。另有一种服务,它们对发生的事情不作反应,只在每天或每周的特定时间,执行一些特定的任务,这些服务平时通常处于休眠状态,但因为它们的工作状态是持续的,所以不应该停止它们,或者可以把它们放入一个循环中,在特定的时间检查它们是否已被停止。
         OnStart()方法是服务的开始之处,并且会在执行完后返回,在此方法完成之前,服务一般不会显示为“已启动”。这就意味着,不能在OnStart()中放入一个经常使用的循环,或从别处直接调用的任何方法。最直接的方法是设置好一个单独的方法,并在一个新线程中调用它,如下所示:
 
private:
 bool stopping;
 int loopsleep;              //毫秒
 Threading::Thread* servicethread;
 
protected:
 //设置好服务应做的工作
    void OnStart(String* args[])
    {
      Threading::ThreadStart* threadStart =
               new Threading::ThreadStart(this,mainLoop);
      servicethread = new Threading::Thread(threadStart);
      servicethread->Start();
    }
    void mainLoop()
    {
      loopsleep = 1000;       //毫秒
      stopping = false;
      while (!stopping)
      {
        Threading::Thread::Sleep(loopsleep);
      }
    }
 
         这个循环将会一直运行,直到服务停止,因为OnStop()设置了停止标志:
 
void OnStop()
{
 stopping = true;
}
 
         如果你增加loopsleep值,则会在停止时,增加服务的响应时间。
 
 
         安装服务
         尽管这个服务什么也不做,但你仍可对它进行安装、启动和停止。为简化安装过程,可在工程中加入一个安装程序,这可在设计视图中完成(如果你喜欢,可在设计视图中打开属性窗口,并修改ServiceName属性;而向导会在工程名后加上WinService,这最好在添加安装程序之前完成,否则,就需要在多处修改服务名。),鼠标右键单击设计视图,选择添加安装程序。这将创建一个服务安装程序和一个服务过程安装程序,并显示在设计视图中,以供你设置它们的属性。
         如果已经阅读了有关Windows服务的 .NET文档,你可能会想为什么要添加一个安装程序呢?难道不可以自动添加吗?实际上,如果是使用VB或C#,是可以自动添加的,而C++却不行。
         服务过程安装程序只有一个比较让人感兴趣的属性:服务所运行的账户。单击serviceProcessInstaller1选择它,打开其属性窗口。默认情况下,账户属性为User,这意味着在安装服务时,将会提示输入一个ID和密码,而且服务将会运行于user权限下——这在服务运行于system账户时非常有用。通常有三个选项:LocalSystem是服务被安装于未运行Windows 2003的电脑上时的唯一选择;如果服务是面向Windows 2003的,那么LocalService的权限更少,因为是更好的选择;而NetworkService允许服务验证另一台电脑,所以只在需要使用它(例如,一个服务加载了一个web页),相反,在使用公共web服务时,就不需要作为NetworkService运行,因为它不需验证远程电脑。
         而服务安装程序中需要注意的属性是StartType:手动、自动、禁用。在此例中为手动。
 
         现在,可以生成服务,并准备安装了。打开Visual Studio命令提示符,定位到工程的Debug文件夹,输入以下命令:
 
InstallUtil CGNotifier.exe
 
         以下是屏幕的输出:
 
Microsoft (R) .NET Framework Installation utility
              Version 1.1.4322.573
Copyright (C) Microsoft Corporation 1998-2002.
              All rights reserved.
 
Exception occurred while initializing the installation:
System.IO.FileLoadException: Unverifiable image 'CGNotifier.exe'
cannot be run.
 
 
         这真是难以理解,不是吗?在C++中编写可验证代码向来都是不可能的,且非常难以实现。为什么工程向导创建了一个服务,但却没有提示你代码必须为可验证的呢?其实不必使你的服务程序产生可验证代码。
         打开解决方案资源管理器,找到并打开相应的 .cpp文件,你将会发现隐藏在此的一个main()函数——正是这个main()函数以一种“聪明”的方式为你调用了InstallUtil,并产生了整个的“可验证代码”问题。现在回到命令提示符窗口,像以下这样安装服务:
 
CGNotifier.exe -Install
 
         你可看到服务轻松、流畅地安装上去了。
         为进行测试,现在打开“计算机管理”,并展开“服务和应用程序”项,选择“服务”,你可看到新安装上去的服务:右键单击它选择启动。一旦服务启动,切换回Visual Studio,选择服务器资源管理器查看此服务:依次选择视图、服务器资源管理器,展开你的计算机名,再展开服务,你将看到一个新服务,而带有的绿色三角形表明它正在运行。
 
 
 
         在服务器资源管理器中右击此服务,选择停止。现在,请在“事件查看器”中查看事件记录,可看到二个日志记录:一个告诉你服务已启动,而另一个告诉你服务已停止。如果你不想产生事件日志记录,请在服务的设计视图中修改AutoLog属性为False。
 
 
         卸载服务
         如果你从Debug目录中安装此服务,在对它进行修改期间,并不需要卸载,把它停止,重新生成,再启动就行了。但是,如果你想卸载它,请回到Visual Studio命令提示符窗口,定位到Debug目录,输入以下命令:
 
CGNotifier.exe -Install /u
 
         现在,服务就会从“服务器资源管理器”和“计算机管理”的服务列表中消失了,也许,需要刷新列表才能看到变化。
 
 
         唤醒后做一些事情
         当然,以上所示的服务到目前为止并不能做任何事情,为把它变成一个“在设定时刻唤醒”的服务,第一步应在工程中加入一个配置文件,示例如下:
 
<configuration>
 <appSettings>
    <add key="runhour" value="22" />
 </appSettings>
</configuration>
 
         另外,还需要复制带应用程序名如app.config文件到目标工程目录(Debug或Release):
 
copy app.config $(ConfigurationName)/$(TargetFileName).config
 
         为了读取配置,可在OnStart()或mainLoop()中循环之前加入相应的代码,在此倾向于尽可能地保持OnStart()为空,因此在mainLoop()中加入以下代码:
 
String* sHour = Configuration::ConfigurationSettings::
                 AppSettings->get_Item("runhour");
int runHour = System::Int32::Parse(sHour);
bool rantoday = false;
 
         而循环则如下所示:
 
stopping = false;
while (!stopping)
{
   if (DateTime::Now.Hour == runHour && !rantoday)
   {
      //执行相应的任务
      rantoday = true;
   }
   else
      rantoday = false;
   Threading::Thread::Sleep(loopsleep);
}
 
         因为到了事先约定的时间,只想要上述代码运行一次,因此,在服务执行完相应的任务之后,必须把rantoday标志设为true,只要在其他时间,都会被设为false。
 
         你可以在服务中查找数据库的新记录、或查找过期的文件并删除它们,当然,在服务中可以做的事情远远不只这些。但不管要执行的任务是什么,都需要告诉其他人你做过什么,因为服务不具备一个用户界面,所以也不能弹出一个消息框,因此,使用事件日志是一个不错的方法。
         请在mainLoop()的循环之前加入以下代码,以用于设置事件日志记录:
 
Diagnostics::EventLog* log ;
if (! Diagnostics::EventLog::SourceExists("CGNotifierService") )
    Diagnostics::EventLog::CreateEventSource("CGNotifierService",
                                              CGNotifierLog");
log = new Diagnostics::EventLog("CGNotifierLog");
log->Source = "CGNotifierService";
 
         虽然不用同时设置日志和源代码,但这样做的话,消息会在服务器浏览器的事件日志之下,创建它们自己的节点。
 
 
 
         为向日志中写入,通常只需一行代码——可把它放在“执行相应任务”的注释之后:
 
log->WriteEntry("服务的运行时间到了。",
                Diagnostics::EventLogEntryType::Information);
 
         现在,我们大功告成:一个可以安装、卸载、启动、停止,并每天向事件日志中写入一条信息的服务诞生了!从此以后,你将无往不利,用C++编写的Windows服务可不像其他那些 .NET应用程序,它只局限于你的想象力。另外,在创建服务工程时,还要注意分清C++与VB及C#之间的细微差别。还等什么呢,赶快动手啊!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值