第一部分是服务控制管理器Service Control Manager(SCM)。每个Windows NT/2000系统都有一个SCM,SCM存在于Service.exe中,在Windows启动的时候会自动运行,伴随着操作系统的启动和关闭而产生和终止。这个进程以系统特权运行,并且提供一个统一的、安全的手段去控制服务。它其实是一个Remote Procedure Call Protocol(RPC) Server,因此我们可以远程安装和管理服务,不过这不在本文讨论的范围之内。SCM包含一个储存着已安装的服务和驱动程序的信息的数据库,通过SCM可以统一的、安全的管理这些信息,因此一个服务程序的安装过程就是将自身的信息写入这个数据库。
第二部分就是服务本身。一个服务拥有能从SCM收到信号和命令所必需的的特殊代码,并且能够在处理后将它的状态回传给SCM。
第三部分是一个服务控制调度器Service Control Dispatcher(SCP)。它是一个拥有用户界面,允许用户开始、停止、暂停、继续,并且控制一个或多个安装在计算机上服务的Win32应用程序。SCP的作用是与SCM通讯,Windows 2000管理工具中的“服务”(win7中计算机右击->管理->服务和应用程序->服务)就是一个典型的SCP。
在这三个组成部分中,用户最可能去写服务本身,同时也可能不得不写一个与其伴随的客户端程序作为一个SCP去和SCM通讯,本文只讨论去设计和实现一个服务。
(以上引用自百度文库)
下面我们通过代码来解释一个服务的程序的编写。(其中代码来自MSDN)。windows服务程序编写包括以下几个部分:1、服务安装程序;2、服务卸载程序3、服务程序
1、服务安装程序。使用OpenSCManager打开SCM数据库,CreateService创建服务,将服务信息鞋服SCM的服务信息数据库。
2、 服务卸载程序。服务卸载程序其实就是安装过程的逆过程。打开SCM数据库,打开服务,先停止服务,然后用DeleteService来删除数据库中该服务的信息。服务的安装和删除过程都是通过服务名称来查找的,因此不同服务必须有不同的名字,即使服务程序不一样了,只要名字一样,其他服务的卸载程序也可以卸载该服务。void InstallService(PWSTR pszServiceName, PWSTR pszDisplayName, DWORD dwStartType, PWSTR pszDependencies, PWSTR pszAccount, PWSTR pszPassword) { wchar_t szPath[MAX_PATH]; SC_HANDLE schSCManager = NULL; SC_HANDLE schService = NULL; if (GetModuleFileName(NULL, szPath, ARRAYSIZE(szPath)) == 0) { wprintf(L"GetModuleFileName failed w/err 0x%08lx\n", GetLastError()); goto Cleanup; } // Open the local default service control manager database schSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT | SC_MANAGER_CREATE_SERVICE); if (schSCManager == NULL) { wprintf(L"OpenSCManager failed w/err 0x%08lx\n", GetLastError()); goto Cleanup; } // Install the service into SCM by calling CreateService schService = CreateService( schSCManager, // SCManager database pszServiceName, // Name of service pszDisplayName, // Name to display SERVICE_QUERY_STATUS, // Desired access SERVICE_WIN32_OWN_PROCESS, // Service type dwStartType, // Service start type SERVICE_ERROR_NORMAL, // Error control type szPath, // Service's binary NULL, // No load ordering group NULL, // No tag identifier pszDependencies, // Dependencies pszAccount, // Service running account pszPassword // Password of the account ); if (schService == NULL) { wprintf(L"CreateService failed w/err 0x%08lx\n", GetLastError()); goto Cleanup; } wprintf(L"%s is installed.\n", pszServiceName); Cleanup: // Centralized cleanup for all allocated resources. if (schSCManager) { CloseServiceHandle(schSCManager); schSCManager = NULL; } if (schService) { CloseServiceHandle(schService); schService = NULL; } }
void UninstallService(PWSTR pszServiceName) { SC_HANDLE schSCManager = NULL; SC_HANDLE schService = NULL; SERVICE_STATUS ssSvcStatus = {}; // Open the local default service control manager database schSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT); if (schSCManager == NULL) { wprintf(L"OpenSCManager failed w/err 0x%08lx\n", GetLastError()); goto Cleanup; } // Open the service with delete, stop, and query status permissions schService = OpenService(schSCManager, pszServiceName, SERVICE_STOP | SERVICE_QUERY_STATUS | DELETE); if (schService == NULL) { wprintf(L"OpenService failed w/err 0x%08lx\n", GetLastError()); goto Cleanup; } // Try to stop the service if (ControlService(schService, SERVICE_CONTROL_STOP, &ssSvcStatus)) { wprintf(L"Stopping %s.", pszServiceName); Sleep(1000); while (QueryServiceStatus(schService, &ssSvcStatus)) { if (ssSvcStatus.dwCurrentState == SERVICE_STOP_PENDING) { wprintf(L"."); Sleep(1000); } else break; } if (ssSvcStatus.dwCurrentState == SERVICE_STOPPED) { wprintf(L"\n%s is stopped.\n", pszServiceName); } else { wprintf(L"\n%s failed to stop.\n", pszServiceName); } } // Now remove the service by calling DeleteService. if (!DeleteService(schService)) { wprintf(L"DeleteService failed w/err 0x%08lx\n", GetLastError()); goto Cleanup; } wprintf(L"%s is removed.\n", pszServiceName); Cleanup: // Centralized cleanup for all allocated resources. if (schSCManager) { CloseServiceHandle(schSCManager); schSCManager = NULL; } if (schService) { CloseServiceHandle(schService); schService = NULL; } }
3、服务程序本身。服务程序一般情况下没有界面,因此编写服务一般为控制台应用程序,并且大多数程序入口点都为main。
int wmain(int argc, wchar_t *argv[]) { /*std::ofstream fo; fo.open("D:\\MyDoc\\ws\\run_err.log",std::ios::trunc); fo<<"hehe"<<std::endl; fo.close();*/ if ((argc > 1) && ((*argv[1] == L'-' || (*argv[1] == L'/')))) { if (_wcsicmp(L"install", argv[1] + 1) == 0) { // Install the service when the command is // "-install" or "/install". InstallService( SERVICE_NAME, // Name of service SERVICE_DISPLAY_NAME, // Name to display SERVICE_START_TYPE, // Service start type SERVICE_DEPENDENCIES, // Dependencies SERVICE_ACCOUNT, // Service running account SERVICE_PASSWORD // Password of the account ); } else if (_wcsicmp(L"remove", argv[1] + 1) == 0) { // Uninstall the service when the command is // "-remove" or "/remove". UninstallService(SERVICE_NAME); } } else { wprintf(L"Parameters:\n"); wprintf(L" -install to install the service.\n"); wprintf(L" -remove to remove the service.\n"); CSampleService service(SERVICE_NAME); if (!CServiceBase::Run(service)) { wprintf(L"Service failed to run w/err 0x%08lx\n", GetLastError()); } } return 0; }
在入口点之后,需要初始化ServiceTable。它是一个二维数组,用python的语言来说的话他就是一个字典,每一条记录都是一个服务名称和他的入口函数。一个服务程序中可以包含多个服务,而一个服务对应着一个服务名称和一个ServiceMain函数。然后这个数组将会被传递给StartServiceCtrlDispatcher,这个函数将会给每一个ServiceMain函数创建一个新的线程。而他本身则进入循环之中或者叫做睡眠状态。
BOOL CServiceBase::Run(CServiceBase &service) { s_service = &service; SERVICE_TABLE_ENTRY serviceTable[] = { { service.m_name, ServiceMain }, { NULL, NULL } }; // Connects the main thread of a service process to the service control // manager, which causes the thread to be the service control dispatcher // thread for the calling process. This call returns when the service has // stopped. The process should simply terminate when the call returns. return StartServiceCtrlDispatcher(serviceTable); }
StartServiceCtrlDispatcher只有在两种情况下苏醒:
StartServiceCtrlDispatcher创建的新线程执行ServiceMain函数,首先要注册这个服务的ServiceCtrlHandler。这个程序由用户编写,规定了这个服务如何响应SCM发送的命令。通常情况下这个函数是一个switch语句。然后开启服务。
- 当SCM向这个StartServiceCtrlDispatcher管辖的ServiceMain发送控制(开始,停止,暂停,继续等)命令的时候,它会调用ServiceCtrlHandler。
- 在它管辖的服务线程退出的时候,StartServiceCtrlDispatcher会将他所管辖的服务数减一。当它管辖的所有服务全部退出的时候,它就返回返回入口点函数main。
void WINAPI CServiceBase::ServiceCtrlHandler(DWORD dwCtrl) { switch (dwCtrl) { case SERVICE_CONTROL_STOP: s_service->Stop(); break; case SERVICE_CONTROL_PAUSE: s_service->Pause(); break; case SERVICE_CONTROL_CONTINUE: s_service->Continue(); break; case SERVICE_CONTROL_SHUTDOWN: s_service->Shutdown(); break; case SERVICE_CONTROL_INTERROGATE: break; default: break; } } void WINAPI CServiceBase::ServiceMain(DWORD dwArgc, PWSTR *pszArgv) { assert(s_service != NULL); // Register the handler function for the service s_service->m_statusHandle = RegisterServiceCtrlHandler( s_service->m_name, ServiceCtrlHandler); if (s_service->m_statusHandle == NULL) { throw GetLastError(); } // Start the service. s_service->Start(dwArgc, pszArgv); }
这里以开启服务来解释服务对SCM命令的响应过程,开启服务函数一般情况下会包括这几个部分。
- 告诉SCM他要开启了
- 做开启时要做的事情
- 告诉SCM他已经开启了
一般情况下在OnStart开启一个新的线程,在新的线程中做服务要做的事情。void CServiceBase::Start(DWORD dwArgc, PWSTR *pszArgv) { try { // Tell SCM that the service is starting. SetServiceStatus(SERVICE_START_PENDING); // Perform service-specific initialization. OnStart(dwArgc, pszArgv); // Tell SCM that the service is started. SetServiceStatus(SERVICE_RUNNING); } catch (DWORD dwError) { // Log the error. WriteErrorLogEntry(L"Service Start", dwError); // Set the service status to be stopped. SetServiceStatus(SERVICE_STOPPED, dwError); } catch (...) { // Log the error. WriteEventLogEntry(L"Service failed to start.", EVENTLOG_ERROR_TYPE); // Set the service status to be stopped. SetServiceStatus(SERVICE_STOPPED); } }