Windows服务编写综述
作者:李朝中
摘要:几乎所有的操作系统在启动的时候都会启动一些不需要与用户交互的进程,这些进程在Windows中就被称作服务。它由服务程序、服务控制程序(SCP,service control program)和服务控制管理器(SCM,service control manager)三个组件构成。本文针对服务程序与服务控制程序的编写进行综合讲述。
关键词:Windows,服务,VC++
1 服务介绍
几乎所有的操作系统在启动的时候都会启动一些不需要与用户交互的进程,这些进程在Windows中就被称作服务。它通常用于实现客户/服务器模式中的服务器方,如我们常见的Web服务IIS,当操作系统在启动后它就自动被运行,不管是否有人登陆到系统只要系统开启它就能得到运行。
服务程序、服务控制程序(SCP,service control program)和服务控制管理器(SCM,service control manager)组成了Windows服务。我们可以通过服务控制程序操纵服务控制管理器来配置、启动、暂停、停止服务程序。其中服务程序和服务控制程序可以由我们自己来编写扩展,而服务控制管理器(windowssystem32servics.exe)则是操作系统内置的一个部件。首先我们来了解一下SCM的工作情况,然后我们介绍服务程序的编写和服务控制时所涉及API的使用。
2 服务控制管理器
SCM本身也是一个服务程序(windowssystem32servics.exe),作为windows的后台服务运行的。Winlogon在系统引导的早期会将SCM启动起来。SCM的服务入口函数首先创建一个初始化为无信号的同步事件对象(SvcCtrlEvent_A3752DX);接下来,它开始建立一个内部服务数据库,这个数据库要按事先规定好的一个顺序列出所有服务组,并记录与服务相关的详细信息;当这个数据库建立完成时SCM就开始按顺序启动那些启动方式为自动的服务,如果有服务要动行于指定用户账户中时还要调用LSASS,如果服务启动失败则会被放入一个名为ScFailedDrivers的列表中。当这些工作都完成后,SCM将同步事件对象SvcCtrlEvent_A3752DX置为有信号状态;并做好系统停机的准备。
当系统要关机时会向Windows子系统进程Csrss发送一个消息,以便调用Csrss的停机例程。Csrss会对所有活动的进程循环通知系统正在停机。对于除SCM以外的每一个系统进程如果没有返回退出的响应Csrss 都会等待由HKEY_USER.DEFAULTControl PanelDesktopWaitToKillAppTimeout指定的毫秒数(我的系统中是20000,也就是20秒),然后知通下一个进程结束。当遇到SCM时也会通知SCM进程系统正在停机,但不同的是会使用一个专用的超时间隔值(SCM在系统初始化时要向Csrss登记,于是Csrss就将SCM的进程ID保存了下来Csrss也就是通过个ID来识别SCM的)。这个专用的超时间隔位于HKEY_LOCAL_MACHINESYSTEMCurrentControlSetControlWaitToKillServiceTimeout中,有趣的是默认值也是20秒。这在段时间里SCM要通知所有在初始化时(服务程序自身初始化)请求SCM通知自己系统停机的服务,SCM有一个停机处理器负责这项工作。通知下达后SCM就等待服务退出,服务在接收到停机消息后会返回给SCM一个等待时间,SCM跟踪所有服务返回等待时间找出其中的最大值,当这个最大值达到后,如果有一个服务或多个服务又告诉SCM它们正在处理停机工作,那么SCM还会循环等待下去,但Csrss也再等待SCM当Csrss等待超时后,会继续后面的停机工作最终完成停机。
这就要求服务程序要在Csrss等待SCM的这段时内完成自己的停机处理工作,否则服务就没机会在系统停机前完成自己的关闭工作了。
3 服务程序
Windows服务程序其实并不神秘,它只是遵循特定规则编写的一个程序。只要遵循这个特定的规则与服务控制管理器正确的交互,就可实现我们的服务程序。而我们只要能实现一个简单的服务程序,设计一个能处理复杂业务的服务也并非难事,因为从结构上看两者并没有太大的区别。只要遵循与SCM交互的规则,设计服务程序与设计普通的应用程序几乎没什么区别。
3.1 程序结构概要
服务程序的与普通应用程序一样也需要一个主函数(main())作为程序的入口,与之不同的是作为一个服务程序它需要在主函数(main())中立即调用StartServiceCtrlDispatcher来注册一个服务的入口函数
(ServerMain(DWORD argc,LPTSTR *argv),当然这个名字可自由命名)。StartServiceCtrlDispatcher函数的原型是:
BOOL StartServiceCtrlDispatcher(
LPSERVICE_TABLE_ENTRY
lpServiceStartTable
);
它的参数是一个指向SERVICE_TABLE_ENTRY
的指针;
SERVICE_TABLE_ENTRY
结构有两个域;第一个域存储服务的内部名称,第二个域是服务入口函数的指针。这个函数完成后,SCM就要可以服务启动的时候调用服务的入口函数。
例如:管理员在服务管理器启动一个服务,SCM就会在一个单独的线程中调用服务注册的入口函数。这时我们在服务的这个入口函数中必须调用RegisterServiceCtrlHandler完成Handler函数的注册,这个函数用来接收和处理SCM的控制消息。下面列出Hander要处理的控制消息和RegisterServiceCtrlHandler的函数原型:
VOID WINAPI Handler(
DWORD
fdwControl
//
请求控制消息代码
);
控制消息宏定义
|
说明
|
SERVICE_CONTROL_STOP |
要服务停止 |
SERVICE_CONTROL_PAUSE |
要服务暂停 |
SERVICE_CONTROL_CONTINUE |
要服务继续 |
SERVICE_CONTROL_INTERROGATE |
要服务马上报告它的状态 |
SERVICE_CONTROL_SHUTDOWN |
告诉服务即将关机 |
SERVICE_STATUS_HANDLE RegisterServiceCtrlHandler(
LPCTSTR
lpServiceName
,
//
服务的内部名称
LPHANDLER_FUNCTION
lpHandlerProc
// Handler
函数的地址
);
RegisterServiceCtrlHandler
调用完成后我们就可以开始我们的业务处理的初始化工作。初始化完成后向
SCM报告服务开始运行(SERVICE_RUNNING)的消息。如果
ServerMain(DWORD argc,LPTSTR *argv)函数退出服务也就停止了。下面让我总结一下实现服务程序的步骤:
(1)在main()调用StartServiceCtrlDispatcher来注册一个服务的入口函数;
(2)在ServerMain(DWORD argc,LPTSTR *argv)中调用RegisterServiceCtrlHandler
注册Handler函数。
(3)
完成业务处理程序的初始化工作,如果初始化时间较长要实时向SCM报告当前正在启动
(4)
初始化完毕,报告服务正在运行;开始业务处理工作。
3.2 程序实例分析
(1)main()函数
int main(int argc, char* argv[])
{
SERVICE_TABLE_ENTRY serviceTable[]=
{
{
SERVICE_NAME,
(LPSERVICE_MAIN_FUNCTION)ServiceMain
}
{
NULL,NULL
}
};
BOOL success;
success = StartServiceCtrlDispatcher(serviceTable);
if (!success)
{
ErrorHandler("In StartServiceCtrlDispatcher",GetLastError());
}
return 0;
}
StartServiceCtrlDispatcher的参数必须是一个以NULL结尾的数组指针,我们可以在一个程序文件中注册多个服务实例,只在把所要注册的服务名和服务入口函数地址写到数组中即可,在我们调用CreateService创建服务时要把dwServiceType 参数设为共享进程(SERVICE_WIN32_SHARE_PROCESS);不过当要创建独立进程的服务时(dwServiceType 参数为SERVICE_WIN32_OWN_PROCESS时)在这里就只能注册一个服务实例。
(2)服务入口函数ServerMain()
VOID ServiceMain(DWORD argc,LPTSTR *argv)
{
BOOL success;
StatusHandler=
RegisterServiceCtrlHandler(SERVICE_NAME,(LPHANDLER_FUNCTION)Handler);
if (!serviceStatusHandler)
{
return;
}
success = ReportStatus (SERVICE_START_PENDING,
NO_ERROR,0,1,5000);
if (!success)
{
return;
}
endEvent = CreateEvent(0,TRUE,FALSE,0);
if (!endEvent)
{
return;
}
success = ReportStatus (SERVICE_START_PENDING,
NO_ERROR,0,2,5000);
if (!success)
{
return;
}
//////init parameter start
RecvParam(argc,argv);
//////init parameter end
success = ReportStatus (SERVICE_START_PENDING,
NO_ERROR,0,3,5000);
if (!success)
{
return;
}
success = InitService();
if (!success)
{
return;
}
success = ReportStatus (SERVICE_RUNNING,NO_ERROR,0,0,0);
if (!success)
{
return;
}
WaitForSingleObject(endEvent,INFINITE);
}
RegisterServiceCtrlHandler完成Handler函数的注册( Handler函数的具体实现我们在第三小节中介绍),它的第一个参数是调用CreateService创建服务时lpServiceName
指向的名服务名称,每二个参数是
Handler函数的地址;函数名可以自由命名。
ReportStatus是向SCM报告服务当前状态的一个自定义函数。它内部调用SetServiceStatus向SCM报告服务的当状态,此函数有两个参数第一个就是RegisterServiceCtrlHandler完成时返回的SERVICE_STATUS_HANDLE,第二个参数是一个SERVICE_STATUS
变量的指针,它指示了服务当前的状态信息
;当注册完Handler函数后向SCM报告一下自己当前的状态(正在启动)。接着创建endEvent事件对像,它是当我们收到SCM的退出控制代码时通知服务主函数退出的,大家可看ServiceMain的最后一句。下面又是向SCM报告自己正在启动,当初始化所花费的时间非常短时这样做并不是必须的,但如果很长就必须这样做。RecvParam(argc,argv)使用了ServiceMain函数的两个参数,大家可以看出ServiceMain和main有着一样的形参;说明ServiceMain和main一样可以接收配置参数,稍后我们会在服务控制程序的编写中给大家介绍如何给服务配置参数。InitService()完成我们的业务初始化工作并开始业务处理。最后报告服务启动完成,等待endEvent事件退出服务。下面我们再来看一下SCM控制消息的处理。
(3)SCM控制消息处理(Handler函数)
VOID Handler(DWORD controlCode)
{
DWORD currentState = 0;
BOOL success;
switch(controlCode)
{
case SERVICE_CONTROL_STOP:
success=ReportStatus(SERVICE_STOP_PENDING,NO_ERROR,0,1,5000);
CloseTask();
success=ReportStatus(SERVICE_STOPPED,NO_ERROR,0,0,0);
return;
case SERVICE_CONTROL_PAUSE:
if (runningService&&!pauseService)
{
success=ReportStatus(SERVICE_PAUSE_PENDING,NO_ERROR,0,1,1000);
pauseService=TRUE;
ServicePause();
currentState=SERVICE_PAUSED;
}
break;
case SERVICE_CONTROL_CONTINUE:
if (runningService&&pauseService)
{
success = ReportStatus(SERVICE_CONTINUE_PENDING,
NO_ERROR,0,1,1000);
pauseService = FALSE;
ServiceContinue();
currentState=SERVICE_RUNNING;
}
break;
case SERVICE_CONTROL_INTERROGATE://检索更新状态的时
break;
case SERVICE_CONTROL_SHUTDOWN://告诉服务即将关机
success=ReportStatus(SERVICE_STOP_PENDING,NO_ERROR,0,1,5000);
CloseTask();
return;
default:
break;
}
ReportStatus(currentState,NO_ERROR,0,0,0);
}
Handler只有一个参数就是SCM传来的控制消息代码;这里处理的了停止,暂停,继续,更新,关机五个控制消息。但并不是这五个消息SCM都会向服务发送,要在向服务报告状时向SCM报告自己可以响应的控制消息,只要设置SERVICE_STATUS
结构中的
dwControlsAccepted域即可,它对应的值有:SERVICE_ACCEPT_STOP,SERVICE_ACCEPT_PAUSE_CONTINUE,SERVICE_ACCEPT_SHUTDOWN,当要设置多个时只要把宏相或(|)传给dwControlsAccepted域即可。在响应SCM控制消息时也要注意及时报告服务当前的状态信息,否则SCM会认为服务响应超时出错了。
(4)服务的安装与卸载
服务程序编写完成并编译通过后,还要安装注册到操作系统中,这样它才会出现在管理工具->服务,那个管理器里面。API给我们提供了一个函数来实现我们注册服务的功能; SC_HANDLE CreateService(
SC_HANDLE
hSCManager
,
//
服务控制管理器的句柄
LPCTSTR lpServiceName, //
指向服务的内部名称
LPCTSTR
lpServiceName
,,
//
指向服务的显示名称
DWORD
dwDesiredAccess
,
//
服务的访问类型
DWORD
dwServiceType
,
//
服务的类型
DWORD
dwStartType
,
//
服务的启动方式(自动,手动,禁用)
DWORD
dwErrorControl
,
//
错误控制方式
LPCTSTR
lpBinaryPathName
,
//
服务程序的路径
LPCTSTR
lpLoadOrderGroup
,
//
服务组的名称
LPDWORD
lpdwTagId
,
//
服务的标签号
LPCTSTR
lpDependencies
,
//
服务依赖的服务或组名
LPCTSTR
lpServiceStartName
,
//
服务的启动帐户
LPCTSTR
lpPassword
//
服务启动帐户的密码
);
hSCManager
:
这是函数的第一个参数-
SCM的句柄。它要调用
OpenSCManager
来获得,稍后我们会讲它怎么调用方法。
lpServiceName
和
lpServiceName
:
分别的服务的名称和服务的显示名称,服务是显示名称就服务管理器中看到的那个服务名。则是服务在
SCM中注册的名称,比如调用OpenService打开服务时就会用到它。
dwDesiredAccess
:
标出服务同意请求的访问,可以是下面任意任值:
SERVICE_ALL_ACCESS
SERVICE_CHANGE_CONFIG
SERVICE_ENUMERATE_DEPENDENTS
SERVICE_INTERROGATE
SERVICE_PAUSE_CONTINUE
SERVICE_QUERY_CONFIG
SERVICE_QUERY_STATUS
SERVICE_START
SERVICE_STOP
SERVICE_USER_DEFINED_CONTROL
我们可以指定一个或多个,如果有多个的话要用或符号(|)联结起来。
dwServiceType
:
注册服务的类型,它必须是下面的值:
SERVICE_WIN32_OWN_PROCESS
SERVICE_WIN32_SHARE_PROCESS
SERVICE_KERNEL_DRIVER
SERVICE_FILE_SYSTEM_DRIVER如果指定的是SERVICE_WIN32_OWN_PROCESS类型的服务还可以加上SERVICE_WIN32_OWN_PROCESS(允许用户桌面交互),我们这里介绍的服务只能注册为SERVICE_WIN32_OWN_PROCESS或SERVICE_WIN32_SHARE_PROCESS;另两种类型是驱动级的服务用的,有兴趣大家可查看相关资料。
dwStartType
:
服务的启动类型
SERVICE_BOOT_START、SERVICE_SYSTEM_START、SERVICE_AUTO_START、SERVICE_DEMAND_START、SERVICE_DISABLED。分别为前两种类型仅对驱动程序用效,所在我们这里所说的这类服务能后三种(自动,手动,禁用
)。
dwErrorControl
:
服务的错误控制标记
SERVICE_ERROR_IGNORE:忽略所有错误
SERVICE_ERROR_NORMAL:正常报告服务返回的错误
SERVICE_ERROR_SEVERE:当服务返回错误出现时,如果最后已知好控制集(最后已知好控制集:是系统最后一次成功引导时使用的服务注册表配置)尚未使用,则重新引导进入最后已知好控制集,否则重新引导。
SERVICE_ERROR_CRITICAL:当服务返回错误出现时,如果最后已知好控制集尚未使用,则重新引导进入最后已知好控制集,否则蓝屏崩溃。
lpBinaryPathName
:
服务程序的文件路径
lpLoadOrderGroup
:
服务所属的组
lpdwTagId
:
在组中的唯一标识
lpDependencies
:
服务所依赖的其它组和服务
lpServiceStartName
和lpPassword
:
服务由哪个用户启动,也即服务运行在哪个用户权限下,分别指定用户名和密码.
下再说两重要的函数:OpenSCManager和CloseServiceHandle给出它们的原型
SC_HANDLE OpenSCManager(
LPCTSTR
lpMachineName
,
//
机器名,打开本机的SCM时可为NULL
LPCTSTR
lpDatabaseName
,
//
指向SCM数据库的名字可为NULL
DWORD
dwDesiredAccess
//
访问权限类型如:
SC_MANAGER_ALL_ACCESS等
);
BOOL CloseServiceHandle(
SC_HANDLE
hSCObject
//
服务控制句柄
);
这果列出一段注册服务的代码供大家参考:
SC_HANDLE newService,scm;
BOOL success = FALSE;
SERVICE_STATUS status;
scm = OpenSCManager(NULL,NULL,
SC_MANAGER_ENUMERATE_SERVICE|SC_MANAGER_CREATE_SERVICE);
if (!scm){
OUT_DEBUG("OpenSCManager ERROR!");
return false;
}
newService = CreateService(scm,pszServiceName,pszDisplayName,
SERVICE_ALL_ACCESS|SERVICE_STOP,SERVICE_WIN32_OWN_PROCESS,
SERVICE_AUTO_START,SERVICE_ERROR_NORMAL,pszServicePath,
0,0,0,0,0);
if (!newService){
OUT_DEBUG("CreateService ERROR!");
CloseServiceHandle(scm);
return false;
}
CloseServiceHandle(newService);
CloseServiceHandle(scm);
return true;
删除服务时调用DeleteService;它只有一个参数(服务句柄)。我们可分四步完成1)、打开SCM句柄。2)、打开要删除的服务。3)、检查当前服务的状态确保服务已经停止。4)、删除服务并关闭所有打开的句柄。下面是一段删除服务的程序。
SC_HANDLE Service,scm;
SERVICE_STATUS status;
BOOL success;
if (pszServiceName==NULL)
{
return false;
}
scm = OpenSCManager(NULL,NULL,SC_MANAGER_ENUMERATE_SERVICE);
if (!scm){
cout<<"OpenSCManager ERROR:"<<GetLastError()<<endl;
CloseServiceHandle(scm);
return false;
}
Service = OpenService(scm,pszServiceName,SERVICE_ALL_ACCESS|DELETE);
if (!Service){
cout<<"OpenService ERROR:"<<GetLastError()<<endl;
CloseServiceHandle(Service);
CloseServiceHandle(scm);
return false;
}
success = QueryServiceStatus(Service,&status);
if (!success){
cout<<"QueryServiceStatus ERROR:"<<GetLastError()<<endl;
CloseServiceHandle(Service);
CloseServiceHandle(scm);
return false;
}
if (status.dwCurrentState!=SERVICE_STOPPED)
{
success = ControlService(Service,SERVICE_CONTROL_STOP,&status);
if (!success){
cout<<"ControlService ERROR:"<<GetLastError()<<endl;
CloseServiceHandle(Service);
CloseServiceHandle(scm);
return false;
}
}
success = DeleteService(Service);
if (!success){
cout<<"DeleteService ERROR:"<<GetLastError()<<endl;
CloseServiceHandle(Service);
CloseServiceHandle(scm);
return false;
}
CloseServiceHandle(Service);
CloseServiceHandle(scm);
return true;
4 服务控制程序
4.1 服务控制程序概要
在上面我们了解服务程序的编写,现在我们来看一下控制服务时会用到的几个常用API。服务控制程序的编写与标准的Windows应用程序无异,它要用到服务管理函数,如:OpenSCManager
、OpenService、QueryServiceConfig、StartService、QueryServiceStatus、ControlService等;它都在系统的
advapi32.dll中实现。在使用SCM的函数时,SCP必须要首先调用OpenSCManager
函数,打开一个通向SCM的通道。调用这个函数的时候,SCP还必须指定它想要执行的动作类型;也就是我们上一节所提到的
dwDesiredAccess
参数它的取值:
SC_MANAGER_ALL_ACCESS、SC_MANAGER_CREATE_SERVICE、SC_MANAGER_ENUMERATE_SERVICE、 SC_MANAGER_QUERY_LOCK_STATUS、SC_MANAGER_ENUMERATE_SERVICE 、SC_MANAGER_QUERY_LOCK_STATUS、 SC_MANAGER_LOCK、SC_MANAGER_CONNECT。例如:我们要枚举当前所有的服务就必须给dwDesiredAccess
参数指定
SC_MANAGER_ENUMERATE_SERVICE;同时也可以指定其它的值,当指写多个值时我们要把它用按位或(|)符号连接起来。
与此同时我们在调用OpenService
时也必须告知SCM我们要对服务进行的动作;它有三个参数,最后一个参数
dwDesiredAccess
指出要对服务进行的操作,这些操作的标记与CreateService中的dwDesiredAccess参数标记值一样。当我们以
SERVICE_ALL_ACCESS访问权限打开服务后就可以对它进行配置(QueryServiceConfig)、控制(ControlService)、查询状态(QueryServiceStatus)、设置状态(SetServiceStatus)、删除(DeleteService)等所有访问操作。下面我们可以来看两服务控制的实例。
4.2
枚举服务
我们先来看EnumDependentStatus
函数原型:
BOOL EnumServicesStatus (
SC_HANDLE
hService
,
// SCM
控制句柄
DWORD
dwServiceType
,
//
要枚举服务还有驱动
DWORD
dwServiceState
,
//
要枚举什么状态的服务
LPENUM_SERVICE_STATUS
lpServices
,
//
存储枚举出服务的内存地址
DWORD
cbBufSize
,
// lpServices
指向
内存区大小
LPDWORD
pcbBytesNeeded
,
//
实际需要的内存大小
LPDWORD
lpServicesReturned
//
返回枚举到服务年个数
LPDWORD
lpResumeHandle
//
指向下一个有效的入口
);
dwServiceState
参数由:
SERVICE_ACTIVE、SERVICE_INACTIVE、SERVICE_STATE_ALL三种值分别枚举当前活动、不活动、全部的服务。函数会返回一个ENUM_SERVICE_STATUS
的
数组,ENUM_SERVICE_STATUS
有三个域分别指出服务的服务名、显示名和当前状态一个指针;我们可以根据服务名打开枚举出的服务,以得到它更加详细的信息
。下面具体让我们看一段程序的例子。
//清空服务信息队列
DeletItemAll();
LPENUM_SERVICE_STATUS st=NULL;
st=NULL;
DWORD ret=0;
DWORD size=0;
ServiceInfo info;
SC_HANDLE sc=OpenSCManager(NULL,NULL,SC_MANAGER_ALL_ACCESS);
SC_HANDLE sh;
char* szInfo[1024*8];
DWORD dwSize=1024*8;
CString str;
//第一次调用来得到需要多大的内存区
EnumServicesStatus(sc,SERVICE_WIN32,SERVICE_STATE_ALL,st,size,&size,&ret,NULL);
//申请需要的内存
st=(LPENUM_SERVICE_STATUS)LocalAlloc(LPTR,size);
EnumServicesStatus(sc,SERVICE_WIN32,SERVICE_STATE_ALL,st,size,&size,&ret,NULL);
//开始记录枚举出服务的信息
for(DWORD i=0;i<ret;i++){
dwSize=1024*8;
ZeroMemory(szInfo,dwSize);
info.Name.Format("%s",st[i].lpDisplayName);
info.serviceNmae.Format("%s",st[i].lpServiceName);
info.State.Format("%d",st[i].ServiceStatus.dwCurrentState);
sh=OpenService(sc,st[i].lpServiceName,SERVICE_ALL_ACCESS);
//得到服务描述信息
QueryServiceConfig2(sh,SERVICE_CONFIG_DESCRIPTION,(LPBYTE)szInfo,dwSize,&dwSize);
info.Desc.Format("%s",((LPSERVICE_DESCRIPTION)szInfo)->lpDescription);
//得到服务的启动账户名
ZeroMemory(szInfo,dwSize);
dwSize=1024*8;
QueryServiceConfig(sh,(LPQUERY_SERVICE_CONFIG)szInfo,dwSize,&dwSize);
info.LoginUser.Format("%s",((LPQUERY_SERVICE_CONFIG)szInfo)->lpServiceStartName);
CloseServiceHandle(sh);
//添加到信息队列中
ItemAdd(&info);
}
CloseServiceHandle(sc);
return TRUE;
上面程序中用到了两个查询服务当前配置的函数QueryServiceConfig2和QueryServiceConfig。它们有所不同是QueryServiceConfig2可以通过设置第二个参数是SERVICE_CONFIG_DESCRIPTION还是SERVICE_CONFIG_FAILURE_ACTIONS来得到服务的描述信息和失败的活动;而QueryServiceConfig则查询返回一个QUERY_SERVICE_CONFIG
结构,这个结构存储了服务的类型、启动类型、错误控制标记、服务文件所在路径、显示名等信息详细可以查看MSDN。与这个两函数相对应还有两个配置函数ChangeServiceConfig2和ChangeServiceConfig。它们的具体使用方法我们来看下面的这段程序。
4.3
配置服务
在3.2中我们举了一个创建服务的程序片段,其中我们只是创建服务并未设置服务描述信息,启动服务的操作。下面的程序片段给出了示例:
bool RegterService(char* pszServiceName,
char* pszDisplayName,
char* pszServicePath,
char* pszDescription)
{
SC_HANDLE newService,scm;
BOOL success = FALSE;
SERVICE_STATUS status;
SERVICE_DESCRIPTION description;
if (pszDisplayName==NULL&&pszServiceName==NULL&&pszServicePath==NULL)
{
return false;
}
description.lpDescription=pszDescription;
scm = OpenSCManager(NULL,NULL,SC_MANAGER_ENUMERATE_SERVICE|SC_MANAGER_CREATE_SERVICE);
if (!scm){
OUT_DEBUG("OpenSCManager ERROR!");
return false;
}
newService = CreateService(scm,pszServiceName,pszDisplayName,
SERVICE_ALL_ACCESS|SERVICE_STOP,SERVICE_WIN32_OWN_PROCESS,
SERVICE_AUTO_START,SERVICE_ERROR_NORMAL,pszServicePath,
0,0,0,0,0);
if (!newService){
OUT_DEBUG("CreateService ERROR!");
CloseServiceHandle(scm);
return false;
}
if (description.lpDescription!=NULL)
{
success=ChangeServiceConfig2(newService,
SERVICE_CONFIG_DESCRIPTION,
&description);
}
success = QueryServiceStatus(newService,&status);
if (!success){
cout<<"QueryServiceStatus ERROR:"<<GetLastError()<<endl;
CloseServiceHandle(newService);
CloseServiceHandle(scm);
return false;
}
if (status.dwCurrentState!=SERVICE_RUNNING)
{
success = StartService(newService,NULL,NULL);
if (!success){
cout<<"ControlService ERROR:"<<GetLastError()<<endl;
CloseServiceHandle(newService);
CloseServiceHandle(scm);
return false;
}
}
CloseServiceHandle(newService);
CloseServiceHandle(scm);
return true;
}
ChangeServiceConfig函数可以配置更多关于服务的信息,下面列出其原型:
BOOL ChangeServiceConfig(
SC_HANDLE
hService
//
打开服务时返回的句柄
DWORD
dwServiceType
,
//
服务的类型
DWORD
dwStartType
,
//
何时启动服务
DWORD
dwErrorControl
,
//
错误控制代码
LPCTSTR
lpBinaryPathName
,
//
服务的路径
LPCTSTR
lpLoadOrderGroup
,
//
服务所属的组
LPDWORD
lpdwTagId
,
//
服务的标记
LPCTSTR
lpDependencies
,
//
依赖的其它服务和组
LPCTSTR
lpServiceStartName
,
//
服务的启动用户
LPCTSTR
lpPassword
,
//
服务启动用户的密码
LPCTSTR
lpDisplayName
//
服务的显示名
);
大家可以看到ChangeServiceConfig
与CreateServiceee 有着相似
的参数,它们的使用方法也十分相似可以参照CreateServiceee
函数调用
。它主要在服务安装完成后,需要对服务的配置进行修改时调用,除了lpDisplayName改变时需要服务停止才能生效外,其它都可以运行时动态改变;更详细信息可查阅MSDN。
4.4
控制服务
有时我们要根据实际情况启动、暂停、停止一个服务。在4.3中的程序示例里面就一个启动服务的调用。这里我们再简单介绍一下这个函数:
BOOL StartService(
SC_HANDLE
hService
,
//
打开服务时返回的句柄
DWORD
dwNumServiceArgs
,
//
服务程序参数的个数
LPCTSTR
*lpServiceArgVectors
//
存放服务程序参数的数组
);
当服务程序需要配置启动参数时就需要使用StartService
后面的两个参数,服务程序的入口函数也就可以接到相关的参数了
。函数调用成功时返回非零,当返回零时调用失败;更详细的出错信息可以调用GetLastError
获得。
启动后如果我们还要控制其暂停、继续、停止的话,还需要另一个函数调用来完成。在上面删除服务的例示程序片段中我们调用了
ControlService函数来停止服务,下面我们介绍一下它的详细信息:
BOOL ControlService(
SC_HANDLE
hService
,
//
打开服务时返回的句柄
DWORD
dwControl
,
//
控制代码
LPSERVICE_STATUS
lpServiceStatus
//
服务的状态
);
调用此函数会把控制代码发给指定服务程序的Handler处理函数同时返回服务的状态,服务程序得到相应的控制代码后根据协议要执行相应的操作;控制代码就是Handler规定响应的所有代码。我们不能启动和停止服务安全描述符不允许的服务程序。默认的安全描述符只允许
LocalSystem、 Administrators和 Power Users来启动和停止服务程序。服务的安全描述符来用SetServiceObjectSecurity来设置(更详细信息的信息可查阅MSDN)。
5 结束语
我们在这里从总体上讲述了SCM的工作流程序、服务程序编写的方法及控制服务所用到的一些函数。文中只列出了一部分函数的信息,更详细的信息大家可以查阅MSDN。希望本文讲述的内容能帮助大家理解服务程序的编写与控制。
参考资料
[1] 潘爱民译《深入解析Windows 操作系统》电子工业出版社 2007
其二
调用 StartServiceCtrlDispatcher 時回 1063 error【来源】
Service 程式在call StartServiceCtrlDispatcher 時回 1063 error ...,try 了一個下午,嘗試過把防毒程式卸下,還是不行,下不知為什麼 ? 以下是程式片段 ....
void main(int argc,
char* argv[])
{
SERVICE_TABLE_ENtry servicetable[]=
{
{strServiceName,(LPSERVICE_MAIN_FUNCTION)ServiceMain},
{NULL,NULL}
};
if(
(StartServiceCtrlDispatcher(servicetable)) != success ) {
// 這裡發生了 1063 的錯誤
}
}
void ServiceMain(DWORD
argc, LPTSTR *argv)
{
BOOL success;
//註冊 service handle function
nServiceStatusHandle=RegisterServiceCtrlHandler(strServiceName,
(LPHANDLER_FUNCTION)ServiceCtrlHandler);
if(!nServiceStatusHandle)
{
return;
}
...........
..........
.........
}
謝謝大家
!
答题1:
程式看起來並沒有問題,
可能是其他的問題. 最常見的, 便是"自己"去執行這個service程式, 那麼就會產生1063的錯誤 (無法連結service
controller).
答题2:
Service程式的啟用過程為:
1.用CreateService安裝service程式
2.用StartService啟用service程式
(或電腦重啟時, 由系統自動執行, 或直接在服務管理程式中啟用)
啟用service程式後,
SCM會自動去執行你的service程式 (不可自己直接執行), 然後進到StartServiceCtrlDispatcher,
再進到你的ServiceMain程式.
因為沒看到你如何安裝與啟用,
因此比較猜測是這一方面的問題. 尤其你認知中的service程式建立次序是錯的.
答题3:
service程式必須透過SCM
(service controll manager)來安裝與啟動, 不能自己直接執行這個service程式.
1.首先要有一隻service程式,
裡面就是StartServiceCtrlDispatcher與ServiceMain等.
2.CreateService是用來安裝前述的service程式,
指定service name與前述程式的執行路徑檔名. 注意service name兩者必須相同.
3.安裝後,
service程式並不會立即自動啟動, 必須下列三種情況之一才能啟動:
(1)系統重開機
(2)利用服務管理員手動來啟動
(3)呼叫StartService啟動
StartService傳回5,
表示Access Deny. 有可能權限不足, 試著用Administrator的身份來執行看看.
我稍微看了你的程式,
也有可能是因為在開啟handle時沒有指定對應的權限.
建議OpenSCManager與OpenService給的權限值都使用SC_MANAGER_ALL_ACCESS, 以避免此一情況.
4.當service程式啟動時,
SCM會去找該service安裝時所指定的執行檔名, 然後執行該程式.
而所執行的service程式便必須呼叫StartServiceCtrlDispatcher與SCM連繫,
最後進入ServiceMain函數開始正式處理服務的相關事宜. service程式必須透過SCM來執行, 如果自己直接執行, 就會出現1063的錯誤了
(無法與SCM連繫).
答题4:
我的情况:自己执行服务程序,提示StartServiceCtrlDispatcher 调用错误,错误代码1063,经过下午3个多小时的对比和跟踪服务程序,应该是系统在手动点击启动服务器或者是電腦重啟時,
由系統自動執行时,触发启动SCM,SCM接管控制服务程序的运行,有SCM找到服务程序并控制运行,然后调用StartServiceCtrlDispatcher
函数,注册服务执行函数ServiceMain,调用成功,StartServiceCtrlDispatcher等待控制函数NTServiceHandler的状态改变为STOP后或者执行函数ServiceMain结束后返回,整个服务结束运行。
PS:
第一,StartServiceCtrlDispatcher函数则并不立即返回,相反它会驻留在一个循环内。当在该循环内时,StartServiceCtrlDispatcher悬挂起自己,等待下面两个事件中的一个发生。第一,如果SCM要去送一个控制通知给运行在这个进程内一个服务的时候,这个线程就会激活。当控制通知到达后,线程激活并调用相应服务的CtrlHandler函数。CtrlHandler函数处理这个服务控制通知,并返回到StartServiceCtrlDispatcher。StartServiceCtrlDispatcher循环回去后再一次悬挂自己。
第二,如果服务线程中的一个服务中止,这个线程也将激活。在这种情况下,该进程将运行在它里面的服务数减一。如果服务数为零,StartServiceCtrlDispatcher就会返回到入口点函数,以便能够执行任何与进程有关的清除工作并结束进程。如果还有服务在运行,哪怕只是一个服务,StartServiceCtrlDispatcher也会继续循环下去,继续等待其它的控制通知或者剩下的服务线程中止。
第三,经过调试发现StartServiceCtrlDispatcher在服务主线程结束后并不返回,要等到服务控制通知为STOP状态才能返回,通过XYNTService服务程序得出结论
在服务没有结束之前StartServiceCtrlDispatcher不会返回。
If StartServiceCtrlDispatcher succeeds,
it connects the calling thread to the service control manager and does
not return until all running services in the process have entered the
SERVICE_STOPPED state.