用Delphi编写Windows服务程序

一、Windows服务简介

  服务程序(Service Application)是一种运行于WinNT的后台程序,每个服务程序(Service Application)中可能包含若干个服务(Service),每个服务就是其中的一个线程(该服务也可以创建多个子线程)。采用服务,应用程序可以获得特殊的权限,而且不会被用户通过Win2000的任务管理器直接结束程序,所以服务常常用来实现一些特殊的目标。

  通过Win2000控制面板中的服务管理工具,我们可以设置/查看服务的特性:

  (1)服务名称;(2)显示名称;(3)描述;(4)启动类型;(5)依赖关系;

  其中,服务名称是标识给服务的。

  以Win2000的C:\WINNT\System32\services.exe程序为例子,该Exe文件对应一个Service Application,是该服务程序的可见实体;该exe中包含多个服务(Service),例如Alerter,Dhcp(DHCP Client),Messenger等。当我们结束一个服务的时候,该服务所在的Service Application中的其他服务并没有被终止。

  在Delphi中,Borland的工程师为我们提供了TServiceApplication,TService,TServiceThread等类,封装了大量细节,简化了服务程序的开发。

二、TServiceApplication

  在Delphi中,类TServiceApplication就对应上述的ServiceApplication。利用Delphi的开发环境,我们新建一个Service Application Project,同时就创建了一个继承自TService的类。项目文件中的Application对象就是一个TServiceApplication实例。每个TServiceApplication包含若干个TService对象,正好对应上述的服务程序和服务之间的数量关系。

  通过阅读TServiceApplication和TService类的定义,可以得知,TServiceApplication从TComponent类继承而来,TService从类TDataModule基础而来,Application对象负责各个TService对象的Create和Destroy。跟踪下列代码

  Application.CreateForm(TService1, Service1);

  可以发现创建的TService对象的Owner都是Application对象;在VCL FrameWork中Owner总是负责Destroy各个Component对象(VCL的TComponent类采用了Composite模式),所以TServiceApplication也将Destroy各个TService对象。

  下面跟踪TServiceApplication.Run的代码,可以发现TServiceApplication首先解析运行参数,实现了服务的Install和Uninstall。然后,初始化一个ServiceStartTable数组,该数组包含了各个service对象的服务名称和运行入口;最后创建一个TServiceStartThread 对象,该对象是一个线程对象,从线程调用API:StartServiceCtrlDispatcher来启动ServiceStartTable中指定的若干个服务;而ServiceApplication主线程就不断循环,处理消息,比如接收请求来停止/暂停某个服务。

三、TService

  TService类继承自类 TDataModule,这意味着我们可以加入大量的VCL控件,实现丰富的功能。此外,我们还可以处理OnStart,OnPause,OnStop,OnContinue,OnCreate,OnShutDown等事件。其中需要说明的是:OnStop表示该服务被停止;而OnShutDown表示该ServiceApplication停止运行,这意味着其他服务也被终止了;两者含义是不一样的。

 

  前面讲过,ServiceApplication通过调用StartServiceCtrlDispatcher来启动各个服务。StartServiceCtrlDispatcher启动TService的入口,该入库就是TService.Main。TService.Main首先注册该服务,然后调用TService.DoStart。TService.DoStart创建一个内部TServiceThread成员对象,这是一个线程对象;考察TServiceThread.Execute可以得知,当我们处理的TService1. OnExecute,那么TService会把所有的请求委托给该TServiceThread成员对象处理,该对象以默认的方式处理所有的请求。

  TService. ServiceExecute是TService的主体内容。一个服务要正常运行,除了需要处理它要关注的目标(比如监听某个端口、执行某个任务等)外,还要响应外部命令/请求:比如终止、暂停、恢复该服务。因此可以考虑创建一个专门的线程来完成该任务,而在ServiceExecute中处理外面命令/请求。因此代码如下:

       while not Terminated do begin

              ServiceThread.ProcessRequests(False);

       end;

  当然,也可以在OnExecute中处理某些任务,如监听某个端口,但是这常常会导致该Service不能及时响应Stop/Pause等请求。当OnExecute执行完了,该服务实际上就完成了任务要结束了(terminate)。


Windows的服务是一个比较实用的功能,你的程序可以在Windows未进行登录的时候就开始运行,不受用户注销的影响,也不容易被用户误关闭。

但是编写服务也许不是一件容易的事情,幸好Delphi给我们提供了一个模板,可以很容易的编写一个标准的Windows服务程序。

首先,在Delphi内新建一个Service Application。

此时,Delphi已经给我们建好了一个Service程序的框架,我们只需要把我们的代码加到合适的位置就行了。

一般情况下,Service内需要一个线程来不断的工作,也许定时器也可以,但线程工作起来更好。

Delphi会生成一个可视化的Service容器,你可以一些必要的控件在它上面,但是由于它是服务程序,是没有界面显示的,因此不建议在上面安放Edit之类的控件,服务只是应该做处理工作的,显示界面应该由其它的程序来完成。Service控件的DisplayName属性是显示在管理工具-》服务的左边的名称的内容,而Name属性则是服务名称,当你用命令提示符来启动、停止服务时,就需要用到。

在事件OnStart内,我们应该完成启动线程的工作。

如:



procedure TS2HConv.ServiceStart(Sender: TService;
var Started: Boolean);
var Reg:TRegistry;
LogFileName,LogPath:String;
slTemp:TStringList;

begin

CoInitialize(
nil);
Reg:
=TRegistry.Create;
Reg.RootKey:
=HKEY_LOCAL_MACHINE;
Reg.OpenKey(
'\SoftWare\BHome\Education',True);
LogPath:
=Trim(Reg.ReadString('LogPath'));
SourceConnStr:
=Trim(Reg.ReadString('SourceConnStr'));
if Trim(LogPath)='' then
LogPath:
='C:\';
Reg.CloseKey;
Reg.Free;

if RightStr(LogPath,1)<>'\' then
LogPath:
=LogPath+'\';
LogFileName:
=LogPath+FormatDateTime('yyyymmdd',Now)+'Log.txt';
Try
if not FileExists(LogFileName) then begin
slTemp:
=TStringList.Create;
slTemp.Clear;
slTemp.SaveToFile(LogFileName);
slTemp.Free;
end;
AssignFile(LogFile, LogFileName);
Append(LogFile);
Except
Started:
=False;
Exit;
End;
Started:
=True;

try
AC_Source:
=TADOConnection.Create(nil);
Q_Source:
=TADOQuery.Create(nil) ;
Q_Source.Connection:
=AC_Source;

try
AC_Source.Close;
AC_Source.ConnectionString:
=SourceConnStr;
AC_Source.Open;
SYSLog(
'与源数据库连接成功!');
DBOK:
=True;
Except
on E:Exception 
do begin
DBOK:
=False;
SYSLog(
'数据库连接失败!'+E.Message);
end;
End;

MyPHSThread :
= TPHSSendThread.Create();
MyPHSThread.FreeOnTerminate:
=True;
MyPHSThread.Priority:
= tpLower ;

end;



在OnStop事件内,我们应该停止线程,并释放打开的资源,需要注意的是当你停止线程时,一般用Terminate方法,在线程内用Terminated属性来判断是否需要结束线程,而由于是线程,和主进程是时间运行的,有可能你刚好在进行Terminate时,线程已经刚进行过Terminated判断,正在进行比较费时的处理工作,而此时主进程立即执行Terminate后就进行释放资源的工作时,会造成线程执行错误,因此应该等到线程真正的正确停止后,主进程才能进行资源释放工作。

如:

procedure TS2HConv.ServiceStop(Sender: TService;
var Stopped: Boolean);
begin
try
MyPHSThread.Terminate;
while __ThreadIsRun do
sleep(
1000);
AC_Source.Close;
CloseFile(LogFile);
FreeAndNil(Q_Source);
FreeAndNil(AC_Source);
CoUnInitialize;
Except
End;
Stopped:
=True;
end


procedure TS2HConv.ServiceStart(Sender: TService;
var Started: Boolean);
var Reg:TRegistry;
LogFileName,LogPath:String;
slTemp:TStringList;

begin

CoInitialize(
nil);
Reg:
=TRegistry.Create;
Reg.RootKey:
=HKEY_LOCAL_MACHINE;
Reg.OpenKey(
'\SoftWare\BHome\Education',True);
LogPath:
=Trim(Reg.ReadString('LogPath'));
SourceConnStr:
=Trim(Reg.ReadString('SourceConnStr'));
if Trim(LogPath)='' then
LogPath:
='C:\';
Reg.CloseKey;
Reg.Free;

if RightStr(LogPath,1)<>'\' then
LogPath:
=LogPath+'\';
LogFileName:
=LogPath+FormatDateTime('yyyymmdd',Now)+'Log.txt';
Try
if not FileExists(LogFileName) then begin
slTemp:
=TStringList.Create;
slTemp.Clear;
slTemp.SaveToFile(LogFileName);
slTemp.Free;
end;
AssignFile(LogFile, LogFileName);
Append(LogFile);
Except
Started:
=False;
Exit;
End;
Started:
=True;

try
AC_Source:
=TADOConnection.Create(nil);
Q_Source:
=TADOQuery.Create(nil) ;
Q_Source.Connection:
=AC_Source;

try
AC_Source.Close;
AC_Source.ConnectionString:
=SourceConnStr;
AC_Source.Open;
SYSLog(
'与源数据库连接成功!');
DBOK:
=True;
Except
on E:Exception 
do begin
DBOK:
=False;
SYSLog(
'数据库连接失败!'+E.Message);
end;
End;

MyPHSThread :
= TPHSSendThread.Create();
MyPHSThread.FreeOnTerminate:
=True;
MyPHSThread.Priority:
= tpLower ;

end;

你可能感兴趣的:(delphi)