Windows服务编写
生活随笔
收集整理的這篇文章主要介紹了
Windows服务编写
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
摘要:幾乎所有的操作系統在啟動的時候都會啟動一些不需要與用戶交互的進程,這些進程在Windows中就被稱作服務。它由服務程序、服務控制程序(SCP,service control program)和服務控制管理器(SCM,service control manager)三個組件構成。本文針對服務程序與服務控制程序的編寫進行綜合講述。
??? 關鍵詞:Windows,服務,VC++
?1??????? 服務介紹
幾乎所有的操作系統在啟動的時候都會啟動一些不需要與用戶交互的進程,這些進程在Windows中就被稱作服務。它通常用于實現客戶/服務器模式中的服務器方,如我們常見的Web服務IIS,當操作系統在啟動后它就自動被運行,不管是否有人登陸到系統只要系統開啟它就能得到運行。
服務程序、服務控制程序(SCP,service control program)和服務控制管理器(SCM,service control manager)組成了Windows服務。我們可以通過服務控制程序操縱服務控制管理器來配置、啟動、暫停、停止服務程序。其中服務程序和服務控制程序可以由我們自己來編寫擴展,而服務控制管理器(\windows\system32\servics.exe)則是操作系統內置的一個部件。首先我們來了解一下SCM的工作情況,然后我們介紹服務程序的編寫和服務控制時所涉及API的使用。
?2??????? 服務控制管理器
SCM本身也是一個服務程序(\windows\system32\servics.exe),作為windows的后臺服務運行的。Winlogon在系統引導的早期會將SCM啟動起來。SCM的服務入口函數首先創建一個初始化為無信號的同步事件對象(SvcCtrlEvent_A3752DX);接下來,它開始建立一個內部服務數據庫,這個數據庫要按事先規定好的一個順序列出所有服務組,并記錄與服務相關的詳細信息;當這個數據庫建立完成時SCM就開始按順序啟動那些啟動方式為自動的服務,如果有服務要動行于指定用戶賬戶中時還要調用LSASS,如果服務啟動失敗則會被放入一個名為ScFailedDrivers的列表中。當這些工作都完成后,SCM將同步事件對象SvcCtrlEvent_A3752DX置為有信號狀態;并做好系統停機的準備。
當系統要關機時會向Windows子系統進程Csrss發送一個消息,以便調用Csrss的停機例程。Csrss會對所有活動的進程循環通知系統正在停機。對于除SCM以外的每一個系統進程如果沒有返回退出的響應Csrss 都會等待由HKEY_USER\.DEFAULT\Control Panel\Desktop\WaitToKillAppTimeout指定的毫秒數(我的系統中是20000,也就是20秒),然后知通下一個進程結束。當遇到SCM時也會通知SCM進程系統正在停機,但不同的是會使用一個專用的超時間隔值(SCM在系統初始化時要向Csrss登記,于是Csrss就將SCM的進程ID保存了下來Csrss也就是通過個ID來識別SCM的)。這個專用的超時間隔位于HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\WaitToKillServiceTimeout中,有趣的是默認值也是20秒。這在段時間里SCM要通知所有在初始化時(服務程序自身初始化)請求SCM通知自己系統停機的服務,SCM有一個停機處理器負責這項工作。通知下達后SCM就等待服務退出,服務在接收到停機消息后會返回給SCM一個等待時間,SCM跟蹤所有服務返回等待時間找出其中的最大值,當這個最大值達到后,如果有一個服務或多個服務又告訴SCM它們正在處理停機工作,那么SCM還會循環等待下去,但Csrss也再等待SCM當Csrss等待超時后,會繼續后面的停機工作最終完成停機。
這就要求服務程序要在Csrss等待SCM的這段時內完成自己的停機處理工作,否則服務就沒機會在系統停機前完成自己的關閉工作了。
?3??????? 服務程序
Windows服務程序其實并不神秘,它只是遵循特定規則編寫的一個程序。只要遵循這個特定的規則與服務控制管理器正確的交互,就可實現我們的服務程序。而我們只要能實現一個簡單的服務程序,設計一個能處理復雜業務的服務也并非難事,因為從結構上看兩者并沒有太大的區別。只要遵循與SCM交互的規則,設計服務程序與設計普通的應用程序幾乎沒什么區別。
?3.1??? 程序結構概要
??? 服務程序的與普通應用程序一樣也需要一個主函數(main())作為程序的入口,與之不同的是作為一個服務程序它需要在主函數(main())中立即調用StartServiceCtrlDispatcher來注冊一個服務的入口函數
(ServerMain(DWORD argc,LPTSTR *argv),當然這個名字可自由命名)。StartServiceCtrlDispatcher函數的原型是: BOOL StartServiceCtrlDispatcher(
? LPSERVICE_TABLE_ENTRY lpServiceStartTable ?
);它的參數是一個指向SERVICE_TABLE_ENTRY的指針; SERVICE_TABLE_ENTRY結構有兩個域;第一個域存儲服務的內部名稱,第二個域是服務入口函數的指針。這個函數完成后,SCM就要可以服務啟動的時候調用服務的入口函數。
??? 例如:管理員在服務管理器啟動一個服務,SCM就會在一個單獨的線程中調用服務注冊的入口函數。這時我們在服務的這個入口函數中必須調用RegisterServiceCtrlHandler完成Handler函數的注冊,這個函數用來接收和處理SCM的控制消息。下面列出Hander要處理的控制消息和RegisterServiceCtrlHandler的函數原型:
VOID WINAPI Handler(? DWORD fdwControl?? // 請求控制消息代碼);控制消息宏定義
?說明
?
SERVICE_CONTROL_STOP
?要服務停止
?
SERVICE_CONTROL_PAUSE
?要服務暫停
?
SERVICE_CONTROL_CONTINUE
?要服務繼續
?
SERVICE_CONTROL_INTERROGATE
?要服務馬上報告它的狀態
?
SERVICE_CONTROL_SHUTDOWN
?告訴服務即將關機
?
SERVICE_STATUS_HANDLE RegisterServiceCtrlHandler(? LPCTSTR lpServiceName,???????????? // 服務的內部名稱? LPHANDLER_FUNCTION lpHandlerProc?? // Handler函數的地址);??? RegisterServiceCtrlHandler調用完成后我們就可以開始我們的業務處理的初始化工作。初始化完成后向SCM報告服務開始運行(SERVICE_RUNNING)的消息。如果
ServerMain(DWORD argc,LPTSTR *argv)函數退出服務也就停止了。下面讓我總結一下實現服務程序的步驟:(1)在main()調用StartServiceCtrlDispatcher來注冊一個服務的入口函數;(2)在ServerMain(DWORD argc,LPTSTR *argv)中調用RegisterServiceCtrlHandler注冊Handler函數。(3)完成業務處理程序的初始化工作,如果初始化時間較長要實時向SCM報告當前正在啟動(4)初始化完畢,報告服務正在運行;開始業務處理工作。?3.2???? 程序實例分析
(1)main()函數
int main(int argc, char* argv[])
{
???? SERVICE_TABLE_ENTRY serviceTable[]=
???? {
???????? {
????????????? SERVICE_NAME,
????????????? (LPSERVICE_MAIN_FUNCTION)ServiceMain
???????? }
???????? {
????????????? NULL,NULL
???????? }
???? };
???? BOOL success;
???? success = StartServiceCtrlDispatcher(serviceTable);
???? if (!success)
???? {
???????? ErrorHandler("In StartServiceCtrlDispatcher",GetLastError());
???? }
???? return 0;
}
StartServiceCtrlDispatcher的參數必須是一個以NULL結尾的數組指針,我們可以在一個程序文件中注冊多個服務實例,只在把所要注冊的服務名和服務入口函數地址寫到數組中即可,在我們調用CreateService創建服務時要把dwServiceType 參數設為共享進程(SERVICE_WIN32_SHARE_PROCESS);不過當要創建獨立進程的服務時(dwServiceType 參數為SERVICE_WIN32_OWN_PROCESS時)在這里就只能注冊一個服務實例。
(2)服務入口函數ServerMain()
VOID ServiceMain(DWORD argc,LPTSTR *argv)
{
???? BOOL success;
???? StatusHandler=
???????? RegisterServiceCtrlHandler(SERVICE_NAME,(LPHANDLER_FUNCTION)Handler);
???? if (!serviceStatusHandler)
???? {
???????? return;
???? }
???? success = ReportStatus (SERVICE_START_PENDING,
???????????????????????????????? NO_ERROR,0,1,5000);
???? if (!success)
???? {
???????? return; ?
???? }
???? endEvent = CreateEvent(0,TRUE,FALSE,0);
??? ?
???? if (!endEvent)
???? {
???????? return;
???? }
???? success = ReportStatus (SERVICE_START_PENDING,
???????????????????????????????? NO_ERROR,0,2,5000);
???? if (!success)
???? {
???????? return;
???? }
??? //init parameter start
???? RecvParam(argc,argv);
???? //init parameter end
??? success = ReportStatus (SERVICE_START_PENDING,
???????????????????????????????? NO_ERROR,0,3,5000);
???? if (!success)
???? {
???????? return;
???? }
???? success = InitService();
??? ?
???? if (!success)
???? {
???????? return;
???? }
???? success = ReportStatus (SERVICE_RUNNING,NO_ERROR,0,0,0);
???? if (!success)
???? {
???????? return;
???? }
???? WaitForSingleObject(endEvent,INFINITE);
}
?????? RegisterServiceCtrlHandler完成Handler函數的注冊( Handler函數的具體實現我們在第三小節中介紹),它的第一個參數是調用CreateService創建服務時lpServiceName指向的名服務名稱,每二個參數是Handler函數的地址;函數名可以自由命名。ReportStatus是向SCM報告服務當前狀態的一個自定義函數。它內部調用SetServiceStatus向SCM報告服務的當狀態,此函數有兩個參數第一個就是RegisterServiceCtrlHandler完成時返回的SERVICE_STATUS_HANDLE,第二個參數是一個SERVICE_STATUS變量的指針,它指示了服務當前的狀態信息;當注冊完Handler函數后向SCM報告一下自己當前的狀態(正在啟動)。接著創建endEvent事件對像,它是當我們收到SCM的退出控制代碼時通知服務主函數退出的,大家可看ServiceMain的最后一句。下面又是向SCM報告自己正在啟動,當初始化所花費的時間非常短時這樣做并不是必須的,但如果很長就必須這樣做。RecvParam(argc,argv)使用了ServiceMain函數的兩個參數,大家可以看出ServiceMain和main有著一樣的形參;說明ServiceMain和main一樣可以接收配置參數,稍后我們會在服務控制程序的編寫中給大家介紹如何給服務配置參數。InitService()完成我們的業務初始化工作并開始業務處理。最后報告服務啟動完成,等待endEvent事件退出服務。下面我們再來看一下SCM控制消息的處理。
(3)SCM控制消息處理(Handler函數)
VOID Handler(DWORD controlCode)
{
???? DWORD currentState = 0;
???? BOOL success;
???? switch(controlCode)
???? {
???? case SERVICE_CONTROL_STOP:
???????? success=ReportStatus(SERVICE_STOP_PENDING,NO_ERROR,0,1,5000);
???????? CloseTask();
???????? success=ReportStatus(SERVICE_STOPPED,NO_ERROR,0,0,0);
???????? return;
???? case SERVICE_CONTROL_PAUSE:
???????? if (runningService&&!pauseService)
???????? {
????????????? success=ReportStatus(SERVICE_PAUSE_PENDING,NO_ERROR,0,1,1000);
????????????? pauseService=TRUE;
????????????? ServicePause();
????????????? currentState=SERVICE_PAUSED;
???????? }
???????? break;
???? case SERVICE_CONTROL_CONTINUE:
???????? if (runningService&&pauseService)
???????? {
????????????? success = ReportStatus(SERVICE_CONTINUE_PENDING,
????????????????????????????????????????? NO_ERROR,0,1,1000);
????????????? pauseService = FALSE;
????????????? ServiceContinue();
????????????? currentState=SERVICE_RUNNING;
???????? }
???????? break;
???? case SERVICE_CONTROL_INTERROGATE://檢索更新狀態的時
???????? break;
???? case SERVICE_CONTROL_SHUTDOWN://告訴服務即將關機
???????? success=ReportStatus(SERVICE_STOP_PENDING,NO_ERROR,0,1,5000);
???????? CloseTask();
???????? return;
???? default:
???????? break;
???? }
???? ReportStatus(currentState,NO_ERROR,0,0,0);
}
Handler只有一個參數就是SCM傳來的控制消息代碼;這里處理的了停止,暫停,繼續,更新,關機五個控制消息。但并不是這五個消息SCM都會向服務發送,要在向服務報告狀時向SCM報告自己可以響應的控制消息,只要設置SERVICE_STATUS結構中的dwControlsAccepted域即可,它對應的值有:SERVICE_ACCEPT_STOP,SERVICE_ACCEPT_PAUSE_CONTINUE,SERVICE_ACCEPT_SHUTDOWN,當要設置多個時只要把宏相或(|)傳給dwControlsAccepted域即可。在響應SCM控制消息時也要注意及時報告服務當前的狀態信息,否則SCM會認為服務響應超時出錯了。
(4)服務的安裝與卸載
??? 服務程序編寫完成并編譯通過后,還要安裝注冊到操作系統中,這樣它才會出現在管理工具->服務,那個管理器里面。API給我們提供了一個函數來實現我們注冊服務的功能; SC_HANDLE CreateService(? SC_HANDLE hSCManager,? // 服務控制管理器的句柄?? LPCTSTR lpServiceName, // 指向服務的內部名稱? LPCTSTR lpServiceName,, // 指向服務的顯示名稱? DWORD dwDesiredAccess, // 服務的訪問類型? DWORD dwServiceType,?? // 服務的類型? DWORD dwStartType,???? // 服務的啟動方式(自動,手動,禁用)? DWORD dwErrorControl,? // 錯誤控制方式? LPCTSTR lpBinaryPathName,? // 服務程序的路徑? LPCTSTR lpLoadOrderGroup,? // 服務組的名稱?? LPDWORD lpdwTagId,???? // 服務的標簽號? LPCTSTR lpDependencies,? // 服務依賴的服務或組名? LPCTSTR lpServiceStartName, // 服務的啟動帳戶? LPCTSTR lpPassword?????? // 服務啟動帳戶的密碼);hSCManager:這是函數的第一個參數-SCM的句柄。它要調用OpenSCManager來獲得,稍后我們會講它怎么調用方法。
lpServiceName和lpServiceName:分別的服務的名稱和服務的顯示名稱,服務是顯示名稱就服務管理器中看到的那個服務名。則是服務在SCM中注冊的名稱,比如調用OpenService打開服務時就會用到它。
dwDesiredAccess:標出服務同意請求的訪問,可以是下面任意任值:
SERVICE_ALL_ACCESS
SERVICE_CHANGE_CONFIG
SERVICE_ENUMERATE_DEPENDENTS
SERVICE_INTERROGATE
SERVICE_PAUSE_CONTINUE
SERVICE_QUERY_CONFIG
SERVICE_QUERY_STATUS
SERVICE_START
SERVICE_STOP
SERVICE_USER_DEFINED_CONTROL
我們可以指定一個或多個,如果有多個的話要用或符號(|)聯結起來。
dwServiceType:注冊服務的類型,它必須是下面的值:SERVICE_WIN32_OWN_PROCESS
SERVICE_WIN32_SHARE_PROCESS
SERVICE_KERNEL_DRIVER
SERVICE_FILE_SYSTEM_DRIVER如果指定的是SERVICE_WIN32_OWN_PROCESS類型的服務還可以加上SERVICE_WIN32_OWN_PROCESS(允許用戶桌面交互),我們這里介紹的服務只能注冊為SERVICE_WIN32_OWN_PROCESS或SERVICE_WIN32_SHARE_PROCESS;另兩種類型是驅動級的服務用的,有興趣大家可查看相關資料。
dwStartType:服務的啟動類型SERVICE_BOOT_START、SERVICE_SYSTEM_START、SERVICE_AUTO_START、SERVICE_DEMAND_START、SERVICE_DISABLED。分別為前兩種類型僅對驅動程序用效,所在我們這里所說的這類服務能后三種(自動,手動,禁用)。
dwErrorControl:服務的錯誤控制標記
SERVICE_ERROR_IGNORE:忽略所有錯誤
SERVICE_ERROR_NORMAL:正常報告服務返回的錯誤
SERVICE_ERROR_SEVERE:當服務返回錯誤出現時,如果最后已知好控制集(最后已知好控制集:是系統最后一次成功引導時使用的服務注冊表配置)尚未使用,則重新引導進入最后已知好控制集,否則重新引導。
SERVICE_ERROR_CRITICAL:當服務返回錯誤出現時,如果最后已知好控制集尚未使用,則重新引導進入最后已知好控制集,否則藍屏崩潰。
lpBinaryPathName:服務程序的文件路徑
lpLoadOrderGroup:服務所屬的組
lpdwTagId:在組中的唯一標識
lpDependencies:服務所依賴的其它組和服務
lpServiceStartName和lpPassword:服務由哪個用戶啟動,也即服務運行在哪個用戶權限下,分別指定用戶名和密碼.
?????? 下再說兩重要的函數:OpenSCManager和CloseServiceHandle給出它們的原型
SC_HANDLE OpenSCManager(? LPCTSTR lpMachineName,? // 機器名,打開本機的SCM時可為NULL? LPCTSTR lpDatabaseName,? // 指向SCM數據庫的名字可為NULL? DWORD dwDesiredAccess?? // 訪問權限類型如:SC_MANAGER_ALL_ACCESS等);BOOL CloseServiceHandle(? SC_HANDLE hSCObject?? // 服務控制句柄 );?????? 這果列出一段注冊服務的代碼供大家參考:
SC_HANDLE newService,scm;
BOOL success = FALSE;
SERVICE_STATUS status;
scm = OpenSCManager(NULL,NULL,
SC_MANAGER_ENUMERATE_SERVICE|SC_MANAGER_CREATE_SERVICE);
???? if (!scm){
???????? OUT_DEBUG("OpenSCManager ERROR!");
???????? return false;
???? }
newService = CreateService(scm,pszServiceName,pszDisplayName,
???????? SERVICE_ALL_ACCESS|SERVICE_STOP,SERVICE_WIN32_OWN_PROCESS,
???????? SERVICE_AUTO_START,SERVICE_ERROR_NORMAL,pszServicePath,
???????? 0,0,0,0,0);
???? if (!newService){
???????? OUT_DEBUG("CreateService ERROR!");
???????? CloseServiceHandle(scm);
???????? return false;
???? }
CloseServiceHandle(newService);
CloseServiceHandle(scm);
return true;
?????? 刪除服務時調用DeleteService;它只有一個參數(服務句柄)。我們可分四步完成1)、打開SCM句柄。2)、打開要刪除的服務。3)、檢查當前服務的狀態確保服務已經停止。4)、刪除服務并關閉所有打開的句柄。下面是一段刪除服務的程序。
SC_HANDLE Service,scm;
???? SERVICE_STATUS status;
???? BOOL success;
???? if (pszServiceName==NULL)
???? {
???????? return false;
???? }
???? scm = OpenSCManager(NULL,NULL,SC_MANAGER_ENUMERATE_SERVICE);
???? if (!scm){
???????? cout<<"OpenSCManager ERROR:"<<GetLastError()<<endl;
???????? CloseServiceHandle(scm);
???????? return false;
???? }
???? Service = OpenService(scm,pszServiceName,SERVICE_ALL_ACCESS|DELETE);
???? if (!Service){
???????? cout<<"OpenService ERROR:"<<GetLastError()<<endl;
???????? CloseServiceHandle(Service);
???????? CloseServiceHandle(scm);
???????? return false;
???? }
???? success = QueryServiceStatus(Service,&status);
???? if (!success){
???????? cout<<"QueryServiceStatus ERROR:"<<GetLastError()<<endl;
???????? CloseServiceHandle(Service);
???????? CloseServiceHandle(scm);
???????? return false;
???? }
???? if (status.dwCurrentState!=SERVICE_STOPPED)
???? {
???????? success = ControlService(Service,SERVICE_CONTROL_STOP,&status);
???????? if (!success){
????????????? cout<<"ControlService ERROR:"<<GetLastError()<<endl;
????????????? CloseServiceHandle(Service);
????????????? CloseServiceHandle(scm);
????????????? return false;
???????? }
???? }
???? success = DeleteService(Service);
???? if (!success){
???????? cout<<"DeleteService ERROR:"<<GetLastError()<<endl;
???????? CloseServiceHandle(Service);
???????? CloseServiceHandle(scm);
???????? return false;
???? }
???? CloseServiceHandle(Service);
???? CloseServiceHandle(scm);
???? return true;
?4??????? 服務控制程序
?4.1??? 服務控制程序概要
在上面我們了解服務程序的編寫,現在我們來看一下控制服務時會用到的幾個常用API。服務控制程序的編寫與標準的Windows應用程序無異,它要用到服務管理函數,如:OpenSCManager、OpenService、QueryServiceConfig、StartService、QueryServiceStatus、ControlService等;它都在系統的advapi32.dll中實現。在使用SCM的函數時,SCP必須要首先調用OpenSCManager函數,打開一個通向SCM的通道。調用這個函數的時候,SCP還必須指定它想要執行的動作類型;也就是我們上一節所提到的dwDesiredAccess 參數它的取值:SC_MANAGER_ALL_ACCESS、SC_MANAGER_CREATE_SERVICE、SC_MANAGER_ENUMERATE_SERVICE、 SC_MANAGER_QUERY_LOCK_STATUS、SC_MANAGER_ENUMERATE_SERVICE 、SC_MANAGER_QUERY_LOCK_STATUS、 SC_MANAGER_LOCK、SC_MANAGER_CONNECT。例如:我們要枚舉當前所有的服務就必須給dwDesiredAccess參數指定SC_MANAGER_ENUMERATE_SERVICE;同時也可以指定其它的值,當指寫多個值時我們要把它用按位或(|)符號連接起來。
與此同時我們在調用OpenService時也必須告知SCM我們要對服務進行的動作;它有三個參數,最后一個參數dwDesiredAccess指出要對服務進行的操作,這些操作的標記與CreateService中的dwDesiredAccess參數標記值一樣。當我們以SERVICE_ALL_ACCESS訪問權限打開服務后就可以對它進行配置(QueryServiceConfig)、控制(ControlService)、查詢狀態(QueryServiceStatus)、設置狀態(SetServiceStatus)、刪除(DeleteService)等所有訪問操作。下面我們可以來看兩服務控制的實例。
?4.2??? 枚舉服務
我們先來看EnumDependentStatus函數原型:
BOOL EnumServicesStatus (? SC_HANDLE hService,????? // SCM控制句柄DWORD dwServiceType,?? //要枚舉服務還有驅動? DWORD dwServiceState,??? // 要枚舉什么狀態的服務? LPENUM_SERVICE_STATUS lpServices,// 存儲枚舉出服務的內存地址? DWORD cbBufSize,???????? // lpServices指向內存區大小? LPDWORD pcbBytesNeeded,? //實際需要的內存大小? LPDWORD lpServicesReturned //返回枚舉到服務年個數LPDWORD lpResumeHandle //指向下一個有效的入口);dwServiceState參數由:SERVICE_ACTIVE、SERVICE_INACTIVE、SERVICE_STATE_ALL三種值分別枚舉當前活動、不活動、全部的服務。函數會返回一個ENUM_SERVICE_STATUS的數組,ENUM_SERVICE_STATUS有三個域分別指出服務的服務名、顯示名和當前狀態一個指針;我們可以根據服務名打開枚舉出的服務,以得到它更加詳細的信息。下面具體讓我們看一段程序的例子。
//清空服務信息隊列
DeletItemAll();
LPENUM_SERVICE_STATUS st=NULL;
st=NULL;
DWORD ret=0;
DWORD size=0;
ServiceInfo info;
SC_HANDLE sc=OpenSCManager(NULL,NULL,SC_MANAGER_ALL_ACCESS);
SC_HANDLE sh;
char* szInfo[1024*8];
DWORD dwSize=1024*8;
CString str;
//第一次調用來得到需要多大的內存區
EnumServicesStatus(sc,SERVICE_WIN32,SERVICE_STATE_ALL,st,size,&size,&ret,NULL);
//申請需要的內存
st=(LPENUM_SERVICE_STATUS)LocalAlloc(LPTR,size);
EnumServicesStatus(sc,SERVICE_WIN32,SERVICE_STATE_ALL,st,size,&size,&ret,NULL);
//開始記錄枚舉出服務的信息
for(DWORD i=0;i<ret;i++){
???? dwSize=1024*8;
???? ZeroMemory(szInfo,dwSize);
???? info.Name.Format("%s",st[i].lpDisplayName);
???? info.serviceNmae.Format("%s",st[i].lpServiceName);
???? info.State.Format("%d",st[i].ServiceStatus.dwCurrentState);
???? sh=OpenService(sc,st[i].lpServiceName,SERVICE_ALL_ACCESS);
???? //得到服務描述信息
???? QueryServiceConfig2(sh,SERVICE_CONFIG_DESCRIPTION,(LPBYTE)szInfo,dwSize,&dwSize);
???? info.Desc.Format("%s",((LPSERVICE_DESCRIPTION)szInfo)->lpDescription);
???? //得到服務的啟動賬戶名
???? ZeroMemory(szInfo,dwSize);
???? dwSize=1024*8;
???? QueryServiceConfig(sh,(LPQUERY_SERVICE_CONFIG)szInfo,dwSize,&dwSize);
???? info.LoginUser.Format("%s",((LPQUERY_SERVICE_CONFIG)szInfo)->lpServiceStartName);
???? CloseServiceHandle(sh);
???? //添加到信息隊列中
???? ItemAdd(&info);
}
CloseServiceHandle(sc);
return TRUE;
上面程序中用到了兩個查詢服務當前配置的函數QueryServiceConfig2和QueryServiceConfig。它們有所不同是QueryServiceConfig2可以通過設置第二個參數是SERVICE_CONFIG_DESCRIPTION還是SERVICE_CONFIG_FAILURE_ACTIONS來得到服務的描述信息和失敗的活動;而QueryServiceConfig則查詢返回一個QUERY_SERVICE_CONFIG結構,這個結構存儲了服務的類型、啟動類型、錯誤控制標記、服務文件所在路徑、顯示名等信息詳細可以查看MSDN。與這個兩函數相對應還有兩個配置函數ChangeServiceConfig2和ChangeServiceConfig。它們的具體使用方法我們來看下面的這段程序。
?4.3??? 配置服務
在3.2中我們舉了一個創建服務的程序片段,其中我們只是創建服務并未設置服務描述信息,啟動服務的操作。下面的程序片段給出了示例:
bool RegterService(char* pszServiceName,
????????????????????? char* pszDisplayName,
????????????????????? char* pszServicePath,
????????????????????? char* pszDescription)
{
???? SC_HANDLE newService,scm;
???? BOOL success = FALSE;
???? SERVICE_STATUS status;
???? SERVICE_DESCRIPTION description;
???? if (pszDisplayName==NULL&&pszServiceName==NULL&&pszServicePath==NULL)
???? {
???????? return false;
???? }
???? description.lpDescription=pszDescription;
???? scm = OpenSCManager(NULL,NULL,SC_MANAGER_ENUMERATE_SERVICE|SC_MANAGER_CREATE_SERVICE);
???? if (!scm){
???????? OUT_DEBUG("OpenSCManager ERROR!");
???????? return false;
???? }
???? newService = CreateService(scm,pszServiceName,pszDisplayName,
???????? SERVICE_ALL_ACCESS|SERVICE_STOP,SERVICE_WIN32_OWN_PROCESS,
???????? SERVICE_AUTO_START,SERVICE_ERROR_NORMAL,pszServicePath,
???????? 0,0,0,0,0);
???? if (!newService){
???????? OUT_DEBUG("CreateService ERROR!");
????????? CloseServiceHandle(scm);
???????? return false;
???? }
???? if (description.lpDescription!=NULL)
???? {
???????? success=ChangeServiceConfig2(newService,
?????????????????????? SERVICE_CONFIG_DESCRIPTION,
?????????????????????? &description);
???? }
???? success = QueryServiceStatus(newService,&status);
???? if (!success){
???????? cout<<"QueryServiceStatus ERROR:"<<GetLastError()<<endl;
???????? CloseServiceHandle(newService);
???????? CloseServiceHandle(scm);
???????? return false;
???? }
???? if (status.dwCurrentState!=SERVICE_RUNNING)
???? {
???????? success = StartService(newService,NULL,NULL);
???????? if (!success){
????????????? cout<<"ControlService ERROR:"<<GetLastError()<<endl;
????????????? CloseServiceHandle(newService);
????????????? CloseServiceHandle(scm);
????????????? return false;
???????? }
???? }
???? CloseServiceHandle(newService);
???? CloseServiceHandle(scm);
???? return true;
}
ChangeServiceConfig函數可以配置更多關于服務的信息,下面列出其原型:
BOOL ChangeServiceConfig(? SC_HANDLE hService???? // 打開服務時返回的句柄? DWORD dwServiceType,?? // 服務的類型? DWORD dwStartType,???? // 何時啟動服務? DWORD dwErrorControl,? // 錯誤控制代碼? LPCTSTR lpBinaryPathName,? // 服務的路徑? LPCTSTR lpLoadOrderGroup,? // 服務所屬的組? LPDWORD lpdwTagId,???? // 服務的標記? LPCTSTR lpDependencies,??? // 依賴的其它服務和組? LPCTSTR lpServiceStartName,// 服務的啟動用戶? LPCTSTR lpPassword,?? //服務啟動用戶的密碼? LPCTSTR lpDisplayName????? // 服務的顯示名);大家可以看到ChangeServiceConfig與CreateServiceee 有著相似的參數,它們的使用方法也十分相似可以參照CreateServiceee函數調用。它主要在服務安裝完成后,需要對服務的配置進行修改時調用,除了lpDisplayName改變時需要服務停止才能生效外,其它都可以運行時動態改變;更詳細信息可查閱MSDN。
?4.4???? 控制服務
有時我們要根據實際情況啟動、暫停、停止一個服務。在4.3中的程序示例里面就一個啟動服務的調用。這里我們再簡單介紹一下這個函數:
BOOL StartService(? SC_HANDLE hService,??????????? // 打開服務時返回的句柄? DWORD dwNumServiceArgs,??????? // 服務程序參數的個數? LPCTSTR *lpServiceArgVectors?? // 存放服務程序參數的數組 );當服務程序需要配置啟動參數時就需要使用StartService后面的兩個參數,服務程序的入口函數也就可以接到相關的參數了。函數調用成功時返回非零,當返回零時調用失敗;更詳細的出錯信息可以調用GetLastError獲得。
??? 啟動后如果我們還要控制其暫停、繼續、停止的話,還需要另一個函數調用來完成。在上面刪除服務的例示程序片段中我們調用了ControlService函數來停止服務,下面我們介紹一下它的詳細信息:
BOOL ControlService(? SC_HANDLE hService,? // 打開服務時返回的句柄? DWORD dwControl,???? // 控制代碼? LPSERVICE_STATUS lpServiceStatus // 服務的狀態);調用此函數會把控制代碼發給指定服務程序的Handler處理函數同時返回服務的狀態,服務程序得到相應的控制代碼后根據協議要執行相應的操作;控制代碼就是Handler規定響應的所有代碼。我們不能啟動和停止服務安全描述符不允許的服務程序。默認的安全描述符只允許LocalSystem、 Administrators和 Power Users來啟動和停止服務程序。服務的安全描述符來用SetServiceObjectSecurity來設置(更詳細信息的信息可查閱MSDN)。?5??????? 結束語
我們在這里從總體上講述了SCM的工作流程序、服務程序編寫的方法及控制服務所用到的一些函數。文中只列出了一部分函數的信息,更詳細的信息大家可以查閱MSDN。希望本文講述的內容能幫助大家理解服務程序的編寫與控制。
本文來自CSDN博客,轉載請標明出處:http://blog.csdn.net/lziog/archive/2009/04/10/4062941.aspx
??? 關鍵詞:Windows,服務,VC++
?1??????? 服務介紹
幾乎所有的操作系統在啟動的時候都會啟動一些不需要與用戶交互的進程,這些進程在Windows中就被稱作服務。它通常用于實現客戶/服務器模式中的服務器方,如我們常見的Web服務IIS,當操作系統在啟動后它就自動被運行,不管是否有人登陸到系統只要系統開啟它就能得到運行。
服務程序、服務控制程序(SCP,service control program)和服務控制管理器(SCM,service control manager)組成了Windows服務。我們可以通過服務控制程序操縱服務控制管理器來配置、啟動、暫停、停止服務程序。其中服務程序和服務控制程序可以由我們自己來編寫擴展,而服務控制管理器(\windows\system32\servics.exe)則是操作系統內置的一個部件。首先我們來了解一下SCM的工作情況,然后我們介紹服務程序的編寫和服務控制時所涉及API的使用。
?2??????? 服務控制管理器
SCM本身也是一個服務程序(\windows\system32\servics.exe),作為windows的后臺服務運行的。Winlogon在系統引導的早期會將SCM啟動起來。SCM的服務入口函數首先創建一個初始化為無信號的同步事件對象(SvcCtrlEvent_A3752DX);接下來,它開始建立一個內部服務數據庫,這個數據庫要按事先規定好的一個順序列出所有服務組,并記錄與服務相關的詳細信息;當這個數據庫建立完成時SCM就開始按順序啟動那些啟動方式為自動的服務,如果有服務要動行于指定用戶賬戶中時還要調用LSASS,如果服務啟動失敗則會被放入一個名為ScFailedDrivers的列表中。當這些工作都完成后,SCM將同步事件對象SvcCtrlEvent_A3752DX置為有信號狀態;并做好系統停機的準備。
當系統要關機時會向Windows子系統進程Csrss發送一個消息,以便調用Csrss的停機例程。Csrss會對所有活動的進程循環通知系統正在停機。對于除SCM以外的每一個系統進程如果沒有返回退出的響應Csrss 都會等待由HKEY_USER\.DEFAULT\Control Panel\Desktop\WaitToKillAppTimeout指定的毫秒數(我的系統中是20000,也就是20秒),然后知通下一個進程結束。當遇到SCM時也會通知SCM進程系統正在停機,但不同的是會使用一個專用的超時間隔值(SCM在系統初始化時要向Csrss登記,于是Csrss就將SCM的進程ID保存了下來Csrss也就是通過個ID來識別SCM的)。這個專用的超時間隔位于HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\WaitToKillServiceTimeout中,有趣的是默認值也是20秒。這在段時間里SCM要通知所有在初始化時(服務程序自身初始化)請求SCM通知自己系統停機的服務,SCM有一個停機處理器負責這項工作。通知下達后SCM就等待服務退出,服務在接收到停機消息后會返回給SCM一個等待時間,SCM跟蹤所有服務返回等待時間找出其中的最大值,當這個最大值達到后,如果有一個服務或多個服務又告訴SCM它們正在處理停機工作,那么SCM還會循環等待下去,但Csrss也再等待SCM當Csrss等待超時后,會繼續后面的停機工作最終完成停機。
這就要求服務程序要在Csrss等待SCM的這段時內完成自己的停機處理工作,否則服務就沒機會在系統停機前完成自己的關閉工作了。
?3??????? 服務程序
Windows服務程序其實并不神秘,它只是遵循特定規則編寫的一個程序。只要遵循這個特定的規則與服務控制管理器正確的交互,就可實現我們的服務程序。而我們只要能實現一個簡單的服務程序,設計一個能處理復雜業務的服務也并非難事,因為從結構上看兩者并沒有太大的區別。只要遵循與SCM交互的規則,設計服務程序與設計普通的應用程序幾乎沒什么區別。
?3.1??? 程序結構概要
??? 服務程序的與普通應用程序一樣也需要一個主函數(main())作為程序的入口,與之不同的是作為一個服務程序它需要在主函數(main())中立即調用StartServiceCtrlDispatcher來注冊一個服務的入口函數
(ServerMain(DWORD argc,LPTSTR *argv),當然這個名字可自由命名)。StartServiceCtrlDispatcher函數的原型是: BOOL StartServiceCtrlDispatcher(
? LPSERVICE_TABLE_ENTRY lpServiceStartTable ?
);它的參數是一個指向SERVICE_TABLE_ENTRY的指針; SERVICE_TABLE_ENTRY結構有兩個域;第一個域存儲服務的內部名稱,第二個域是服務入口函數的指針。這個函數完成后,SCM就要可以服務啟動的時候調用服務的入口函數。
??? 例如:管理員在服務管理器啟動一個服務,SCM就會在一個單獨的線程中調用服務注冊的入口函數。這時我們在服務的這個入口函數中必須調用RegisterServiceCtrlHandler完成Handler函數的注冊,這個函數用來接收和處理SCM的控制消息。下面列出Hander要處理的控制消息和RegisterServiceCtrlHandler的函數原型:
VOID WINAPI Handler(? DWORD fdwControl?? // 請求控制消息代碼);控制消息宏定義
?說明
?
SERVICE_CONTROL_STOP
?要服務停止
?
SERVICE_CONTROL_PAUSE
?要服務暫停
?
SERVICE_CONTROL_CONTINUE
?要服務繼續
?
SERVICE_CONTROL_INTERROGATE
?要服務馬上報告它的狀態
?
SERVICE_CONTROL_SHUTDOWN
?告訴服務即將關機
?
SERVICE_STATUS_HANDLE RegisterServiceCtrlHandler(? LPCTSTR lpServiceName,???????????? // 服務的內部名稱? LPHANDLER_FUNCTION lpHandlerProc?? // Handler函數的地址);??? RegisterServiceCtrlHandler調用完成后我們就可以開始我們的業務處理的初始化工作。初始化完成后向SCM報告服務開始運行(SERVICE_RUNNING)的消息。如果
ServerMain(DWORD argc,LPTSTR *argv)函數退出服務也就停止了。下面讓我總結一下實現服務程序的步驟:(1)在main()調用StartServiceCtrlDispatcher來注冊一個服務的入口函數;(2)在ServerMain(DWORD argc,LPTSTR *argv)中調用RegisterServiceCtrlHandler注冊Handler函數。(3)完成業務處理程序的初始化工作,如果初始化時間較長要實時向SCM報告當前正在啟動(4)初始化完畢,報告服務正在運行;開始業務處理工作。?3.2???? 程序實例分析
(1)main()函數
int main(int argc, char* argv[])
{
???? SERVICE_TABLE_ENTRY serviceTable[]=
???? {
???????? {
????????????? SERVICE_NAME,
????????????? (LPSERVICE_MAIN_FUNCTION)ServiceMain
???????? }
???????? {
????????????? NULL,NULL
???????? }
???? };
???? BOOL success;
???? success = StartServiceCtrlDispatcher(serviceTable);
???? if (!success)
???? {
???????? ErrorHandler("In StartServiceCtrlDispatcher",GetLastError());
???? }
???? return 0;
}
StartServiceCtrlDispatcher的參數必須是一個以NULL結尾的數組指針,我們可以在一個程序文件中注冊多個服務實例,只在把所要注冊的服務名和服務入口函數地址寫到數組中即可,在我們調用CreateService創建服務時要把dwServiceType 參數設為共享進程(SERVICE_WIN32_SHARE_PROCESS);不過當要創建獨立進程的服務時(dwServiceType 參數為SERVICE_WIN32_OWN_PROCESS時)在這里就只能注冊一個服務實例。
(2)服務入口函數ServerMain()
VOID ServiceMain(DWORD argc,LPTSTR *argv)
{
???? BOOL success;
???? StatusHandler=
???????? RegisterServiceCtrlHandler(SERVICE_NAME,(LPHANDLER_FUNCTION)Handler);
???? if (!serviceStatusHandler)
???? {
???????? return;
???? }
???? success = ReportStatus (SERVICE_START_PENDING,
???????????????????????????????? NO_ERROR,0,1,5000);
???? if (!success)
???? {
???????? return; ?
???? }
???? endEvent = CreateEvent(0,TRUE,FALSE,0);
??? ?
???? if (!endEvent)
???? {
???????? return;
???? }
???? success = ReportStatus (SERVICE_START_PENDING,
???????????????????????????????? NO_ERROR,0,2,5000);
???? if (!success)
???? {
???????? return;
???? }
??? //init parameter start
???? RecvParam(argc,argv);
???? //init parameter end
??? success = ReportStatus (SERVICE_START_PENDING,
???????????????????????????????? NO_ERROR,0,3,5000);
???? if (!success)
???? {
???????? return;
???? }
???? success = InitService();
??? ?
???? if (!success)
???? {
???????? return;
???? }
???? success = ReportStatus (SERVICE_RUNNING,NO_ERROR,0,0,0);
???? if (!success)
???? {
???????? return;
???? }
???? WaitForSingleObject(endEvent,INFINITE);
}
?????? RegisterServiceCtrlHandler完成Handler函數的注冊( Handler函數的具體實現我們在第三小節中介紹),它的第一個參數是調用CreateService創建服務時lpServiceName指向的名服務名稱,每二個參數是Handler函數的地址;函數名可以自由命名。ReportStatus是向SCM報告服務當前狀態的一個自定義函數。它內部調用SetServiceStatus向SCM報告服務的當狀態,此函數有兩個參數第一個就是RegisterServiceCtrlHandler完成時返回的SERVICE_STATUS_HANDLE,第二個參數是一個SERVICE_STATUS變量的指針,它指示了服務當前的狀態信息;當注冊完Handler函數后向SCM報告一下自己當前的狀態(正在啟動)。接著創建endEvent事件對像,它是當我們收到SCM的退出控制代碼時通知服務主函數退出的,大家可看ServiceMain的最后一句。下面又是向SCM報告自己正在啟動,當初始化所花費的時間非常短時這樣做并不是必須的,但如果很長就必須這樣做。RecvParam(argc,argv)使用了ServiceMain函數的兩個參數,大家可以看出ServiceMain和main有著一樣的形參;說明ServiceMain和main一樣可以接收配置參數,稍后我們會在服務控制程序的編寫中給大家介紹如何給服務配置參數。InitService()完成我們的業務初始化工作并開始業務處理。最后報告服務啟動完成,等待endEvent事件退出服務。下面我們再來看一下SCM控制消息的處理。
(3)SCM控制消息處理(Handler函數)
VOID Handler(DWORD controlCode)
{
???? DWORD currentState = 0;
???? BOOL success;
???? switch(controlCode)
???? {
???? case SERVICE_CONTROL_STOP:
???????? success=ReportStatus(SERVICE_STOP_PENDING,NO_ERROR,0,1,5000);
???????? CloseTask();
???????? success=ReportStatus(SERVICE_STOPPED,NO_ERROR,0,0,0);
???????? return;
???? case SERVICE_CONTROL_PAUSE:
???????? if (runningService&&!pauseService)
???????? {
????????????? success=ReportStatus(SERVICE_PAUSE_PENDING,NO_ERROR,0,1,1000);
????????????? pauseService=TRUE;
????????????? ServicePause();
????????????? currentState=SERVICE_PAUSED;
???????? }
???????? break;
???? case SERVICE_CONTROL_CONTINUE:
???????? if (runningService&&pauseService)
???????? {
????????????? success = ReportStatus(SERVICE_CONTINUE_PENDING,
????????????????????????????????????????? NO_ERROR,0,1,1000);
????????????? pauseService = FALSE;
????????????? ServiceContinue();
????????????? currentState=SERVICE_RUNNING;
???????? }
???????? break;
???? case SERVICE_CONTROL_INTERROGATE://檢索更新狀態的時
???????? break;
???? case SERVICE_CONTROL_SHUTDOWN://告訴服務即將關機
???????? success=ReportStatus(SERVICE_STOP_PENDING,NO_ERROR,0,1,5000);
???????? CloseTask();
???????? return;
???? default:
???????? break;
???? }
???? ReportStatus(currentState,NO_ERROR,0,0,0);
}
Handler只有一個參數就是SCM傳來的控制消息代碼;這里處理的了停止,暫停,繼續,更新,關機五個控制消息。但并不是這五個消息SCM都會向服務發送,要在向服務報告狀時向SCM報告自己可以響應的控制消息,只要設置SERVICE_STATUS結構中的dwControlsAccepted域即可,它對應的值有:SERVICE_ACCEPT_STOP,SERVICE_ACCEPT_PAUSE_CONTINUE,SERVICE_ACCEPT_SHUTDOWN,當要設置多個時只要把宏相或(|)傳給dwControlsAccepted域即可。在響應SCM控制消息時也要注意及時報告服務當前的狀態信息,否則SCM會認為服務響應超時出錯了。
(4)服務的安裝與卸載
??? 服務程序編寫完成并編譯通過后,還要安裝注冊到操作系統中,這樣它才會出現在管理工具->服務,那個管理器里面。API給我們提供了一個函數來實現我們注冊服務的功能; SC_HANDLE CreateService(? SC_HANDLE hSCManager,? // 服務控制管理器的句柄?? LPCTSTR lpServiceName, // 指向服務的內部名稱? LPCTSTR lpServiceName,, // 指向服務的顯示名稱? DWORD dwDesiredAccess, // 服務的訪問類型? DWORD dwServiceType,?? // 服務的類型? DWORD dwStartType,???? // 服務的啟動方式(自動,手動,禁用)? DWORD dwErrorControl,? // 錯誤控制方式? LPCTSTR lpBinaryPathName,? // 服務程序的路徑? LPCTSTR lpLoadOrderGroup,? // 服務組的名稱?? LPDWORD lpdwTagId,???? // 服務的標簽號? LPCTSTR lpDependencies,? // 服務依賴的服務或組名? LPCTSTR lpServiceStartName, // 服務的啟動帳戶? LPCTSTR lpPassword?????? // 服務啟動帳戶的密碼);hSCManager:這是函數的第一個參數-SCM的句柄。它要調用OpenSCManager來獲得,稍后我們會講它怎么調用方法。
lpServiceName和lpServiceName:分別的服務的名稱和服務的顯示名稱,服務是顯示名稱就服務管理器中看到的那個服務名。則是服務在SCM中注冊的名稱,比如調用OpenService打開服務時就會用到它。
dwDesiredAccess:標出服務同意請求的訪問,可以是下面任意任值:
SERVICE_ALL_ACCESS
SERVICE_CHANGE_CONFIG
SERVICE_ENUMERATE_DEPENDENTS
SERVICE_INTERROGATE
SERVICE_PAUSE_CONTINUE
SERVICE_QUERY_CONFIG
SERVICE_QUERY_STATUS
SERVICE_START
SERVICE_STOP
SERVICE_USER_DEFINED_CONTROL
我們可以指定一個或多個,如果有多個的話要用或符號(|)聯結起來。
dwServiceType:注冊服務的類型,它必須是下面的值:SERVICE_WIN32_OWN_PROCESS
SERVICE_WIN32_SHARE_PROCESS
SERVICE_KERNEL_DRIVER
SERVICE_FILE_SYSTEM_DRIVER如果指定的是SERVICE_WIN32_OWN_PROCESS類型的服務還可以加上SERVICE_WIN32_OWN_PROCESS(允許用戶桌面交互),我們這里介紹的服務只能注冊為SERVICE_WIN32_OWN_PROCESS或SERVICE_WIN32_SHARE_PROCESS;另兩種類型是驅動級的服務用的,有興趣大家可查看相關資料。
dwStartType:服務的啟動類型SERVICE_BOOT_START、SERVICE_SYSTEM_START、SERVICE_AUTO_START、SERVICE_DEMAND_START、SERVICE_DISABLED。分別為前兩種類型僅對驅動程序用效,所在我們這里所說的這類服務能后三種(自動,手動,禁用)。
dwErrorControl:服務的錯誤控制標記
SERVICE_ERROR_IGNORE:忽略所有錯誤
SERVICE_ERROR_NORMAL:正常報告服務返回的錯誤
SERVICE_ERROR_SEVERE:當服務返回錯誤出現時,如果最后已知好控制集(最后已知好控制集:是系統最后一次成功引導時使用的服務注冊表配置)尚未使用,則重新引導進入最后已知好控制集,否則重新引導。
SERVICE_ERROR_CRITICAL:當服務返回錯誤出現時,如果最后已知好控制集尚未使用,則重新引導進入最后已知好控制集,否則藍屏崩潰。
lpBinaryPathName:服務程序的文件路徑
lpLoadOrderGroup:服務所屬的組
lpdwTagId:在組中的唯一標識
lpDependencies:服務所依賴的其它組和服務
lpServiceStartName和lpPassword:服務由哪個用戶啟動,也即服務運行在哪個用戶權限下,分別指定用戶名和密碼.
?????? 下再說兩重要的函數:OpenSCManager和CloseServiceHandle給出它們的原型
SC_HANDLE OpenSCManager(? LPCTSTR lpMachineName,? // 機器名,打開本機的SCM時可為NULL? LPCTSTR lpDatabaseName,? // 指向SCM數據庫的名字可為NULL? DWORD dwDesiredAccess?? // 訪問權限類型如:SC_MANAGER_ALL_ACCESS等);BOOL CloseServiceHandle(? SC_HANDLE hSCObject?? // 服務控制句柄 );?????? 這果列出一段注冊服務的代碼供大家參考:
SC_HANDLE newService,scm;
BOOL success = FALSE;
SERVICE_STATUS status;
scm = OpenSCManager(NULL,NULL,
SC_MANAGER_ENUMERATE_SERVICE|SC_MANAGER_CREATE_SERVICE);
???? if (!scm){
???????? OUT_DEBUG("OpenSCManager ERROR!");
???????? return false;
???? }
newService = CreateService(scm,pszServiceName,pszDisplayName,
???????? SERVICE_ALL_ACCESS|SERVICE_STOP,SERVICE_WIN32_OWN_PROCESS,
???????? SERVICE_AUTO_START,SERVICE_ERROR_NORMAL,pszServicePath,
???????? 0,0,0,0,0);
???? if (!newService){
???????? OUT_DEBUG("CreateService ERROR!");
???????? CloseServiceHandle(scm);
???????? return false;
???? }
CloseServiceHandle(newService);
CloseServiceHandle(scm);
return true;
?????? 刪除服務時調用DeleteService;它只有一個參數(服務句柄)。我們可分四步完成1)、打開SCM句柄。2)、打開要刪除的服務。3)、檢查當前服務的狀態確保服務已經停止。4)、刪除服務并關閉所有打開的句柄。下面是一段刪除服務的程序。
SC_HANDLE Service,scm;
???? SERVICE_STATUS status;
???? BOOL success;
???? if (pszServiceName==NULL)
???? {
???????? return false;
???? }
???? scm = OpenSCManager(NULL,NULL,SC_MANAGER_ENUMERATE_SERVICE);
???? if (!scm){
???????? cout<<"OpenSCManager ERROR:"<<GetLastError()<<endl;
???????? CloseServiceHandle(scm);
???????? return false;
???? }
???? Service = OpenService(scm,pszServiceName,SERVICE_ALL_ACCESS|DELETE);
???? if (!Service){
???????? cout<<"OpenService ERROR:"<<GetLastError()<<endl;
???????? CloseServiceHandle(Service);
???????? CloseServiceHandle(scm);
???????? return false;
???? }
???? success = QueryServiceStatus(Service,&status);
???? if (!success){
???????? cout<<"QueryServiceStatus ERROR:"<<GetLastError()<<endl;
???????? CloseServiceHandle(Service);
???????? CloseServiceHandle(scm);
???????? return false;
???? }
???? if (status.dwCurrentState!=SERVICE_STOPPED)
???? {
???????? success = ControlService(Service,SERVICE_CONTROL_STOP,&status);
???????? if (!success){
????????????? cout<<"ControlService ERROR:"<<GetLastError()<<endl;
????????????? CloseServiceHandle(Service);
????????????? CloseServiceHandle(scm);
????????????? return false;
???????? }
???? }
???? success = DeleteService(Service);
???? if (!success){
???????? cout<<"DeleteService ERROR:"<<GetLastError()<<endl;
???????? CloseServiceHandle(Service);
???????? CloseServiceHandle(scm);
???????? return false;
???? }
???? CloseServiceHandle(Service);
???? CloseServiceHandle(scm);
???? return true;
?4??????? 服務控制程序
?4.1??? 服務控制程序概要
在上面我們了解服務程序的編寫,現在我們來看一下控制服務時會用到的幾個常用API。服務控制程序的編寫與標準的Windows應用程序無異,它要用到服務管理函數,如:OpenSCManager、OpenService、QueryServiceConfig、StartService、QueryServiceStatus、ControlService等;它都在系統的advapi32.dll中實現。在使用SCM的函數時,SCP必須要首先調用OpenSCManager函數,打開一個通向SCM的通道。調用這個函數的時候,SCP還必須指定它想要執行的動作類型;也就是我們上一節所提到的dwDesiredAccess 參數它的取值:SC_MANAGER_ALL_ACCESS、SC_MANAGER_CREATE_SERVICE、SC_MANAGER_ENUMERATE_SERVICE、 SC_MANAGER_QUERY_LOCK_STATUS、SC_MANAGER_ENUMERATE_SERVICE 、SC_MANAGER_QUERY_LOCK_STATUS、 SC_MANAGER_LOCK、SC_MANAGER_CONNECT。例如:我們要枚舉當前所有的服務就必須給dwDesiredAccess參數指定SC_MANAGER_ENUMERATE_SERVICE;同時也可以指定其它的值,當指寫多個值時我們要把它用按位或(|)符號連接起來。
與此同時我們在調用OpenService時也必須告知SCM我們要對服務進行的動作;它有三個參數,最后一個參數dwDesiredAccess指出要對服務進行的操作,這些操作的標記與CreateService中的dwDesiredAccess參數標記值一樣。當我們以SERVICE_ALL_ACCESS訪問權限打開服務后就可以對它進行配置(QueryServiceConfig)、控制(ControlService)、查詢狀態(QueryServiceStatus)、設置狀態(SetServiceStatus)、刪除(DeleteService)等所有訪問操作。下面我們可以來看兩服務控制的實例。
?4.2??? 枚舉服務
我們先來看EnumDependentStatus函數原型:
BOOL EnumServicesStatus (? SC_HANDLE hService,????? // SCM控制句柄DWORD dwServiceType,?? //要枚舉服務還有驅動? DWORD dwServiceState,??? // 要枚舉什么狀態的服務? LPENUM_SERVICE_STATUS lpServices,// 存儲枚舉出服務的內存地址? DWORD cbBufSize,???????? // lpServices指向內存區大小? LPDWORD pcbBytesNeeded,? //實際需要的內存大小? LPDWORD lpServicesReturned //返回枚舉到服務年個數LPDWORD lpResumeHandle //指向下一個有效的入口);dwServiceState參數由:SERVICE_ACTIVE、SERVICE_INACTIVE、SERVICE_STATE_ALL三種值分別枚舉當前活動、不活動、全部的服務。函數會返回一個ENUM_SERVICE_STATUS的數組,ENUM_SERVICE_STATUS有三個域分別指出服務的服務名、顯示名和當前狀態一個指針;我們可以根據服務名打開枚舉出的服務,以得到它更加詳細的信息。下面具體讓我們看一段程序的例子。
//清空服務信息隊列
DeletItemAll();
LPENUM_SERVICE_STATUS st=NULL;
st=NULL;
DWORD ret=0;
DWORD size=0;
ServiceInfo info;
SC_HANDLE sc=OpenSCManager(NULL,NULL,SC_MANAGER_ALL_ACCESS);
SC_HANDLE sh;
char* szInfo[1024*8];
DWORD dwSize=1024*8;
CString str;
//第一次調用來得到需要多大的內存區
EnumServicesStatus(sc,SERVICE_WIN32,SERVICE_STATE_ALL,st,size,&size,&ret,NULL);
//申請需要的內存
st=(LPENUM_SERVICE_STATUS)LocalAlloc(LPTR,size);
EnumServicesStatus(sc,SERVICE_WIN32,SERVICE_STATE_ALL,st,size,&size,&ret,NULL);
//開始記錄枚舉出服務的信息
for(DWORD i=0;i<ret;i++){
???? dwSize=1024*8;
???? ZeroMemory(szInfo,dwSize);
???? info.Name.Format("%s",st[i].lpDisplayName);
???? info.serviceNmae.Format("%s",st[i].lpServiceName);
???? info.State.Format("%d",st[i].ServiceStatus.dwCurrentState);
???? sh=OpenService(sc,st[i].lpServiceName,SERVICE_ALL_ACCESS);
???? //得到服務描述信息
???? QueryServiceConfig2(sh,SERVICE_CONFIG_DESCRIPTION,(LPBYTE)szInfo,dwSize,&dwSize);
???? info.Desc.Format("%s",((LPSERVICE_DESCRIPTION)szInfo)->lpDescription);
???? //得到服務的啟動賬戶名
???? ZeroMemory(szInfo,dwSize);
???? dwSize=1024*8;
???? QueryServiceConfig(sh,(LPQUERY_SERVICE_CONFIG)szInfo,dwSize,&dwSize);
???? info.LoginUser.Format("%s",((LPQUERY_SERVICE_CONFIG)szInfo)->lpServiceStartName);
???? CloseServiceHandle(sh);
???? //添加到信息隊列中
???? ItemAdd(&info);
}
CloseServiceHandle(sc);
return TRUE;
上面程序中用到了兩個查詢服務當前配置的函數QueryServiceConfig2和QueryServiceConfig。它們有所不同是QueryServiceConfig2可以通過設置第二個參數是SERVICE_CONFIG_DESCRIPTION還是SERVICE_CONFIG_FAILURE_ACTIONS來得到服務的描述信息和失敗的活動;而QueryServiceConfig則查詢返回一個QUERY_SERVICE_CONFIG結構,這個結構存儲了服務的類型、啟動類型、錯誤控制標記、服務文件所在路徑、顯示名等信息詳細可以查看MSDN。與這個兩函數相對應還有兩個配置函數ChangeServiceConfig2和ChangeServiceConfig。它們的具體使用方法我們來看下面的這段程序。
?4.3??? 配置服務
在3.2中我們舉了一個創建服務的程序片段,其中我們只是創建服務并未設置服務描述信息,啟動服務的操作。下面的程序片段給出了示例:
bool RegterService(char* pszServiceName,
????????????????????? char* pszDisplayName,
????????????????????? char* pszServicePath,
????????????????????? char* pszDescription)
{
???? SC_HANDLE newService,scm;
???? BOOL success = FALSE;
???? SERVICE_STATUS status;
???? SERVICE_DESCRIPTION description;
???? if (pszDisplayName==NULL&&pszServiceName==NULL&&pszServicePath==NULL)
???? {
???????? return false;
???? }
???? description.lpDescription=pszDescription;
???? scm = OpenSCManager(NULL,NULL,SC_MANAGER_ENUMERATE_SERVICE|SC_MANAGER_CREATE_SERVICE);
???? if (!scm){
???????? OUT_DEBUG("OpenSCManager ERROR!");
???????? return false;
???? }
???? newService = CreateService(scm,pszServiceName,pszDisplayName,
???????? SERVICE_ALL_ACCESS|SERVICE_STOP,SERVICE_WIN32_OWN_PROCESS,
???????? SERVICE_AUTO_START,SERVICE_ERROR_NORMAL,pszServicePath,
???????? 0,0,0,0,0);
???? if (!newService){
???????? OUT_DEBUG("CreateService ERROR!");
????????? CloseServiceHandle(scm);
???????? return false;
???? }
???? if (description.lpDescription!=NULL)
???? {
???????? success=ChangeServiceConfig2(newService,
?????????????????????? SERVICE_CONFIG_DESCRIPTION,
?????????????????????? &description);
???? }
???? success = QueryServiceStatus(newService,&status);
???? if (!success){
???????? cout<<"QueryServiceStatus ERROR:"<<GetLastError()<<endl;
???????? CloseServiceHandle(newService);
???????? CloseServiceHandle(scm);
???????? return false;
???? }
???? if (status.dwCurrentState!=SERVICE_RUNNING)
???? {
???????? success = StartService(newService,NULL,NULL);
???????? if (!success){
????????????? cout<<"ControlService ERROR:"<<GetLastError()<<endl;
????????????? CloseServiceHandle(newService);
????????????? CloseServiceHandle(scm);
????????????? return false;
???????? }
???? }
???? CloseServiceHandle(newService);
???? CloseServiceHandle(scm);
???? return true;
}
ChangeServiceConfig函數可以配置更多關于服務的信息,下面列出其原型:
BOOL ChangeServiceConfig(? SC_HANDLE hService???? // 打開服務時返回的句柄? DWORD dwServiceType,?? // 服務的類型? DWORD dwStartType,???? // 何時啟動服務? DWORD dwErrorControl,? // 錯誤控制代碼? LPCTSTR lpBinaryPathName,? // 服務的路徑? LPCTSTR lpLoadOrderGroup,? // 服務所屬的組? LPDWORD lpdwTagId,???? // 服務的標記? LPCTSTR lpDependencies,??? // 依賴的其它服務和組? LPCTSTR lpServiceStartName,// 服務的啟動用戶? LPCTSTR lpPassword,?? //服務啟動用戶的密碼? LPCTSTR lpDisplayName????? // 服務的顯示名);大家可以看到ChangeServiceConfig與CreateServiceee 有著相似的參數,它們的使用方法也十分相似可以參照CreateServiceee函數調用。它主要在服務安裝完成后,需要對服務的配置進行修改時調用,除了lpDisplayName改變時需要服務停止才能生效外,其它都可以運行時動態改變;更詳細信息可查閱MSDN。
?4.4???? 控制服務
有時我們要根據實際情況啟動、暫停、停止一個服務。在4.3中的程序示例里面就一個啟動服務的調用。這里我們再簡單介紹一下這個函數:
BOOL StartService(? SC_HANDLE hService,??????????? // 打開服務時返回的句柄? DWORD dwNumServiceArgs,??????? // 服務程序參數的個數? LPCTSTR *lpServiceArgVectors?? // 存放服務程序參數的數組 );當服務程序需要配置啟動參數時就需要使用StartService后面的兩個參數,服務程序的入口函數也就可以接到相關的參數了。函數調用成功時返回非零,當返回零時調用失敗;更詳細的出錯信息可以調用GetLastError獲得。
??? 啟動后如果我們還要控制其暫停、繼續、停止的話,還需要另一個函數調用來完成。在上面刪除服務的例示程序片段中我們調用了ControlService函數來停止服務,下面我們介紹一下它的詳細信息:
BOOL ControlService(? SC_HANDLE hService,? // 打開服務時返回的句柄? DWORD dwControl,???? // 控制代碼? LPSERVICE_STATUS lpServiceStatus // 服務的狀態);調用此函數會把控制代碼發給指定服務程序的Handler處理函數同時返回服務的狀態,服務程序得到相應的控制代碼后根據協議要執行相應的操作;控制代碼就是Handler規定響應的所有代碼。我們不能啟動和停止服務安全描述符不允許的服務程序。默認的安全描述符只允許LocalSystem、 Administrators和 Power Users來啟動和停止服務程序。服務的安全描述符來用SetServiceObjectSecurity來設置(更詳細信息的信息可查閱MSDN)。?5??????? 結束語
我們在這里從總體上講述了SCM的工作流程序、服務程序編寫的方法及控制服務所用到的一些函數。文中只列出了一部分函數的信息,更詳細的信息大家可以查閱MSDN。希望本文講述的內容能幫助大家理解服務程序的編寫與控制。
本文來自CSDN博客,轉載請標明出處:http://blog.csdn.net/lziog/archive/2009/04/10/4062941.aspx
轉載于:https://www.cnblogs.com/nimorl/archive/2009/12/16/1625988.html
總結
以上是生活随笔為你收集整理的Windows服务编写的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: pursuit of happiness
- 下一篇: C# CheckedListBox控件的