編寫(xiě)Windows服務(wù)程序

注意: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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末刑巧,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌啊楚,老刑警劉巖吠冤,帶你破解...
    沈念sama閱讀 217,734評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異恭理,居然都是意外死亡拯辙,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門(mén)颜价,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)涯保,“玉大人,你說(shuō)我怎么就攤上這事周伦∠Υ海” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,133評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵专挪,是天一觀的道長(zhǎng)及志。 經(jīng)常有香客問(wèn)我,道長(zhǎng)狈蚤,這世上最難降的妖魔是什么困肩? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,532評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮脆侮,結(jié)果婚禮上锌畸,老公的妹妹穿的比我還像新娘。我一直安慰自己靖避,他們只是感情好潭枣,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,585評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著幻捏,像睡著了一般盆犁。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上篡九,一...
    開(kāi)封第一講書(shū)人閱讀 51,462評(píng)論 1 302
  • 那天谐岁,我揣著相機(jī)與錄音,去河邊找鬼榛臼。 笑死伊佃,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的沛善。 我是一名探鬼主播航揉,決...
    沈念sama閱讀 40,262評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼金刁!你這毒婦竟也來(lái)了帅涂?” 一聲冷哼從身側(cè)響起议薪,我...
    開(kāi)封第一講書(shū)人閱讀 39,153評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎媳友,沒(méi)想到半個(gè)月后斯议,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,587評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡庆锦,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,792評(píng)論 3 336
  • 正文 我和宋清朗相戀三年捅位,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片搂抒。...
    茶點(diǎn)故事閱讀 39,919評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡艇搀,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出求晶,到底是詐尸還是另有隱情焰雕,我是刑警寧澤,帶...
    沈念sama閱讀 35,635評(píng)論 5 345
  • 正文 年R本政府宣布芳杏,位于F島的核電站矩屁,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏爵赵。R本人自食惡果不足惜吝秕,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,237評(píng)論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望空幻。 院中可真熱鬧烁峭,春花似錦、人聲如沸秕铛。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,855評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)但两。三九已至鬓梅,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間谨湘,已是汗流浹背绽快。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,983評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留紧阔,地道東北人谎僻。 一個(gè)月前我還...
    沈念sama閱讀 48,048評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像寓辱,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子赤拒,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,864評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容