windows services


Windows Services 學習

一、學習點滴

1、本機服務查看:services.msc /s
2、服務手動安裝(使用sc.exe):
sc create MemoryStatus binpath= c:\MyServices\MemoryStatus.exe
sc delete MemoryStatus
3、把服務所在進程殺掉,服務也就停止了。
4、服務刪除,錯誤信息:
D:\>sc delete Sample_Srv
[SC] DeleteService FAILED 1072
D:\>sc stop Sample_srv
[SC] ControlService FAILED 1052
解決:service中不能有MessageBox這樣的UI框,對話框是不會彈出來的,但是程序會被阻塞,導致無法Stop、無法Delete服務,需要關機重啟,服務才能被清除。另外,如果服務啟動的進程是沒有界面的,那么這個新進程的功能仍然是可以執行的。
5、如何使用DebugView調試
需要在Capture中選中Capture Global Win32
6、服務的界面。Interactive Services
最便捷的方式是使用 WTSSendMessage API 彈出一個對話框,無需任何額外工作。
如果是直接的 MessageBox API,或者是WinExec直接新啟動一個GUI進程,需要在CreateService的時候Service類型參數帶上SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS參數。但是,win7下任何UI都會導致顯示“交互式服務檢測”對話框。網絡上的那段”打開用戶的winsta0”而沒有使用CreateProcessAsUser API 的代碼在win7下一樣有顯示“交互服務監測”對話框的問題,是不靠譜的,估計是xp下的傑作。這個彈窗的問題是可以解決的,MSDN上給了解決方案——使用CreateProcessAsUser API 新啟動一個進程做界面,服務進程跟界面進程可以使用命名管道做進程間通信。
如果沒有設置上面說到的參數,則服務在啟動子進程或者是孫進程的整個過程中,不能出現任何界面,不然服務就死了,而且還無法Stop、無法Delete服務,需要關機重啟,服務才能被清除。
7、如果CreateProcessAsUser API使用了參數CREATE_NEW_CONSOLE,那么從Process Explorer就不會看到服務進程跟被啟動進程的關聯關系。
8、通過WTSSendMessage api顯示對話框。
DWORD resp = 0;
WTSSendMessage(
WTS_CURRENT_SERVER_HANDLE,
WTSGetActiveConsoleSessionId(),
"Hello", 5,
"Hello_2", 7,
0, 0, &resp, false);
9、一個完善的例子,包括服務程序和界面程序,以及它們之間的通信。但是有些代碼無法編譯,看看思路就好。
Interaction between services and applications at user level in Windows Vista
http://www.codeproject.com/Articles/36581/Interaction-between-services-and-applications
10、非常完善的從服務啟動GUI進程。
NT Services 2013

http://www.codeproject.com/Articles/640245/NT-Services-2013
10、服務開機啟動:
http://msdn.microsoft.com/en-us/library/windows/desktop/ms681957(v=vs.85).aspx
11、Sessions,Desktops and Windows Stations
http://blogs.technet.com/b/askperf/archive/2007/07/24/sessions-desktops-and-windows-stations.aspx
12、一個相當簡單的服務程序:http://www.vckbase.com/index.php/wv/1193
13、一個包含了安裝服務程序、服務程序的完整例子:http://blog.csdn.net/itcastcpp/article/details/7079574 (代碼似乎是來自windows核心編程,或者是來自這里:http://bbs.pediy.com/showthread.php?t=114122)
14、一個監控子進程的服務,如果子進程有界面,則會顯示出win7下的彈窗,這例子不太好,代碼邏輯簡單看看就行。
http://www.codeproject.com/Articles/16488/A-Windows-Service-Application

二、MSDN閱讀

http://msdn.microsoft.com/en-us/library/ms685477(v=vs.85).aspx
服務入口點
服務程序一般寫成控制台應用程序,main函數為入口函數,main函數的參數在CreateService函數中指定。當SCM啟動一個服務程序時,SCM等待服務程序調用StartServiceCtrlDispatcher函數,如果服務進程沒有及時調用該函數,則會導致啟動服務失敗(譬如,手動啟動時,StartService函數失敗)。對於StartServiceCtrlDispatcher函數調用的時機,有下面的規則:
1、如果服務程序是SERVICE_WIN32_OWN_PROCESS類型,服務程序必須在主線程立即調用StartServiceCtrlDispatcher,初始化的工作可以在服務啟動之后做,也就是在服務主函數中做。
2、如果服務程序是SERVICE_WIN32_SHARE_PROCESS類型,並且初始化是屬於本程序中所有的服務共有的,則可以在調用StartServiceCtrlDispatcher之前執行,但是最多只能執行30秒。可以考慮啟動一個新線程去做公共的初始化任務。如果有服務獨自的初始化任務,仍然需要在服務啟動后在服務主函數中做。
StartServiceCtrlDispatcher函數的參數是SERVICE_TABLE_ENTRY結構體數組,結構體包含了:1、服務名稱,這個名稱只是在服務主函數中用上,如果服務是SERVICE_WIN32_OWN_PROCESS類型的,那么這個名稱是可以忽略的;2、服務主函數指針。
如果StartServiceCtrlDispatcher函數執行成功,調用線程(也就是服務進程的主線程)不會返回,直到所有的服務進入到SERVICE_STOPPED狀態。調用線程扮演着控制分發的角色,干這樣的事情:1、在新的服務啟動時啟動新線程去調用服務主函數(主意:服務的任務是在新線程中做的);2、當服務有請求時(注意:請求是由SCM發給它的),調用它對應的處理函數(主意:這相當於主線程“陷入”了,它在等待控制消息並對消息做處理)。
demo代碼所在:http://msdn.microsoft.com/en-us/library/windows/desktop/ms687416(v=vs.85).aspx

http://msdn.microsoft.com/en-us/library/ms685984(v=vs.85).aspx
服務主函數
當服務控制程序啟動一個新的服務時,服務控制管理器(SCM)啟動服務進程,然后服務進程發送開始請求到控制分配器,控制分配器創建一個新的線程去執行服務主函數。服務主函數需要做這樣的事情:
1、初始化所有全局變量
2、調用RegisterServiceCtrlHandler API去注冊服務的控制請求處理函數,函數的返回值是服務狀態句柄,在通知SCM當前服務的狀態時用上。
3、完成初始化。如果初始化時間很短(小於1秒),那么初始化可以直接在服務主函數中執行。否則,應該選擇下邊的一種方式做:
(1)調用SetServiceStatus API把服務狀態設置為SERVICE_RUNNING,同時SERVICE_STATUS結構體中的dwControlsAccepted要設置為0,保證在初始化期間SCM不會發送控制請求給本服務,也讓SCM可以去管理其他服務。推薦這種方式,以便提高性能,特別是自動運行服務。
(2)調用SetServiceStatus API把服務狀態設置為SERVICE_START_PENDING,這時候不會接收控制請求,並且定義了一個超時時間。如果服務的初始化時間超過了預定義的等待時間,那么必須周期性的調用SetServiceStatus API重新定義超時時間,但要確保初始化確實是有進度的,因為此時SCM認為初始化是有進度的,它會阻塞其他服務的開啟。如果超時了,而等待的進度信息未改變,SCM或者服務控制程序(可以是自己寫的啟動服務的程序)會認為發生了錯誤而需要終止服務(但是如果該服務與其他服務共享進程,則不會結束該服務所在進程)。使用這種方式初始化,可以根據實際進度周期性的調用SetServiceStatus API增加“檢查點”的值,服務的啟動者可以通過QueryServiceStatus或者QueryServiceStatusEx API去獲得檢查點的值,也就獲得了服務初始化進度。
4、當初始化完成時,調用SetServiceStatus API進入到SERVICE_RUNNING狀態、可以接收控制請求。
5、執行服務任務。如果當前沒有任務(也就是服務主函數執行完了),控制權還給了調用者(也就是control dispatcher)。如果是單個服務調用 StartServiceCtrlDispatcher,服務主函數執行完了, StartServiceCtrlDispatcher還是不會返回的,要直到服務進入停止SERVICE_STOPPED狀態才返回,進程才結束。一般情況下我們不會讓出控制權,可以寫個死循環去干活,反正主線程退出時會把服務主函數所在線程也結束掉的。另外,任何服務狀態的改變都是通過SetServiceStatus做的。
6、如果在服務初始化、執行任務的過程中出現錯誤,服務在最后一個要被結束的線程中進入SERVICE_STOPPED狀態,如果結束服務的清理工作時間很長,則需要進入 SERVICE_STOP_PENDDING狀態,執行清理工作。可以通過SERVICE_STATUS結構體的dwServiceSpecificExitCode和dwWin32ExitCode參數告知外界錯誤信息。
demo代碼所在:http://msdn.microsoft.com/en-us/library/windows/desktop/ms687414(v=vs.85).aspx

http://msdn.microsoft.com/en-us/library/ms685149(v=vs.85).aspx
服務控制處理函數
每一個服務都有一個控制處理函數,當服務進程收到控制請求時,由控制分發器調用該函數,所以這個函數的是在控制分發器線程中執行的,也就是服務進程的主線程。當服務控制處理函數被調用時,可以通過SetServiceStatus API把服務設置進不同的狀態(也就是通知SCM當前服務是什么狀態)。
外界可以通過ControlService API 發送控制請求。所有的服務必須接收並處理SERVICE_CONTROL_INTERROGATE 控制碼,可以通過SetServiceStatus API 設置接收哪些控制碼,如果要接收SERVICE_CONTROL_DEVICEEVENT控制碼,必須調用RegisterDeviceNotification API,服務也能接收用戶自定義的控制碼。
控制處理函數必須在30秒內返回(但是實際上我測試了Sleep40秒返回也不會導致SCM停止),當有長時間任務時,必須開始新線程去執行。譬如在停止的時候,應該讓服務進入到SERVICE_STOP_PENDDING狀態,開啟新線程執行任務,讓服務控制處理函數立即返回。
當系統關閉時,如果有設置了SERVICE_ACCEPT_PRESHUTDOWN,則控制處理函數能收到SERVICE_CONTROL_PRESHUTDOWN 控制碼。SCM會等待所有服務停止,或者等待時間超過關機前通知超時值,超時值可以通過ChangeServiceConfig2 API 去設置。這個控制碼使用時需要謹慎,避免阻塞關機。
在關機前通知執行完畢之后,所有設置了SERVICE_ACCEPT_SHUTDOWN的服務都能收到SERVICE_CONTROL_SHUTDOWN控制碼,通知的順序是它們安裝服務時的順序。一般情況下,一個服務在關機前有20秒鍾的時間處理任務,超時后系統執行關機,無論服務是否完成任務。當系統處於關機狀態時,服務仍然是在運行的。
如果一個服務需要更多的時間去做清理工作,它會發送STOP_PENDING狀態並設置等待時間。但是,為了防止服務阻止關機,對超時有限制。
在關機期間,SCM發送關機消息時,不會考慮服務之間的依賴關系,所有一個服務可能會因為它所依賴的服務已經停止了而失敗。可以手動或者通過API設置服務關閉的依賴關系。
demo代碼所在:http://msdn.microsoft.com/en-us/library/windows/desktop/ms687413(v=vs.85).aspx


http://msdn.microsoft.com/en-us/library/ee126211(v=vs.85).aspx
服務狀態轉換
服務負責向SCM報告狀態的改變。服務控制程序和系統只能從SCM獲取到服務的狀態。服務程序通過SetServiceStatus API設置服務狀態。
服務的初始狀態是SERVICE_STOPPED,當SCM啟動服務后,服務狀態進入SERVICE_START_PENDING狀態、調用服務主函數,接着服務完成初始化、設置能接受的控制請求,接着通過SetServiceStatus通知SCM進入到SERVICE_RUNNING狀態,如果進入的不是SERVICE_RUNNING狀態,則SCM、服務監控工具會認為該服務啟動失敗。
SCM只會發送指定的控制請求給服務(除了SERVICE_CONTROL_INTERROGATE請求,它無需指定)。SERVICE_STATUS結構體的dwControlsAccepted成員指定了哪些控制請求是需求通知服務的。如果要收到設備事件,則需要通過RegisterDeviceNotification API設置。
通常是響應控制請求而去改變服務狀態。會引起服務狀態改變的控制請求包括:SERVICE_CONTROL_STOP、SERVICE_CONTROL_PAUSE、SERVICE_CONTROL_CONTINUE。如果服務響應時間很長,必須創建第二個線程去完成任務同時告訴SCM進入相應的pendding狀態,完成任務之后再進入完成狀態。為了更好的性能,vista以及之后版本的系統推薦使用thread pool。
服務狀態有效轉換圖:


服務上報給SCM的狀態決定了跟SCM的互動(也就是接下來SCM能發給服務的控制請求)。譬如,如果服務告訴SCM它進入了SERVICE_STOP_PENDDING狀態,意味着這時候服務正在關閉,接下來服務只能是通知SCM進入SERVICE_STOPPED狀態。
控制服務停止demo:http://msdn.microsoft.com/en-us/library/windows/desktop/ms686335(v=vs.85).aspx

http://msdn.microsoft.com/en-us/library/ms685145(v=vs.85).aspx
服務和注冊表
服務不能訪問HEKY_CURRENT_USER和HKEY_CLASSES_ROOT,應該使用RegOpenCurrentUser和RegOpenUserClassesRoot函數。
簡結:
整個服務的流程可以理解為這樣子:假設進程只有單個服務,SCM把服務進程拉起來,服務進程的主線程調用服務控制分發函數,這個函數直到服務停止或者服務開啟失敗才返回。服務控制分發函數啟動新線程執行服務主函數,服務主函數會注冊控制處理函數,這是個回調函數,由主線程去調用響應外界的通知,控制回調函數里根據通知把服務設置進不同的狀態,這也是告訴SCM服務進入了另一個狀態。當服務的狀態進入到SERVICE_STOPPED狀態時,服務控制分發函數返回,主線程結束,進程結束。

http://msdn.microsoft.com/en-us/library/windows/desktop/ms683502(v=vs.85).aspx
交互式服務
一般情況下,服務是一個沒有界面的不需要交互的控制台程序。但是,一些服務有時候要求跟使用者有交互。

在vista系統中服務無法直接跟用戶交互。

用戶與服務間接交互。
在windows支持的所有版本上,可以使用下邊的技術實現:
1、使用WTSSendMessage函數給用戶顯示對話框。
2、創建一個隱藏的GUI應用程序,使用CreateProcessAsUser API使應用程序在可以跟用戶交互的環境下運行。GUI程序使用IPC跟服務程序做數據通信,如果通信使用的是命名管道,那么通過提供基於會話ID的管道名,服務能夠區分多用戶的進程。
使用交互式服務。
默認情況下服務使用不可交互的“窗口站”,無法跟用戶交互。但是,一個可交互的服務能夠顯示用戶界面和接收用戶輸入。
......
這種方式顯示的窗口,是會彈窗提示的。沒啥用。
最后文檔也說了:所有的服務運行在終端服務session 0。因此,如果一個交互服務顯示用戶界面,只能是那些進入到session 0的用戶才能看到,所有在win7下才會顯示一個提示框,讓用戶進入到另一個界面,也就是session 0。

三、總結

    注意到,從服務中啟動GUI進程這個可以解決win7下程序要求有管理員權限時,UAC彈框的問題。而服務啟動GUI進程,主要需要使用CreateProcessAsUser API。無法讓服務進程彈出漂亮的窗口,最多只能直接彈出一個MessageBox,如果想要使用服務的高權限來做些事情,又想要有漂亮的用戶界面,則可以讓UI進程借服務進程干活,UI進程負責與用戶交互,這需要涉及進程間通信(IPC),使用命名管道也很容易實現一個demo,這里就不說了。
服務demo代碼所在:https://github.com/cswuyg/simple_win/tree/master/my_service_example,包括了:服務進程、服務中啟動GUI進程、服務控制進程的代碼。

2014.3 補充

1、服務編程:http://msdn.microsoft.com/en-us/library/windows/desktop/ms685969(v=vs.85).aspx
2、一個完整的服務demo:http://msdn.microsoft.com/en-us/library/bb540476(v=vs.85).aspx
3、服務控制回調函數的響應:
必須在Control Handle函數里正確、及時通知SCM本服務狀態的改變。
用戶通過服務面板將服務設置為某種狀態之后,系統通過檢查SCM里該服務的狀態是否進入用戶想要的狀態,來確定操作是否有效,所以,對於服務來說,只需要在Control Handle按照通知碼修改對應的狀態,就行了,服務是否真的停止了,就看我們程序的意願了。
如果用戶操作了“停止”服務,但是我們的程序沒有通知SCM本服務狀態改變,就會導致出“Windows 無法停止....服務”的窗口,這種情況發生兩次之后,服務進程退出。
4、如何修改服務的一些屬性,包括Enabled、Disabled、開機運行服務(這個也可以在CreateService的時候設置SERVICE_AUTO_START)、描述信息:http://msdn.microsoft.com/en-us/windows/desktop/ms682006(v=vs.100).aspx
5、 在編寫開機運行服務的時候,需要有自己的log,才能定位是哪塊沒有執行,windows本身也有日志,但信息太少,windows日志所在:命令行運行運行compmgmt.msc,定位到系統工具\事件查看器\Windows日志\系統,可以看到服務啟動相關的信息。在我編寫的時候,出現過這種服務無法開機啟動的錯誤:“等待 cswuyg_Svc 服務的連接超時(30000 毫秒)”。多次測試之后,發現把服務進程EXE從Debug版換成Release版本就解決,估計是Debug模式下的性能太差,導致service main函數線程無法及時啟動(從log看出service main函數沒有被執行到)。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM