標籤:好的 響應 分配 controls 瞭解 query 對話 函數指標 delete
轉載:http://blog.csdn.net/fakine/article/details/42107571
一、學習點滴
1、本機服務查看:services.msc /s
2、服務手動安裝(使用sc.exe):
建立服務
sc create ServiceName binpath= "c:\MyServices\Test.exe"
“=” 後有空格
刪除服務
sc delete ServiceName
啟動服務
net start ServiceName
停止服務
net stop ServiceName
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函數沒有被執行到)。
Windows Services 學習(轉載)