用托管C++编写Windows服务

发表于:2007-05-25来源:作者:点击数: 标签:windows多年以来C++托管编写
多年以来,只要提到编写 Windows 服务,就会想到用Visual C++编写,同时,这也是其中一件C++ 程序员 可以做,而 VB 程序员不可以做的事情。以前,我们只称其为服务或NT服务,现在,它们被命名为Windows服务,而且用VB.NET或C#也可以很容易地编写。 但是,如
  多年以来,只要提到编写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()中放入一个经常使用的循环,或从别处直接调用的任何方法。最直接的方法是设置好一个单独的方法,并在一个新线程中调用它,如下所示:

clearcase/" target="_blank" >cccccc" width="90%" align="center" bgcolor="#e7e9e9" border="1">
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#之间的细微差别。还等什么呢,赶快动手啊!

原文转自:http://www.ltesting.net

评论列表(网友评论仅供网友表达个人看法,并不表明本站同意其观点或证实其描述)