注意:windows上的服務(wù)通常是控制臺(tái)應(yīng)用程序痊焊,即程序一般是沒(méi)有界面的芹彬。
編寫(xiě)服務(wù)程序主要關(guān)注四個(gè)點(diǎn):
狀態(tài)反饋
服務(wù)入口點(diǎn)(Service Entry Point)
服務(wù)主函數(shù)(Service ServiceMain Function)
服務(wù)控制函數(shù)(Service Control Handler Function)
按照我的理解贼急,狀態(tài)反饋即將程序當(dāng)前的狀態(tài)反饋給SCM。服務(wù)入口點(diǎn)即為服務(wù)從哪里開(kāi)始坡倔。服務(wù)主函數(shù)即為服務(wù)啟動(dòng)后最先執(zhí)行的函數(shù)抄肖。服務(wù)控制函數(shù)即為服務(wù)運(yùn)行中接收各種命令的處理函數(shù),比如響應(yīng)停止许蓖,關(guān)閉等蝴猪。
狀態(tài)反饋
狀態(tài)反饋是通過(guò)SetServiceStatus
函數(shù)完成的,需要將程序連接到SCM后使用膊爪。連接SCM的操作通過(guò)StartServiceCtrlDispatcher
函數(shù)完成自阱。
當(dāng)程序連接到SCM之后,需要使用RegisterServiceCtrlHandlerEx
函數(shù)注冊(cè)服務(wù)控制函數(shù)米酬,同時(shí)該函數(shù)會(huì)返回當(dāng)前程序的狀態(tài)信息句柄沛豌。
SetServiceStatus
函數(shù)需要輸入兩個(gè)參數(shù),第一個(gè)就是當(dāng)前程序的狀態(tài)信息句柄(類(lèi)型為SERVICE_STATUS_HANDLE
)赃额,第二個(gè)就是程序最近的狀態(tài)信息指針(類(lèi)型為LPSERVICE_STATUS
)加派,注意這兩個(gè)參數(shù)的區(qū)別。
服務(wù)入口點(diǎn)
當(dāng)主線程啟動(dòng)后(main函數(shù))跳芳,應(yīng)該在主線程中馬上調(diào)用StartServiceCtrlDispatcher
函數(shù)芍锦,這個(gè)函數(shù)會(huì)將主線程連接到SCM。注意相關(guān)初始化工作最好在服務(wù)主函數(shù)中進(jìn)行筛严。代碼如下:
#include <Windows.h>
#define SERVICENAME L"Your Service Name"
void ServiceMain(int argc, char** argv)
{
}
int main()
{
WCHAR ServiceName[] = SERVICENAME;
SERVICE_TABLE_ENTRY DispatchTable[2];
// 服務(wù)名字
DispatchTable[0].lpServiceName = ServiceName;
// 服務(wù)主函數(shù)
DispatchTable[0].lpServiceProc = (LPSERVICE_MAIN_FUNCTION)ServiceMain;
// 注意需要加上下面兩行醉旦,因?yàn)镾tartServiceCtrlDispatcher規(guī)定最后一個(gè)元素必須為NULL
DispatchTable[1].lpServiceName = NULL;
DispatchTable[1].lpServiceProc = NULL;
// 服務(wù)入口點(diǎn)
StartServiceCtrlDispatcher(DispatchTable);
return 0;
}
雖然ServiceMain
作為服務(wù)主函數(shù)名不是唯一的饶米,但是微軟建議不要更改服務(wù)主函數(shù)名,即保持為ServiceMain
车胡。目前代碼可以編譯成功檬输,但是還需要繼續(xù)添加相關(guān)控制。
服務(wù)主函數(shù)
進(jìn)入到服務(wù)主函數(shù)后匈棘,首先初始化全局變量丧慈,然后調(diào)用RegisterServiceCtrlHandler
函數(shù)來(lái)注冊(cè)服務(wù)控制函數(shù),并獲取當(dāng)前程序的狀態(tài)信息句柄主卫,同時(shí)將程序的狀態(tài)反饋給SCM逃默。最后執(zhí)行其他初始化,注意在狀態(tài)未更改為SERVICE_RUNNING
之前簇搅,初始化過(guò)程不要消耗太長(zhǎng)時(shí)間完域,最好控制在1秒之內(nèi)完成。代碼如下:
#include <Windows.h>
#define SERVICENAME L"Your Service Name"
// 當(dāng)前程序的狀態(tài)信息句柄
SERVICE_STATUS_HANDLE gSvcStatusHandle;
// 程序最近的狀態(tài)信息
SERVICE_STATUS gSvcStatus;
// 服務(wù)控制函數(shù)
void WINAPI ServiceControlHandler(DWORD request)
{
}
void ServiceMain(int argc, char** argv)
{
// 下面填充當(dāng)前服務(wù)的基本信息
// 服務(wù)類(lèi)型
gSvcStatus.dwServiceType = SERVICE_WIN32;
// 服務(wù)狀態(tài):正在啟動(dòng)中
gSvcStatus.dwCurrentState = SERVICE_START_PENDING;
// 當(dāng)前服務(wù)接收的控制有哪些
gSvcStatus.dwControlsAccepted = SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP;
// 啟動(dòng)或停止時(shí)的服務(wù)錯(cuò)誤碼
gSvcStatus.dwWin32ExitCode = 0;
// 其它詳細(xì)信息請(qǐng)查看文檔: https://docs.microsoft.com/en-us/windows/win32/api/winsvc/ns-winsvc-service_status
gSvcStatus.dwServiceSpecificExitCode = 0;
gSvcStatus.dwCheckPoint = 0;
gSvcStatus.dwWaitHint = 0;
// 返回狀態(tài)信息句柄
gSvcStatusHandle = RegisterServiceCtrlHandler(SERVICENAME, ServiceControlHandler);
if (gSvcStatusHandle == 0)
{
return;
}
// 將狀態(tài)更改為running瘩将,即代表程序可以接收SCM發(fā)送的控制信息
gSvcStatus.dwCurrentState = SERVICE_RUNNING;
// 反饋狀態(tài)信息給SCM
SetServiceStatus(gSvcStatusHandle, &gSvcStatus);
return;
}
int main()
{
WCHAR ServiceName[] = SERVICENAME;
SERVICE_TABLE_ENTRY DispatchTable[2];
// 服務(wù)名字
DispatchTable[0].lpServiceName = ServiceName;
// 服務(wù)主函數(shù)
DispatchTable[0].lpServiceProc = (LPSERVICE_MAIN_FUNCTION)ServiceMain;
// 注意需要加上下面兩行吟税,因?yàn)镾tartServiceCtrlDispatcher規(guī)定最后一個(gè)元素必須為NULL
DispatchTable[1].lpServiceName = NULL;
DispatchTable[1].lpServiceProc = NULL;
// 服務(wù)入口點(diǎn)
StartServiceCtrlDispatcher(DispatchTable);
return 0;
}
服務(wù)控制函數(shù)
在服務(wù)控制函數(shù)中編寫(xiě)針對(duì)SCM發(fā)送的各種請(qǐng)求進(jìn)行處理即可。
// 服務(wù)控制函數(shù)
void WINAPI ServiceControlHandler(DWORD request)
{
switch (request)
{
// 服務(wù)停止
case SERVICE_CONTROL_STOP:
gSvcStatus.dwWin32ExitCode = 0;
gSvcStatus.dwCurrentState = SERVICE_STOPPED;
break;
// 系統(tǒng)關(guān)機(jī)
case SERVICE_CONTROL_SHUTDOWN:
gSvcStatus.dwWin32ExitCode = 0;
gSvcStatus.dwCurrentState = SERVICE_STOPPED;
break;
default:
break;
}
SetServiceStatus(gSvcStatusHandle, &gSvcStatus);
}
最后完善代碼
#include <Windows.h>
#include <stdio.h>
#define SERVICENAME L"ServiceDemo"
// 當(dāng)前程序的狀態(tài)信息句柄
SERVICE_STATUS_HANDLE gSvcStatusHandle;
// 程序最近的狀態(tài)信息
SERVICE_STATUS gSvcStatus;
// 停止事件
HANDLE ghSvcStopEvent = NULL;
void WriteLog(const char* str)
{
FILE* fp;
fopen_s(&fp, "E:\\ServiceOutFile.txt", "a+");
if (fp == NULL)
return;
fprintf(fp, "%s\n", str);
fclose(fp);
}
// 服務(wù)控制函數(shù)
void WINAPI ServiceControlHandler(DWORD request)
{
switch (request)
{
// 服務(wù)停止
case SERVICE_CONTROL_STOP:
gSvcStatus.dwWin32ExitCode = 0;
gSvcStatus.dwCurrentState = SERVICE_STOPPED;
WriteLog("stop!");
SetEvent(ghSvcStopEvent);
break;
// 系統(tǒng)關(guān)機(jī)
case SERVICE_CONTROL_SHUTDOWN:
gSvcStatus.dwWin32ExitCode = 0;
gSvcStatus.dwCurrentState = SERVICE_STOPPED;
WriteLog("shutdown!");
SetEvent(ghSvcStopEvent);
break;
default:
break;
}
SetServiceStatus(gSvcStatusHandle, &gSvcStatus);
}
void ServiceMain(int argc, char** argv)
{
// 下面填充當(dāng)前服務(wù)的基本信息
// 服務(wù)類(lèi)型
gSvcStatus.dwServiceType = SERVICE_WIN32;
// 服務(wù)狀態(tài):正在啟動(dòng)中
gSvcStatus.dwCurrentState = SERVICE_START_PENDING;
// 當(dāng)前服務(wù)接收的控制有哪些
gSvcStatus.dwControlsAccepted = SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP;
// 啟動(dòng)或停止時(shí)的服務(wù)錯(cuò)誤碼
gSvcStatus.dwWin32ExitCode = 0;
// 其它詳細(xì)信息請(qǐng)查看文檔: https://docs.microsoft.com/en-us/windows/win32/api/winsvc/ns-winsvc-service_status
gSvcStatus.dwServiceSpecificExitCode = 0;
gSvcStatus.dwCheckPoint = 0;
gSvcStatus.dwWaitHint = 0;
// 返回狀態(tài)信息句柄
gSvcStatusHandle = RegisterServiceCtrlHandler(SERVICENAME, ServiceControlHandler);
if (gSvcStatusHandle == 0)
{
return;
}
// 將狀態(tài)更改為running姿现,即代表程序可以接收SCM發(fā)送的控制信息
gSvcStatus.dwCurrentState = SERVICE_RUNNING;
// 反饋狀態(tài)信息給SCM
SetServiceStatus(gSvcStatusHandle, &gSvcStatus);
ghSvcStopEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (ghSvcStopEvent == 0)
{
return;
}
WriteLog("enter while\n");
while (WaitForSingleObject(ghSvcStopEvent, 0) == WAIT_TIMEOUT)
{
WriteLog("while....\n");
Sleep(500);
}
return;
}
int main()
{
WCHAR ServiceName[] = SERVICENAME;
SERVICE_TABLE_ENTRY DispatchTable[2];
// 服務(wù)名字
DispatchTable[0].lpServiceName = ServiceName;
// 服務(wù)主函數(shù)
DispatchTable[0].lpServiceProc = (LPSERVICE_MAIN_FUNCTION)ServiceMain;
// 注意需要加上下面兩行肠仪,因?yàn)镾tartServiceCtrlDispatcher規(guī)定最后一個(gè)元素必須為NULL
DispatchTable[1].lpServiceName = NULL;
DispatchTable[1].lpServiceProc = NULL;
// 服務(wù)入口點(diǎn)
StartServiceCtrlDispatcher(DispatchTable);
return 0;
}
安裝和刪除服務(wù)
程序編譯完成之后會(huì)生成exe文件,由于沒(méi)有在代碼中加入安裝操作备典,所以需要借助其它工具來(lái)將這個(gè)exe文件安裝到服務(wù)中异旧,這個(gè)工具就是sc.exe
,已經(jīng)內(nèi)置到系統(tǒng)中了提佣。
第一步:首先使用管理員權(quán)限打開(kāi)命令提示符吮蛹。
第二步:安裝服務(wù),運(yùn)行命令sc create ServiceDemo binpath=D:\c++\ServiceProgramDemo\x64\Debug\ServiceProgramDemo.exe
镐依。注意替換自己的路徑匹涮。
第三步:查看自己的服務(wù)并配置啟動(dòng)項(xiàng)。搜索框搜索"運(yùn)行"槐壳,輸入services.msc
,然后找到自己的服務(wù)名字ServiceDemo
喜每,右鍵點(diǎn)擊啟動(dòng)务唐,服務(wù)就會(huì)啟動(dòng)了,并在E盤(pán)下輸出相關(guān)日志信息带兜。
刪除服務(wù)很簡(jiǎn)單枫笛,先停止我們安裝的服務(wù),然后運(yùn)行命令sc delete ServiceDemo
即可刚照。
參考文獻(xiàn)
https://docs.microsoft.com/en-us/windows/win32/services/service-programs
https://docs.microsoft.com/en-us/windows/win32/services/configuring-a-service-using-sc