天天看点

Windows系统编程之编写Windows服务(1)

一、Windows服务简介:

Windows服务,也称NT服务,提供将服务器转换为可以用命令或者在启动时初始化的服务所需的管理能力,初始化发生 在任何用户登录之 前,服务可以暂停、恢复、终止、监控。
注册表维护与服务的有关信息。

    Windows的所有服务在如下图所示位置:     

Windows系统编程之编写Windows服务(1)
使用Windows 服务功能的情况很多,比如DNS客户、许多Sql Server服务以及终端服务。

二、Windows服务的管理方法:

Windows服务在服务控制管理器(SCM)的控制下运行。有三种方式可以与SCM交互,实现对服务的控制管理: 1)在控制面板的管理工具下使用标签为"服务"的管理插件(也可在cmd里面输入Services.msc);结果如上图所示 2)使用sc.exe命令行工具来控制服务; 3)编程控制SCM。(这是我们要学习的主要内容)

三、编写Windows服务

(1)三个步骤:
1)创建一个新的main进入点用户将服务注册给SCM,提供逻辑的服务器进入点和名称;
2)将老的main()进入点函数转换为ServiceMain(),在这里注册服务控制处理程序并将其状态通知SCM。
   ServiceMain()这个名称是逻辑服务名的占位符,在单个进程中可以有一个或多个逻辑服务;
3)编写服务控制处理程序函数以便对来自SCM的命令做出响应
下面的图显示了步骤流程:
Windows系统编程之编写Windows服务(1)
(2)具体步骤分析
1)在程序入口点(如main)中,向SCM注册服务的主函数和名称
通过StartServiceCtrlDispatcher函数。
BOOL StartServiceCtrlDispatcher(
SERVICE_TABLE_ENTRY *lpServiceStartTable
)
惟一的参数 lpServiceStartTable是SERVICE_TABLE_ENTRY项的数组的地址,每个项都是逻辑服务名称和进入点。数组的末尾通过一对NULL进入点来表示
如果注册成功返回TRUE。
SERVICE_TABLE_ENTRY的原型为:
typedef struct _SERVICE_TABLE_ENTRY{
LPTSTR lpServiceName;
LPSERVICE_MAIN_FUNCTION lpServiceProc;
}SERVICE_TABLE_ENTRY, *LPSERVICE_TABLE_ENTRY;
其中 lpServiceName为服务名称, lpServiceProc为指向ServiceMain的函数指针。
只要将函数的指针赋值给lpServiceProc,再调用StartServiceCtrlDispatcher,这样,这个函数( ServiceMain )就成为了服务主函数。
main:主服务进入点
void WINAPI ServiceMain(DWORD argc, LPTSTR argv[]);
static LPTSTR serviceName = _T("MyService");
//Main routine that start the service control dispatcher
VOID _tmain(int argc, LPTSTR argv[])
{
SERVICE_TABLE_ENTRY dispatchTable[] = 
{
{serviceName, ServiceMain},
{NULL, NULL}
};
if(!StartServiceCtrlDispatcher(dispatcherTable))
{
printf("StartServiceCtrlDispatcher error\n");
}

//ServiceMain() will not run until started by SCM

//Return here only when all services have terminated

return;
}
2)在 ServiceMain()中注册服务控制处理程序并将其状态通知SCM
服务主函数一般称为ServiceMain函数,其函数名没有什么特殊要求, 但是其参数借口和调用类型,必须和要求一致。
ServiceMain函数的原型:
VOID WINAPI ServiceMain(
DWORD dwArgc,
LPTSTR* lpszArgv
);
服务主函数的参数与main函数的参数使用方法相似,但是服务主函数的参数不是通过在命令行启动时设定的,
而是通过SCM的相关API进行传递的(StartService函数,后面介绍)
在ServiceMain()中注册服务控制处理程序:
每个逻辑服务必须使用RegisterServiceHandlerEx立即注册一个处理程序,这个函数调用必须位于ServiceMain的开始处,而且不能再次调用。
SCM在接收到服务的控制请求之后,会调用这个处理程序。
RegisterServiceHandlerEx函数原型:
SERVICE_STATUS_HANDLE  RegisterServiceCtrlHandlerEx(
LPCTSTR lpServiceName,
LPHANDLER_FUNCTION_EX lpHandlerPorc,
LPVOID lpContext
)
lpServiceName为服务名称,和使用StartServiceCtrlDispatcher向SCM注册的服务名称相同。
lpHandlerProc是扩展处理程序函数的地址。
lpContext是用户定义的要传递给控制处理程序的数据。一般可省略
返回值是一个SERVICE_STATUS_HANDLE对象,如果为0表示有误。
在ServiceMain()中设置服务状态:
BOOL SetServiceStatus(
SERVICE_STATUS_HANDLE hServiceStatus,
LPSERVICE_STATUS lpServiceStatus
)
hServiceStatus是RegisterServiceHandleEx返回的 SERVICE_STATUS_HANDLE 。
lpServicesStatus指向一个SERVICE_STATUS结构,这个结构描述服务属性、状态和能力。
SERVICE_STATUS结构的定义:
typedef struct _SERVICE_STATUS { 
  DWORD dwServiceType; 
  DWORD dwCurrentState; 
  DWORD dwControlsAccepted; 
  DWORD dwWin32ExitCode; 
  DWORD dwServiceSpecificExitCode; 
  DWORD dwCheckPoint; 
  DWORD dwWaitHint; 
} SERVICE_STATUS, *LPSERVICE_STATUS;       
具体参数含义见MSDN。
ServiceMain:
VOID WINAPI ServiceMain(DWORD, LPTSTR argv[])
{
  ServiceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
 ServiceStatus.dwCurrentState = SERVICE_START_PENDING;
 ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
 ServiceStatus.dwWin32ExitCode = 0;
 ServiceStatus.dwServiceSpecificExitCode = 0;
 ServiceStatus.dwCheckPoint = 0;
 ServiceStatus.dwWaitHint = 0;
 hStatus = RegisterServiceCtrlHandler("serviceName",(LPHANDLER_FUNCTION)HandlerEx);
 if (hStatus == (SERVICE_STATUS_HANDLE)0)
 {
  // Registering Control Handler failed
  return;
 }
 ServiceStatus.dwCurrentState = SERVICE_RUNNING;
 SetServiceStatus (hStatus, &ServiceStatus);
}
3)编写服务控制处理程序函数以便对来自SCM的命令做出响应
服务控制处理程序是一个在RegisterServiceCtrlHandleEx中指定的回调函数。
DWORD WINAPI HandlerEx(
DWORD dwControl,
DWORD dwEventType,
LPVOID lpEventData,
LPVOID lpContext
)
dwControl参数表示由SCM发送的应该被处理的实际控制信号.
dwEventType通常是0,而非零值用于设备管理。一般可省略
lpEventData提供这些事件中的一些所需要的额外数据。一般可省略
lpContext是在注册处理程序时,传递给RegisterServiceCtrlHandler的用户自定义数据。一般可省略
HandlerEx:
void HandlerEx(DWORD request)
{
 switch(request)
 {
 case SERVICE_CONTROL_STOP: 
 case SERVICE_CONTROL_SHUTDOWN:
  ServiceStatus.dwWin32ExitCode = 0;
  ServiceStatus.dwCurrentState = SERVICE_STOPPED;
  SetServiceStatus (hStatus, &ServiceStatus);
  break;
  case SERVICE_CONTROL_PAUSE:
ServiceStatus.dwWin32ExitCode = 0;
ServiceStatus.dwCurrentState =  SERVICE_PAUSED  ;
SetServiceStatus (hStatus, &ServiceStatus);
break;
 case SERVICE_CONTROL_CONTINUE:
ServiceStatus.dwWin32ExitCode = 0;
ServiceStatus.dwCurrentState =  SERVICE_RUNNING ;
SetServiceStatus (hStatus, &ServiceStatus);
break;
 default:
  break;
 }
 // Report current status
 SetServiceStatus (hStatus, &ServiceStatus);
 return;
}

四、The last, This is my Windows Service

#include "stdafx.h" #include <windows.h> #include <string.h> #include <time.h> #define SERVICE_NAME "serviceName" // service name SERVICE_STATUS ServiceStatus; SERVICE_STATUS_HANDLE hStatus; void WriteLog(char* str) {  FILE* log;  log = fopen("c:\\info.txt", "a+");  if (log == NULL)  {   return ;  }  char cTimeString[30] = ("");  time_t currentTime = time(NULL);  strncat(cTimeString, ctime(&currentTime), 30);  cTimeString[strlen(cTimeString) - 1] = '\0';  fprintf(log, "%s. ", cTimeString);  fprintf(log, "%s", str);  fclose(log); } void HandlerEx(DWORD request) {  switch(request)  {  case SERVICE_CONTROL_STOP:  case SERVICE_CONTROL_SHUTDOWN:   WriteLog("Service stopped.\n");   ServiceStatus.dwWin32ExitCode = 0;   ServiceStatus.dwCurrentState = SERVICE_STOPPED;   SetServiceStatus (hStatus, &ServiceStatus);   break;  case SERVICE_CONTROL_PAUSE:   WriteLog("Service pause.\n");   ServiceStatus.dwWin32ExitCode = 0;   ServiceStatus.dwCurrentState = SERVICE_PAUSED ;   SetServiceStatus (hStatus, &ServiceStatus);   break;  case SERVICE_CONTROL_CONTINUE:   WriteLog("Service continue.\n");   ServiceStatus.dwWin32ExitCode = 0;   ServiceStatus.dwCurrentState = SERVICE_RUNNING;   SetServiceStatus (hStatus, &ServiceStatus);   break;  default:   break;  }  // Report current status  SetServiceStatus (hStatus, &ServiceStatus);  return; } VOID WINAPI ServiceMain(DWORD, LPTSTR argv[]) {  WriteLog("Enter ServiceMain().\n");  ServiceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS; //表示Windows服务运行于自己的进程,使用自己的资源  ServiceStatus.dwCurrentState = SERVICE_START_PENDING;  ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN | SERVICE_PAUSE_CONTINUE ;   ServiceStatus.dwWin32ExitCode = 0;  ServiceStatus.dwServiceSpecificExitCode = 0;  ServiceStatus.dwCheckPoint = 0;  ServiceStatus.dwWaitHint = 0;  hStatus = RegisterServiceCtrlHandler(SERVICE_NAME,(LPHANDLER_FUNCTION)HandlerEx);  if (hStatus == (SERVICE_STATUS_HANDLE)0)  {   // Registering Control Handler failed   WriteLog("RegisterServiceCtrlHandler failed.\n");   return;  }  WriteLog("RegisterServiceCtrlHandler success.\n");  ServiceStatus.dwCurrentState = SERVICE_RUNNING;  SetServiceStatus (hStatus, &ServiceStatus); } int _tmain(int argc, _TCHAR* argv[]) {  SERVICE_TABLE_ENTRY ServiceTable[2];  ServiceTable[0].lpServiceName = SERVICE_NAME;  ServiceTable[0].lpServiceProc = (LPSERVICE_MAIN_FUNCTION)ServiceMain;  ServiceTable[1].lpServiceName = NULL;  ServiceTable[1].lpServiceProc = NULL;  // 启动服务的控制分派机线程  WriteLog("Starting Service Control Dispatcher.\n");  StartServiceCtrlDispatcher(ServiceTable);  return 0; }

测试结果: 1)在cmd里面创建服务和开始服务  

Windows系统编程之编写Windows服务(1)
Windows系统编程之编写Windows服务(1)
2)停止服务和查询服务:
Windows系统编程之编写Windows服务(1)
3)删除服务:
Windows系统编程之编写Windows服务(1)

    4)文本记录日志内容

Windows系统编程之编写Windows服务(1)
注意:如果使用Windows 7 或Windows8 系统,sc create 服务名称 binPath= "exe应用程序路径" 可能会创建失败,提示错误是5,无法访问。这是权限的问题,你可以把创建的命令放在批处理文件里面,然后右击选择管理员身份运行就Ok了。

继续阅读