编写Windows服务程序

注意:windows上的服务通常是控制台应用程序,即程序一般是没有界面的。

编写服务程序主要关注四个点:

  • 状态反馈

  • 服务入口点(Service Entry Point)

  • 服务主函数(Service ServiceMain Function)

  • 服务控制函数(Service Control Handler Function)

按照我的理解,状态反馈即将程序当前的状态反馈给SCM。服务入口点即为服务从哪里开始。服务主函数即为服务启动后最先执行的函数。服务控制函数即为服务运行中接收各种命令的处理函数,比如响应停止,关闭等。

状态反馈

状态反馈是通过SetServiceStatus函数完成的,需要将程序连接到SCM后使用。连接SCM的操作通过StartServiceCtrlDispatcher函数完成。

当程序连接到SCM之后,需要使用RegisterServiceCtrlHandlerEx函数注册服务控制函数,同时该函数会返回当前程序的状态信息句柄。

SetServiceStatus函数需要输入两个参数,第一个就是当前程序的状态信息句柄(类型为SERVICE_STATUS_HANDLE),第二个就是程序最近的状态信息指针(类型为LPSERVICE_STATUS),注意这两个参数的区别。

服务入口点

当主线程启动后(main函数),应该在主线程中马上调用StartServiceCtrlDispatcher函数,这个函数会将主线程连接到SCM。注意相关初始化工作最好在服务主函数中进行。代码如下:

#include 

#define SERVICENAME L"Your Service Name"

void ServiceMain(int argc, char** argv)
{

}

int main()
{
    WCHAR ServiceName[] = SERVICENAME;
    SERVICE_TABLE_ENTRY DispatchTable[2];
    // 服务名字
    DispatchTable[0].lpServiceName = ServiceName;
    // 服务主函数
    DispatchTable[0].lpServiceProc = (LPSERVICE_MAIN_FUNCTION)ServiceMain;

    // 注意需要加上下面两行,因为StartServiceCtrlDispatcher规定最后一个元素必须为NULL
    DispatchTable[1].lpServiceName = NULL;
    DispatchTable[1].lpServiceProc = NULL;

    // 服务入口点
    StartServiceCtrlDispatcher(DispatchTable);
    return 0;
}

虽然ServiceMain作为服务主函数名不是唯一的,但是微软建议不要更改服务主函数名,即保持为ServiceMain。目前代码可以编译成功,但是还需要继续添加相关控制。

服务主函数

进入到服务主函数后,首先初始化全局变量,然后调用RegisterServiceCtrlHandler函数来注册服务控制函数,并获取当前程序的状态信息句柄,同时将程序的状态反馈给SCM。最后执行其他初始化,注意在状态未更改为SERVICE_RUNNING之前,初始化过程不要消耗太长时间,最好控制在1秒之内完成。代码如下:

#include 

#define SERVICENAME L"Your Service Name"

// 当前程序的状态信息句柄
SERVICE_STATUS_HANDLE gSvcStatusHandle;
// 程序最近的状态信息
SERVICE_STATUS gSvcStatus;


// 服务控制函数
void WINAPI ServiceControlHandler(DWORD request)
{

}

void ServiceMain(int argc, char** argv)
{
    // 下面填充当前服务的基本信息
    // 服务类型
    gSvcStatus.dwServiceType = SERVICE_WIN32;
    // 服务状态:正在启动中
    gSvcStatus.dwCurrentState = SERVICE_START_PENDING;
    // 当前服务接收的控制有哪些
    gSvcStatus.dwControlsAccepted = SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP;
    // 启动或停止时的服务错误码
    gSvcStatus.dwWin32ExitCode = 0;
    // 其它详细信息请查看文档: https://docs.microsoft.com/en-us/windows/win32/api/winsvc/ns-winsvc-service_status
    gSvcStatus.dwServiceSpecificExitCode = 0;
    gSvcStatus.dwCheckPoint = 0;
    gSvcStatus.dwWaitHint = 0;

    // 返回状态信息句柄
    gSvcStatusHandle = RegisterServiceCtrlHandler(SERVICENAME, ServiceControlHandler);
    if (gSvcStatusHandle == 0)
    {
        return;
    }

    // 将状态更改为running,即代表程序可以接收SCM发送的控制信息
    gSvcStatus.dwCurrentState = SERVICE_RUNNING;
    // 反馈状态信息给SCM
    SetServiceStatus(gSvcStatusHandle, &gSvcStatus);

    return;
}

int main()
{
    WCHAR ServiceName[] = SERVICENAME;
    SERVICE_TABLE_ENTRY DispatchTable[2];
    // 服务名字
    DispatchTable[0].lpServiceName = ServiceName;
    // 服务主函数
    DispatchTable[0].lpServiceProc = (LPSERVICE_MAIN_FUNCTION)ServiceMain;

    // 注意需要加上下面两行,因为StartServiceCtrlDispatcher规定最后一个元素必须为NULL
    DispatchTable[1].lpServiceName = NULL;
    DispatchTable[1].lpServiceProc = NULL;

    // 服务入口点
    StartServiceCtrlDispatcher(DispatchTable);
    return 0;
}

服务控制函数

在服务控制函数中编写针对SCM发送的各种请求进行处理即可。

// 服务控制函数
void WINAPI ServiceControlHandler(DWORD request)
{
    switch (request)
    {
        // 服务停止
    case SERVICE_CONTROL_STOP:
        gSvcStatus.dwWin32ExitCode = 0;
        gSvcStatus.dwCurrentState = SERVICE_STOPPED;
        break;
        // 系统关机
    case SERVICE_CONTROL_SHUTDOWN:
        gSvcStatus.dwWin32ExitCode = 0;
        gSvcStatus.dwCurrentState = SERVICE_STOPPED;
        break;
    default:
        break;
    }
    SetServiceStatus(gSvcStatusHandle, &gSvcStatus);
}

最后完善代码

#include 
#include 

#define SERVICENAME L"ServiceDemo"

// 当前程序的状态信息句柄
SERVICE_STATUS_HANDLE gSvcStatusHandle;
// 程序最近的状态信息
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);
}

// 服务控制函数
void WINAPI ServiceControlHandler(DWORD request)
{
    switch (request)
    {
        // 服务停止
    case SERVICE_CONTROL_STOP:
        gSvcStatus.dwWin32ExitCode = 0;
        gSvcStatus.dwCurrentState = SERVICE_STOPPED;
        WriteLog("stop!");
        SetEvent(ghSvcStopEvent);
        break;
        // 系统关机
    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)
{
    // 下面填充当前服务的基本信息
    // 服务类型
    gSvcStatus.dwServiceType = SERVICE_WIN32;
    // 服务状态:正在启动中
    gSvcStatus.dwCurrentState = SERVICE_START_PENDING;
    // 当前服务接收的控制有哪些
    gSvcStatus.dwControlsAccepted = SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP;
    // 启动或停止时的服务错误码
    gSvcStatus.dwWin32ExitCode = 0;
    // 其它详细信息请查看文档: https://docs.microsoft.com/en-us/windows/win32/api/winsvc/ns-winsvc-service_status
    gSvcStatus.dwServiceSpecificExitCode = 0;
    gSvcStatus.dwCheckPoint = 0;
    gSvcStatus.dwWaitHint = 0;

    // 返回状态信息句柄
    gSvcStatusHandle = RegisterServiceCtrlHandler(SERVICENAME, ServiceControlHandler);
    if (gSvcStatusHandle == 0)
    {
        return;
    }

    // 将状态更改为running,即代表程序可以接收SCM发送的控制信息
    gSvcStatus.dwCurrentState = SERVICE_RUNNING;
    // 反馈状态信息给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];
    // 服务名字
    DispatchTable[0].lpServiceName = ServiceName;
    // 服务主函数
    DispatchTable[0].lpServiceProc = (LPSERVICE_MAIN_FUNCTION)ServiceMain;

    // 注意需要加上下面两行,因为StartServiceCtrlDispatcher规定最后一个元素必须为NULL
    DispatchTable[1].lpServiceName = NULL;
    DispatchTable[1].lpServiceProc = NULL;

    // 服务入口点
    StartServiceCtrlDispatcher(DispatchTable);
    return 0;
}

安装和删除服务

程序编译完成之后会生成exe文件,由于没有在代码中加入安装操作,所以需要借助其它工具来将这个exe文件安装到服务中,这个工具就是sc.exe,已经内置到系统中了。

第一步:首先使用管理员权限打开命令提示符。

第二步:安装服务,运行命令sc create ServiceDemo binpath=D:\c++\ServiceProgramDemo\x64\Debug\ServiceProgramDemo.exe。注意替换自己的路径。

第三步:查看自己的服务并配置启动项。搜索框搜索"运行",输入services.msc,然后找到自己的服务名字ServiceDemo,右键点击启动,服务就会启动了,并在E盘下输出相关日志信息。

删除服务很简单,先停止我们安装的服务,然后运行命令sc delete ServiceDemo即可。

参考文献

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

你可能感兴趣的:(编写Windows服务程序)