1. 程式人生 > >Windows系統程式設計之編寫Windows服務(1)

Windows系統程式設計之編寫Windows服務(1)

一、Windows服務簡介:
Windows服務,也稱NT服務,提供將伺服器轉換為可以用命令或者在啟動時初始化的服務所需的管理能力,初始化發生在任何使用者登入之前,服務可以暫停、恢復、終止、監控。
登錄檔維護與服務的有關資訊。
    Windows的所有服務在如下圖所示位置:     
使用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的命令做出響應
下面的圖顯示了步驟流程:

(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結構的定義:
具體引數含義見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裡面建立服務和開始服務

2)停止服務和查詢服務:

3)刪除服務:

    4)文字記錄日誌內容

注意:如果使用Windows 7 或Windows8 系統,sc create 服務名稱 binPath= "exe應用程式路徑" 可能會建立失敗,提示錯誤是5,無法訪問。這是許可權的問題,你可以把建立的命令放在批處理檔案裡面,然後右擊選擇管理員身份執行就Ok了。