有那么一類應(yīng)用程序,,是能夠?yàn)楦鞣N用戶(包括本地用戶和遠(yuǎn)程用戶)所用的,, 擁有用戶授權(quán)級進(jìn)行管理的能力,并且不論用戶是否物理的與正在運(yùn)行該應(yīng)用程 序的計(jì)算機(jī)相連都能正常執(zhí)行,,這就是所謂的服務(wù)了,。 (一)服務(wù)的基礎(chǔ)知識 Question 1. 什么是服務(wù)?它的特征是什么,? 在NT/2000中,,服務(wù)是一類受到操作系統(tǒng)優(yōu)待的程序。一個(gè)服務(wù)首先是一個(gè) Win32可執(zhí)行程序,,如果要寫一個(gè)功能完備且強(qiáng)大的服務(wù),,需要熟悉動(dòng)態(tài)連接庫 (Dlls)、結(jié)構(gòu)異常處理,、內(nèi)存映射文件,、虛擬內(nèi)存、設(shè)備I/O、線程及其同步,、 Unicode以及其他的由WinAPI函數(shù)提供的應(yīng)用接口,。當(dāng)然本文討論的只是建立一 個(gè)可以安裝、運(yùn)行,、啟動(dòng),、停止的沒有任何其他功能的服務(wù),所以無需上述知識 仍可以繼續(xù)看下去,,我會(huì)在過程中將理解本文所需要的知識逐一講解,。 第二要知道的是一個(gè)服務(wù)決不需要用戶界面。大多數(shù)的服務(wù)將運(yùn)行在那些被 鎖在某些黑暗的,,冬暖夏涼的小屋子里的強(qiáng)大的服務(wù)器上面,,即使有用戶界面一 般也沒有人可以看到。如果服務(wù)提供任何用戶界面如消息框,,那么用戶錯(cuò)過這些 消息的可能性就極高了,,所以服務(wù)程序通常以控制臺(tái)程序的形式被編寫,進(jìn)入點(diǎn) 函數(shù)是main()而不是WinMain(),。 也許有人有疑問:沒有用戶界面的話,,要怎樣設(shè)置、管理一個(gè)服務(wù),?怎樣開 始,、停止它?服務(wù)如何發(fā)出警告或錯(cuò)誤信息,、如何報(bào)告關(guān)于它的執(zhí)行情況的統(tǒng)計(jì) 數(shù)據(jù),?這些問題的答案就是服務(wù)能夠被遠(yuǎn)程管理,Windows NT/2000提供了大量 的管理工具,,這些工具允許通過網(wǎng)絡(luò)上的其它計(jì)算機(jī)對某臺(tái)機(jī)器上面的服務(wù)進(jìn)行 管理,。比如Windows 2000里面的“控制臺(tái)”程序(mmc.exe),用它添加“管理單 元”就可以管理本機(jī)或其他機(jī)器上的服務(wù),。 Question 2. 服務(wù)的安全性… 想要寫一個(gè)服務(wù),,就必須熟悉Win NT/2000的安全機(jī)制,在上述操作系統(tǒng)之 中,,所有安全都是基于用戶的,。換句話說——進(jìn)程、線程,、文件,、注冊表鍵、信 號,、事件等等等等都屬于一個(gè)用戶,。當(dāng)一個(gè)進(jìn)程被產(chǎn)生的時(shí)候,,它都是執(zhí)行在一 個(gè)用戶的上下文(context),這個(gè)用戶賬號可能在本機(jī),,也可能在網(wǎng)絡(luò)中的其他 機(jī)器上,,或者是在一個(gè)特殊的賬號:System Account——即系統(tǒng)賬號的上下文 如果一個(gè)進(jìn)程正在一個(gè)用戶賬號下執(zhí)行,那么這個(gè)進(jìn)程就同時(shí)擁有這個(gè)用戶 所能擁有的一切訪問權(quán)限,,不論是在本機(jī)還是網(wǎng)絡(luò),。系統(tǒng)賬號則是一個(gè)特殊的賬 號,它用來標(biāo)識系統(tǒng)本身,,而且運(yùn)行在這個(gè)賬號下的任何進(jìn)程都擁有系統(tǒng)上的所 有訪問權(quán)限,,但是系統(tǒng)賬號不能在域上使用,無法訪問網(wǎng)絡(luò)資源… 服務(wù)也是Win32可執(zhí)行程序,,它也需要執(zhí)行在一個(gè)context,,通常服務(wù)都是在 系統(tǒng)賬號下運(yùn)行,但是也可以根據(jù)情況選擇讓它運(yùn)行在一個(gè)用戶賬號下,,也就會(huì) 因此獲得相應(yīng)的訪問資源的權(quán)限,。 Question 3. 服務(wù)的三個(gè)組成部分 一個(gè)服務(wù)由三部分組成,第一部分是Service Control Manager(SCM),。每個(gè) Windows NT/2000系統(tǒng)都有一個(gè)SCM,,SCM存在于Service.exe中,在Windows啟動(dòng) 的時(shí)候會(huì)自動(dòng)運(yùn)行,,伴隨著操作系統(tǒng)的啟動(dòng)和關(guān)閉而產(chǎn)生和終止,。這個(gè)進(jìn)程以系 統(tǒng)特權(quán)運(yùn)行,并且提供一個(gè)統(tǒng)一的,、安全的手段去控制服務(wù),。它其實(shí)是一個(gè)RPC Server,因此我們可以遠(yuǎn)程安裝和管理服務(wù),,不過這不在本文討論的范圍之內(nèi)。 SCM包含一個(gè)儲(chǔ)存著已安裝的服務(wù)和驅(qū)動(dòng)程序的信息的數(shù)據(jù)庫,,通過SCM可以統(tǒng)一 的,、安全的管理這些信息,因此一個(gè)服務(wù)程序的安裝過程就是將自身的信息寫入 這個(gè)數(shù)據(jù)庫,。 第二部分就是服務(wù)本身,。一個(gè)服務(wù)擁有能從SCM收到信號和命令所必需的的 特殊代碼,并且能夠在處理后將它的狀態(tài)回傳給SCM,。 第三部分也就是最后一部分,,是一個(gè)Service Control Dispatcher(SCP)。 它是一個(gè)擁有用戶界面,,允許用戶開始,、停止,、暫停、繼續(xù),,并且控制一個(gè)或多 個(gè)安裝在計(jì)算機(jī)上服務(wù)的Win32應(yīng)用程序,。SCP的作用是與SCM通訊,Windows 2000管理工具中的“服務(wù)”就是一個(gè)典型的SCP,。 在這三個(gè)組成部分中,,用戶最可能去寫服務(wù)本身,同時(shí)也可能不得不寫一個(gè) 與其伴隨的客戶端程序作為一個(gè)SCP去和SCM通訊,,本文只討論去設(shè)計(jì)和實(shí)現(xiàn)一個(gè) 服務(wù),,關(guān)于如何去實(shí)現(xiàn)一個(gè)SCP則在以后的其它文章中介紹。 Question 4. 怎樣開始設(shè)計(jì)服務(wù) 還記得前面我提到服務(wù)程序的入口點(diǎn)函數(shù)一般都是main()嗎,?一個(gè)服務(wù)擁有 很重要的三個(gè)函數(shù),,第一個(gè)就是入口點(diǎn)函數(shù),其實(shí)用WinMain()作為入口點(diǎn)函數(shù) 也不是不可以,,雖然說服務(wù)不應(yīng)該有用戶界面,,但是其實(shí)存在很少的幾個(gè)例外, 這就是下面圖中的選項(xiàng)存在的原因,。 由于要和用戶桌面進(jìn)行信息交互,,服務(wù)程序有時(shí)會(huì)以WinMain()作為入口點(diǎn) 函數(shù)。 入口函數(shù)負(fù)責(zé)初始化整個(gè)進(jìn)程,,由這個(gè)進(jìn)程中的主線程來執(zhí)行,。這意味著它 應(yīng)用于這個(gè)可執(zhí)行文件中的所有服務(wù)。要知道,,一個(gè)可執(zhí)行文件中能夠包含多個(gè) 服務(wù)以使得執(zhí)行更加有效,。主進(jìn)程通知SCM在可執(zhí)行文件中含有幾個(gè)服務(wù),并且 給出每一個(gè)服務(wù)的ServiceMain回調(diào)(Call Back)函數(shù)的地址,。一旦在可執(zhí)行文件 內(nèi)的所有服務(wù)都已經(jīng)停止運(yùn)行,,主線程就在進(jìn)程終止前對整個(gè)進(jìn)程進(jìn)行清除。 第二個(gè)很重要的函數(shù)就是ServiceMain,,我看過一些例子程序里面對自己的 服務(wù)的進(jìn)入點(diǎn)函數(shù)都固定命名為ServiceMain,,其實(shí)并沒有規(guī)定過一定要那樣命 名,任何的函數(shù)只要符合下列的形式都可以作為服務(wù)的進(jìn)入點(diǎn)函數(shù),。 VOID WINAPI ServiceMain( 這個(gè)函數(shù)由操作系統(tǒng)調(diào)用,,并執(zhí)行能完成服務(wù)的代碼。一個(gè)專用的線程執(zhí)行 每一個(gè)服務(wù)的ServiceMain函數(shù),,注意是服務(wù)而不是服務(wù)程序,,這是因?yàn)槊總€(gè)服 務(wù)也都擁有與自己唯一對應(yīng)的ServiceMain函數(shù),關(guān)于這一點(diǎn)可以用“管理工具 ”里的“服務(wù)”去察看Win2000里面自帶的服務(wù),,就會(huì)發(fā)現(xiàn)其實(shí)很多服務(wù)都是由 service.exe單獨(dú)提供的,。當(dāng)主線程調(diào)用Win32函數(shù)StartServiceCtrlDispatcher 的時(shí)候,,SCM為這個(gè)進(jìn)程中的每一個(gè)服務(wù)產(chǎn)生一個(gè)線程。這些線程中的每一個(gè)都 和它的相應(yīng)的服務(wù)的ServiceMain函數(shù)一起執(zhí)行,,這就是服務(wù)總是多線程的原因 ——一個(gè)僅有一個(gè)服務(wù)的可執(zhí)行文件將有一個(gè)主線程,,其它的線程執(zhí)行服務(wù)本身 。 第三個(gè)也就是最后的一個(gè)重要函數(shù)是CtrlHandler,,它必須擁有下面的原型 : VOID WINAPI CtrlHandler( 像ServiceMain一樣,,CtrlHandler也是一個(gè)回調(diào)函數(shù),用戶必須為它的服務(wù) 程序中每一個(gè)服務(wù)寫一個(gè)單獨(dú)的CtrlHandler函數(shù),,因此如果有一個(gè)程序含有兩 個(gè)服務(wù),,那么它至少要擁有5個(gè)不同的函數(shù):作為入口點(diǎn)的main()或WinMain(), 用于第一個(gè)服務(wù)的ServiceMain函數(shù)和CtrlHandler函數(shù),,以及用于第二個(gè)服務(wù)的 ServiceMain函數(shù)和CtrlHandler函數(shù),。 SCM調(diào)用一個(gè)服務(wù)的CtrlHandler函數(shù)去改變這個(gè)服務(wù)的狀態(tài)。例如,,當(dāng)某個(gè) 管理員用管理工具里的“服務(wù)”嘗試停止你的服務(wù)的時(shí)候,,你的服務(wù)的 CtrlHandler函數(shù)將收到一個(gè)SERVICE_CONTROL_STOP通知。CtrlHandler函數(shù)負(fù)責(zé) 執(zhí)行停止服務(wù)所需的一切代碼,。由于是進(jìn)程的主線程執(zhí)行所有的CtrlHandler函 數(shù),,因而必須盡量優(yōu)化你的CtrlHandler函數(shù)的代碼,使它運(yùn)行起來足夠快,,以 便相同進(jìn)程中的其它服務(wù)的CtrlHandler函數(shù)能在適當(dāng)?shù)臅r(shí)間內(nèi)收到屬于它們的 通知,。而且基于上述原因,你的CtrlHandler函數(shù)必須要能夠?qū)⑾胍獋鬟_(dá)的狀態(tài) 送到服務(wù)線程,,這個(gè)傳遞過程沒有固定的方法,,完全取決于你的服務(wù)的用途。 (二)對服務(wù)的深入討論之上 上一章其實(shí)只是概括性的介紹,,下面開始才是真正的細(xì)節(jié)所在,。在進(jìn)入點(diǎn)函 數(shù)里面要完成ServiceMain的初始化,準(zhǔn)確點(diǎn)說是初始化一個(gè) SERVICE_TABLE_ENTRY結(jié)構(gòu)數(shù)組,,這個(gè)結(jié)構(gòu)記錄了這個(gè)服務(wù)程序里面所包含的所 有服務(wù)的名稱和服務(wù)的進(jìn)入點(diǎn)函數(shù),,下面是一個(gè)SERVICE_TABLE_ENTRY的例子: SERVICE_TABLE_ENTRY service_table_entry[] = 第一個(gè)成員代表服務(wù)的名字,第二個(gè)成員是ServiceMain回調(diào)函數(shù)的地址,, 上面的服務(wù)程序因?yàn)閾碛袃蓚€(gè)服務(wù),所以有三個(gè)SERVICE_TABLE_ENTRY元素,,前 兩個(gè)用于服務(wù),,最后的NULL指明數(shù)組的結(jié)束。 接下來這個(gè)數(shù)組的地址被傳遞到StartServiceCtrlDispatcher函數(shù): BOOL StartServiceCtrlDispatcher( 這個(gè)Win32函數(shù)表明可執(zhí)行文件的進(jìn)程怎樣通知SCM包含在這個(gè)進(jìn)程中的服務(wù) ,。就像上一章中講的那樣,,StartServiceCtrlDispatcher為每一個(gè)傳遞到它的數(shù) 組中的非空元素產(chǎn)生一個(gè)新的線程,,每一個(gè)進(jìn)程開始執(zhí)行由數(shù)組元素中的 lpServiceStartTable指明的ServiceMain函數(shù)。 SCM啟動(dòng)一個(gè)服務(wù)程序之后,,它會(huì)等待該程序的主線程去調(diào) StartServiceCtrlDispatcher,。如果那個(gè)函數(shù)在兩分鐘內(nèi)沒有被調(diào)用,SCM將會(huì) 認(rèn)為這個(gè)服務(wù)有問題,,并調(diào)用TerminateProcess去殺死這個(gè)進(jìn)程,。這就要求你的 主線程要盡可能快的調(diào)用StartServiceCtrlDispatcher。 StartServiceCtrlDispatcher函數(shù)則并不立即返回,,相反它會(huì)駐留在一個(gè)循 環(huán)內(nèi),。當(dāng)在該循環(huán)內(nèi)時(shí),StartServiceCtrlDispatcher懸掛起自己,,等待下面兩 個(gè)事件中的一個(gè)發(fā)生,。第一,如果SCM要去送一個(gè)控制通知給運(yùn)行在這個(gè)進(jìn)程內(nèi) 一個(gè)服務(wù)的時(shí)候,,這個(gè)線程就會(huì)激活,。當(dāng)控制通知到達(dá)后,線程激活并調(diào)用相應(yīng) 服務(wù)的CtrlHandler函數(shù),。CtrlHandler函數(shù)處理這個(gè)服務(wù)控制通知,,并返回到 StartServiceCtrlDispatcher。StartServiceCtrlDispatcher循環(huán)回去后再一次 懸掛自己,。 第二,,如果服務(wù)線程中的一個(gè)服務(wù)中止,這個(gè)線程也將激活,。在這種情況下 ,,該進(jìn)程將運(yùn)行在它里面的服務(wù)數(shù)減一。如果服務(wù)數(shù)為零,, StartServiceCtrlDispatcher就會(huì)返回到入口點(diǎn)函數(shù),,以便能夠執(zhí)行任何與進(jìn)程 有關(guān)的清除工作并結(jié)束進(jìn)程。如果還有服務(wù)在運(yùn)行,,哪怕只是一個(gè)服務(wù),, StartServiceCtrlDispatcher也會(huì)繼續(xù)循環(huán)下去,繼續(xù)等待其它的控制通知或者 剩下的服務(wù)線程中止,。 上面的內(nèi)容是關(guān)于入口點(diǎn)函數(shù)的,,下面的內(nèi)容則是關(guān)于ServiceMain函數(shù)的 。還記得以前講過的ServiceMain函數(shù)的的原型嗎,?但實(shí)際上一個(gè)ServiceMain函 數(shù)通常忽略傳遞給它的兩個(gè)參數(shù),,因?yàn)榉?wù)一般不怎么傳遞參數(shù)。設(shè)置一個(gè)服務(wù) 最好的方法就是設(shè)置注冊表,,一般服務(wù)在 ters 一個(gè)客戶應(yīng)用程序去進(jìn)行服務(wù)的背景設(shè)置,,這個(gè)客戶應(yīng)用程序?qū)⑦@些信息存在注 冊表中,,以便服務(wù)讀取。當(dāng)一個(gè)外部應(yīng)用程序已經(jīng)改變了某個(gè)正在運(yùn)行中的服務(wù) 的設(shè)置數(shù)據(jù)的時(shí)候,,這個(gè)服務(wù)能夠用RegNotifyChangeKeyValue函數(shù)去接受一個(gè) 通知,,這樣就允許服務(wù)快速的重新設(shè)置自己。 前面講到StartServiceCtrlDispatcher為每一個(gè)傳遞到它的數(shù)組中的非空元 素產(chǎn)生一個(gè)新的線程,。接下來,,一個(gè)ServiceMain要做些什么呢?MSDN里面的原 文是這樣說的:The ServiceMain function should immediately call the RegisterServiceCtrlHandler function to specify a Handler function to handle control requests. Next, it should call the SetServiceStatus function to send status information to the service control manager. 為 什么呢,?因?yàn)榘l(fā)出啟動(dòng)服務(wù)請求之后,,如果在一定時(shí)間之內(nèi)無法完成服務(wù)的初始 化,SCM會(huì)認(rèn)為服務(wù)的啟動(dòng)已經(jīng)失敗了,,這個(gè)時(shí)間的長度在Win NT 4.0中是80秒 ,,Win2000中不詳… 基于上面的理由,ServiceMain要迅速完成自身工作,,首先是必不可少的兩 項(xiàng)工作,,第一項(xiàng)是調(diào)用RegisterServiceCtrlHandler函數(shù)去通知SCM它的 CtrlHandler回調(diào)函數(shù)的地址: SERVICE_STATUS_HANDLE RegisterServiceCtrlHandler(
數(shù)是CtrlHandler函數(shù)的地址,。lpServiceName必須和在SERVICE_TABLE_ENTRY里 面被初始化的服務(wù)的名字相匹配,。RegisterServiceCtrlHandler返回一個(gè) SERVICE_STATUS_HANDLE,這是一個(gè)32位的句柄,。SCM用它來唯一確定這個(gè)服務(wù),。 當(dāng)這個(gè)服務(wù)需要把它當(dāng)時(shí)的狀態(tài)報(bào)告給SCM的時(shí)候,就必須把這個(gè)句柄傳給需要 它的Win32函數(shù),。注意:這個(gè)句柄和其他大多數(shù)的句柄不同,,你無需關(guān)閉它。 SCM要求ServiceMain函數(shù)的線程在一秒鐘內(nèi)調(diào)用 RegisterServiceCtrlHandler函數(shù),,否則SCM會(huì)認(rèn)為服務(wù)已經(jīng)失敗,。但在這種情 況下,SCM不會(huì)終止服務(wù),,不過在NT 4中將無法啟動(dòng)這個(gè)服務(wù),,同時(shí)會(huì)返回一個(gè) 不正確的錯(cuò)誤信息,這一點(diǎn)在Windows 2000中得到了修正,。 在RegisterServiceCtrlHandler函數(shù)返回后,,ServiceMain線程要立即告訴 SCM服務(wù)正在繼續(xù)初始化。具體的方法是通過調(diào)用SetServiceStatus函數(shù)傳遞 SERVICE_STATUS數(shù)據(jù)結(jié)構(gòu),。 BOOL SetServiceStatus( 這個(gè)函數(shù)要求傳遞給它指明服務(wù)的句柄(剛剛通過調(diào)用 RegisterServiceCtrlHandler得到),,和一個(gè)初始化的SERVICE_STATUS結(jié)構(gòu)的地 址: typedef struct _SERVICE_STATUS SERVICE_STATUS結(jié)構(gòu)含有七個(gè)成員,它們反映服務(wù)的現(xiàn)行狀態(tài),。所有這些成 員必須在這個(gè)結(jié)構(gòu)被傳遞到SetServiceStatus之前正確的設(shè)置,。 成員dwServiceType指明服務(wù)可執(zhí)行文件的類型。如果你的可執(zhí)行文件中只 有一個(gè)單獨(dú)的服務(wù),,就把這個(gè)成員設(shè)置成SERVICE_WIN32_OWN_PROCESS,;如果擁 有多個(gè)服務(wù)的話,就設(shè)置成SERVICE_WIN32_SHARE_PROCESS,。除了這兩個(gè)標(biāo)志之 外,,如果你的服務(wù)需要和桌面發(fā)生交互(當(dāng)然不推薦這樣做),就要用“OR”運(yùn)算 符附加上SERVICE_INTERACTIVE_PROCESS,。這個(gè)成員的值在你的服務(wù)的生存期內(nèi) 絕對不應(yīng)該改變,。 成員dwCurrentState是這個(gè)結(jié)構(gòu)中最重要的成員,它將告訴SCM你的服務(wù)的 現(xiàn)行狀態(tài),。為了報(bào)告服務(wù)仍在初始化,,應(yīng)該把這個(gè)成員設(shè)置成 SERVICE_START_PENDING。在以后具體講述CtrlHandler函數(shù)的時(shí)候具體解釋其它 可能的值,。 成員dwControlsAccepted指明服務(wù)愿意接受什么樣的控制通知,。如果你允許 一個(gè)SCP去暫停/繼續(xù)服務(wù),就把它設(shè)成SERVICE_ACCEPT_PAUSE_CONTINUE,。很多 服務(wù)不支持暫?;蚶^續(xù),就必須自己決定在服務(wù)中它是否可用,。如果你允許一個(gè) SCP去停止服務(wù),,就要設(shè)置它為SERVICE_ACCEPT_STOP。如果服務(wù)要在操作系統(tǒng)關(guān) 閉的時(shí)候得到通知,,設(shè)置它為SERVICE_ACCEPT_SHUTDOWN可以收到預(yù)期的結(jié)果,。 這些標(biāo)志可以用“OR”運(yùn)算符組合。 成員dwWin32ExitCode和dwServiceSpecificExitCode是允許服務(wù)報(bào)告錯(cuò)誤的 關(guān)鍵,,如果希望服務(wù)去報(bào)告一個(gè)Win32錯(cuò)誤代碼(預(yù)定義在WinError.h中),,它就 設(shè)置dwWin32ExitCode為需要的代碼。一個(gè)服務(wù)也可以報(bào)告它本身特有的,、沒有 映射到一個(gè)預(yù)定義的Win32錯(cuò)誤代碼中的錯(cuò)誤,。為了這一點(diǎn),要把 dwWin32ExitCode設(shè)置為ERROR_SERVICE_SPECIFIC_ERROR,,然后還要設(shè)置成員 dwServiceSpecificExitCode為服務(wù)特有的錯(cuò)誤代碼,。當(dāng)服務(wù)運(yùn)行正常,沒有錯(cuò) 誤可以報(bào)告的時(shí)候,就設(shè)置成員dwWin32ExitCode為NO_ERROR,。 最后的兩個(gè)成員dwCheckPoint和dwWaitHint是一個(gè)服務(wù)用來報(bào)告它當(dāng)前的事 件進(jìn)展情況的,。當(dāng)成員dwCurrentState被設(shè)置成SERVICE_START_PENDING的時(shí)候 ,應(yīng)該把dwCheckPoint設(shè)成0,,dwWaitHint設(shè)成一個(gè)經(jīng)過多次嘗試后確定比較合 適的數(shù),,這樣服務(wù)才能高效運(yùn)行。一旦服務(wù)被完全初始化,,就應(yīng)該重新初始化 SERVICE_STATUS結(jié)構(gòu)的成員,,更改dwCurrentState為SERVICE_RUNNING,然后把 dwCheckPoint和dwWaitHint都改為0,。 dwCheckPoint成員的存在對用戶是有益的,,它允許一個(gè)服務(wù)報(bào)告它處于進(jìn)程 的哪一步。每一次調(diào)用SetServiceStatus時(shí),,可以增加它到一個(gè)能指明服務(wù)已經(jīng) 執(zhí)行到哪一步的數(shù)字,,它可以幫助用戶決定多長時(shí)間報(bào)告一次服務(wù)的進(jìn)展情況。 如果決定要報(bào)告服務(wù)的初始化進(jìn)程的每一步,,就應(yīng)該設(shè)置dwWaitHint為你認(rèn)為到 達(dá)下一步所需的毫秒數(shù),,而不是服務(wù)完成它的進(jìn)程所需的毫秒數(shù)。 在服務(wù)的所有初始化都完成之后,,服務(wù)調(diào)用SetServiceStatus指明 SERVICE_RUNNING,,在那一刻服務(wù)已經(jīng)開始運(yùn)行。通常一個(gè)服務(wù)是把自己放在一 個(gè)循環(huán)之中來運(yùn)行的,。在循環(huán)的內(nèi)部這個(gè)服務(wù)進(jìn)程懸掛自己,,等待指明它下一步 是應(yīng)該暫停、繼續(xù)或停止之類的網(wǎng)絡(luò)請求或通知,。當(dāng)一個(gè)請求到達(dá)的時(shí)候,,服務(wù) 線程激活并處理這個(gè)請求,然后再循環(huán)回去等待下一個(gè)請求/通知,。 如果一個(gè)服務(wù)由于一個(gè)通知而激活,,它會(huì)先處理這個(gè)通知,除非這個(gè)服務(wù)得 到的是停止或關(guān)閉的通知,。如果真的是停止或關(guān)閉的通知,,服務(wù)線程將退出循環(huán) ,執(zhí)行必要的清除操作,,然后從這個(gè)線程返回,。當(dāng)ServiceMain線程返回并中止 時(shí),引起在StartServiceCtrlDispatcher內(nèi)睡眠的線程激活,,并像在前面解釋過 的那樣,,減少它運(yùn)行的服務(wù)的計(jì)數(shù)。 (三)對服務(wù)的深入討論之下 現(xiàn)在我們還剩下一個(gè)函數(shù)可以在細(xì)節(jié)上討論,那就是服務(wù)的CtrlHandler函 數(shù),。 當(dāng)調(diào)用RegisterServiceCtrlHandler函數(shù)時(shí),,SCM得到并保存這個(gè)回調(diào)函數(shù) 的地址。一個(gè)SCP調(diào)一個(gè)告訴SCM如何去控制服務(wù)的Win32函數(shù),,現(xiàn)在已經(jīng)有10個(gè) 預(yù)定義的控制請求: Control code Meaning must have SERVICE_STOP access. handle must have SERVICE_PAUSE_CONTINUE access. hService handle must have SERVICE_PAUSE_CONTINUE access. its current status information to the service control manager. The hService handle must have SERVICE_INTERROGATE access. tasks, because the system is shutting down. For more information, see Remarks. reread its startup parameters. The hService handle must have SERVICE_PAUSE_CONTINUE access. update its network binding. The hService handle must have SERVICE_PAUSE_CONTINUE access. that a component for binding has been removed. The service should reread its binding information and unbind from the removed component. that a disabled binding has been enabled. The service should reread its binding information and add the new binding. service that one of its bindings has been disabled. The service should reread its binding information and remove the binding. 上表中標(biāo)有Windows 2000字樣的就是2000中新添加的控制代碼,。除了這些代 碼之外,服務(wù)也可以接受用戶定義的,,范圍在128-255之間的代碼。 當(dāng)CtrlHandler函數(shù)收到一個(gè)SERVICE_CONTROL_STOP,、 SERVICE_CONTROL_PAUSE,、 SERVICE_CONTROL_CONTINUE控制代碼的時(shí)候, SetServiceStatus必須被調(diào)用去確認(rèn)這個(gè)代碼,,并指定你認(rèn)為服務(wù)處理這個(gè)狀態(tài) 變化所需要的時(shí)間,。 例如:你的服務(wù)收到了停止請求,首先要把SERVICE_STATUS結(jié)構(gòu)的 dwCurrentState成員設(shè)置成SERVICE_STOP_PENDING,,這樣可以使SCM確定你已經(jīng) 收到了控制代碼,。當(dāng)一個(gè)服務(wù)的暫停或停止操作正在執(zhí)行的時(shí)候,,必須指定你認(rèn) 為這種操作所需要的時(shí)間:這是因?yàn)橐粋€(gè)服務(wù)也許不能立即改變它的狀態(tài),,它可 能必須等待一個(gè)網(wǎng)絡(luò)請求被完成或者數(shù)據(jù)被刷新到一個(gè)驅(qū)動(dòng)器上。指定時(shí)間的方 法就像我上一章說的那樣,,用成員dwCheckPoint和dwWaitHint來指明它完成狀態(tài) 改變所需要的時(shí)間,。如果需要,可以用增加dwCheckPoint成員的值和設(shè)置 dwWaitHint成員的值去指明你期待的服務(wù)到達(dá)下一步的時(shí)間的方式周期性的報(bào)告 進(jìn)展情況,。 當(dāng)整個(gè)啟動(dòng)的過程完成之后,,要再一次調(diào)用SetServiceStatus。這時(shí)就要把 SERVICE_STATUS結(jié)構(gòu)的dwCurrentState成員設(shè)置成SERVICE_STOPPED,,當(dāng)報(bào)告狀 態(tài)代碼的同時(shí),,一定要把成員dwCheckPoint和dwWaitHint設(shè)置為0,因?yàn)榉?wù)已 經(jīng)完成了它的狀態(tài)變化,。暫?;蚶^續(xù)服務(wù)的時(shí)候方法也一樣。 當(dāng)CtrlHandler函數(shù)收到一個(gè)SERVICE_CONTROL_INTERROGATE控制代碼的時(shí)候 ,,服務(wù)將簡單的將dwCurrentState成員設(shè)置成服務(wù)當(dāng)前的狀態(tài),,同時(shí),把成員 dwCheckPoint和dwWaitHint設(shè)置為0,,然后再調(diào)用SetServiceStatus就可以了,。 在操作系統(tǒng)關(guān)閉的時(shí)候,CtrlHandler函數(shù)收到一個(gè) SERVICE_CONTROL_SHUTDOWN控制代碼。服務(wù)根本無須回應(yīng)這個(gè)代碼,,因?yàn)橄到y(tǒng)即 將關(guān)閉,。它將執(zhí)行保存數(shù)據(jù)所需要的最小行動(dòng)集,這是為了確定機(jī)器能及時(shí)關(guān)閉 ,。缺省時(shí)系統(tǒng)只給很少的時(shí)間去關(guān)閉所有的服務(wù),,MSDN里面說大概是20秒的時(shí)間 ,不過那可能是Windows NT 4的設(shè)置,,在我的Windows 2000 Server里這個(gè)時(shí)間 是10秒,,你可以手動(dòng)的修改這個(gè)數(shù)值,它被記錄在 HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Control子鍵里面的 WaitToKillServiceTimeout,,單位是毫秒,。 當(dāng)CtrlHandler函數(shù)收到任何用戶定義的代碼時(shí),它應(yīng)該執(zhí)行期望的用戶自 定義行動(dòng),。除非用戶自定義的行動(dòng)要強(qiáng)制服務(wù)去暫停,、繼續(xù)或停止,否則不調(diào) SetServiceStatus函數(shù),。如果用戶定義的行動(dòng)強(qiáng)迫服務(wù)的狀態(tài)發(fā)生變化,, SetServiceStatus將被調(diào)用去設(shè)置dwCurrentState、dwCheckPoint和dwWaitHint ,,具體控制代碼和前面說的一樣,。 如果你的CtrlHandler函數(shù)需要很長的時(shí)間執(zhí)行操作的話,千萬要注意:假 如CtrlHandler函數(shù)在30秒內(nèi)沒有返回的話,,SCM將返回一個(gè)錯(cuò)誤,,這不是我們所 期望的。所以如果出現(xiàn)上述情況,,最好的辦法是再建立一個(gè)線程,,讓它去繼續(xù)執(zhí) 行操作,以便使得CtrlHandler函數(shù)能夠迅速的返回,。例如,,當(dāng)收到一個(gè) SERVICE_CONTROL_STOP請求的時(shí)候,就像上面說的一樣,,服務(wù)可能正在等待一個(gè) 網(wǎng)絡(luò)請求被完成或者數(shù)據(jù)被刷新到一個(gè)驅(qū)動(dòng)器上,,而這些操作所需要的時(shí)間是你 不能估計(jì)的,那么就要建立一個(gè)新的線程等待操作完成后執(zhí)行停止命令,, CtrlHandler函數(shù)在返回之前仍然要報(bào)告SERVICE_STOP_PENDING狀態(tài),,當(dāng)新的線 程執(zhí)行完操作之后,再由它將服務(wù)的狀態(tài)設(shè)置成SERVICE_STOPPED,。如果當(dāng)前操 作的時(shí)間可以估計(jì)的到就不要這樣做,,仍然使用前面交待的方法處理,。 CtrlHandler函數(shù)我就先講這些,下面說說服務(wù)怎么安裝,。一個(gè)服務(wù)程序可 以使用CreateService函數(shù)將服務(wù)的信息添加到SCM的數(shù)據(jù)庫,。 SC_HANDLE CreateService( SC_HANDLE hSCManager, // handle to SCM database LPCTSTR lpServiceName, // name of service to start LPCTSTR lpDisplayName, // display name DWORD dwDesiredAccess, // type of access to service DWORD dwServiceType, // type of service DWORD dwStartType, // when to start service DWORD dwErrorControl, // severity of service failure LPCTSTR lpBinaryPathName, // name of binary file LPCTSTR lpLoadOrderGroup, // name of load ordering group LPDWORD lpdwTagId, // tag identifier LPCTSTR lpDependencies, // array of dependency names LPCTSTR lpServiceStartName, // account name LPCTSTR lpPassword // account password ); hSCManager是一個(gè)標(biāo)示SCM數(shù)據(jù)庫的句柄,可以簡單的通過調(diào)用 OpenSCManager得到,。 SC_HANDLE OpenSCManager( LPCTSTR lpMachineName, // computer name LPCTSTR lpDatabaseName, // SCM database name DWORD dwDesiredAccess // access type ); lpMachineName是目標(biāo)機(jī)器的名字,,還記得我在第一章里說過可以在其它的 機(jī)器上面安裝服務(wù)嗎?這就是實(shí)現(xiàn)的方法,。對方機(jī)器名字必須以“//”開始,。如 果傳遞NULL或者一個(gè)空的字符串的話就默認(rèn)是本機(jī)。 lpDatabaseName是目標(biāo)機(jī)器上面SCM數(shù)據(jù)庫的名字,,但MSDN里面說這個(gè)參數(shù) 要默認(rèn)的設(shè)置成SERVICES_ACTIVE_DATABASE,,如果傳遞NULL,就默認(rèn)的打開 SERVICES_ACTIVE_DATABASE,。所以我還沒有真的搞明白這個(gè)參數(shù)的存在意義,總 之使用的時(shí)候傳遞NULL就行了,。 dwDesiredAccess是SCM數(shù)據(jù)庫的訪問權(quán)限,,具體值見下表: Object access Description to all of the access types listed in this table. function to create a service object and add it to the database. function to list the services that are in the database. acquire a lock on the database. QueryServiceLockStatus function to retrieve the lock status information for the database. 想要獲得訪問權(quán)限的話,似乎沒那么復(fù)雜,。MSDN里面說所有進(jìn)程都被允許獲 得對所有SCM數(shù)據(jù)庫的SC_MANAGER_CONNECT, SC_MANAGER_ENUMERATE_SERVICE, and SC_MANAGER_QUERY_LOCK_STATUS權(quán)限,,這些權(quán)限使得你可以連接SCM數(shù)據(jù)庫 ,枚舉目標(biāo)機(jī)器上安裝的服務(wù)和查詢目標(biāo)數(shù)據(jù)庫是否已被鎖住,。但如果要?jiǎng)?chuàng)建服 務(wù),,首先你需要擁有目標(biāo)機(jī)器的管理員權(quán)限,一般的傳遞 SC_MANAGER_ALL_ACCESS就可以了,。這個(gè)函數(shù)返回的句柄可以被 CloseServiceHandle函數(shù)關(guān)閉,。 lpServiceName是服務(wù)的名字,lpDisplayName是服務(wù)在“服務(wù)”管理工具里 顯示的名字,。 dwDesiredAccess也是訪問的權(quán)限,,有一個(gè)比上面的還長的多的一個(gè)表,各 位自己查MSDN吧,。我們要安裝服務(wù),,仍然簡單的傳遞SC_MANAGER_ALL_ACCESS。 dwServiceType是指你的服務(wù)是否和其它的進(jìn)程相關(guān)聯(lián),,一般是 SERVICE_WIN32_OWN_PROCESS,,表示不和任何進(jìn)程相關(guān)聯(lián)。如果你確認(rèn)你的服務(wù) 需要和某些進(jìn)程相關(guān)聯(lián),,就設(shè)置成SERVICE_WIN32_SHARE_PROCESS,。當(dāng)你的服務(wù) 要和桌面相關(guān)聯(lián)的時(shí)候,,需要設(shè)置成SERVICE_INTERACTIVE_PROCESS。 dwStartType是服務(wù)的啟動(dòng)方式,。服務(wù)有三種啟動(dòng)方式,,分別是“自動(dòng) (SERVICE_AUTO_START)”“手動(dòng)(SERVICE_DEMAND_START)”和“禁用 (SERVICE_DISABLED)”。在MSDN里還有另外的兩種方式,,不過是專為驅(qū)動(dòng)程序設(shè) 置的,。 dwErrorControl決定服務(wù)如果在系統(tǒng)啟動(dòng)的時(shí)候啟動(dòng)失敗的話要怎么辦。 值 意義 啟動(dòng) configuration啟動(dòng)的話,啟動(dòng)會(huì)繼續(xù),。否則會(huì)以last-known-good configuration重新啟動(dòng)計(jì)算機(jī),。 last-known-good configuration啟動(dòng)的話,,啟動(dòng)會(huì)失敗。否則會(huì)以last-known -good configuration重新啟動(dòng)計(jì)算機(jī),。好嚴(yán)重的錯(cuò)誤啊,。 lpBinaryPathName是服務(wù)程序的路徑。MSDN里面特別提到如果服務(wù)路徑里面 有空格的話一定要將路徑用引號引起來,。例如"d://my share//myservice.exe" 就一定要指定為"/"d://my share//myservice.exe/"",。 lpLoadOrderGroup的意義在于,如果有一組服務(wù)要按照一定的順序啟動(dòng)的話 ,,這個(gè)參數(shù)用于指定一個(gè)組名用于標(biāo)志這個(gè)啟動(dòng)順序組,,不過我還沒有用過這個(gè) 參數(shù)。你的服務(wù)如果不屬于任何啟動(dòng)順序組,,只要傳遞NULL或者一個(gè)空的字符串 就行了,。 lpdwTagId是應(yīng)用了上面的參數(shù)之后要指定的值,專用于驅(qū)動(dòng)程序,,與本文 內(nèi)容無關(guān),。傳遞NULL。 lpDependencies標(biāo)示一個(gè)字符串?dāng)?shù)組,,用于指明一串服務(wù)的名字或者一個(gè)啟 動(dòng)順序組,。當(dāng)與一個(gè)啟動(dòng)順序組建立關(guān)聯(lián)的時(shí)候,這個(gè)參數(shù)的含義就是只有你指 定的啟動(dòng)順序組里有至少一個(gè)經(jīng)過對整個(gè)組里所有的成員已經(jīng)全部嘗試過啟動(dòng)后 ,,有至少一個(gè)成員成功啟動(dòng),,你的服務(wù)才能啟動(dòng),。不需要建立依存關(guān)系的話,仍 是傳遞NULL或者一個(gè)空的字符串,。但如果你要指定啟動(dòng)順序組的話,,必須為組名 加上SC_GROUP_IDENTIFIER前綴,因?yàn)榻M名和服務(wù)名是共享一個(gè)命名空間的,。 lpServiceStartName是服務(wù)的啟動(dòng)賬號,,如果你設(shè)置你的服務(wù)的關(guān)聯(lián)類型是 SERVICE_WIN32_OWN_PROCESS的話,你需要以DomainName/UserName的格式指定用 戶名,,如果這個(gè)賬戶在你本機(jī)的話,,用./UserName就可以指定。如果傳遞NULL的 話,,會(huì)以本地的系統(tǒng)賬戶登陸,。如果是Win NT 4.0或更早的版本的話,如果你指 定了SERVICE_WIN32_SHARE_PROCESS,,就必須傳遞./System指定服務(wù)使用本地的 系統(tǒng)賬戶,。最后,如果你指定了SERVICE_INTERACTIVE_PROCESS,,你必須使服務(wù) 運(yùn)行在本機(jī)系統(tǒng)賬戶,。 看名字就知道了,lpPassword是賬戶的密碼,。如果指定系統(tǒng)賬戶的話,傳遞 NULL,。如果賬戶沒有密碼的話,,傳遞空字符串。 總之服務(wù)的基本原理就是這樣子了,,到了這里這篇文章似乎可以告一段落了 ,,但實(shí)際上還有很多內(nèi)容必須要討論,所以我還不能草草收筆,,敬請關(guān)注下一章 ,。 |
|