上一章其實(shí)只是概括性的介紹,下面開(kāi)始才是真正的細(xì)節(jié)所在。在進(jìn)入點(diǎn)函數(shù)里面要完成ServiceMain的初始化,準(zhǔn)確點(diǎn)說(shuō)是初始化一個(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[] =
{
{ "MyFTPd" , FtpdMain },
{ "MyHttpd", Httpserv},
{ NULL, NULL },
};
第一個(gè)成員代表服務(wù)的名字,第二個(gè)成員是ServiceMain回調(diào)函數(shù)的地址,上面的服務(wù)程序因?yàn)閾碛袃蓚€(gè)服務(wù),所以有三個(gè)SERVICE_TABLE_ENTRY元素,前兩個(gè)用于服務(wù),最后的NULL指明數(shù)組的結(jié)束。
接下來(lái)這個(gè)數(shù)組的地址被傳遞到StartServiceCtrlDispatcher函數(shù):
BOOL StartServiceCtrlDispatcher(
LPSERVICE_TABLE_ENTRY lpServiceStartTable
)
這個(gè)Win32函數(shù)表明可執(zhí)行文件的進(jìn)程怎樣通知SCM包含在這個(gè)進(jìn)程中的服務(wù)。就像上一章中講的那樣,StartServiceCtrlDispatcher為每一個(gè)傳遞到它的數(shù)組中的非空元素產(chǎn)生一個(gè)新的線程,每一個(gè)進(jìn)程開(kāi)始執(zhí)行由數(shù)組元素中的lpServiceStartTable指明的ServiceMain函數(shù)。
SCM啟動(dòng)一個(gè)服務(wù)程序之后,它會(huì)等待該程序的主線程去調(diào)StartServiceCtrlDispatcher。如果那個(gè)函數(shù)在兩分鐘內(nèi)沒(méi)有被調(diào)用,SCM將會(huì)認(rèn)為這個(gè)服務(wù)有問(wèn)題,并調(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ù)的。還記得以前講過(guò)的ServiceMain函數(shù)的的原型嗎?但實(shí)際上一個(gè)ServiceMain函數(shù)通常忽略傳遞給它的兩個(gè)參數(shù),因?yàn)榉?wù)一般不怎么傳遞參數(shù)。設(shè)置一個(gè)服務(wù)最好的方法就是設(shè)置注冊(cè)表,一般服務(wù)在
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Service\ServiceName\Parameters
子鍵下存放自己的設(shè)置,這里的ServiceName是服務(wù)的名字。事實(shí)上,可能要寫(xiě)一個(gè)客戶應(yīng)用程序去進(jìn)行服務(wù)的背景設(shè)置,這個(gè)客戶應(yīng)用程序?qū)⑦@些信息存在注冊(cè)表中,以便服務(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è)新的線程。接下來(lái),一個(gè)ServiceMain要做些什么呢?MSDN里面的原文是這樣說(shuō)的: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ù)請(qǐng)求之后,如果在一定時(shí)間之內(nèi)無(wú)法完成服務(wù)的初始化,SCM會(huì)認(rèn)為服務(wù)的啟動(dòng)已經(jīng)失敗了,這個(gè)時(shí)間的長(zhǎng)度在Win NT 4.0中是80秒,Win2000中不詳...
基于上面的理由,ServiceMain要迅速完成自身工作,首先是必不可少的兩項(xiàng)工作,第一項(xiàng)是調(diào)用RegisterServiceCtrlHandler函數(shù)去通知SCM它的CtrlHandler回調(diào)函數(shù)的地址:
SERVICE_STATUS_HANDLE RegisterServiceCtrlHandler(
LPCTSTR lpServiceName, //服務(wù)的名字
LPHANDLER_FUNCTION lpHandlerProc //CtrlHandler函數(shù)地址
)
第一個(gè)參數(shù)指明你正在建立的CtrlHandler是為哪一個(gè)服務(wù)所用,第二個(gè)參數(shù)是CtrlHandler函數(shù)的地址。lpServiceName必須和在SERVICE_TABLE_ENTRY里面被初始化的服務(wù)的名字相匹配。RegisterServiceCtrlHandler返回一個(gè)SERVICE_STATUS_HANDLE,這是一個(gè)32位的句柄。SCM用它來(lái)唯一確定這個(gè)服務(wù)。當(dāng)這個(gè)服務(wù)需要把它當(dāng)時(shí)的狀態(tài)報(bào)告給SCM的時(shí)候,就必須把這個(gè)句柄傳給需要它的Win32函數(shù)。注意:這個(gè)句柄和其他大多數(shù)的句柄不同,你無(wú)需關(guān)閉它。
SCM要求ServiceMain函數(shù)的線程在一秒鐘內(nèi)調(diào)用RegisterServiceCtrlHandler函數(shù),否則SCM會(huì)認(rèn)為服務(wù)已經(jīng)失敗。但在這種情況下,SCM不會(huì)終止服務(wù),不過(guò)在NT 4中將無(wú)法啟動(dòng)這個(gè)服務(wù),同時(shí)會(huì)返回一個(gè)不正確的錯(cuò)誤信息,這一點(diǎn)在Windows 2000中得到了修正。
在RegisterServiceCtrlHandler函數(shù)返回后,ServiceMain線程要立即告訴SCM服務(wù)正在繼續(xù)初始化。具體的方法是通過(guò)調(diào)用SetServiceStatus函數(shù)傳遞SERVICE_STATUS數(shù)據(jù)結(jié)構(gòu)。
BOOL SetServiceStatus(
SERVICE_STATUS_HANDLE hService, //服務(wù)的句柄
SERVICE_STATUS lpServiceStatus //SERVICE_STATUS結(jié)構(gòu)的地址
)
這個(gè)函數(shù)要求傳遞給它指明服務(wù)的句柄(剛剛通過(guò)調(diào)用RegisterServiceCtrlHandler得到),和一個(gè)初始化的SERVICE_STATUS結(jié)構(gòu)的地址:
typedef struct _SERVICE_STATUS
{
DWORD dwServiceType;
DWORD dwCurrentState;
DWORD dwControlsAccepted;
DWORD dwWin32ExitCode;
DWORD dwServiceSpecificExitCode;
DWORD dwCheckPoint;
DWORD dwWaitHint;
} SERVICE_STATUS, *LPSERVICE_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)絕對(duì)不應(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)告它本身特有的、沒(méi)有映射到一個(gè)預(yù)定義的Win32錯(cuò)誤代碼中的錯(cuò)誤。為了這一點(diǎn),要把dwWin32ExitCode設(shè)置為ERROR_SERVICE_SPECIFIC_ERROR,然后還要設(shè)置成員dwServiceSpecificExitCode為服務(wù)特有的錯(cuò)誤代碼。當(dāng)服務(wù)運(yùn)行正常,沒(méi)有錯(cuò)誤可以報(bào)告的時(shí)候,就設(shè)置成員dwWin32ExitCode為NO_ERROR。
最后的兩個(gè)成員dwCheckPoint和dwWaitHint是一個(gè)服務(wù)用來(lái)報(bào)告它當(dāng)前的事件進(jìn)展情況的。當(dāng)成員dwCurrentState被設(shè)置成SERVICE_START_PENDING的時(shí)候,應(yīng)該把dwCheckPoint設(shè)成0,dwWaitHint設(shè)成一個(gè)經(jīng)過(guò)多次嘗試后確定比較合適的數(shù),這樣服務(wù)才能高效運(yùn)行。一旦服務(wù)被完全初始化,就應(yīng)該重新初始化SERVICE_STATUS結(jié)構(gòu)的成員,更改dwCurrentState為SERVICE_RUNNING,然后把dwCheckPoint和dwWaitHint都改為0。
dwCheckPoint成員的存在對(duì)用戶是有益的,它允許一個(gè)服務(wù)報(bào)告它處于進(jìn)程的哪一步。每一次調(diào)用SetServiceStatus時(shí),可以增加它到一個(gè)能指明服務(wù)已經(jīng)執(zhí)行到哪一步的數(shù)字,它可以幫助用戶決定多長(zhǎng)時(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)開(kāi)始運(yùn)行。通常一個(gè)服務(wù)是把自己放在一個(gè)循環(huán)之中來(lái)運(yùn)行的。在循環(huán)的內(nèi)部這個(gè)服務(wù)進(jìn)程懸掛自己,等待指明它下一步是應(yīng)該暫停、繼續(xù)或停止之類的網(wǎng)絡(luò)請(qǐng)求或通知。當(dāng)一個(gè)請(qǐng)求到達(dá)的時(shí)候,服務(wù)線程激活并處理這個(gè)請(qǐng)求,然后再循環(huán)回去等待下一個(gè)請(qǐng)求/通知。
如果一個(gè)服務(wù)由于一個(gè)通知而激活,它會(huì)先處理這個(gè)通知,除非這個(gè)服務(wù)得到的是停止或關(guān)閉的通知。如果真的是停止或關(guān)閉的通知,服務(wù)線程將退出循環(huán),執(zhí)行必要的清除操作,然后從這個(gè)線程返回。當(dāng)ServiceMain線程返回并中止時(shí),引起在StartServiceCtrlDispatcher內(nèi)睡眠的線程激活,并像在前面解釋過(guò)的那樣,減少它運(yùn)行的服務(wù)的計(jì)數(shù)。