将控制台程序改造为windows服务程序

每一步:改造main入口。

int main(int argc, char *argv[])
{
    SERVICE_TABLE_ENTRY ServiceTable[] =
    {
        { SERVICE_NAME, (LPSERVICE_MAIN_FUNCTION)ServiceMain },
        { NULL, NULL }
    };
    ::StartServiceCtrlDispatcher(ServiceTable);
    return 0;
}

程序入口还是跟普通的控制台程序一样,但是不能在main里直接写程序逻辑(理论上可以,但是如果不能尽快开启派遣并完成服务注册的话,服务控制管理器最终就会强制终止程序)。改造后的main函数主要用于启动服务入口派遣,StartServiceCtrlDispatcher()为SERVICE_TABLE_ENTRY的每一项开启一个线程,并且监视它们的运行状态,只有当所有的服务入口函数/线程退出时,StartServiceCtrlDispatcher()才返回。

第二步:编写服务入口函数。

void WINAPI ServiceMain(int argc, char* argv[])
{
    // 注册服务控制句柄
    hServiceStatus = ::RegisterServiceCtrlHandler(SERVICE_NAME, CtrlHandler);
    if (hServiceStatus == NULL)
    {
        ::OutputDebugStringA("@ServiceMain RegisterServiceCtrlHandler() failed");
        return;
    }
    ::OutputDebugStringA("@ServiceMain RegisterServiceCtrlHandler() ok");

    // 初使设置启动中状态
    ServiceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
    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;
    ::SetServiceStatus(hServiceStatus, &ServiceStatus);
    ::OutputDebugStringA("@ServiceMain SetServiceStatus(SERVICE_START_PENDING)");

    // 初使化程序逻辑
    // 注:在这里调用程序的初使化逻辑,如果初使化失败,需要更新为停止状态
    // 这里假设初使化失败,更新为停止状态
    // if (!InitServer())
    // {
    //     ServiceStatus.dwCurrentState = SERVICE_STOPPED;
    //     ::SetServiceStatus(hServiceStatus, &ServiceStatus);
    //     ::OutputDebugStringA("@ServiceMain init failed");
    //     return;
    // }

    // 设置运行状态
    ServiceStatus.dwCurrentState = SERVICE_RUNNING;
    ::SetServiceStatus(hServiceStatus, &ServiceStatus);
    ::OutputDebugStringA("@ServiceMain SetServiceStatus(SERVICE_RUNNING)");

    // 运行程序逻辑,并且等待退出
    // 这里RealServer()必须阻塞住,一旦函数返回,下面将自行更改自己的运行状态为停止
    // RealServer();

    // 设置停止状态
    ServiceStatus.dwCurrentState = SERVICE_STOPPED;
    ::SetServiceStatus(hServiceStatus, &ServiceStatus);
    ::OutputDebugStringA("@ServiceMain SetServiceStatus(SERVICE_STOPPED)");

    ::OutputDebugStringA("@ServiceMain exit");
    return;
}

从上面的代码及注释可以看到,服务入口函数主要包含两部分操作:
1)注册服务控制句柄,并及时的调整自己的运行状态。
2)调用程序初使化逻辑及运行逻辑。

关于第一部分操作,服务的运行状态包括启动(SERVICE_START_PENDING)、运行中(SERVICE_RUNNING)、停止(SERVICE_STOPPED),服务程序需要根据自己的运行状态及时向服务控制器汇报。
第于第二部分操作,其实就是普通控制台程序main原来的逻辑代码,但是需要配合服务控制器进行状态汇报。

第三步:编写服务控制函数。

void WINAPI CtrlHandler(DWORD request)
{
    ::OutputDebugStringA("@CtrlHandler call");

    switch (request)
    {
    case SERVICE_CONTROL_STOP:
    case SERVICE_CONTROL_SHUTDOWN:
        ServiceStatus.dwWin32ExitCode = 0;
        ServiceStatus.dwCurrentState = SERVICE_STOPPED;
        ::OutputDebugStringA("@CtrlHandler set SERVICE_STOPPED");
        break;

    case SERVICE_CONTROL_PAUSE:
        bPause = true;
        ServiceStatus.dwCurrentState = SERVICE_PAUSED;
        ::OutputDebugStringA("@CtrlHandler set SERVICE_PAUSED");
        break;

    case SERVICE_CONTROL_CONTINUE:
        bPause = false;
        ServiceStatus.dwCurrentState = SERVICE_RUNNING;
        ::OutputDebugStringA("@CtrlHandler set SERVICE_RUNNING");
        break;

    default:
        break;
    }

    ::SetServiceStatus(hServiceStatus, &ServiceStatus);

    ::OutputDebugStringA("@CtrlHandler exit");
    return;
}

这个函数主要用于接收服务管理器发起的状态控制动作,如暂停、恢复、停止,但不包括启动,因为启动动作由main()及服务入口函数来控制。

一般来说,完成以上三个步骤后,控制台程序就可以以windows注册服务的模式来运行了,并且通过控制面板的服务管理器(services.msc)即可控制服务的启动和停止。但是,在此之前,还需要执行服务的安装。

这里补充上面程序改造过程中用到的全局变量定义:

TCHAR SERVICE_NAME[] = _T("mytest");
void WINAPI ServiceMain(int argc, char* argv[]);
void WINAPI CtrlHandler(DWORD request);
SERVICE_STATUS ServiceStatus;
SERVICE_STATUS_HANDLE hServiceStatus;

第四步:安装服务。

有两种方法安装服务:
1)调用sc命令。
2)将安装命令集成到服务程序中。

先讲sc命令的基本用法:

安装服务:
sc create 服务名 binPath= 程序路径
例如:
sc create mytest1 binPath= E:\testservice\Debug\testservice.exe type= own start= auto
[SC] CreateService 成功

移除服务:
sc create 服务名
例如:
sc delete mytest1
[SC] DeleteService 成功
但是,在删除服务之前,需要确保服务已经停止,可以使用服务管理器或者sc stop命令停止服务。

更详细的命令用法请百度。

接下来再讲如何让服务程序支持自安装及删除。

修改main()入口函数,加入对参数的处理。-i参数表示安装服务,-u表示移除服务,-s表示以控制台的方式运行。

int main(int argc, char* argv[])
{
    ::OutputDebugStringA("@main call");

    if (argc >= 2)
    {
        // 安装服务
        if (strcmp(argv[1], "-i") == 0)
        {
            InstallService(SERVICE_NAME);
        }
        // 移除服务
        else if (strcmp(argv[1], "-u") == 0)
        {
            RemoveService(SERVICE_NAME);
        }
        // 以控制台的方式运行
        if (strcmp(argv[1], "-s") == 0)
        {
            RealServer();
        }
    }
    else
    {
        // 启动服务派遣
        SERVICE_TABLE_ENTRY ServiceTable[] =
        {
            { SERVICE_NAME, (LPSERVICE_MAIN_FUNCTION)ServiceMain },
            { NULL, NULL }
        };
        ::StartServiceCtrlDispatcher(ServiceTable);
    }
    
    ::OutputDebugStringA("@main exit");
    return 0;
}

然后加上InstallService()和RemoveService()函数的实现。

// 安装服务
int InstallService(TCHAR *SERVICE_NAME)
{
    SC_HANDLE       hScm;
    SC_HANDLE       hSrv;
    SERVICE_STATUS  InstallStatus;
    DWORD           dwErrCode;

    // 取服务完整路径
    TCHAR sFilePath[MAX_PATH] = { 0 };
    GetModuleFileName(NULL, sFilePath, sizeof(sFilePath));

    // 打开服务管理器
    hScm = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
    if (hScm == NULL)
    {
        ::OutputDebugStringA("@InstallService OpenSCManager() failed");
        printf("OpenSCManager() failed \n");
        return -1;
    }

    // 尝试创建服务
    hSrv = CreateService(hScm, SERVICE_NAME, SERVICE_NAME, SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS,
        SERVICE_AUTO_START, SERVICE_ERROR_NORMAL, sFilePath, NULL, NULL, NULL, NULL, NULL);
    if (hSrv == NULL)
    {
        dwErrCode = GetLastError();
        if (dwErrCode != ERROR_SERVICE_EXISTS)
        {
            ::OutputDebugStringA("@InstallService CreateService() failed");
            printf("CreateService() failed \n");
            CloseServiceHandle(hScm);
            return -1;
        }

        // 尝试打开服务
        hSrv = OpenService(hScm, SERVICE_NAME, SERVICE_ALL_ACCESS);
        if (hSrv == NULL)
        {
            ::OutputDebugStringA("@InstallService OpenService() failed");
            printf("OpenService() failed \n");
            CloseServiceHandle(hScm);
            return -1;
        }
    }

    // 启动服务
    if (StartService(hSrv, 0, NULL) == 0)
    {
        dwErrCode = GetLastError();
        if (dwErrCode == ERROR_SERVICE_ALREADY_RUNNING)
        {
            ::OutputDebugStringA("@InstallService StartService() already Running");
            printf("StartService() already Running \n");
            CloseServiceHandle(hScm);
            CloseServiceHandle(hSrv);
            return 0;
        }
    }

    // 查询服务状态
    while (QueryServiceStatus(hSrv, &InstallStatus) != 0)
    {
        if (InstallStatus.dwCurrentState != SERVICE_START_PENDING)
            break;

        Sleep(100);
    }

    if (InstallStatus.dwCurrentState != SERVICE_RUNNING)
    {
        ::OutputDebugStringA("@InstallService StartService failed");
        printf("StartService failed \n");
    }
    else
    {
        ::OutputDebugStringA("@InstallService StartService success");
        printf("StartService success \n");
    }

    CloseServiceHandle(hScm);
    CloseServiceHandle(hSrv);

    return 0;
}

// 移除服务
int RemoveService(TCHAR *SERVICE_NAME)
{
    SC_HANDLE        hScm;
    SC_HANDLE        hSrv;
    SERVICE_STATUS   RemoveStatus;
    DWORD            dwErrCode;

    // 打开服务管理器
    hScm = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
    if (hScm == NULL)
    {
        ::OutputDebugStringA("@RemoveService OpenSCManager() failed");
        printf("OpenSCManager() failed \n");
        return -1;
    }

    // 尝试打开服务
    hSrv = OpenService(hScm, SERVICE_NAME, SERVICE_ALL_ACCESS);
    if (hSrv == NULL)
    {
        dwErrCode = GetLastError();
        if (dwErrCode == 1060)
        {
            ::OutputDebugStringA("@RemoveService OpenService() no Exists");
            printf("OpenService() no Exists \n");
        }
        else
        {
            ::OutputDebugStringA("@RemoveService OpenService() no Exists");
            printf("OpenService() failed \n");
        }

        CloseServiceHandle(hScm);
        return -1;
    }

    // 查询服务状态
    if (QueryServiceStatus(hSrv, &RemoveStatus) != 0)
    {
        if (RemoveStatus.dwCurrentState == SERVICE_STOPPED)
        {
            ::OutputDebugStringA("@RemoveService QueryServiceStatus() already Stopped");
            printf("already Stopped \n");
        }
        else
        {
            // 尝试停止服务
            if (ControlService(hSrv, SERVICE_CONTROL_STOP, &RemoveStatus) != 0)
            {
                while (RemoveStatus.dwCurrentState == SERVICE_STOP_PENDING)
                {
                    Sleep(10);
                    QueryServiceStatus(hSrv, &RemoveStatus);
                }

                if (RemoveStatus.dwCurrentState == SERVICE_STOPPED)
                {
                    ::OutputDebugStringA("@RemoveService ControlService() StopService Success");
                    printf("StopService Success \n");
                }
                else
                {
                    ::OutputDebugStringA("@RemoveService ControlService() StopService failed");
                    printf("StopService failed \n");
                }
            }
            else
            {
                ::OutputDebugStringA("@RemoveService ControlService() StopService failed");
                printf("StopService failed \n");
            }
        }
    }
    else
    {
        ::OutputDebugStringA("@RemoveService QueryServiceStatus() Query failed");
        printf("Query failed \n");
    }

    // 删除服务
    if (DeleteService(hSrv) == 0)
    {
        ::OutputDebugStringA("@RemoveService DeleteService() DeleteService failed");
        printf("DeleteService failed \n");
    }
    else
    {
        ::OutputDebugStringA("@RemoveService DeleteService() DeleteService Success");
        printf("DeleteService Success \n");
    }

    CloseServiceHandle(hScm);
    CloseServiceHandle(hSrv);

    return 0;
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,277评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,689评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,624评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,356评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,402评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,292评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,135评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,992评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,429评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,636评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,785评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,492评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,092评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,723评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,858评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,891评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,713评论 2 354