用匯編寫系統(tǒng)服務(wù)程序本想寫一篇關(guān)于服務(wù)的文章,結(jié)果搜了一遍以后,發(fā)現(xiàn)發(fā)表于天極網(wǎng)上的一篇文章《Win32程序設(shè)計(jì)之服務(wù)》把要說(shuō)的大部分東西都說(shuō)了,為了不做重復(fù)勞動(dòng),所以在這里首先引用這篇文章來(lái)說(shuō)明服務(wù)程序的原理,在后面的補(bǔ)充一節(jié)中再補(bǔ)上一些內(nèi)容和一個(gè)用匯編實(shí)現(xiàn)服務(wù)的源代碼。
====================================================================== 第一部分:轉(zhuǎn)載《Win32程序設(shè)計(jì)之服務(wù)》
發(fā)表于:yesky 編譯:QQ新人類 原始位置:http://www.chinabyte.com/20010528/181751.shtml ======================================================================
每個(gè)操作系統(tǒng)都需要有在后臺(tái)執(zhí)行任務(wù)的方法,無(wú)論是誰(shuí)正在使用這部機(jī)器,這些任務(wù)都可以繼續(xù)運(yùn)行,后臺(tái)任務(wù)可以處理各種重要的服務(wù),包括系統(tǒng)的或者用戶的。例如,一個(gè)信使服務(wù)可以監(jiān)控網(wǎng)絡(luò),并且在接收到另一臺(tái)機(jī)子的信息時(shí),可以顯示一個(gè)對(duì)話框。一個(gè)發(fā)送和接收傳真的應(yīng)用需要在啟動(dòng)的時(shí)候運(yùn)行,并且不斷地監(jiān)控負(fù)責(zé)傳真的modem,看有沒(méi)有傳真進(jìn)來(lái)。一個(gè)家庭的或者辦公室的安全程序,用來(lái)控制一件檢測(cè)設(shè)備時(shí),它需要不時(shí)地查詢傳感器,并且在適當(dāng)?shù)臅r(shí)候響應(yīng)它。所有這些任務(wù)都需要CPU時(shí)間來(lái)執(zhí)行它們,不過(guò)由于它們需要的CPU時(shí)間很少,因此可以放在后臺(tái)而不影響用戶使用系統(tǒng)。
在MS-DOS中,后臺(tái)的任務(wù)是通過(guò)TSR(Terminate and Stay Resident)程序來(lái)處理的。這些程序經(jīng)由autoexec.bat文件開(kāi)始。在UNIX中,后臺(tái)任務(wù)是通過(guò)Daemons來(lái)處理的。在每次啟動(dòng)UNIX的過(guò)程中,你都可以看到操作系統(tǒng)啟動(dòng)一些任務(wù),例如定時(shí)的程序(Cron)和Finger的daemons,然后才可以讓首個(gè)用戶登錄。在Windows NT中,后臺(tái)的任務(wù)被稱為服務(wù)。服務(wù)可在每次NT啟動(dòng)的時(shí)候運(yùn)行,并且不管是誰(shuí)登陸,都會(huì)一直運(yùn)行下去。
Windows NT的服務(wù)都是通過(guò)一般的可執(zhí)行程序?qū)崿F(xiàn)的,不同的是,它遵循內(nèi)部的一個(gè)特定協(xié)議來(lái)設(shè)計(jì),以便它們能夠與服務(wù)控制管理器(SCM,Service Control Manager)進(jìn)行正確的交互。在這篇文章中,你將學(xué)習(xí)到如何在Windows NT中創(chuàng)建和安裝簡(jiǎn)單的Win32服務(wù)。一旦你懂得了這個(gè)簡(jiǎn)單的服務(wù),你要建立自己的服務(wù)也不難了,因?yàn)樗械姆⻊?wù),不論是如何地復(fù)雜,都必須包含有同樣基本的SCM接口代碼。只要符合SCM的要求,其實(shí)為服務(wù)設(shè)計(jì)的可執(zhí)行文件和一般的程序并沒(méi)有多少的區(qū)別。
無(wú)論是對(duì)于編程者或者系統(tǒng)管理員,了解NT的服務(wù)如何工作都是很重要的。編程者就不必說(shuō)了,因?yàn)樗麄円獎(jiǎng)?chuàng)建自己的服務(wù),而對(duì)于系統(tǒng)管理員,也是同樣重要的。因?yàn)楹笈_(tái)的任務(wù)可以是很危險(xiǎn)的。MS-DOS和Macintosh系統(tǒng)都是一個(gè)病毒的溫床,因?yàn)樗鼈冊(cè)诎踩苑矫嫦忍觳蛔悖鼈兌伎梢栽试S任何人或者程序在任何時(shí)間創(chuàng)建后臺(tái)的任務(wù)。Windows NT和UNIX系統(tǒng)是較安全的,因?yàn)橹挥邢到y(tǒng)管理員才可以為系統(tǒng)增加后臺(tái)的任務(wù),不過(guò),如果系統(tǒng)管理員加入了一個(gè)破壞性的后臺(tái)程序,就它就可以為所欲為了。因此系統(tǒng)管理員要了解Windows NT服務(wù)的技巧和權(quán)限設(shè)置,就可以避免加入有潛在危險(xiǎn)的后臺(tái)任務(wù)。
基本的概念
服務(wù)有兩種不同的形式。驅(qū)動(dòng)器服務(wù)使用驅(qū)動(dòng)器協(xié)議,讓NT可以與特定的硬件進(jìn)行通信。另一個(gè)是Win32服務(wù),通過(guò)一般的Win32 API來(lái)實(shí)現(xiàn)后臺(tái)任務(wù)。這篇文章的重點(diǎn)是談Win32服務(wù),因?yàn)樗鼈兏鼮槌R?jiàn),而且創(chuàng)建起來(lái)也很容易。任何的NT編程者通過(guò)使用一般的NT SDK(或者Visual C++),并且可以用管理員的身份訪問(wèn)一臺(tái)NT機(jī)器,都可以實(shí)現(xiàn)和安裝自己的Win32服務(wù)。如果你想創(chuàng)建一些在Windows NT啟動(dòng)時(shí)就運(yùn)行的程序,并且要求它會(huì)在系統(tǒng)中一直運(yùn)行,你就要使用Win32的服務(wù)。
在NT中,服務(wù)通過(guò)控制面板進(jìn)行管理。在控制面板中,你會(huì)發(fā)現(xiàn)有一個(gè)服務(wù)的圖標(biāo),打開(kāi)它你會(huì)看到所有Win32服務(wù)的清單。在那里你可以開(kāi)始、停止、暫停和繼續(xù)某個(gè)服務(wù)。你按下其中的啟動(dòng)按鈕后,就會(huì)出現(xiàn)一個(gè)對(duì)話框,你可以修改啟動(dòng)操作以及服務(wù)使用的默認(rèn)帳號(hào)。一個(gè)服務(wù)可以在系統(tǒng)啟動(dòng)的時(shí)候自動(dòng)運(yùn)行,也可以被完全禁止。或者設(shè)置為手動(dòng)執(zhí)行。在手動(dòng)的時(shí)候,用戶還可以設(shè)置啟動(dòng)的參數(shù)。要對(duì)服務(wù)中的項(xiàng)目作修改的話,你需要以一個(gè)管理員或者超級(jí)用戶的身份登錄。
Windows NT自帶有一些預(yù)裝的任務(wù),用來(lái)處理諸如網(wǎng)絡(luò)信使服務(wù)的操作或者使用“at”命令定時(shí)執(zhí)行的操作,以及分布的RPC命名。在你創(chuàng)建自己的服務(wù)時(shí),你必須執(zhí)行一個(gè)獨(dú)立的安裝步驟,以將服務(wù)的信息插入到服務(wù)管理工具的列表中,這些信息包括有新服務(wù)的名字、執(zhí)行文件的名字和啟動(dòng)的類型等,都會(huì)寫入到注冊(cè)表中,這樣在機(jī)器下次啟動(dòng)的時(shí)候,SCM就會(huì)得到新服務(wù)的相關(guān)信息。
創(chuàng)建一個(gè)新的服務(wù)
執(zhí)行服務(wù)的程序也是一個(gè)EXE文件,不過(guò)它必須符合某些特定的規(guī)范,以便可以與SCM進(jìn)行正確的交互。微軟很細(xì)致地設(shè)計(jì)了函數(shù)調(diào)用的流程,你必須遵循這些流程,否則你的服務(wù)就不能工作。具體的規(guī)定如下所列。你可以在Win32編程者的參考指南中找到以下涉及的函數(shù),這些資料在SDK的Win32在線幫助或者Visual C++都有:
.服務(wù)的代碼必須要有一個(gè)一般的main或者WinMain函數(shù)。這個(gè)函數(shù)應(yīng)該會(huì)馬上調(diào)用StartServiceCrtlDispatcher函數(shù)。通過(guò)調(diào)用這個(gè)函數(shù),你可以讓SCM得到ServiceMain函數(shù)的指針,這樣在SCM要啟動(dòng)該服務(wù)時(shí),就可以調(diào)用它
.在SCM要啟動(dòng)服務(wù)的時(shí)候,就會(huì)調(diào)用ServiceMain函數(shù)。例如,如果管理員在服務(wù)管理器中按下啟動(dòng)的按鈕,SCM就會(huì)在一個(gè)獨(dú)立的線程中執(zhí)行ServiceMain函數(shù)。ServiceMain應(yīng)該調(diào)用RegisterServiceCtrlHandler函數(shù),這樣可以注冊(cè)一個(gè)Handler函數(shù),以便SCM對(duì)服務(wù)進(jìn)行控制。Handler函數(shù)的名字可以是任意的,不過(guò)它會(huì)在Handler下的文檔中列出來(lái)。RegisterServiceCtrlHandler函數(shù)會(huì)返回一個(gè)句柄,在服務(wù)需要發(fā)送狀態(tài)信息給SCM時(shí),可以通過(guò)該句柄進(jìn)行。
.ServiceMain函數(shù)也必須啟動(dòng)做該服務(wù)實(shí)際工作的線程。在服務(wù)停止前,ServiceMain函數(shù)是不應(yīng)該有返回的。當(dāng)它返回的時(shí)候,服務(wù)已經(jīng)停止了。
.Handler函數(shù)包含了一個(gè)switch語(yǔ)句,用來(lái)分析由SCM傳送過(guò)來(lái)的請(qǐng)求。默認(rèn)的情況,SCM可以發(fā)送以下任何的的控制常數(shù):
SERVICE_CONTROL_STOP - 要服務(wù)停止 SERVICE_CONTROL_PAUSE - 要服務(wù)暫停 SERVICE_CONTROL_CONTINUE - 要服務(wù)繼續(xù) SERVICE_CONTROL_INTERROGATE - 要服務(wù)馬上報(bào)告它的狀態(tài) SERVICE_CONTROL_SHUTDOWN - 告訴服務(wù)即將關(guān)機(jī)
也可以創(chuàng)建自定義的常數(shù)(值在128到255之間),并且通過(guò)SCM發(fā)送給服務(wù)。
如果你創(chuàng)建的EXE包括有以上提到的main、ServiceMain和Handler函數(shù),以及執(zhí)行服務(wù)自身任務(wù)的線程函數(shù),那么你的服務(wù)程序設(shè)計(jì)就完成了。以下的圖總結(jié)了這些不同的函數(shù)和SCM之間的交互:
在本文的最后還有幾段程序的列表,其中列表一為我們展示了一個(gè)可能是最簡(jiǎn)單的服務(wù)。該服務(wù)只發(fā)出"嘟"的響聲。默認(rèn)的狀態(tài)下,它每?jī)擅腠懸淮巍D憧梢酝ㄟ^(guò)啟動(dòng)的參數(shù)來(lái)修改發(fā)聲的間隔。這個(gè)服務(wù)挺完整,它可以正確響應(yīng)SCM傳來(lái)的每個(gè)控制信號(hào)。因此,這個(gè)程序可作為你創(chuàng)建自己服務(wù)的一個(gè)很好的模板。
main函數(shù)通過(guò)調(diào)用StartServiceCtrlDispatcher來(lái)注冊(cè)ServiceMain函數(shù)。注冊(cè)的操作使用了一個(gè)SERVICE_TABLE_ENTRY結(jié)構(gòu)的數(shù)組。在這個(gè)例子中,該程序只包含了一個(gè)服務(wù),因此在表中只會(huì)有一個(gè)項(xiàng)目。不過(guò),對(duì)于一個(gè)EXE文件,可以創(chuàng)建幾個(gè)任務(wù),這樣在表中就會(huì)有幾項(xiàng),以識(shí)別不同的ServiceMain函數(shù)。在調(diào)用StartServiceCtrlDispatcher之前,可在main函數(shù)中放入初始化的代碼,不過(guò)這些代碼必須在少于30秒內(nèi)完成,否則,SCM會(huì)認(rèn)為某些地方出錯(cuò)而終止服務(wù)。
在自動(dòng)或者手動(dòng)啟動(dòng)服務(wù)時(shí),將會(huì)調(diào)用ServiceMain函數(shù)。ServiceMain函數(shù)將包含有以下的步驟:
1.它馬上調(diào)用RegisterServiceCtrlHandler來(lái)注冊(cè)Handler函數(shù),作為SCM控制該服務(wù)的Handler函數(shù)
2.然后它將調(diào)用SendStatusToSCM函數(shù),將當(dāng)前的進(jìn)程通報(bào)給SCM。第四個(gè)參數(shù)是一個(gè)“click count”值,在程序每次更新?tīng)顟B(tài)時(shí),它的值就會(huì)增加。SCM和其它的程序可以根據(jù)click count的值來(lái)知道初始化期間進(jìn)行的處理。最后的參數(shù)是"wait hint",是用來(lái)告訴SCM在click count下次更新前,它需要等待的時(shí)間(以毫秒計(jì)算)。
3.ServiceMain接著會(huì)創(chuàng)建一個(gè)事情,該事件在函數(shù)的底部使用,可讓它一直運(yùn)行,直到SCM發(fā)出一個(gè)STOP的請(qǐng)求。
4.接著,ServiceMain檢查啟動(dòng)的參數(shù)。參數(shù)可在用戶手動(dòng)啟動(dòng)服務(wù)時(shí),通過(guò)服務(wù)管理工具中的啟動(dòng)參數(shù)傳送過(guò)來(lái)。這些參數(shù)以一個(gè)argv形式的數(shù)組進(jìn)入ServiceMain函數(shù)。
5.如果你的服務(wù)需要處理其它初始化的任務(wù),它們應(yīng)該放在這一步,在調(diào)用InitService之前。
6.ServiceMain函數(shù)接著調(diào)用InitService函數(shù),這將啟動(dòng)線程并做服務(wù)的真正工作。如果該調(diào)用成功,SverviceMain將會(huì)通知SCM服務(wù)已經(jīng)成功啟動(dòng)。
7.ServiceMain將調(diào)用WaitForSingleObject,用來(lái)等待terminateEvent事件對(duì)象被設(shè)置。這個(gè)對(duì)象通過(guò)Handler函數(shù)設(shè)置,一旦它被設(shè)置,ServiceMain將調(diào)用終止的函數(shù)來(lái)做清除的工作,然后就返回并停止服務(wù)。
你從上面可以看到,在這個(gè)函數(shù)中并沒(méi)有多少靈活的地方。除了第5步外,你必須按步執(zhí)行以上提到的任務(wù),否則服務(wù)將不可以正確啟動(dòng)。
終止函數(shù)清除所有打開(kāi)的句柄,并且發(fā)送一個(gè)狀態(tài)的信息給SCM,告訴它服務(wù)現(xiàn)已停止。
在SCM要暫停、繼續(xù)、詢問(wèn)或者停止服務(wù)時(shí),它就會(huì)調(diào)用Handler函數(shù)。要停止服務(wù),Handler設(shè)置terminateEvent,這樣做會(huì)導(dǎo)致ServiceMain(它會(huì)以一個(gè)獨(dú)立線程的形式執(zhí)行)終止并且返回。一旦ServiceMain返回,服務(wù)就停止了。
SendStatusToSCM 函數(shù)負(fù)責(zé)發(fā)送服務(wù)的當(dāng)前狀態(tài)給SCM。
在需要啟動(dòng)服務(wù)線程時(shí),ServiceMain就會(huì)調(diào)用InitService函數(shù)。該函數(shù)調(diào)用CreateThread來(lái)為服務(wù)創(chuàng)建一個(gè)新的線程。
ServiceThread函數(shù)包含有該服務(wù)真正要做的工作。在這個(gè)例子中,該線程包含有一個(gè)無(wú)限的循環(huán),以一個(gè)預(yù)定義的時(shí)間間隔發(fā)出響聲。在你創(chuàng)建自己的服務(wù)時(shí),你可以在該線程中放入任何的代碼,調(diào)用Win32函數(shù)或者你自己的函數(shù)。
安裝和移除服務(wù)
為了使用上面提到的發(fā)響聲服務(wù),你必須安裝它。安裝可讓SCM知道這個(gè)服務(wù),并且讓SCM將它加入到控制面板中的服務(wù)列表中。表單二的代碼展示了如何安裝一個(gè)服務(wù)。
表單2先通過(guò)OpenSCManager函數(shù)打開(kāi)一個(gè)到SCM的連接。在OpenSCManager的調(diào)用中,你必須指定你要做的東西,以便SCM可以驗(yàn)證這個(gè)行為。如果你登錄的帳號(hào)沒(méi)有足夠的權(quán)限,該調(diào)用將返回NULL。
CreateService的調(diào)用是真正用來(lái)裝入新服務(wù)的。它使用OpenSCManager返回至SCM的指針、名字、標(biāo)簽和命令行中的EXE文件,還有一些標(biāo)準(zhǔn)的參數(shù)值。使用SERVICE_WIN32_OWN_PROCESS表明該服務(wù)的EXE文件只包含有一個(gè)服務(wù),而SERVICE_DEMAND_START則表明該服務(wù)是手動(dòng)而不是自動(dòng)啟動(dòng)的。使用命令行安裝程序的一個(gè)典型格式如下:
install BeepService "Beeper" c:\winnt\beep.exe
第一個(gè)參數(shù)是SCM內(nèi)部使用的服務(wù)名字。這個(gè)名字也用在以后移除服務(wù)。第二個(gè)參數(shù)是一個(gè)標(biāo)識(shí)符,即服務(wù)管理中顯示的名字。第三個(gè)參數(shù)指出該服務(wù)的執(zhí)行文件的路徑。在你安裝服務(wù)后,可在控制面板的服務(wù)管理中啟動(dòng)它。如果有錯(cuò),可在Win32 API的在線文檔中查出錯(cuò)誤代碼的含義。
要移除服務(wù),你要按列表3的步驟進(jìn)行。它首先打開(kāi)一個(gè)到SCM的連接,然后使用OpenService函數(shù)打開(kāi)一個(gè)與服務(wù)的連接。列表3中接著查詢服務(wù)來(lái)看它是否現(xiàn)已停止了。如果不是的話,就會(huì)停止它。DeleteService 用來(lái)從控制面板中移除服務(wù),移除操作的典型格式如下:
remove BeepService
需要的話,你也可以在馬上重新安裝服務(wù)。
結(jié)論
服務(wù)對(duì)于Windows NT是很重要的一部分,因?yàn)樗勺屇銓?duì)操作系統(tǒng)進(jìn)行擴(kuò)展。使用列表1的代碼作為一個(gè)模板,你將會(huì)發(fā)現(xiàn)要建立一個(gè)自己的新服務(wù)是非常簡(jiǎn)單的。
列表1
實(shí)現(xiàn)可能是最簡(jiǎn)單NT服務(wù)的代碼
//*************************************************************** // From the book "Win32 System Services: The Heart of Windows NT" // by Marshall Brain // Published by Prentice Hall file:// // This code implements the simplest possible service. // It beeps every 2 seconds, or at a user specified interval. file://***************************************************************
// beepserv.cpp
#include #include #include #include
#define DEFAULT_BEEP_DELAY 2000
// Global variables
// The name of the service char *SERVICE_NAME = "BeepService"; // Event used to hold ServiceMain from completing HANDLE terminateEvent = NULL; // Handle used to communicate status info with // the SCM. Created by RegisterServiceCtrlHandler SERVICE_STATUS_HANDLE serviceStatusHandle; // The beep interval in ms. int beepDelay = DEFAULT_BEEP_DELAY; // Flags holding current state of service BOOL pauseService = FALSE; BOOL runningService = FALSE; // Thread for the actual work HANDLE threadHandle = 0;
void ErrorHandler(char *s, DWORD err) { cout << s << endl; cout << "Error number: " << err << endl; ExitProcess(err); }
// This function consolidates the activities of // updating the service status with // SetServiceStatus BOOL SendStatusToSCM (DWORD dwCurrentState, DWORD dwWin32ExitCode, DWORD dwServiceSpecificExitCode, DWORD dwCheckPoint, DWORD dwWaitHint) { BOOL success; SERVICE_STATUS serviceStatus;
// Fill in all of the SERVICE_STATUS fields serviceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS; serviceStatus.dwCurrentState = dwCurrentState;
// If in the process of doing something, then accept // no control events, else accept anything if (dwCurrentState == SERVICE_START_PENDING) serviceStatus.dwControlsAccepted = 0; else serviceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE | SERVICE_ACCEPT_SHUTDOWN;
// if a specific exit code is defined, set up // the win32 exit code properly if (dwServiceSpecificExitCode == 0) serviceStatus.dwWin32ExitCode = dwWin32ExitCode; else serviceStatus.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR; serviceStatus.dwServiceSpecificExitCode =dwServiceSpecificExitCode; serviceStatus.dwCheckPoint = dwCheckPoint; serviceStatus.dwWaitHint = dwWaitHint;
// Pass the status record to the SCM success = SetServiceStatus (serviceStatusHandle, &serviceStatus); return success; }
DWORD ServiceThread(LPDWORD param) { while (1) { Beep(200,200); Sleep(beepDelay); } return 0; }
// Initializes the service by starting its thread BOOL InitService() { DWORD id;
// Start the service's thread threadHandle = CreateThread(0, 0,(LPTHREAD_START_ROUTINE) ServiceThread,0, 0, &id);
if (threadHandle==0) return FALSE; else { runningService = TRUE; return TRUE; } }
// Dispatches events received from the SCM VOID Handler (DWORD controlCode) { DWORD currentState = 0; BOOL success;
switch(controlCode) { // There is no START option because // ServiceMain gets called on a start
// Stop the service case SERVICE_CONTROL_STOP: // Tell the SCM what's happening success = SendStatusToSCM(SERVICE_STOP_PENDING,NO_ERROR, 0, 1, 5000); runningService=FALSE; // Set the event that is holding ServiceMain // so that ServiceMain can return SetEvent(terminateEvent); return; // Pause the service case SERVICE_CONTROL_PAUSE: if (runningService && !pauseService) { // Tell the SCM what's happening success = SendStatusToSCM(SERVICE_PAUSE_PENDING, NO_ERROR, 0, 1, 1000); pauseService = TRUE; SuspendThread(threadHandle); currentState = SERVICE_PAUSED; } break; // Resume from a pause case SERVICE_CONTROL_CONTINUE: if (runningService && pauseService) { // Tell the SCM what's happening success = SendStatusToSCM(SERVICE_CONTINUE_PENDING, NO_ERROR, 0, 1, 1000); pauseService=FALSE; ResumeThread(threadHandle); currentState = SERVICE_RUNNING; } break; // Update current status case SERVICE_CONTROL_INTERROGATE: // it will fall to bottom and send status break; // Do nothing in a shutdown. Could do cleanup // here but it must be very quick. case SERVICE_CONTROL_SHUTDOWN: return; default: break; } SendStatusToSCM(currentState, NO_ERROR, 0, 0, 0); }
// Handle an error from ServiceMain by cleaning up // and telling SCM that the service didn't start. VOID terminate(DWORD error) { // if terminateEvent has been created, close it. if (terminateEvent) CloseHandle(terminateEvent);
// Send a message to the scm to tell about stopage if (serviceStatusHandle) SendStatusToSCM(SERVICE_STOPPED, error,0, 0, 0);
// If the thread has started, kill it off if (threadHandle) CloseHandle(threadHandle);
// Do not need to close serviceStatusHandle }
// ServiceMain is called when the SCM wants to // start the service. When it returns, the service // has stopped. It therefore waits on an event // just before the end of the function, and // that event gets set when it is time to stop. // It also returns on any error because the // service cannot start if there is an eror. VOID ServiceMain(DWORD argc, LPTSTR *argv) { BOOL success;
// immediately call Registration function serviceStatusHandle = RegisterServiceCtrlHandler( SERVICE_NAME, (LPHANDLER_FUNCTION)Handler); if (!serviceStatusHandle) {terminate(GetLastError()); return;}
// Notify SCM of progress success = SendStatusToSCM(SERVICE_START_PENDING, NO_ERROR, 0, 1, 5000); if (!success) {terminate(GetLastError()); return;}
// create the termination event terminateEvent = CreateEvent (0, TRUE, FALSE, 0); if (!terminateEvent) {terminate(GetLastError()); return;}
// Notify SCM of progress success = SendStatusToSCM(SERVICE_START_PENDING, NO_ERROR, 0, 2, 1000); if (!success) {terminate(GetLastError()); return;}
// Check for startup params if (argc == 2) { int temp = atoi(argv[1]); if (temp < 1000) beepDelay = DEFAULT_BEEP_DELAY; else beepDelay = temp; }
// Notify SCM of progress success = SendStatusToSCM(SERVICE_START_PENDING, NO_ERROR, 0, 3, 5000); if (!success) {terminate(GetLastError()); return;}
// Start the service itself success = InitService(); if (!success) {terminate(GetLastError()); return;}
// The service is now running. // Notify SCM of progress success = SendStatusToSCM(SERVICE_RUNNING, NO_ERROR, 0, 0, 0); if (!success) {terminate(GetLastError()); return;}
// Wait for stop signal, and then terminate WaitForSingleObject (terminateEvent, INFINITE);
terminate(0); }
VOID main(VOID) { SERVICE_TABLE_ENTRY serviceTable[] = { { SERVICE_NAME, (LPSERVICE_MAIN_FUNCTION) ServiceMain}, { NULL, NULL } }; BOOL success;
// Register with the SCM success = StartServiceCtrlDispatcher(serviceTable); if (!success) ErrorHandler("In StartServiceCtrlDispatcher", GetLastError()); }
列表2 安裝NT服務(wù)的代碼
file://*************************************************************** // From the book "Win32 System Services: The Heart of Windows NT" // by Marshall Brain // Published by Prentice Hall file:// // This code installs a service. file://***************************************************************
// install.cpp
#include #include
void ErrorHandler(char *s, DWORD err) { cout << s << endl; cout << "Error number: " << err << endl; ExitProcess(err); }
void main(int argc, char *argv[]) { SC_HANDLE newService, scm;
if (argc != 4) { cout << "Usage:\n"; cout << " install service_name \ service_label executable\n"; cout << " service_name is the \ name used internally by the SCM\n"; cout << " service_label is the \ name that appears in the Services applet\n"; cout << " (for multiple \ words, put them in double quotes)\n"; cout << " executable is the \ full path to the EXE\n\n"; return; }
// open a connection to the SCM scm = OpenSCManager(0, 0, SC_MANAGER_CREATE_SERVICE); if (!scm) ErrorHandler("In OpenScManager", GetLastError());
// Install the new service newService = CreateService( scm, argv[1], // eg "beep_srv" argv[2], // eg "Beep Service" SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL, argv[3], // eg "c:\winnt\xxx.exe" 0, 0, 0, 0, 0); if (!newService) ErrorHandler("In CreateService", GetLastError()); else cout << "Service installed\n";
// clean up CloseServiceHandle(newService); CloseServiceHandle(scm); }
列表3 移除NT服務(wù)的代碼 file://*************************************************************** // From the book "Win32 System Services: The Heart of Windows NT" // by Marshall Brain // Published by Prentice Hall file:// // This code removes a service from the Services applet in the // Control Panel. file://***************************************************************
// remove.cpp
#include #include
void ErrorHandler(char *s, DWORD err) { cout << s << endl; cout << "Error number: " << err << endl; ExitProcess(err); }
void main(int argc, char *argv[]) { SC_HANDLE service, scm; BOOL success; SERVICE_STATUS status;
if (argc != 2) { cout << "Usage:\n remove service_name\n"; return; }
// Open a connection to the SCM scm = OpenSCManager(0, 0, SC_MANAGER_CREATE_SERVICE); if (!scm) ErrorHandler("In OpenScManager", GetLastError());
// Get the service's handle service = OpenService(scm, argv[1], SERVICE_ALL_ACCESS | DELETE); if (!service) ErrorHandler("In OpenService", GetLastError());
// Stop the service if necessary success = QueryServiceStatus(service, &status); if (!success) ErrorHandler("In QueryServiceStatus", GetLastError()); if (status.dwCurrentState != SERVICE_STOPPED) { cout << "Stopping service...\n"; success = ControlService(service, SERVICE_CONTROL_STOP, &status); if (!success) ErrorHandler("In ControlService", GetLastError()); }
// Remove the service success = DeleteService(service); if (success) cout << "Service removed\n"; else ErrorHandler("In DeleteService", GetLastError());
// Clean up CloseServiceHandle(service); CloseServiceHandle(scm); }
====================================================================== 第二部分:用Win32匯編實(shí)現(xiàn)系統(tǒng)服務(wù) by:羅云彬
好了,經(jīng)過(guò)了上面這篇文章,大家對(duì)服務(wù)應(yīng)該有個(gè)初步的了解了,但是要注意分辨文章中提及的 API 名稱和程序自己的模塊名稱,比如CreateService是一個(gè) API,而 ServiceMain 是程序中一個(gè)子程序的名稱,千萬(wàn)不要把子程序名當(dāng)系統(tǒng)的 API 去使用了 :) 在這部分的補(bǔ)充中,我再歸納一下實(shí)現(xiàn)各種功能所使用的 API 函數(shù)名和使用的步驟,并提供一個(gè)匯編源程序例子來(lái)供大家參考,大家也可以下載整個(gè)源代碼包并修改使用。 一般來(lái)說(shuō),整個(gè)服務(wù)程序系統(tǒng)由兩個(gè)單獨(dú)的程序組成:服務(wù)程序和服務(wù)控制程序,經(jīng)過(guò)了上面的介紹,大家一定明白這兩個(gè)程序應(yīng)該由哪些部分組成了:
服務(wù)程序的結(jié)構(gòu)
服務(wù)程序是被用來(lái)實(shí)現(xiàn)服務(wù)功能的部分,它由相對(duì)獨(dú)立的3個(gè)部分組成:
1. 程序入口 -> 調(diào)用StartServiceCtrlDispatcher(注冊(cè)服務(wù)主程序) 2. 服務(wù)主程序(被SCM調(diào)用)-> 調(diào)用RegisterServiceCtrlHandler(注冊(cè)控制handler)-> 調(diào)用 SetServiceStatus 設(shè)置服務(wù)的正確狀態(tài) -> 執(zhí)行服務(wù)的功能代碼 -> 需要結(jié)束服務(wù)的時(shí)候則返回。 3. 控制Handler -> 設(shè)置內(nèi)部標(biāo)志以控制第2部分服務(wù)主程序中的代碼走向,并調(diào)用 SetServiceStatus 更新服務(wù)的狀態(tài)。
可見(jiàn),服務(wù)程序必須用到的 API 只有 StartServiceCtrlDispatcher、RegisterServiceCtrlHandler和SetServiceStatus ,其余的大部分代碼是執(zhí)行本身的功能模塊。這里有一個(gè)服務(wù)程序的簡(jiǎn)單例子,實(shí)現(xiàn)的是每秒種讓喇叭發(fā)聲一次的功能。
下面的源代碼是 Service.asm 程序:
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; Windows NT 服務(wù)程序模板 ; by 羅云彬, http://asm.yeah.net ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; 服務(wù)端程序 Ver 1.0 ; ; 2002.06.20 ----- 第1版 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> .386 .model flat, stdcall option casemap :none ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; Include 文件定義 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> include windows.inc include user32.inc includelib user32.lib include kernel32.inc includelib kernel32.lib include AdvApi32.inc includelib AdvApi32.lib ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; 數(shù)據(jù)段 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> .data?
stSS SERVICE_STATUS <> ;服務(wù)的狀態(tài) hSS dd ? ;服務(wù)的狀態(tài)句柄 dwOption dd ? F_STOP equ 0001h ;停止服務(wù)
include ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; 代碼段 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> .code ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; 服務(wù)控制程序 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> _ProcHandler proc _dwControl
pushad mov eax,_dwControl .if eax == SERVICE_CONTROL_STOP or dwOption,F_STOP mov stSS.dwCurrentState,SERVICE_STOPPED invoke SetServiceStatus,hSS,addr stSS .elseif eax == SERVICE_CONTROL_INTERROGATE invoke SetServiceStatus,hSS,addr stSS .endif popad ret
_ProcHandler endp ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; 服務(wù)主程序 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> _ServiceMain proc _dwArgc,_lpszArgv
pushad invoke RegisterServiceCtrlHandler,addr szServiceName,offset _ProcHandler mov hSS,eax mov stSS.dwServiceType,SERVICE_WIN32_OWN_PROCESS or SERVICE_INTERACTIVE_PROCESS mov stSS.dwCurrentState,SERVICE_START_PENDING mov stSS.dwControlsAccepted,SERVICE_ACCEPT_STOP mov stSS.dwWin32ExitCode,NO_ERROR invoke SetServiceStatus,hSS,addr stSS ;******************************************************************** ; 如果初始化代碼比較多,那么需要首先把狀態(tài)設(shè)置為 pending,等完成以后 ; 再設(shè)置為 Running。(在這里加入初始化代碼) ;******************************************************************** mov stSS.dwCurrentState,SERVICE_RUNNING invoke SetServiceStatus,hSS,addr stSS ;******************************************************************** ; 服務(wù)的具體執(zhí)行代碼 ; 在這里是每隔1秒種讓喇叭發(fā)聲 ;******************************************************************** .repeat invoke MessageBeep,-1 invoke Sleep,1000 .until dwOption & F_STOP popad ret
_ServiceMain endp ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; 主程序 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> _WinMain proc local @stSTE[2]:SERVICE_TABLE_ENTRY
invoke RtlZeroMemory,addr @stSTE,sizeof @stSTE mov @stSTE[0].lpServiceName,offset szServiceName mov @stSTE[0].lpServiceProc,offset _ServiceMain invoke StartServiceCtrlDispatcher,addr @stSTE ret
_WinMain endp ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> start: invoke _WinMain invoke ExitProcess,NULL ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> end start
這里是服務(wù)名稱的定義文件 Define.inc,這個(gè)文件已經(jīng)在前面用include語(yǔ)句包含進(jìn)來(lái):
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; 公用文本信息 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> .const
szServiceEXE db 'Service.exe',0 ;在這里定義運(yùn)行服務(wù)的 exe 文件名 szServiceName db 'ServiceTemplate',0 ;在這里定義服務(wù)的名稱 szDisplayName db 'Service Template for test',0 ;在這里定義服務(wù)顯示的名稱 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
要注意的是,服務(wù)程序部分的代碼并沒(méi)有什么靈活性可言,它必須按照這樣的步驟來(lái)實(shí)現(xiàn),否則Windows就不認(rèn)為它是一個(gè)服務(wù)程序了,另外,需要補(bǔ)充說(shuō)明的是:
1. StartServiceCtrlDispatcher 函數(shù)被調(diào)用后并不馬上返回,這時(shí)系統(tǒng)會(huì)去調(diào)用服務(wù)主程序(上面的_ServiceMain子程序),只有當(dāng)服務(wù)主程序返回的時(shí)候,函數(shù)才會(huì)返回,這時(shí)主程序繼續(xù)執(zhí)行并執(zhí)行 ExitProcess 終止執(zhí)行。 2. 從第1點(diǎn)可以發(fā)現(xiàn):在服務(wù)程序中,服務(wù)主程序的退出就意味著服務(wù)自行終止了,所以假如程序創(chuàng)建了多個(gè)線程來(lái)執(zhí)行功能代碼的話,在線程被創(chuàng)建以后,主程序還需要在這里等待,否則子程序返回以后服務(wù)進(jìn)程就結(jié)束了,這些線程又怎么能繼續(xù)執(zhí)行呢?
服務(wù)控制程序的結(jié)構(gòu)
好了,有了服務(wù)程序以后,還需要把服務(wù)正確安裝才能讓它運(yùn)行,這個(gè)工作就是服務(wù)控制程序的事情了,一般來(lái)說(shuō),服務(wù)控制程序的結(jié)構(gòu)并沒(méi)有什么特殊的要求,它只是一個(gè)普通的Win32可執(zhí)行文件而已,在服務(wù)控制程序中可以使用下面的 API 來(lái)進(jìn)行各種控制,這些 API的具體語(yǔ)法請(qǐng)查看 Win32 API 手冊(cè):
1. 在進(jìn)行和服務(wù)相關(guān)的各種操作之前,必須使用OpenSCManager來(lái)聯(lián)系服務(wù)管理器,并從這個(gè) API 得到一個(gè) 服務(wù)管理器的句柄 hSCM。 2. 操作某個(gè)具體的服務(wù)前,需要使用OpenService來(lái)打開(kāi)服務(wù),并得到服務(wù)的句柄hService,接下來(lái)就可以使用這個(gè)句柄來(lái)控制服務(wù)(當(dāng)然,安裝服務(wù)的時(shí)候并不需要此步驟,這時(shí)服務(wù)還不存在呢,又如何打開(kāi)?) 3. 安裝服務(wù):使用 CreateService 函數(shù),這個(gè)函數(shù)將服務(wù)程序的可執(zhí)行文件在SCM中注冊(cè),注意:“注冊(cè)”并不意味著執(zhí)行,它只是在SCM的數(shù)據(jù)庫(kù)中登記一條包含可執(zhí)行文件名、啟動(dòng)方式、服務(wù)名稱等信息的數(shù)據(jù)而已,執(zhí)行這個(gè)函數(shù)以后,在控制面板的服務(wù)一欄中就可以看到服務(wù)列表中已經(jīng)出現(xiàn)這個(gè)服務(wù)了,由于這僅僅是“注冊(cè)”,所以系統(tǒng)并不檢查可執(zhí)行文件的合法性,也就是說(shuō),即使注冊(cè)一個(gè)不存在的可執(zhí)行文件,注冊(cè)操作仍然會(huì)成功,但這樣以后要啟動(dòng)服務(wù)的時(shí)候就會(huì)失敗。注冊(cè)過(guò)一次以后,SCM數(shù)據(jù)庫(kù)中的信息會(huì)被自動(dòng)保存,所以“注冊(cè)”操作是一次性的過(guò)程,并不需要在每次啟動(dòng)服務(wù)的時(shí)候都去注冊(cè)一遍。 4. 啟動(dòng)服務(wù):當(dāng)安裝了服務(wù)以后,可以用StartService函數(shù)來(lái)啟動(dòng)服務(wù),這時(shí)系統(tǒng)才真正根據(jù)注冊(cè)的文件名去執(zhí)行服務(wù)程序。 5. 刪除服務(wù):刪除服務(wù)是安裝服務(wù)的逆過(guò)程,也就是在SCM的數(shù)據(jù)庫(kù)中將服務(wù)程序的信息刪除,刪除服務(wù)使用DeleteService函數(shù)。 6. 服務(wù)的控制:服務(wù)的暫停、停止等工作都可以由服務(wù)控制函數(shù)ControlService來(lái)完成,需要的僅僅是指定不同的參數(shù)而已。 7. 服務(wù)狀態(tài)的查詢可以由QueryServiceStatus函數(shù)來(lái)完成。 8. 句柄的關(guān)閉:和上面的操作相關(guān)的句柄(包括SCM句柄和服務(wù)句柄)在不再使用以后需要用CloseServiceHandle函數(shù)關(guān)閉。
下面的幾個(gè)子程序舉例說(shuō)明了安裝服務(wù)、刪除服務(wù)、啟動(dòng)服務(wù)和停止服務(wù)的操作方法,注意子程序中用到的hSCM已經(jīng)在其他地方首先調(diào)用OpenSCManager函數(shù)獲取了。更具體的源代碼請(qǐng)查看文章附帶的源代碼包中的Control.asm文件。
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; 安裝服務(wù) ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> _InstallService proc local @hService,@dwReturn
mov @dwReturn,FALSE ;******************************************************************** ; 創(chuàng)建服務(wù) ;******************************************************************** invoke CreateService,hSCM,addr szServiceName,addr szDisplayName,\ SERVICE_ALL_ACCESS,SERVICE_WIN32_OWN_PROCESS or SERVICE_INTERACTIVE_PROCESS,\ SERVICE_AUTO_START,SERVICE_ERROR_NORMAL,addr szServiceFile,\ NULL,NULL,NULL,NULL,NULL .if ! eax invoke GetLastError .if eax == ERROR_DUP_NAME eax == ERROR_SERVICE_EXISTS mov @dwReturn,TRUE .else mov @dwReturn,FALSE .endif jmp @F .endif mov @hService,eax mov @dwReturn,TRUE ;******************************************************************** invoke CloseServiceHandle,@hService @@: mov eax,@dwReturn ret
_InstallService endp ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; 刪除服務(wù) ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> _DeleteService proc local @hService,@dwReturn local @stStatus:SERVICE_STATUS
mov @dwReturn,FALSE ;******************************************************************** ; 打開(kāi)服務(wù) ;******************************************************************** invoke OpenService,hSCM,addr szServiceName,SERVICE_ALL_ACCESS .if ! eax jmp @F .endif mov @hService,eax ;******************************************************************** ; 停止服務(wù)并刪除服務(wù) ;******************************************************************** call _StopService invoke DeleteService,@hService .if eax mov @dwReturn,TRUE .endif invoke CloseServiceHandle,@hService @@: mov eax,@dwReturn ret
_DeleteService endp ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; 啟動(dòng)服務(wù) ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> _StartService proc local @hService
invoke OpenService,hSCM,addr szServiceName,SERVICE_START .if eax mov @hService,eax invoke StartService,@hService,0,NULL .if eax mov eax,TRUE .else mov eax,FALSE .endif push eax invoke CloseServiceHandle,@hService pop eax .else mov eax,FALSE .endif ret
_StartService endp ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; 停止服務(wù) ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> _StopService proc local @hService local @stStatus:SERVICE_STATUS
invoke OpenService,hSCM,addr szServiceName,SERVICE_ALL_ACCESS .if eax mov @hService,eax invoke QueryServiceStatus,@hService,addr @stStatus invoke ControlService,@hService,SERVICE_CONTROL_STOP,addr @stStatus .if eax mov eax,TRUE .else mov eax,FALSE .endif push eax invoke CloseServiceHandle,@hService pop eax .else mov eax,FALSE .endif ret
_StopService endp ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
合并服務(wù)程序和服務(wù)控制程序
在上面的例子中,將服務(wù)程序和服務(wù)控制程序分開(kāi)是為了更好地從概念上說(shuō)明兩者的區(qū)別,但在實(shí)際應(yīng)用中,還是有很多的程序?qū)蓚(gè)部分合在同一個(gè)可執(zhí)行文件中實(shí)現(xiàn),這時(shí)兩部分僅僅是在物理上被放在同一個(gè)exe文件中,在結(jié)構(gòu)上還是無(wú)不相干,完全獨(dú)立的。 雖然兩個(gè)部分的實(shí)現(xiàn)方式是完全不同的,但只要在程序的入口處就按照不同的情況去執(zhí)行不同部分的代碼,將它們合在一個(gè)文件中還是可行的,其關(guān)鍵就是分辨程序被執(zhí)行的時(shí)候是作為服務(wù)程序執(zhí)行還是被作為服務(wù)控制程序執(zhí)行的,常見(jiàn)的方法有:
1. 用命令行參數(shù)來(lái)分辨,使用CreateService函數(shù)安裝服務(wù)的時(shí)候遞交文件名字符串時(shí)可以在文件名后面跟參數(shù),這樣程序入口處用GetCommandLine函數(shù)獲取命令行參數(shù),檢測(cè)到特定參數(shù)則按照服務(wù)方式執(zhí)行,否則按照服務(wù)控制程序方式運(yùn)行。 2. 用運(yùn)行路徑分辨,一些病毒程序初始化的時(shí)候?qū)⒆约旱母北究截惖絎indows目錄下并將這個(gè)副本注冊(cè)為服務(wù),所以到它檢測(cè)到當(dāng)前路徑是Windows目錄的時(shí)候,就按照服務(wù)方式運(yùn)行。
|