多年以来,只要提到编写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文件夹,输入以下命令:
#p#副标题#e#
InstallUtil CGNotifier.exe
在服务器资源管理器中右击此服务,选择停止。现在,请在"事件查看器"中查看事件记录,可看到二个日志记录:一个告诉你服务已启动,而另一个告诉你服务已停止。如果你不想产生事件日志记录,请在服务的设计视图中修改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;