使用
C# Windows
服务的新项目创建向导可以创建
Windows
服务,该项目命名为
QuoteService
,其窗口如图
32-7
所示。注意,在选择项目时不要误选为
Web
服务项目。
图
32-7
在单击
OK
按钮开始创建
Windows
服务应用程序之后,就会出现一个外表与
Windows Forms
应用程序相似的设计器,但是不能在其中**
Windows Forms
组件,因为应用程序不能直接在屏幕上显示任何信息,本章的后面将使用设计器添加性能计数器和事件日志等其他组件。
选择这个服务的属性,可以打开如图
32-8
所示的属性编辑窗口。
图
32-8
使用服务属性可以配置如下值:
●
AutoLog
指定启动和停止服务的事件自动写到日志文件中。
●
CanPauseAndContinue
、
CanShutdown
和
CanStop
指定服务可以处理具体的暂停、继续、关闭和停止服务的请求。
●
ServiceName
是写到注册表中的服务名称,使用这个名称可以控制服务。
●
CanHandlePowerEvent
选项对运行在膝上计算机的服务有效。如果启用这个选项,服务就可以响应低电源事件,改变服务的操作方式。
提示:
不管项目的名称是什么,默认的服务名称都是
WinService1
。可以只安装一个
WinService1
服务。如果在测试过程中出现了安装错误,有可能已经安装了
WinService1
服务。因此,在服务开发的初始阶段,一定要用属性编辑器把服务的名称改为比较适当的名称。
使用属性编辑器改变上述属性,在
InitalizeComponent()
方法中设置
ServiceBase
派生类的值。
Windows Forms
应用程序中也使用
InitalizeComponent()
方法,对于服务而言,这个方法的使用方式与
Windows Forms
应用程序相似。
向导将生成代码,但是我们将把文件名改为
QuoteService.cs
,把命名空间的名称改为
Wrox.ProCSharp.WinServices
,并把类名改为
QuoteService
。后面将详细讨论这些代码。
1. ServiceBase类
ServiceBase
类是所有
.NET
服务的基类。
QuoteService
类就是从
ServiceBase
类派生出来的;
QuoteService
类使用一个未标注的帮助类
System.ServiceProcess.NativeMethods
与
SCM
进行通信,
System.ServiceProcess.NativeMethods
是
Win32 API
调用的包装类。
ServiceBase
类是私有的,因此,不能在这里的代码中使用它。
图
32-9
显示了
SCM
、
QuoteService
类和
System.ServiceProcess
命名空间中的类是怎样相互作用的。在这个图中,垂直方向为对象的生命线,水平方向为通信情况,通信是按照时间的先后顺序而进行的。
SCM
启动应该启动的服务进程。首先调用
Main()
方法。在示例服务的
Main()
方法中,调用
ServiceBase
基类的
Run()
方法。
Run()
使用
SCM
中的
NativeMethods.StartServiceCtrl Dispatcher()
注册
ServiceMainCallback()
方法,并把记录写到事件日志中。
接下来,
SCM
在服务程序中调用已注册的
ServiceMainCallback()
方法。
ServiceMainCallback()
本身使用
NativeMethods.RegisterServiceCtrlHandler[Ex]()
在
SCM
中注册处理程序,并在
SCM
中设置服务的状态。之后调用
OnStart()
方法。在
OnStart()
中,必须执行启动代码。如果
OnStart()
执行成功,就把字符串
Service Started Sucessful
写到事件日志中。
处理程序是在
ServiceCommandCallback()
方法中执行的。当改变了对服务的请求时,
SCM
就调用
ServiceCommandCallback()
方法。
ServiceCommandCallback()
方法再把请求发送给
OnPause()
、
OnContinue()
、
OnStop()
、
OnCustomCommand()
和
OnPowerEvent()
。
图
32-9
2. 主函数
现在讨论服务进程中由应用程序向导生成的主函数。在主函数中,声明了一个元素为
ServiceBase
类的数组
ServicesToRun
。创建
QuoteService
类的一个实例,并作为
ServicesToRun
数组的第一个元素。如果在这个服务进程中要运行多个服务,就需要把具体服务类的多个实例添加到数组中。然后把
ServicesToRun
数组传递给
ServiceBase
类的静态方法
Run()
。使用
ServiceBase
的
Run()
方法,可以把
SCM
引用供给服务的入口点。服务进程的主线程现在处于停滞状态,等待服务的结束。
下面是自动生成的代码:
// The main entry point for the process
static void Main()
{
System.ServiceProcess.ServiceBase[] ServicesToRun;
// More than one user Service may run within the same process. To
// add another service to this process, change the following line
// to create a second service object. For example,
//
// ServicesToRun = New System.ServiceProcess.ServiceBase[]
// {
// new WinService1(), new MySecondUserService()
// };
//
ServicesToRun = new System.ServiceProcess.ServiceBase[]
{
new QuoteService()
};
System.ServiceProcess.ServiceBase.Run(ServicesToRun);
}
如果进程中只有一个服务,就可以删除数组。
Run()
方法接收从
ServiceBase
派生出来的单个对象,因此
Main()
方法可以简化为:
System.ServiceProcess.ServiceBase.Run(new QuoteService());
如果有多个服务,例如
Windows
程序
Services.exe
就包含多个服务,并且需要那些服务有共享的初始化,则共享的初始化必须在
Run()
方法运行之前完成。因为主线程处于停滞状态,直到服务进程停止为止,以后的指令在服务结束之前就不能执行。
初始化花费的时间不应该太长,通常不应该超过
30
秒。如果执行初始化代码所花费的时间过多,则服务控制管理器就认为服务启动失败了。初始化时间不应该超过
30
秒,必须是针对速度最慢的机器而言。如果初始化的时间过长,就应该在另一线程中进行初始化,以便主线程及时地调用
Run()
。然后,事件对象可以用信号通知线程已经完成了它的工作。
3. 服务的启动
在服务启动时,调用
OnStart()
方法。这时,可以启动套接字服务器。为了使用
QuoteServer
,必须引用
QuoteServer.dll
程序集。调用
OnStart()
的线程不能停滞下来,
OnStart()
方法必须返回给调用者
(
即
ServiceBase
类的
ServiceMainCallback()
方法
)
。
ServiceBase
类注册处理程序,并在调用
OnStart()
之后把服务成功启动的消息通知给
SCM
:
protected override void OnStart(string[] args)
{
quoteServer = new QuoteServer(@"c:\ProCSharp\Services\quotes.txt",
5678);
quoteServer.Start();
}
quoteServer
变量声明为类中的私有成员:
namespace Wrox.ProCSharp.WinServices
{
public class QuoteService : System.ServiceProcess.ServiceBase
{
private System.ComponentModel.Container components = null;
private QuoteServer quoteServer;
4. 处理程序方法
当停止服务时,就调用
OnStop
方法。应该在
OnStop
方法中停止服务的功能:
protected override void OnStop()
{
quoteServer.Stop();
}
除了
OnStart()
和
OnStop()
之外,还可以重写服务类中的下列处理程序:
●
OnPause()
:在暂停服务时,调用这个方法。
●
OnContinue()
:当服务从暂停状态返回到正常操作时,调用这个方法。为了调用已重写的
OnPause()
方法和
OnContinue()
方法,
CanPauseAndContinue
属性必须设置为
true
。
●
OnShutdown()
:当
Windows
操作系统关闭时,调用这个方法。通常情况下,
OnShutdown()
方法的行为应该与
OnStop()
方法相似。如果需要更多的时间关闭服务,则可以请求更多的时间。与
OnPause()
和
OnContinue()
相似,必须设置一个属性使行为有效,即
CanShutdown
属性必须设置为
true
。
●
OnCustomCommand()
:这个处理程序可以为服务控制程序发送过来的定制命令提供服务。
OnCustomCommand()
方法有一个用于获取定制命令号码的
int
参数,号码的取值范围是
128
至
256
,小于
128
的值是为系统预留的。在我们的服务中,使用定制命令号码为
128
的命令重新读取引用文件:
protected override void OnPause()
{
quoteServer.Suspend();
}
protected override void OnContinue()
{
quoteServer.Resume();
}
protected override void OnShutdown()
{
OnStop();
}
public const int commandRefresh = 128;
protected override void OnCustomCommand(int command)
{
switch (command)
{
case commandRefresh:
quoteServer.RefreshQuotes();
break;
default:
break;
}
}