Windows服務是其實一種特殊的二進位可執行檔,尾碼名一般為EXE,之所以說它特殊,因為它具有同Windows NT/2K系統的服務控制管理員(SCM: Service Control Manager)通訊。
服務控制管理員通過維護資料庫對已經安裝到系統的所有服務和驅動程式進行統一而安全的控制和管理。服務控制管理員是一個遠程進程調用(RPC)伺服器,在系統匯入時自動啟動。
一個簡單的服務程式至少包括一些幾個部分:
1. Win32/控制台應用主程式;
2. 一個服務主程式,作為服務的匯入點;
3. 一個服務控制處理器,就是同服務控制管理員SCM通訊的函數;
4. 一個服務安裝/反安裝程式用於將一個EXE檔案註冊為一個服務。
下面我們針對上述幾個部分分別介紹怎樣構造一個Windows服務。
控制台應用主程式
在Win32下為WinMain函數,在控制台下為main函數,是服務的主程式。下面是服務主程式中至少要包含的語句。
#include "Winsvc.h" //服務標頭檔
main()
{
......
SERVICE_TABLE_ENTRY Table[]={{"gkeyService",gkeyServiceMain},{NULL,NULL}};
StartServiceCtrlDispatcher(Table);
......
}
當然這是一個非常簡單的主程式了。這裡main只做了一件事情,就是填寫SERVICE_TABLE_ENTRY結構數組Table。Table[0][0]是服務的名字(可以是您喜歡的任一字元串,此處我用的是gkeyService);Table[0][1]指定了服務主程式的名字,實際上這是一個指向服務主程式的函數指標,它也可以用您喜歡的函數名字(我用的是gkeyServiceMain)。現在通過調用參數為SERVICE_TABLE_ENTRY結構數組的函數StartServiceCtrlDispatcher()開始啟動服務解析。注意這個函數的參數必須要符合一定的格式,Table[1][0]和Table[1][1]必須是NULL,就是說到了數組的結尾。當然並非必須這樣,如果需要在這個執行程式中運行多個服務,可以在這個數組列表中加入更多的入口,構成多對服務名稱和服務中程式,自然您需要在以下的步驟中需要為每個服務構造相應的完成函數。
服務主程式
典型的服務主程式的聲明如下:
void WINAPI gkeyServiceMain( DWORD argc, LPTSTR *argv )
在gkeyServiceMain函數中,需要實現的主要步驟包括:
1. 用合適的值填寫SERVICE_STATUS結構來完成同服務控制管理員SCM的通訊;
2. 在列表中註冊前面所說的服務控制處理函數;
3. 調用實際的處理函數。
為了完成上述功能,需要使用兩個全域變數:
SERVICE_STATUS m_ServiceStatus;
SERVICE_STATUS_HANDLE m_ServiceStatusHandle;
服務主程式gkeyServiceMain()能夠象通常的c/c++裡的main()函數一樣接受命令列參數,並且接受參數的方式也完全一樣。第一個參數argc包含了傳遞給服務的參數個數,同c/c++的main()一樣至少有一個參數就是服務應用本身。第二個參數是一個字元指標數組的指標。同main()函數一樣,數組的第一個值總是指向服務的名字。
使用SERVICE_STATUS資料結構記錄服務的目前狀態,並將狀態及時通告給服務控制管理員SCM,使用一個API函數SetServiceStatus()來實現這一目標。SERVICE_STATUS的資料成結構員如下:
dwServiceType = SERVICE_WIN32;
dwCurrentState = SERVICE_START_PENDING; // 試圖啟動(初始狀態)
dwControlsAccepted = SERVICE_ACCEPT_STOP; // 僅接收服務控製程序的啟動/停止,服務控製程序通常在
Windows NT下的控制台或者Windows 2K下的管理工具,我們也可以設定服務接受暫停/繼續功能。
在服務主程式gkeyServiceMain()的開始應該設定SERVICE_STATUS的狀態欄位dwCurrentState為SERVICE_START_PENDING,通知SCM服務處於運行狀態。如果發生錯誤,應該發送SERVICE_STOPPED通知服務控制管理員SCM。預設狀態下,服務控制管理員SCM將監視服務的活動,如果2分鐘之類沒有發現進程活動就殺死這個服務。
使用API函數RegisterServiceCtrlHandler()設定服務控制管理員SCM的服務控制處理函數,這個函數需要兩個參數,一個是服務名稱字串,一個是服務控制處理函數控制代碼。
現在要設定dwCurrentState為SERVICE_RUNNING用以通知服務已經啟動。
服務控制處理函數
服務控制管理員SCM使用服務控制處理函數和服務程式進行通訊來瞭解服務的諸如啟動、停止、暫停或繼續等使用者指令,它主要包含一個switch語句來處理每種情況,調用相應的步驟來啟動、急需、清除和中斷進程。函數收到一個象SERVICE_CONTROL_PAUSE, SERVICE_CONTROL_CONTINUE, SERVICE_CONTROL_STOP, SERVICE_CONTROL_INTERROGATE等作業碼,就需要為每種指令提供相應的處理步驟。
安裝/反安裝
要安裝一個服務,在系統註冊時需要產生一些入口,通常使用Windows有現成的API而不是註冊函數來完成這些步驟,這些函數有CreateService()和DeleteService()。為了安裝服務,首先使用OpenSCManager(NULL,NULL,SC_MANAGER_ALL_ACCESS)開啟服務控制管理員SCM。然後調用CreateService()來建立服務,給出服務的名字,如果要刪除指定的服務,也將需要使用這個名字刪除。
例子代碼如下:
// 建立服務
String strSrvName = Application->ExeName;
SC_HANDLE schService = CreateService(
scm,
"ccrunSrv", // 服務名稱
"ccrun's Service", // 服務詳細說明
SERVICE_ALL_ACCESS,
SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS,
SERVICE_AUTO_START, // 以自動方式開始
SERVICE_ERROR_NORMAL,
strSrvName.c_str(), // Service本體程式路徑,必須與具體位置相符
NULL,
NULL,
NULL,
NULL,
NULL);
if(schService != NULL)
{
CloseServiceHandle(schService);
}
//---------------------------------------------------------------------------
// 開始Service
sHandle = OpenService(scm, "ccrunSrv", SERVICE_START);
if(sHandle!=NULL)
{
StartService(sHandle, 0, NULL);
CloseServiceHandle(sHandle);
}
//---------------------------------------------------------------------------
// 關閉服務管理員
CloseServiceHandle(scm);
常用函數:
----在WindowsNT下 各種Service都存在service control manager database中 因此我們可以通過對service control manager database進行操作來實現對Service的編程。下面介紹常用的函數:
1:SC_HANDLE OpenSCManager(LPCTSTR lpszMachineName, LPCTSTR lpszDatabaseName, DWORD fdwDesiredAccess)
----Open SCManager 函數開啟指定電腦上的service control manager database。其中參數lpszMachineName指定電腦名稱 若為空白則指定為本機。參數lpszDatabaseName指定要開啟的service control manager database,預設為空白。
----參數fdwDesiredAccess指定操作的許可權,可以為下面取值之一
SC_MANAGER_ALL_ACCESS // 所有許可權
SC_MANAGER_CONNECT // 允許串連service control manager
SC_MANAGER_CREATE_SERVICE // 允許建立服務物件並把它加入service control manager database
SC_MANAGER_ENUMERATE_SERVICE // 允許枚舉service control manager database中的服務
SC_MANAGER_LOCK // 允許鎖住service control manager database
SC_MANAGER_QUERY_LOCK_STATUS // 允許擷取servicecontrolmanagerdatabase的封鎖資訊
----函數傳回值:函數執行成功則返回一個指向service control manager database的控制代碼 失敗則返回NULL。
2:SC_HANDLE OpenService(SC_HANDLE schSCManager, LPCTSTR lpszServiceName, DWORD fdwDesiredAccess)
----OpenService函數開啟指定的Service。
----其中參數schSCManager是指向service control manager database的控制代碼 由OpenSCManager函數返回。
----參數lpszServiceName要開啟的服務的名字 注意大小寫。
----參數fdwDesiredAccess指定操作的許可權,可以為下面取值之一
SERVICE_ALL_ACCESS // 所有許可權
SERVICE_CHANGE_CONFIG // 允許更改服務的配置
SERVICE_ENUMERATE_DEPENDENTS // 允許擷取依賴於該服務的其他服務
SERVICE_INTERROGATE // 允許立即擷取服務狀態
SERVICE_PAUSE_CONTINUE // 允許暫停和喚醒服務
SERVICE_QUERY_CONFIG // 允許擷取服務配置
SERVICE_QUERY_STATU // 允許通過訪問service control manager擷取服務狀態
SERVICE_START // 允許啟動服務
SERVICE_STOP // 允許停止服務
SERVICE_USER_DEFINE_CONTROL // 允許使用者指定特殊的服務控制碼
----函數傳回值:函數執行成功則返回指向某項服務的控制代碼 失敗則返回NULL。
3:BOOL QueryServiceStatus(SC_HANDLE schService,LPSERVICE_STATUS lpssServiceStatus)
----QueryServiceStatus函數返回指定服務的目前狀態。
----其中參數schService是指向某項服務的控制代碼 由OpenService函數返回 且必須SERVICE_QUERY_STATUS的許可權。
----參數lpssServiceStatus中存放返回的服務狀態資訊 結構如下
typedef struct _SERVICE_STATUS
{
DWORD dwServiceType // 服務類型
DWORD dwCurrentState // 目前狀態
DWORD dwControlsAccepted // 服務可接受的控制碼
DWORD dwWin32ExitCode // Win32出錯代碼
DWORD dwServiceSpecificExitCode // 服務出錯代碼
DWORD dwCheckPoint // 用於Tracing Service長時間操作
DWORD dwWaitHint // 服務某一操作的最大允許時間,以毫秒為單位
}SERVICE_STATUS, *LPSERVICE_STATUS;
----函數傳回值:函數執行成功則返回True,失敗則返回False。
4:BOOLStartService(SC_HANDLE schService, DWORD dwNumServiceArgs, LPCTSTR * lpszServiceArgs)
----StartService函數啟動指定的服務。
----其中參數schService是指向某項服務的控制代碼 由OpenService函數返回 且必須有SERVICE_START的許可權。
----dwNumServiceArgs為啟動服務所需的參數的個數。
----lpszServiceArgs為啟動服務所需的參數。函數傳回值:函數執行成功則返回True,失敗則返回False。
5:BOOL ControlService(SC_HANDLE hService, DWORD dwControl, LPSERVICE_STATUS lpServiceStatus)
----ControlService函數向Win32service發送控制碼。
----其中參數hService是指向某項服務的控制代碼 由OpenService函數返回。
----參數dwControl為控制碼 常用的有
SERVICE_CONTROL_STOP // 停止服務
SERVICE_CONTROL_PAUSE // 暫停服務
SERVICE_CONTROL_CONTINUE // 喚醒暫停服務
SERVICE_CONTROL_INTERROGATE // 重新整理某服務的狀態
----參數lpServiceStatus指向SERVICE_STATUS結構 用於存放該服務最新的狀態資訊。
----函數傳回值:函數執行成功則返回True,失敗則返回False。
6:BOOL EnumServicesStatus(SC_HANDLE hSCManager, DWORD dwServiceType, DWORD dwServiceState,
LPENUM_SERVICE_STATUS lpServices, DWORD cbBufSize, LPDWORD pcbBytesNeeded,
LPDWORD lpServicesReturned, LPDWORD lpResumeHandle)
----EnumServicesStatus函數用於枚舉NT下存在的Service。
----其中參數hSCManager是指向service control manager database的控制代碼 由OpenSCManager函數返回 且必須有SC_MANAGER_ENUMERATE_SERVICE的許可權。
----參數dwServiceType指定按服務的類型枚舉。
----參數dwServiceState指定按服務的狀態枚舉。
----參數lpServices指向ENUM_SERVICE_STATUS結構 用於存放返回的服務的名字和狀態資訊。
----參數cbBufSize返回參數lpServices的長度 以位元組為單位。
----參數pcbBytesNeeded返回擷取剩餘的Service所需位元組的個數。
----參數lpServicesReturned返回服務的個數。
----參數lpResumeHandle 當第一次調用時該參數為0 當該函數再次被調用以擷取另外的資訊時 該參數表示下一個被讀的Service。
----函數傳回值:函數執行成功則返回True,失敗則返回False。
----值得注意的是通常情況下該函數返回的結果為FALSE 我們可以調用GetLastError()來擷取進一步資訊。因為一台機器上有多種服務存在 所以GetLastError()應為ERROR_MORE_DATA 此時應再次調用EnumServicesStatus函數以擷取正確的Service列表。