Windows服务编写综述

Windows 编程之服务-2 四、自定义服务控制码 除了系统定义的五种控制码外(还有一种是:SERVICE_CONTROL_SHUTDOWN), 用户还可自定义控制码,其取值范围是128-255。用户可以通过控制面板中的服务 项向特定服务程序的控制处理函数发送控制码,程序员可以调用系统函数  ControlService 直接向服务程序的控制处理函数发送控制码。其函数原型如下: BOOL ControlServi 阅读详情
作者:李朝中
    摘要:几乎所有的操作系统在启动的时候都会启动一些不需要与用户交互的进程,这些进程在Windows中就被称作服务。它由服务程序、服务控制程序(SCP,service control program)和服务控制管理器(SCM,service control manager)三个组件构成。本文针对服务程序与服务控制程序的编写进行综合讲述。
    关键词:Windows,服务,VC++
1        服务介绍
几乎所有的操作系统在启动的时候都会启动一些不需要与用户交互的进程,这些进程在Windows中就被称作服务。它通常用于实现客户/服务器模式中的服务器方,如我们常见的Web服务IIS,当操作系统在启动后它就自动被运行,不管是否有人登陆到系统只要系统开启它就能得到运行。
服务程序、服务控制程序(SCP,service control program)和服务控制管理器(SCM,service control manager)组成了Windows服务。我们可以通过服务控制程序操纵服务控制管理器来配置、启动、暂停、停止服务程序。其中服务程序和服务控制程序可以由我们自己来编写扩展,而服务控制管理器(\windows\system32\servics.exe)则是操作系统内置的一个部件。首先我们来了解一下SCM的工作情况,然后我们介绍服务程序的编写和服务控制时所涉及API的使用。
2        服务控制管理器
SCM本身也是一个服务程序(\windows\system32\servics.exe),作为windows的后台服务运行的。Winlogon在系统引导的早期会将SCM启动起来。SCM的服务入口函数首先创建一个初始化为无信号的同步事件对象(SvcCtrlEvent_A3752DX);接下来,它开始建立一个内部服务数据库,这个数据库要按事先规定好的一个顺序列出所有服务组,并记录与服务相关的详细信息;当这个数据库建立完成时SCM就开始按顺序启动那些启动方式为自动的服务,如果有服务要动行于指定用户账户中时还要调用LSASS,如果服务启动失败则会被放入一个名为ScFailedDrivers的列表中。当这些工作都完成后,SCM将同步事件对象SvcCtrlEvent_A3752DX置为有信号状态;并做好系统停机的准备。
当系统要关机时会向Windows子系统进程Csrss发送一个消息,以便调用Csrss的停机例程。Csrss会对所有活动的进程循环通知系统正在停机。对于除SCM以外的每一个系统进程如果没有返回退出的响应Csrss 都会等待由HKEY_USER\.DEFAULT\Control Panel\Desktop\WaitToKillAppTimeout指定的毫秒数(我的系统中是20000,也就是20秒),然后知通下一个进程结束。当遇到SCM时也会通知SCM进程系统正在停机,但不同的是会使用一个专用的超时间隔值(SCM在系统初始化时要向Csrss登记,于是Csrss就将SCM的进程ID保存了下来Csrss也就是通过个ID来识别SCM的)。这个专用的超时间隔位于HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\WaitToKillServiceTimeout中,有趣的是默认值也是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_ENTRYlpServiceStartTable );
它的参数是一个指向SERVICE_TABLE_ENTRY的指针;SERVICE_TABLE_ENTRY结构有两个域;第一个域存储服务的内部名称,第二个域是服务入口函数的指针。这个函数完成后,SCM就要可以服务启动的时候调用服务的入口函数。
    例如:管理员在服务管理器启动一个服务,SCM就会在一个单独的线程中调用服务注册的入口函数。这时我们在服务的这个入口函数中必须调用RegisterServiceCtrlHandler完成Handler函数的注册,这个函数用来接收和处理SCM的控制消息。下面列出Hander要处理的控制消息和RegisterServiceCtrlHandler的函数原型:
VOID WINAPI Handler(
 DWORDfdwControl   // 请求控制消息代码
);
控制消息宏定义
说明
SERVICE_CONTROL_STOP要服务停止SERVICE_CONTROL_PAUSE要服务暂停SERVICE_CONTROL_CONTINUE要服务继续SERVICE_CONTROL_INTERROGATE要服务马上报告它的状态SERVICE_CONTROL_SHUTDOWN告诉服务即将关机
SERVICE_STATUS_HANDLE RegisterServiceCtrlHandler(
 LPCTSTRlpServiceName,             // 服务的内部名称
 LPHANDLER_FUNCTIONlpHandlerProc   // 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_HANDLEhSCManager, // 服务控制管理器的句柄 
  LPCTSTRlpServiceName, // 指向服务的内部名称
 LPCTSTRlpServiceName,, // 指向服务的显示名称
 DWORDdwDesiredAccess, // 服务的访问类型
 DWORDdwServiceType,   // 服务的类型
 DWORDdwStartType,     // 服务的启动方式(自动,手动,禁用)
 DWORDdwErrorControl, // 错误控制方式
 LPCTSTRlpBinaryPathName, // 服务程序的路径
 LPCTSTRlpLoadOrderGroup, // 服务组的名称 
  LPDWORDlpdwTagId,     // 服务的标签号
 LPCTSTRlpDependencies, // 服务依赖的服务或组名
 LPCTSTRlpServiceStartName, // 服务的启动帐户
 LPCTSTRlpPassword       // 服务启动帐户的密码
);
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(
 LPCTSTRlpMachineName, // 机器名,打开本机的SCM时可为NULL
 LPCTSTRlpDatabaseName, // 指向SCM数据库的名字可为NULL
 DWORDdwDesiredAccess   // 访问权限类型如:SC_MANAGER_ALL_ACCESS等
);
BOOL CloseServiceHandle(
 SC_HANDLEhSCObject   // 服务控制句柄 
);
       这果列出一段注册服务的代码供大家参考:
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_HANDLEhService,      // SCM控制句柄
DWORDdwServiceType,   //要枚举服务还有驱动
 DWORDdwServiceState,    // 要枚举什么状态的服务
 LPENUM_SERVICE_STATUSlpServices,// 存储枚举出服务的内存地址
 DWORDcbBufSize,         // lpServices指向内存区大小
 LPDWORDpcbBytesNeeded, //实际需要的内存大小
 LPDWORDlpServicesReturned//返回枚举到服务年个数
LPDWORDlpResumeHandle//指向下一个有效的入口
);
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_HANDLEhService     // 打开服务时返回的句柄
 DWORDdwServiceType,   // 服务的类型
 DWORDdwStartType,     // 何时启动服务
 DWORDdwErrorControl, // 错误控制代码
 LPCTSTRlpBinaryPathName, // 服务的路径
 LPCTSTRlpLoadOrderGroup, // 服务所属的组
 LPDWORDlpdwTagId,     // 服务的标记
 LPCTSTRlpDependencies,    // 依赖的其它服务和组
 LPCTSTRlpServiceStartName,// 服务的启动用户
 LPCTSTRlpPassword,   //服务启动用户的密码
 LPCTSTRlpDisplayName      // 服务的显示名
);
大家可以看到ChangeServiceConfig与CreateServiceee 有着相似的参数,它们的使用方法也十分相似可以参照CreateServiceee函数调用。它主要在服务安装完成后,需要对服务的配置进行修改时调用,除了lpDisplayName改变时需要服务停止才能生效外,其它都可以运行时动态改变;更详细信息可查阅MSDN。
4.4     控制服务
有时我们要根据实际情况启动、暂停、停止一个服务。在4.3中的程序示例里面就一个启动服务的调用。这里我们再简单介绍一下这个函数:
BOOL StartService(
 SC_HANDLEhService,            // 打开服务时返回的句柄
 DWORDdwNumServiceArgs,        // 服务程序参数的个数
 LPCTSTR*lpServiceArgVectors   // 存放服务程序参数的数组 
);
当服务程序需要配置启动参数时就需要使用StartService后面的两个参数,服务程序的入口函数也就可以接到相关的参数了。函数调用成功时返回非零,当返回零时调用失败;更详细的出错信息可以调用GetLastError获得。
    启动后如果我们还要控制其暂停、继续、停止的话,还需要另一个函数调用来完成。在上面删除服务的例示程序片段中我们调用了ControlService函数来停止服务,下面我们介绍一下它的详细信息:
BOOL ControlService(
 SC_HANDLEhService, // 打开服务时返回的句柄
 DWORDdwControl,     // 控制代码
 LPSERVICE_STATUSlpServiceStatus// 服务的状态
);
调用此函数会把控制代码发给指定服务程序的Handler处理函数同时返回服务的状态,服务程序得到相应的控制代码后根据协议要执行相应的操作;控制代码就是Handler规定响应的所有代码。我们不能启动和停止服务安全描述符不允许的服务程序。默认的安全描述符只允许LocalSystem、 Administrators和 Power Users来启动和停止服务程序。服务的安全描述符来用SetServiceObjectSecurity来设置(更详细信息的信息可查阅MSDN)。
5        结束语
我们在这里从总体上讲述了SCM的工作流程序、服务程序编写的方法及控制服务所用到的一些函数。文中只列出了一部分函数的信息,更详细的信息大家可以查阅MSDN。希望本文讲述的内容能帮助大家理解服务程序的编写与控制。
参考资料
[1] 潘爱民译《深入解析Windows 操作系统》电子工业出版社 2007
[2] MSDN Library Visual Studio 6.0 版
Rust编写Windows服务 编写Windows服务可选语言很多, 其中C#最简单。本着练手Rust语言,尝试用Rust编写一个服务。中ping-service示例提取了一个模板,只有替换编写两处/* */代码。笔记本策略经常恢复到合上盖子睡眠功能,写个小服务定时设置合上盖子不做任何操作。逻辑比较简单,定时调用WinAPI函数。获取配置信息,不符合当前设置执行修改。参考官网C/C++创建服务流程。 阅读详情

相关推荐

C++编写windows系统服务

1.声明服务表并启动服务控制分发 SERVICE_TABLE_ENTRY ServiceTable[2];//服务表集合 //第一个服务 ServiceTable[0].lpServiceName = L"MemoryStatus";//服务名 ServiceTable[0].lpServiceProc = (LPSERVICE_MAIN_FUNCTION)ServiceMain;//服务处理函数 //第二个服务 ServiceTable[1].lpServiceName = NULL;...

2096

50.服务程序SERVICE_STATUS、SERVICE_STATUS_HANDLE、RegisterServiceCtrlHandler、SetServiceStatus、SERVICE_TABL

开始之前,先了解几个结构体 SERVICE_STATUS 是服务状态机构,用于标记服务的状态 typedef struct _SERVICE_STATUS { DWORD dwServiceType; //服务可执行文件的类型 DWORD dwCurrentState; DWORD dwControlsAccepted; DWORD dwWin32E...

单于的学习历程 3050

雷光联动自动化标校方法

动态标校需要利用动目标,在雷达和光电同时看到目标时记录光电的方位、俯仰,以及利用雷达给的目标信息计算的方位俯仰,手动记录两者的数值,多方位,多距离段记录,费时费力,且人工记录数据不全,计算容易出差错,耗费成本和精力太大。目标相距设备的水平距离 = acos(cos(设备纬度弧度值) * cos(目标纬度弧度值) * cos(设备经度弧度值 – 目标经度弧度值) + sin(设备纬度弧度值) * sin(目标纬度弧度值)) * 6378137;本发明涉及光电技术领域,安防领域,尤其涉及雷达光电动态标校等。

u013358093的博客 1580

Windows文件操作

读写文件HANDLE CreateFile(LPCTSTR lpFileName, DWORD dwDesiredAccess, DWORD dwSharedMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreateionDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemp

Code搬运工 1038

生信入门第七课:使用FastQC软件进行测序原始fastq数据质控

从实验到数据:RNA定量、文库片段长度、fastqc质控和fastp去接头

微生信 489

解决Unity与3DMax模型单位与中心点偏差的完整指南

本文详细解析了Unity与3DMax模型导入时常见的单位与中心点偏差问题,提供了从3DMax单位设置、FBX导出到Unity接收调整的完整解决方案。通过实战案例和高效工作流建议,帮助开发者实现unity与3dmax的单位统一,避免尺寸错误和轴心偏移,提升3D资源导入效率。

weixin_29015899的博客 286

Windows服务编写

摘要:几乎所有的操作系统在启动的时候都会启动一些不需要与用户交互的进程,这些进程在Windows中就被称作服务。它由服务程序、服务控制程序(SCP,service control program)和服务控制管理器(SCM,service control manager)三个组件构成。本文针对服务程序与服务控制程序的编写进行综合讲述。    关键词:Windows,服务,VC++•1    

lziog的专栏 1805

深入浅出DNS系列(一)- DNS杂谈

黄丝玛:班达山,你好,我记性不好,你告诉我搜狐、新浪的网站IP地址好吗? 班达山:你问问DNS先生吧,他专门干这买卖的

jiangsd198的博客 4129

C++语言编写windows服务

C++语音编写windows服务 1 windows服务 2 DebugView调试工具 3 c语言编写windows服务 4 将程序作为windows服务1、 windows服务通过快捷键”win+R”打开运行框,输入”services.msc”,就能够打开windows服务。鼠标右键任意一个服务,能看见以下的选项,如下图1.1所示 图1.1 windows服务 本文的内容讲的就是通过wind

西西的博客 1万+

【06】新国都——新国都 嵌入式笔试,校招,题目记录及解析

本文总结了新国都嵌入式校招笔试题目及解析,内容涵盖C语言基础、Linux系统、数据结构与算法等多个方面。主要包括:1)不定项选择题10道,涉及字符串常量、蓝牙与GPRS区别、编译错误识别等;2)判断题10道,考察Linux权限、循环控制、内存管理等;3)填空题5道,包括编码计算、逻辑运算、宏定义等;4)简答题2道,解析全局变量引用和进程线程区别。题目难度适中,重点考察嵌入式开发基础知识和编程能力,并提供详细解析和扩展知识链接,对准备嵌入式相关笔试具有参考价值。

程序员,他们想的是什么?他们想的永远都是技术,他们崇尚的也永远都是技术。 1432

TensorFlow2深度学习实战(十四):目标检测算法 YOLOv4 解析

最易上手的【计算机视觉】小白教程!

不积跬步,无以至千里! 7635

C++编写Windows服务

使用C++ 编写Windows 服务,我所看的文章如下(红色的"=" 以内, 来自http://www.programfan.com/article/2920.html ): ============== 前一段时间我写了一篇通过写服务的形式来达到一些监视程序运行的目的的 文章 ,至于如何在windows 下写服务我没有详细介绍,今天就让 我们 一起看看如何来写服务程序。

nodeathphoenix的专栏 1万+

肺癌术后随访离散选择实验(DCE)设计与SAS实现

随着肺癌早期诊断和治疗技术的进步,肺癌患者的生存率显著提高,术后随访管理成为提升患者生活质量的关键环节。然而,如何在有限的医疗资源下设计出既符合患者需求又具有成本效益的随访方案,是临床决策面临的重要挑战。 离散选择实验(Discrete Choice Experiment, DCE)是一种定量研究方法,通过模拟真实世界的选择情境,测量患者对不同随访方案属性的偏好程度。本研究旨在通过DCE方法,系统评估肺癌术后患者对随访服务的偏好,为制定以患者为中心的随访策略提供循证依据。

huanghm88的专栏 99

C#编写windows服务

项目要求: 数据库用有一张表,存放待下载文件的地址,服务需要轮训表将未下载的文件下载下来。 表结构如下: 过程: VS--文件-->新建项目-->windows-->windows服务 添加安装程序 Service1.cs设计 右键-->添加安装程序 选中ServiceInsta...

dhh121195的博客 126

在 SAP SD 模块里,销售订单号既可以由系统“自动”产生,也可以由用户“手工”输入,核心取决于“号码段(Number Range)”与“订单类型(Sales Document Type)”这两处配

SAP销售订单编号配置指南:系统提供内部自动编号和外部手工编号两种方式,通过VN01定义号码段(内部/外部),VOV8分配给订单类型并设置"ExtNo.Rq"选项即可切换。高阶应用支持灵活编号(如加前缀)或通过增强出口完全自定义编号规则。配置时需注意号码段创建、分配及传输问题,常见故障包括号码范围不存在或配置错误。核心是"号码段+订单类型"的组合控制编号生成方式,满足从简单到复杂的业务需求。

金牌架构师 531

最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版

Part-E1-SDIO-Specification-Ver4.00-Final-120220.pdf:最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版,包括SDIO 接口的外设,SDIO 接口官方协议等。

如何编写windows服务程序

用 C 语言编写 Windows 服务程序的五个步骤:http://www.vckbase.com/index.php/wv/1193 http://blog.youkuaiyun.com/zhangpeng_linux/article/details/7001084 如何编写windows服务程序 By:张鹏/Hikvision 这两天想学习关于如何编写windows

孙建军的博客 1098

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

编写Windows服务应该注意的一些心得

心得一 :核心对象的引用计数        Windows系统中核心对象,比如窗口句柄,文件句柄,Event等等,在Windows的内部管理机制中全部采用引用计数方式管理,比如hService = CreateService(..),经过这次调用,系统中hService这个对象的的引用计数加1,而创建后如果不关闭再次通过OpenSCManager和OpenService打开的犹豫上次的服务句柄没有

铜板的专栏 1360

STM32F407中文手册

STM32f407中文手册 本参考手册面向应用开发人员,提供有关使用 STM32F405xx/07xx、STM32F415xx/17xx、 STM32F42xxx 和 STM32F43xxx 微控制器存储器与外设的完整信息。STM32F405xx/07xx、STM32F415xx/17xx、STM32F42xxx 和 STM32F43xxx 构成一个微 控制器系列,各产品具有不同的存储器大小、封装和外设。有关订购信息以及器件的机械与电气特性,请参见数据手册。有关 ARM Cortex™-M4F 内核的信息,请参见《Cortex™-M4F 技术参考手册》。

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

C# 编写Windows服务

关于windows服务的理论知识就不写了,网上多的是,这里直接通过一个简单的示例来入门windows服务 1.通过VS创建Windows服务,这里用的IDE是VS 2015: 2、刚刚创建好的Windows 服务的样子是这样的: 3、向服务的函数中写功能,在这里,当启动和停止服务时,向一个文本文件中写入一些文字信息 4、回到设计窗口点右键选择-添加安装程序-生成serviceIn...

qq_34498806的博客 2606

WINDOWS后台服务程序编写

http://blog.youkuaiyun.com/feijj2002_/article/details/1727228 WINDOWS后台服务程序编写 在2000/XP等基于NT 的操作系统中,有一个服务管理器,它管理的后台进程被称为 service。 服务是一种应用程序类型,它在后台运行,与 UNIX 后台应用程序类似。服务应用程序通常可以 在本地和通

孙建军的博客 866
上一篇: IPicture使用代码
下一篇: MFC Static控件背景透明时文本覆盖重影
ccx_john
博客等级 码龄18年 189粉丝 16原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值