C++创建Windows后台服务程序

前言

后台服务 程序是在后台悄悄运行的。我们通过将自己的程序登记为服务,可以使自己的程序不出现在任务管理器中,并且随系统启动而最先运行,随系统关闭而最后停止。

服务程序通常编写成控制台类型的应用程序,总的来说,一个遵守服务控制管理程序接口要求的程序包含下面三个函数:

  • 服务程序主函数(main):调用系统函数 StartServiceCtrlDispatcher 连接程序主线程到服务控制管理程序。
  • 服务入口点函数(ServiceMain):执行服务初始化任务,同时执行多个服务的服务进程有多个服务入口函数。
  • 控制服务处理程序函数(Handler):在服务程序收到控制请求时由控制分发线程引用。

编写服务程序

1、服务程序主函数

#include <iostream>
#include "Windows.h"

#define SERVICE_NAME "srv_demo"
SERVICE_STATUS ServiceStatus;
SERVICE_STATUS_HANDLE hServiceStatusHandle;

int main (int argc, const char *argv[])
{
    SERVICE_TABLE_ENTRY ServiceTable[2];

    ServiceTable[0].lpServiceName = SERVICE_NAME;
    ServiceTable[0].lpServiceProc = (LPSERVICE_MAIN_FUNCTION)service_main;

    ServiceTable[1].lpServiceName = NULL;
    ServiceTable[1].lpServiceProc = NULL;
    // 启动服务的控制分派机线程
    StartServiceCtrlDispatcher(ServiceTable); 
    return 0;
}

首先声明几个全局变量,以便在程序的多个函数之间共享它们值。之后在主函数中创建一个分派表。分派表是SERVICE_TABLE_ENTRY 类型结构,它有两个域:

  • lpServiceName: 指向表示服务名称字符串的指针;当定义了多个服务时,那么这个域必须指定
  • lpServiceProc: 指向服务主函数的指针(服务入口点)

    一个程序可能包含若干个服务。每一个服务都必须列于分派表中,分派表的最后一项的两个域都必须为 NULL 指针。并且在只有一个服务的情况下,服务名是可选的。

StartServiceCtrlDispatcher函数启动服务的控制分派机线程,当分派表中所有的服务执行完之后(服务为停止状态),或发生运行时错误,该函数调用返回,进程终止。

2、服务入口点函数

void WINAPI service_main(int argc, char** argv) 
{       
    ServiceStatus.dwServiceType        = SERVICE_WIN32; 
    ServiceStatus.dwCurrentState       = SERVICE_START_PENDING; 
    ServiceStatus.dwControlsAccepted   = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_PAUSE_CONTINUE; 
    ServiceStatus.dwWin32ExitCode      = 0; 
    ServiceStatus.dwServiceSpecificExitCode = 0; 
    ServiceStatus.dwCheckPoint         = 0; 
    ServiceStatus.dwWaitHint           = 0;  
    hServiceStatusHandle = RegisterServiceCtrlHandler(SERVICE_NAME, ServiceHandler); 
    if (hServiceStatusHandle==0) 
    {
        DWORD nError = GetLastError();
    }  
    //add your init code here

    //add your service thread here


    // Initialization complete - report running status 
    ServiceStatus.dwCurrentState       = SERVICE_RUNNING; 
    ServiceStatus.dwCheckPoint         = 0; 
    ServiceStatus.dwWaitHint           = 9000;  
    if(!SetServiceStatus(hServiceStatusHandle, &ServiceStatus)) 
    { 
        DWORD nError = GetLastError();
    } 

} 

上面给出的是是服务的入口点函数示例代码。它运行在一个单独的线程当中,这个线程由控制分派器创建。该函数应尽快调用 RegisterServiceCtrlHadler 函数为服务注册控制处理器,注册完控制处理器之后,获得状态句柄(hServiceStatusHandle)。

ServiceStatus 结构的每个域的用途如下:

  • dwServiceType :指示服务类型,创建 Win32 服务。赋值 SERVICE_WIN32
  • dwCurrentState :指定服务的当前状态。因为服务的初始化在这里没有完成,所以这里的状态为SERVICE_START_PENDING
  • dwControlsAccepted :这个域通知 SCM 服务接受哪个域。本文例子是允许 STOP 和 SHUTDOWN 请求。处理控制请求将在第三步讨论
  • dwWin32ExitCode 和 dwServiceSpecificExitCode :这两个域在你终止服务并报告退出细节时很有用。初始化服务时并不退出,因此,它们的值为 0
  • dwCheckPoint 和 dwWaitHint :这两个域表示初始化某个服务进程时要30 秒以上。本文例子服务的初始化过程很短,所以这两个域的值都为 0

    SetServiceStatus 函数用于向 SCM 报告服务的状态。需要提供 hStatus 句柄和 ServiceStatus 结构。

3、控制服务处理程序函数

void WINAPI ServiceHandler(DWORD fdwControl)
{
    switch(fdwControl) 
    {
        case SERVICE_CONTROL_STOP:
        case SERVICE_CONTROL_SHUTDOWN:
        ServiceStatus.dwWin32ExitCode = 0; 
        ServiceStatus.dwCurrentState  = SERVICE_STOPPED; 
        ServiceStatus.dwCheckPoint    = 0; 
        ServiceStatus.dwWaitHint      = 0;

        //add you quit code here

        break; 
        default:
            return; 
    };
    if (!SetServiceStatus(hServiceStatusHandle,  &ServiceStatus)) 
    { 
        DWORD nError = GetLastError();
    } 
}

在第二步中,我们用 RegisterServiceCtrlHadler函数注册了控制处理器函数。控制处理器与处理各种 Windows 消息的窗口回调函数非常类似。它检查 SCM 发送了什么请求并采取相应行动。

STOP 请求是 SCM 终止服务的时候发送的。例如,如果用户在“ 服务” 控制面板中手动终止服务。SHUTDOWN 请求是关闭机器时,由 SCM 发送给所有运行中服务的请求。两种情况的处理方式相同。

控制处理器函数必须报告服务状态,即便 SCM 每次发送控制请求的时候状态保持相同。因此,不管响应什么请求,都要调用 SetServiceStatus 。

创建一个服务

上面代码编译完成后,可以通过下面指令将其添加为一个服务。注意:该指令运行时需要管理员权限,且“=”后面必须空一格。
这里写图片描述
之后我们可以在计算机中看到我们新添加的服务。

完整的sc create指令说明如下
这里写图片描述

另外输入sc可以获取所有相关的控制指令
这里写图片描述

启动服务

可以通过windows提供的控制界面来启动服务,另外还可以使用下面的指令启动服务。
这里写图片描述

若服务启动时出现如下错误信息,这是因为运行作为服务的应用程序不是按服务的流程写的。所以运行提示“服务没有及时响应启动或控制请求”
这里写图片描述

停止服务

这里写图片描述

删除服务

这里写图片描述

后记

我们看到main函数及service_main函数中均有argc、argv参数,也就是说我们是可以向这两个函数中传递参数的,传递参数方法如下。

  • 向main函数中传递参数需要在创建服务时指定
sc create atest binPath= "D:\Project Files\ImosServer\x64\Release  -port=1024"
  • 向service_main函数中传递参数需要在启动服务时指定
sc start atest -port=1024

测试参数传递是否成功,可以在相应位置加入如下代码段。

FILE* log = fopen("D:\\log.txt", "a");
for (int i = 0; i < argc; ++i)
{
    fprintf(log, "main: %s\n", argv[i]);
}
fclose(log);

测试结果如下图所示
这里写图片描述

  • 22
    点赞
  • 133
    收藏
    觉得还不错? 一键收藏
  • 8
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值