首先明确一个概念,什么是windows service? windows service 是一些后台运行的服务,我们可以通过控制板面/管理/服务来查看当前计算机中已有的服务,同时可以控制这些服务开启和关闭。所以从使用的角度来看,这里的控制板面/管理/服务实际上是一个service 管理工具。同时windows提供了一个service的管理者SCM service control manager,它传递消息到各个service。
从代码的角度看,一个service一般是一个console工程,虽然有带有交互界面的service,但是后台服务还是使用console较多。这里又有两个概念:main和service main。前者很简单,就是console工程的main函数,而service main指的是提供service逻辑的入口。
MSDN中的main的例子是这样:
void __cdecl _tmain(int argc, TCHAR *argv[]) { // If command-line parameter is "install", install the service. // Otherwise, the service is probably being started by the SCM. if( lstrcmpi( argv[1], TEXT("install")) == 0 ) { SvcInstall(); return; } // TO_DO: Add any additional services for the process to this table. SERVICE_TABLE_ENTRY DispatchTable[] = { { SVCNAME, (LPSERVICE_MAIN_FUNCTION) SvcMain }, { NULL, NULL } }; // This call returns when the service has stopped. // The process should simply terminate when the call returns. if (!StartServiceCtrlDispatcher( DispatchTable )) { SvcReportEvent(TEXT("StartServiceCtrlDispatcher")); } } 我们暂时专注于红色的两个部分,SERVICE_TABLE_ENTRY是用于记录service name和service main的结构,必须以NULL和NULL结尾(用以判断结束吧,猜测),这个结构传入到StartServiceCtrlDispatcher函数中。这个函数内部会进入循环并等待SCM的消息,如果SCM要求启动某个service,函数就会根据SERVICE_TABLE_ENTRY来调用service main,所以SERVICE_TABLE_ENTRY是一个回调函数表。同时也可以发现,一个main或者说一个exe是可以提供多个service,同时没个service是工作的该进程中的不同线程内的,顺便说一句,main当然是主线程了。
main基本说完了,下面来专注于service main,还是MSDN的例子吧:
VOID WINAPI SvcMain( DWORD dwArgc, LPTSTR *lpszArgv ){ // Register the handler function for the service gSvcStatusHandle = RegisterServiceCtrlHandler( SVCNAME, SvcCtrlHandler); if( !gSvcStatusHandle ) { SvcReportEvent(TEXT("RegisterServiceCtrlHandler")); return; } // These SERVICE_STATUS members remain as set here gSvcStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS; gSvcStatus.dwServiceSpecificExitCode = 0; // Report initial status to the SCM ReportSvcStatus( SERVICE_START_PENDING, NO_ERROR, 3000 ); // Perform service-specific initialization and work. SvcInit( dwArgc, lpszArgv );}
service开始服务,前面说了,我们可以控制service开启,暂停,关闭等,那么就需要让SCM有一个能控制service状态的方法(当然是回调了。。。),RegisterServiceCtrlHandler函数就是为服务注册控制回调函数的方法,例子中的SvcCtrlHandler就是回调函数handler。这里的注册最好是service main做的第一件事,因为SCM对于注册时间是有要求的,具体时间记不清了,需要查查。。。
同时service还需要向SCM提供自己状态,在上面的例子是通过ReportSvcStatus函数的,下面是它的实现,来源MSDN:
VOID ReportSvcStatus( DWORD dwCurrentState, DWORD dwWin32ExitCode, DWORD dwWaitHint){ static DWORD dwCheckPoint = 1; // Fill in the SERVICE_STATUS structure. gSvcStatus.dwCurrentState = dwCurrentState; gSvcStatus.dwWin32ExitCode = dwWin32ExitCode; gSvcStatus.dwWaitHint = dwWaitHint; if (dwCurrentState == SERVICE_START_PENDING) gSvcStatus.dwControlsAccepted = 0; else gSvcStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP; if ( (dwCurrentState == SERVICE_RUNNING) || (dwCurrentState == SERVICE_STOPPED) ) gSvcStatus.dwCheckPoint = 0; else gSvcStatus.dwCheckPoint = dwCheckPoint++; // Report the status of the service to the SCM. SetServiceStatus( gSvcStatusHandle, &gSvcStatus );}最主要的函数就是SetServiceStatus, 它负责向SCM提供当前serivce的状态,SERVICE_STATUS (gSvcStatus) 结构体用来封装所有关于service status的信息,里面的一些字段很有趣的, 比如dwCheckPoint , 用于在pending状态下反应service进度。
等等,所谓pending状态,实际上是指service开启,关闭不可能一下子就结束,除非真有人的代码是暴力terminatethread。。。在收到开启消息时,我们可以设置状态为SERVICE_START_PENDING, 等真正完成初始化在设置为SERVICE_RUNNING。最后就是我们的逻辑代码了这里的例子是封装在SvcInit函数里面。
我们再看一下控制函数的例子, 同样来自MSDN:
VOID WINAPI SvcCtrlHandler( DWORD dwCtrl ){ // Handle the requested control code. switch(dwCtrl) { case SERVICE_CONTROL_STOP: ReportSvcStatus(SERVICE_STOP_PENDING, NO_ERROR, 0); // Signal the service to stop. SetEvent(ghSvcStopEvent); ReportSvcStatus(gSvcStatus.dwCurrentState, NO_ERROR, 0); return; case SERVICE_CONTROL_INTERROGATE: break; default: break; } }这个应该很清楚了,一般都是控制code的处理,这些code可以在MSDN上查到,同时user也可以自定义一些code。
最后我们还需要安装service,这里需要的API主要是createservice和OpenSCManager啦,比较简单,下面的代码还是来源于MSDN:
VOID SvcInstall(){ SC_HANDLE schSCManager; SC_HANDLE schService; TCHAR szPath[MAX_PATH]; if( !GetModuleFileName( NULL, szPath, MAX_PATH ) ) { printf("Cannot install service (%d)/n", GetLastError()); return; } // Get a handle to the SCM database. schSCManager = OpenSCManager( NULL, // local computer NULL, // ServicesActive database SC_MANAGER_ALL_ACCESS); // full access rights if (NULL == schSCManager) { printf("OpenSCManager failed (%d)/n", GetLastError()); return; } // Create the service schService = CreateService( schSCManager, // SCM database SVCNAME, // name of service SVCNAME, // service name to display SERVICE_ALL_ACCESS, // desired access SERVICE_WIN32_OWN_PROCESS, // service type SERVICE_DEMAND_START, // start type SERVICE_ERROR_NORMAL, // error control type szPath, // path to service's binary NULL, // no load ordering group NULL, // no tag identifier NULL, // no dependencies NULL, // LocalSystem account NULL); // no password if (schService == NULL) { printf("CreateService failed (%d)/n", GetLastError()); CloseServiceHandle(schSCManager); return; } else printf("Service installed successfully/n"); CloseServiceHandle(schService); CloseServiceHandle(schSCManager);}注意在main里面会调用这个函数阿,所以我们通过命令行就可以install某个exe上面的service了