C++編寫Windows服務
使用C++ 編寫Windows 服務,我所看的文章如下(紅色的"=" 以內, 來自http://www.programfan.com/article/2920.html ):
==============
前一段時間我寫了一篇通過寫服務的形式來達到一些監視程序運行的目的的 文章 ,至於如何在windows 下寫服務我沒有詳細介紹,今天就讓 我們 一起看看如何來寫服務程序。
Windows 服務被 設計 用於需要在後台運行的應用程序以及實現沒有用戶交互的任務。為了學習這種控制台應用程序的基礎 知識 ,C (不是C++ )是最佳 選擇 。本文將建立並實現一個簡單的服務程序,其功能是查詢系統中可用物理內存數量,然後將結果寫入一個文本文件。 最後 ,你可以用所學知識編寫自己的 Windows 服務。
當初我寫第一個 NT 服務時,我到 MSDN 上找例子。在那裡我找到了一篇 Nigel Thompson 寫的文章:「Creating a Simple Win32 Service in C++」 ,這篇文章附帶一個 C++ 例子。雖然這篇文章很好地解釋了服務的 開發 過程,但是,我仍然感覺缺少我需要的重要 信息 。 我想理解通過什麼框架,調用什麼函數,以及何時調用,但 C++ 在這方面沒有讓我輕鬆多少。面向對象的方法固然方便,但由於用類對底層 Win32 函數調用進行了封裝,它不利於學習服務程序的基本知識。這就是為什麼我覺得 C 更加適合於編寫初級服務程序或者實現簡單後台任務的服務。在你對服務程序有了充分透徹的理解之後,用 C++ 編寫才能遊刃有餘。當我離開原來的工作崗位,不得不向另一個人轉移我的知識的時候,利用我用 C所寫的例子就非常容易解釋 NT 服務之所以然。
服務是一個運行在後台並實現勿需用戶交互的任務的控制台程序。Windows NT/2000/XP 操作系統提供為服務程序提供專門的支持。人們可以用服務控制面板來配置安裝好的服務程序,也就是 Windows 2000/XP 控制面板| 管理工具中的「服務」 (或在「 開始」|「 運行」 對話框中輸入 services.msc /s—— 譯者注)。可以將服務配置成操作系統啟動時自動啟動,這樣你就不必每次再重啟系統後還要手動啟動服務。
本文將首先解釋如何 創建 一個定期查詢可用物理內存並將結果寫入某個文本文件的服務。然後指導你完成生成,安裝和實現服務的整個過程。
第一步:主函數和全局定義
首先,包含所需的頭文件。例子要調用 Win32 函數(windows.h )和磁碟文件寫入(stdio.h ):
#include
#include
接著,定義兩個常量:
#define SLEEP_TIME 5000
#define LOGFILE "C://MyServices//memstatus.txt"
SLEEP_TIME 指定兩次連續查詢可用內存之間的毫秒間隔。在第二步中編寫服務工作循環的時候要使用該常量。
LOGFILE 定義日誌文件的路徑,你將會用 WriteToLog 函數將內存查詢的結果輸出到該文件,WriteToLog 函數定義如下:
int WriteToLog(char* str)
{
FILE* log;
log = fopen(LOGFILE, "a+");
if (log == NULL)
return -1;
fprintf(log, "%s/n", str);
fclose(log);
return 0;
}
聲明幾個全局變數,以便在程序的多個函數之間共享它們值。此外,做一個函數的前向定義:
SERVICE_STATUS ServiceStatus;
SERVICE_STATUS_HANDLE hStatus;
void ServiceMain(int argc, char** argv);
void ControlHandler(DWORD request);
int InitService();
現在,準備工作已經就緒,你可以開始編碼了。服務程序控制台程序的一個子集。因此,開始你可以定義一個 main 函數,它是程序的入口點。對於服務程序來說,main 的代碼令人驚訝地簡短,因為它只創建分派表並啟動控制分派機。
void main()
{
SERVICE_TABLE_ENTRY ServiceTable[2];
ServiceTable[0].lpServiceName = "MemoryStatus";
ServiceTable[0].lpServiceProc = (LPSERVICE_MAIN_FUNCTION)ServiceMain;
ServiceTable[1].lpServiceName = NULL;
ServiceTable[1].lpServiceProc = NULL;
StartServiceCtrlDispatcher(ServiceTable);
}
一個程序可能包含若干個服務。每一個服務都必須列於專門的分派表中(為此該程序定義了一個 ServiceTable 結構數組)。這個表中的每一項都要在 SERVICE_TABLE_ENTRY 結構之中。它有兩個域:
lpServiceName: 指向表示服務名稱字元串的指針;當定義了多個服務時,那麼這個域必須指定;
lpServiceProc: 指向服務主函數的指針(服務入口點);
分派表的最後一項必須是服務名和服務主函數域的 NULL 指針,文本例子程序中只宿主一個服務,所以服務名的定義是可選的。
服務控制管理器(SCM :Services Control Manager)是一個管理系統所有服務的進程,Windows啟動時就啟起來了。個人覺得,像services.msc和sc.exe等service管理配置工具應該都是通過調用SCM來工作的。當通過SCM啟動某個service進程時(即SCM執行某個service對應的exe程序),SCM會等待此service進程的主線程調用StartServiceCtrlDispatcher函數。如果StartServiceCtrlDispatcher調用成功,則service進程的主線程就connect到SCM上了,一直到service進程中所有的running services變為SERVICE_STOPPED狀態才斷開連接。然後,service進程的主線程則扮演控制分派器的角色(通過調用HandlerEx函數發送control和service start請求)或者分別為每個service創建一個新的線程去執行ServiceMain(分派表中可以有多個服務,本文例子中只有一個服務)。詳細見:http://technet.microsoft.com/zh-cn/LIBRARY/ms686324.aspx
SCM和service進程間communicate通訊其實是通過pipe來實現的。詳細見:http://en.wikipedia.org/wiki/Service_Control_Manager
分派表中所有的服務執行完之後(例如,用戶通過「 服務」 控制面板程序停止它們),或者發生錯誤時。StartServiceCtrlDispatcher 調用返回。然後主進程終止。
第二步:ServiceMain 函數
Listing 1 展示了 ServiceMain 的代碼。該函數是服務的入口點。它運行在一個單獨的線程當中,這個線程是由控制分派器創建的。ServiceMain 應該儘可能早早為服務註冊控制處理器。這要通過調用 RegisterServiceCtrlHadler 函數來實現。你要將兩個參數傳遞給此函數:服務名和指向 ControlHandlerfunction 的指針。
它指示控制分派器調用 ControlHandler 函數處理 SCM 控制請求。註冊完控制處理器之後,獲得狀態句柄(hStatus)。通過調用 SetServiceStatus 函數,用 hStatus 向 SCM 報告服務的狀態。
Listing 1 展示了如何指定服務特徵和其當前狀態來初始化 ServiceStatus 結構,ServiceStatus 結構的每個域都有其用途:
dwServiceType :指示服務類型,創建 Win32 服務。賦值 SERVICE_WIN32 ;
dwCurrentState :指定服務的當前狀態。因為服務的初始化在這裡沒有完成,所以這裡的狀態為SERVICE_START_PENDING ;
dwControlsAccepted :這個域通知 SCM 服務接受哪個域。本文例子是允許 STOP 和 SHUTDOWN 請求。處理控制請求將在第三步討論;
dwWin32ExitCode 和 dwServiceSpecificExitCode :這兩個域在你終止服務並報告退出細節時很有用。初始化服務時並不退出,因此,它們的值為 0 ;
dwCheckPoint 和 dwWaitHint :這兩個域表示初始化某個服務進程時要30 秒以上。本文例子服務的初始化過程很短,所以這兩個域的值都為 0 。
調用 SetServiceStatus 函數向 SCM 報告服務的狀態時。要提供 hStatus 句柄和 ServiceStatus 結構。注意ServiceStatus 一個全局變數,所以你可以跨多個函數使用它。ServiceMain 函數中,你給結構的幾個域賦值,它們在服務運行的整個過程中都保持不變,比如:dwServiceType 。
在報告了服務狀態之後,你可以調用 InitService 函數來完成初始化。這個函數只是添加一個說明性字元串到日誌文件。如下面代碼所示:
// 服務初始化
int InitService()
{
int result;
result = WriteToLog("Monitoring started.");
return(result);
}
在 ServiceMain 中,檢查 InitService 函數的返回值。如果初始化有錯(因為有可能寫日誌文件失敗),則將服務狀態置為終止並退出 ServiceMain :
error = InitService();
if (error)
{
// 初始化失敗,終止服務
ServiceStatus.dwCurrentState = SERVICE_STOPPED;
ServiceStatus.dwWin32ExitCode = -1;
SetServiceStatus(hStatus, &ServiceStatus);
// 退出 ServiceMain
return;
}
如果初始化成功,則向 SCM 報告狀態:
// 向 SCM 報告運行狀態
ServiceStatus.dwCurrentState = SERVICE_RUNNING;
SetServiceStatus (hStatus, &ServiceStatus);
接著,啟動工作循環。每五秒鐘查詢一個可用物理內存並將結果寫入日誌文件。
如 Listing 1 所示,循環一直到服務的狀態為 SERVICE_RUNNING 或日誌文件寫入出錯為止。狀態可能在ControlHandler 函數響應 SCM 控制請求時修改。
第三步:處理控制請求
在第二步中,你用 ServiceMain 函數註冊了控制處理器函數。控制處理器與處理各種 Windows 消息的窗口回調函數非常類似。它檢查 SCM 發送了什麼請求並採取相應行動。
每次你調用 SetServiceStatus 函數的時候,必須指定服務接收 STOP 和 SHUTDOWN 請求。Listing 2 示範了如何在 ControlHandler 函數中處理它們。
STOP 請求是 SCM 終止服務的時候發送的。例如,如果用戶在「 服務」 控制面板中手動終止服務。SHUTDOWN 請求是關閉機器時,由 SCM 發送給所有運行中服務的請求。兩種情況的處理方式相同:
寫日誌文件,監視停止;
向 SCM 報告 SERVICE_STOPPED 狀態;
由於 ServiceStatus 結構對於整個程序而言為全局量,ServiceStatus 中的工作循環在當前狀態改變或服務終止後停止。其它的控制請求如:PAUSE 和 CONTINUE 在本文的例子沒有處理。
控制處理器函數必須報告服務狀態,即便 SCM 每次發送控制請求的時候狀態保持相同。因此,不管響應什麼請求,都要調用 SetServiceStatus 。
第四步:安裝和配置服務
程序編好了,將之編譯成 exe 文件。本文例子創建的文件叫 MemoryStatus.exe ,將它拷貝到 C:/MyServices 文件夾。為了在機器上安裝這個服務,需要用 SC.EXE 可執行文件,它是 Win32 Platform SDK 中附帶的一個工具。(譯者註:Visaul Studio .NET 2003 IDE 環境中也有這個工具,具體存放位置在:C:/Program Files/Microsoft Visual Studio .NET 2003/Common7/Tools/Bin/winnt )。使用這個實用工具可以安裝和移除服務。其它控制操作將通過服務控制面板來完成。以下是用命令行安裝 MemoryStatus 服務的方法:
sc create MemoryStatus binpath= c:/MyServices/MemoryStatus.exe
發出此創建命令。指定服務名和二進位文件的路徑(注意 binpath= 和路徑之間的那個空格)。安裝成功後,便可以用服務控制面板來控制這個服務。用控制面板的工具欄啟動和終止這個服務。
MemoryStatus 的啟動類型是手動,也就是說根據需要來啟動這個服務。右鍵單擊該服務,然後選擇上下文菜單中的「屬性」 菜單項,此時顯示該服務的屬性窗口。在這裡可以修改 啟動類型以及其它設置。你還可以從「 常規」 標籤中啟動/ 停止服務。以下是從系統中移除服務的方法:
sc delete MemoryStatus
指定 「delete」 選項和服務名。此服務將被標記為刪除,下次西通重啟後,該服務將被完全移除。
第五步:測試服務
從服務控制面板啟動 MemoryStatus 服務。如果初始化不出錯,表示啟動成功。過一會兒將服務停止。檢查一下C:/MyServices 文件夾中 memstatus.txt 文件的服務輸出。在我的機器上輸出是這樣的:
Monitoring started.
273469440
273379328
273133568
273084416
Monitoring stopped.
為了測試 MemoryStatus 服務在出錯情況下的行為,可以將 memstatus.txt 文件設置成只讀。這樣一來,服務應該無法啟動。
去掉只讀屬性,啟動服務,在將文件設成只讀。服務將停止執行,因為此時日誌文件寫入失敗。如果你更新服務控制面板的內容,會發現服務狀態是已經停止。
原文: Yevgeny Menaker
翻譯:Northtibet
==============
在CSDN 的博客上找到了相關的完整源碼,來自http://blog.csdn.net/birdme007/archive/2008/04/11/2282792.aspx
這個源碼沒有說明應該引用的lib 文件(StartServiceCtrlDispatcher 等函數) ,並且定義的LOGFILE 變數中的字元串的轉義符稍微有點問題。
因此,我改正了這兩點,並將源碼貼出來,整個源碼的權利屬於原作者,我只是稍作修改而已。對於關鍵部分沒有任何改變。原理部分請參見上邊文字或者MSDN 。
#include <windows.h>
#include <stdio.h>
#pragma comment(lib, "Advapi32")
#define SLEEP_TIME 5000
#define LOGFILE "F://memstatus.txt"
SERVICE_STATUS ServiceStatus;
SERVICE_STATUS_HANDLE hStatus;
void ServiceMain(int argc, char** argv);
void ControlHandler(DWORD request);
int InitService();
int WriteToLog(char* str);
int main(int argc, char* argv[])
{
SERVICE_TABLE_ENTRY ServiceTable[2];
ServiceTable[0].lpServiceName = "MemoryStatus";
ServiceTable[0].lpServiceProc = (LPSERVICE_MAIN_FUNCTION)ServiceMain;
ServiceTable[1].lpServiceName = NULL;
ServiceTable[1].lpServiceProc = NULL;
StartServiceCtrlDispatcher(ServiceTable);
}
void ServiceMain(int argc, char** argv)
{
int error;
ServiceStatus.dwServiceType =
SERVICE_WIN32;
ServiceStatus.dwCurrentState =
SERVICE_START_PENDING;
ServiceStatus.dwControlsAccepted =
SERVICE_ACCEPT_STOP |
SERVICE_ACCEPT_SHUTDOWN;
ServiceStatus.dwWin32ExitCode = 0;
ServiceStatus.dwServiceSpecificExitCode = 0;
ServiceStatus.dwCheckPoint = 0;
ServiceStatus.dwWaitHint = 0;
hStatus = RegisterServiceCtrlHandler(
"MemoryStatus",
(LPHANDLER_FUNCTION)ControlHandler);
if (hStatus == (SERVICE_STATUS_HANDLE)0)
{
// Registering Control Handler failed
return;
}
// Initialize Service
error = InitService();
if (!error)
{
// Initialization failed
ServiceStatus.dwCurrentState =
SERVICE_STOPPED;
ServiceStatus.dwWin32ExitCode = -1;
SetServiceStatus(hStatus, &ServiceStatus);
return;
}
// We report the running status to SCM.
ServiceStatus.dwCurrentState =
SERVICE_RUNNING;
SetServiceStatus (hStatus, &ServiceStatus);
MEMORYSTATUS memory;
// The worker loop of a service
while (ServiceStatus.dwCurrentState ==
SERVICE_RUNNING)
{
char buffer[16];
GlobalMemoryStatus(&memory);
sprintf(buffer, "%d", memory.dwAvailPhys);
int result = WriteToLog(buffer);
if (result)
{
ServiceStatus.dwCurrentState =
SERVICE_STOPPED;
ServiceStatus.dwWin32ExitCode = -1;
SetServiceStatus(hStatus,
&ServiceStatus);
return;
}
Sleep(SLEEP_TIME);
}
return;
}
void ControlHandler(DWORD request)
{
switch(request)
{
case SERVICE_CONTROL_STOP:
WriteToLog("Monitoring stopped.");
ServiceStatus.dwWin32ExitCode = 0;
ServiceStatus.dwCurrentState = SERVICE_STOPPED;
SetServiceStatus (hStatus, &ServiceStatus);
return;
case SERVICE_CONTROL_SHUTDOWN:
WriteToLog("Monitoring stopped.");
ServiceStatus.dwWin32ExitCode = 0;
ServiceStatus.dwCurrentState = SERVICE_STOPPED;
SetServiceStatus (hStatus, &ServiceStatus);
return;
default:
break;
}
// Report current status
SetServiceStatus (hStatus, &ServiceStatus);
return;
}
int WriteToLog(char* str)
{
FILE* log;
log = fopen(LOGFILE, "a+");
if (log == NULL)
return -1;
fprintf(log, "%s ", str);
fclose(log);
return 0;
}
int InitService(){
WriteToLog("Monitoring started.");
return true;
}
假設我們編譯好了程序,在F 盤,名為test.exe ,使用SC.exe 工具(VS2005 : Microsoft Visual Studio 8/Common7/Tools/Bin/winnt )命令行下:
sc create Memorymonitor binPath= f:/test.exe
向服務列表添加一個服務名為Memorymonito ,注意option 的等號後邊有一個空格。sc 還有其他的option ,我沒有研究了。
查看「 服務」 ,可以看到Memorymonitor 這個服務,開啟之後就會看到F 盤的產生了一個memstatus.txt 文件,其中記錄著剩餘內存情況。
使用sc delete Memorymonitor 命令可以將該服務從服務里表中刪除(關閉服務之後)。
二 sc.exe 管理service
除了通過「控制面板」>「管理工具」>「服務」來查看服務之外,還有很多種其他的方式可以對Windows服務進行管理。在命令行方式下,你可以使用sc.exe(Service Control的縮寫)來管理服務。
我們可以用sc.exe命令來查詢、啟動、停止,甚至刪除服務。
點擊開始>運行>輸入"cmd"回車,然後在彈出的DOS窗口中輸入sc回車就可以看到sc命令的使用幫助了。
sc命令的語法格式:
sc <server> [command] [service name] <option1> <option2>...
sc命令使用例子:
sc query
查看所有服務的運行狀態
sc query 服務名
查看某個服務的運行狀態。
sc qc 服務名
查看某個服務的配置信息。
sc start 服務名
啟動服務。例如啟動apache2.2伺服器,就寫成 sc start apache2.2。
sc stop 服務名
停止服務。例如 sc stop apache2.2 。
sc delete 服務名
刪除服務。例如 sc delete apache2.2 。
sc config 服務名 start= auto|demand|disabled
修改服務啟動類型。start參數的值可以是demand(手動)、disabled(禁用),auto(自動)。
例如 sc config apache2.2 start= demand,將apache設置為手動啟動。
特別注意:start=後面有一個空格
使用提示
1:如果服務名稱中包含有空格,記得在服務名稱上加引號。例如sc stop "my service"。
2:「服務名稱」和「服務顯示名稱」是不一樣的。sc指令使用的是「服務名稱」。
我們通過控制面板=>「管理工具"=>打開"服務",我們看到服務的顯示名稱,雙擊打開某個服務可以看到真正的服務名字。
3:sc start 和 sc stop 功能上類似於 net start 和 net stop,但速度更快且能停止的服務更多。
4:sc delete 命令的實質都是刪除HKEY_LOCAL_MACHINE SYSTEM CurrentControlSet Services下的ServiceName分支。所以你也可以用reg命令刪除名為ServiceName的服務:
reg delete HKLM SYSTEM CurrentControlSet Services ServiceName
※Kubernetes 最佳實踐:正常終止
※Idea 阿里代碼規約插件安裝
TAG:程序員小新人學習 |