美文网首页
编写Windows服务程序

编写Windows服务程序

作者: 3ni | 来源:发表于2021-12-11 10:08 被阅读0次

    注意: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 <Windows.h>
    
    #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 <Windows.h>
    
    #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 <Windows.h>
    #include <stdio.h>
    
    #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服务程序

          本文链接:https://www.haomeiwen.com/subject/dnqcfrtx.html