標籤:
服務程式是windows上重要的一類程式,它們雖然不與使用者進行介面互動,但是它們對於系統有著重要的意義。windows上為了管理服務程式提供了一個特別的程式:服務控制管理程式,系統上關於服務控制管理的API基本上都與這個程式打交道。下面通過對服務程式的操作來說明這些API函數
擷取系統服務的資訊
在windows系統中有專門用來儲存服務資訊的資料庫,而擷取系統服務資訊主要是通過在這樣的資料庫中尋找。所用到的函數主要有:
OpenSCManager:開啟資料庫
SC_HANDLE WINAPI OpenSCManager( __in LPCTSTR lpMachineName, __in LPCTSTR lpDatabaseName, __in DWORD dwDesiredAccess);
這個函數主要用來串連特定電腦上的服務控制管理員,並開啟服務控制管理員的資料庫。
函數的參數有:
lpMachineName:主機名稱
lpDatabaseName:主機中服務資料庫的名稱
dwDesiredAccess:以何種許可權開啟服務程式
前兩個參數都可以為NULL,如果第一個參數為NULL,則表示在本機上擷取,第二個參數為NULL表示從註冊表中擷取,第三個參數的主要傳入如下值:
SC_MANAGER_ALL_ACCESS (0xF003F) :預設擁有所有許可權
SC_MANAGER_CREATE_SERVICE (0x0002):具有建立服務的許可權
SC_MANAGER_CONNECT (0x0001):串連的權利
SC_MANAGER_ENUMERATE_SERVICE (0x0004) 枚舉裡面資訊的許可權
後面的就不再一一說明了,詳細資料請看MSDN的記錄。在程式中為了方便一般採用SC_MANAGER_ALL_ACCESS 參數
函數如果調用成功,則會返回一個操作資料庫的控制代碼,以後的關於服務的操作都已這個參數作為第一個參數。
EnumServicesStatus:枚舉系統服務
BOOL WINAPI EnumServicesStatus( __in SC_HANDLE hSCManager, __in DWORD dwServiceType, __in DWORD dwServiceState, __out LPENUM_SERVICE_STATUS lpServices, __in DWORD cbBufSize, __out LPDWORD pcbBytesNeeded, __out LPDWORD lpServicesReturned, __in_out LPDWORD lpResumeHandle);
hSCManager:服務資料庫控制代碼
dwServiceType:枚舉服務的類型,主要有:SERVICE_DRIVER(驅動類型服務)、SERVICE_WIN32(win32類型的服務)
dwServiceState:表示枚舉哪中狀態的服務,主要有:SERVICE_ACTIVE(已啟動的服務)、SERVICE_INACTIVE(未啟動的服務)、SERVICE_STATE_ALL(所有服務)
lpServices:這個參數主要是作為一個緩衝區,用來返回服務資訊,類型ENUM_SERVICE_STATUS主要儲存的是服務名稱、顯示名稱以及一個SERVICE_STATUS 結構體,該結構體的原型如下:
typedef struct _SERVICE_STATUS{ DWORD dwServiceType; //服務類型 DWORD dwControlsAccepted;//目前狀態 DWORD dwServiceSpecificExitCode; DWORD dwCheckPoint; DWORD dwWaitHint;} SERVICE_STATUS, *LPSERVICE_STATUS;
cbBufSize:緩衝區的大小
pcbBytesNeeded:實際需要緩衝區的大小
lpServicesReturned:服務的傳回值
lpResumeHandle:額外的控制代碼
每一個ENUM_SERVICE_STATUS結構體儲存的是一個服務的資訊,但是我們事先並不知道有多少個服務,因此不知道該定義多大的服務資訊數組,但是windows考慮到了這一點,當函數調用失敗時利用GetLastError返回ERROR_MORE_DATA時表示提供的緩衝區不夠,這個時候參數pcbBytesNeeded會返回正確的大小,所以使用這個函數一般會經過兩個調用第一次lpServices = NULL, cbBufSize = 0,這個時候函數出錯並返回所需要的實際大小,然後根據大小動態分陪一個記憶體緩衝區或者提供一個數組,並傳入實際大小,以擷取所有服務的資訊。下面提供一個具體的例子:
SC_HANDLE scHandle = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS); if(NULL == scHandle) { return FALSE; } LPENUM_SERVICE_STATUS pServices = NULL; DWORD dwByteNeed = 0; DWORD dwServiceReturn = 0; LPDWORD lpResumeHandle = NULL; //第一次調用,將緩衝區設定為NULL並將緩衝區大小設定為0 BOOL bRet = ::EnumServicesStatus(scHandle, SERVICE_WIN32, SERVICE_STATE_ALL, pServices, 0, &dwByteNeed, &dwServiceReturn, lpResumeHandle); if(!bRet) { //如果是因為緩衝區大小不夠 if(ERROR_MORE_DATA == GetLastError()) { //儲存緩衝區的真實大小 DWORD dwRealNeed = dwByteNeed; //多分配一個是為了儲存字串末尾的0 pServices = (LPENUM_SERVICE_STATUS)new char[dwRealNeed + 1]; ASSERT(NULL != pServices); ZeroMemory(pServices, dwRealNeed + 1); bRet = ::EnumServicesStatus(scHandle, SERVICE_WIN32, SERVICE_STATE_ALL, pServices, dwRealNeed + 1, &dwByteNeed, &dwServiceReturn, lpResumeHandle); //通過上述代碼可以擷取到服務的相關資訊 } }
擷取服務的主程式所在路徑、啟動類型以及依賴項
上述代碼只能擷取到系統服務的部分資訊,比如服務的名稱,顯示名稱,等等至於其他的資訊需要調用另外的API函數擷取
OpenService擷取具體服務的控制代碼
SC_HANDLE WINAPI OpenService( __in SC_HANDLE hSCManager, //服務資料庫的控制代碼 __in LPCTSTR lpServiceName,//服務的名稱 __in DWORD dwDesiredAccess//以何種許可權開啟,為了方便一般填入SERVICE_ALL_ACCESS所有許可權);
QueryServiceConfig 查詢系統服務資訊
BOOL WINAPI QueryServiceConfig( __in SC_HANDLE hService, __out LPQUERY_SERVICE_CONFIG lpServiceConfig, __in DWORD cbBufSize, __out LPDWORD pcbBytesNeeded);
這個函數的第二個參數是一個結構體指標這個結構體的定義如下:
typedef struct _QUERY_SERVICE_CONFIG { DWORD dwServiceType; //服務類型 DWORD dwStartType; //啟動類型 DWORD dwErrorControl;//錯誤碼,服務執行出錯時返回,作業系統根據這個錯誤碼來做相應的處理 LPTSTR lpBinaryPathName;//主程式所在路徑 LPTSTR lpLoadOrderGroup; DWORD dwTagId; LPTSTR lpDependencies; //依賴項 LPTSTR lpServiceStartName; LPTSTR lpDisplayName; //顯示名稱} QUERY_SERVICE_CONFIG, *LPQUERY_SERVICE_CONFIG;
這個函數的調用方式與EnumServicesStatus相同,也是通過兩次調用,第一次獲得所需的空間大小,這個大小通過第四個參數返回。
下面的代碼展示了如何調用這兩個函數
//第一個參數是通過OpenSCManager函數擷取得到的SC_HANDLE h_SCService = OpenService(h_SCHandle, pSrvItem->strSrvName, SERVICE_ALL_ACCESS); if(NULL == h_SCService) { CloseServiceHandle(h_SCHandle); return FALSE; } LPQUERY_SERVICE_CONFIG pSrvConfig = NULL; DWORD dwBuffSize = 0; DWORD dwBuffNeed = 0; BOOL bRet = QueryServiceConfig(h_SCService, pSrvConfig, dwBuffSize, &dwBuffNeed); if(!bRet) { if(ERROR_INSUFFICIENT_BUFFER == GetLastError()) { DWORD dwRealNeed = dwBuffNeed; pSrvConfig = (LPQUERY_SERVICE_CONFIG)new char[dwRealNeed + 1]; ASSERT(NULL != pSrvConfig); bRet = QueryServiceConfig(h_SCService, pSrvConfig, dwRealNeed, &dwBuffNeed); } }
擷取服務的描述資訊
描述資訊一般是有服務開發人員提供,以便解釋服務程式的作用等等資訊,這些資訊在注入服務時由系統記錄,並呈現給使用者。擷取系統服務主要使用的API函數是QueryServiceConfig2
BOOL WINAPI QueryServiceConfig2( __in SC_HANDLE hService, __in DWORD dwInfoLevel,//將擷取何種資訊在這我們需要填寫SERVICE_CONFIG_DESCRIPTION表示擷取描述資訊 __out LPBYTE lpBuffer, __in DWORD cbBufSize, __out LPDWORD pcbBytesNeeded);
這個函數不想上面的QueryServiceConfig一次可以擷取服務的多項資訊,它是根據第二個參數指定需要擷取哪項資訊,然後返回到第3個參數提供的緩衝區中,這個緩衝區是一個BYTE類型的指標,調用者需要根據具體的情況進行類型轉化。同樣這個函數需要進行兩次調用。
BOOL bRet = QueryServiceConfig2(h_SCService, SERVICE_CONFIG_DESCRIPTION, (LPBYTE)lpByte, cbBufSize, &dwBuffNeed); if(!bRet) { if(ERROR_INSUFFICIENT_BUFFER == GetLastError()) { DWORD dwRealNeed = dwBuffNeed; //LPSERVICE_DESCRIPTION結構體中只儲存了一個字串指標lpDescription lpByte = (LPSERVICE_DESCRIPTION)new char[dwRealNeed + 1]; ASSERT(NULL != lpByte); bRet = QueryServiceConfig2(h_SCService, SERVICE_CONFIG_DESCRIPTION, (LPBYTE)lpByte, dwRealNeed, &dwBuffNeed); if(!bRet) { delete[] lpByte; goto __ERROR_RET; } pSrvItem->strSrvDescrible = lpByte->lpDescription; delete[] lpByte; CloseServiceHandle(h_SCService); CloseServiceHandle(h_SCHandle); return TRUE; } }__ERROR_RET: CloseServiceHandle(h_SCService); CloseServiceHandle(h_SCHandle); return FALSE;
服務的控制
服務控制主要控制服務的啟動,暫停,恢複,停止等等。
StartService啟動服務
BOOL WINAPI StartService( __in SC_HANDLE hService, __in DWORD dwNumServiceArgs,//啟動參數的個數 __in LPCTSTR* lpServiceArgVectors//參數列表指標);
這個函數有點類似於main函數,main函數可以傳遞命令列參數給程式,以便實現程式與使用者的互動,這裡同樣可以傳遞參數,以便服務完成特定的功能,當第二個參數為0時第三個參數為NULL
ControlService 控制服務
BOOL WINAPI ControlService( __in SC_HANDLE hService, __in DWORD dwControl,//新狀態 __out LPSERVICE_STATUS lpServiceStatus//服務的原始狀態);
這個函數用來完成除了啟動之外的服務控制,其中第二個參數是服務的狀態,它可使用的參數主要有如下幾個:
| 取值 |
含義 |
| SERVICE_CONTROL_CONTINUE |
繼續運行 |
| SERVICE_CONTROL_PAUSE |
暫停 |
| SERVICE_CONTROL_STOP |
停止 |
其餘的部分不是很常用,故不在這裡一一列舉
下面是一個具體的例子,由於要考慮當前的狀態以及服務是否支援這種狀態,因此這部分的代碼多了許多判斷的部分
SERVICE_STATUS ServiceStatus = {0};//擷取當前的狀態 BOOL bRet = QueryServiceStatus(h_SCService, &ServiceStatus); ASSERT(bRet); if(ServiceStatus.dwCurrentState == dwNewStatus) { goto __RETURN; }//如果當前服務正處在啟動和暫停之中,但是沒有完成這個動作時不允許改變服務狀態 if(ServiceStatus.dwCurrentState == SERVICE_CONTINUE_PENDING || SERVICE_PAUSE_PENDING ==ServiceStatus.dwCurrentState || SERVICE_START_PENDING == ServiceStatus.dwCurrentState || SERVICE_STOP_PENDING == ServiceStatus.dwCurrentState) { bRet = FALSE; goto __RETURN; }//如果服務處在暫停狀態,則只允許繼續運行和停止 if(SERVICE_PAUSED == ServiceStatus.dwCurrentState) { if(SERVICE_CONTROL_CONTINUE == dwNewStatus || SERVICE_CONTROL_STOP == dwNewStatus) { bRet = ControlService(h_SCService, dwNewStatus, &ServiceStatus); goto __RETURN; }else { bRet = FALSE; goto __RETURN; } }//如果服務正在運行,則運行暫停和停止 if(SERVICE_RUNNING == ServiceStatus.dwCurrentState) { if(SERVICE_CONTROL_PAUSE == dwNewStatus || SERVICE_CONTROL_STOP == dwNewStatus) { bRet = ControlService(h_SCService, dwNewStatus, &ServiceStatus); goto __RETURN; }else { bRet = FALSE; goto __RETURN; } }//如果服務處於停止狀態,則允許運行操作 if(SERVICE_STOPPED == ServiceStatus.dwCurrentState) { if(SERVICE_RUNNING == dwNewStatus) { bRet = StartService(h_SCService, 0, NULL); goto __RETURN; }else { bRet = FALSE; goto __RETURN; } }__RETURN: if(bRet) { pIter->pNode->dwCurrentStatus = dwNewStatus; } CloseServiceHandle(h_SCService); CloseServiceHandle(h_SCHandle); return bRet;
當前服務的狀態可以使用EnumServicesStatus函數擷取,但是這個函數是擷取系統中記錄的所有的服務程式,在這樣的情況下,我們只需要擷取一個服務的狀態,調用這個函數總有殺雞用牛刀的意思,所以採用的是另外一個函數QueryServiceStatus,服務程式的主要狀態有如下幾種:
| 取值 |
狀態原因 |
| SERVICE_RUNNING |
已啟動 |
| SERVICE_STOPPED |
已停止 |
| SERVICE_PAUSED |
已暫停 |
| SERVICE_CONTINUE_PENDING |
正在恢複 |
| SERVICE_PAUSE_PENDING |
正在暫停 |
| SERVICE_START_PENDING |
正在啟動 |
| SERVICE_STOP_PENDING |
正在停止 |
其中前幾個是完成時,也就是完成了從一個狀態到另一個的轉化,而後面幾個是進行時,正在這兩種狀態之間
擷取服務的可控類型
在控制服務時不光要考慮服務程式當前的狀態,還要考慮它是否支援這種狀態,比如有的服務就不支援暫停和恢複操作
QueryServiceStatus 查詢服務的狀態資訊
這個函數主要傳遞一個SERVICE_STATUS結構體的指標。這個結構體的定義如下:
typedef struct _SERVICE_STATUS { DWORD dwServiceType;//服務類型 DWORD dwCurrentState; //目前狀態 DWORD dwControlsAccepted;//允許的操作 DWORD dwWin32ExitCode; DWORD dwServiceSpecificExitCode; DWORD dwCheckPoint; DWORD dwWaitHint;} SERVICE_STATUS, *LPSERVICE_STATUS;
改變服務的啟動類型
服務的啟動類型主要有開機啟動、手動啟動、以及禁止啟動等項。改變啟動類型的函數主要是:ChangeServiceConfig。函數原型如下:
BOOL WINAPI ChangeServiceConfig( __in SC_HANDLE hService, __in DWORD dwServiceType,//服務類型 __in DWORD dwStartType,//啟動類型 __in DWORD dwErrorControl, __in LPCTSTR lpBinaryPathName,//服務的主程式路徑 __in LPCTSTR lpLoadOrderGroup, __out LPDWORD lpdwTagId, __in LPCTSTR lpDependencies,//依賴項 __in LPCTSTR lpServiceStartName,//服務名稱 __in LPCTSTR lpPassword,//服務密碼,主要用於控制服務 __in LPCTSTR lpDisplayName//服務的顯示名稱);
函數中傳遞的都是服務的新資訊,如果希望改變則填入相應的值,如果不想改變則對於DWORD類型的成員來說填入SERVICE_NO_CHANGE,對於指標類型的只需要填入NULL即可。
建立服務
建立服務主要使用函數CreateService,該函數的原型如下:
SC_HANDLE WINAPI CreateService( __in SC_HANDLE hSCManager, __in LPCTSTR lpServiceName,//服務名稱 __in LPCTSTR lpDisplayName,//顯示名稱 __in DWORD dwDesiredAccess,//服務許可權 __in DWORD dwServiceType,//服務類型 __in DWORD dwStartType,//服務啟動類型 __in DWORD dwErrorControl, __in LPCTSTR lpBinaryPathName,//主程式路徑 __in LPCTSTR lpLoadOrderGroup, __out LPDWORD lpdwTagId, __in LPCTSTR lpDependencies,//依賴項 __in LPCTSTR lpServiceStartName,啟動名稱 __in LPCTSTR lpPassword//密碼);在啟動時需要填入一些資訊系統的服務控制管理員儲存這些資訊,並根據其中的某些資訊來啟動這個服務,有的選項是必填的,比如服務名稱,這個是用來唯一標識一個服務的,服務所在路徑告知服務控制管理員啟動哪個程式,而向依賴、密碼等等資訊可以不用填寫。下面是調用的例子```cppp SC_HANDLE hRet = ::CreateService(h_SCManager, lpServiceName, lpDisplayName, dwDesiredAccess, SERVICE_WIN32_OWN_PROCESS, dwStartType, SERVICE_ERROR_NORMAL, lpBinaryPathName, NULL, NULL, lpDependencies, lpServiceStartName, lpPassword);<div class="se-preview-section-delimiter"></div>
SERVICE_WIN32_OWN_PROCESS表示服務類型是win32類型擁有獨立進程的服務
SERVICE_ERROR_NORMAL表示服務程式返回的錯誤碼是系統預設的錯誤碼
刪除服務
刪除服務使用的函數是DeleteService,這個函數主要傳入的是服務的控制代碼,這個控制代碼是由函數OpenService返回的。另外需要注意的是這個函數只對已停止的服務起作用,所以在刪除之前需要將服務停止。
“`
SERVICE_WIN32_OWN_PROCESS表示服務類型是win32類型擁有獨立進程的服務SERVICE_ERROR_NORMAL表示服務程式返回的錯誤碼是系統預設的錯誤碼## 刪除服務刪除服務使用的函數是DeleteService,這個函數主要傳入的是服務的控制代碼,這個控制代碼是由函數OpenService返回的。另外需要注意的是這個函數只對已停止的服務起作用,所以在刪除之前需要將服務停止。```cppBOOL bRet = FALSE; DWORD dwSrvAcceptCtrl = GetSrvCtrlAccept(pIter->pNode->strSrvName); if(0 == (dwSrvAcceptCtrl & SERVICE_ACCEPT_STOP)) //服務不能被刪除 { goto __RETURN; } //停止服務,函數CtrlService是我之前封裝的用於控制服務。 bRet = CtrlService(pIter, SERVICE_CONTROL_STOP); if(!bRet) { goto __RETURN; } bRet = ::DeleteService(h_SCService); if (bRet) { DeleteItem(pIter->pNode); }__RETURN: CloseServiceHandle(h_SCService); CloseServiceHandle(h_SCManager); return bRet;
windows服務管理操作