c/c++ 创建windows 服务程序

1 项目介绍

本次的项目是设计windows服务程序监听系统时间,对误差的时间进行修改,解决不连网下的本地时间的误差问题。

2 程序设计

当程序直接运行时为创建该程序为windows服务程序,创建的windows服务程序设置为开机自启且运行带参数"-k runservice"以进行区别为创建服务还是运行程序。


#define  _WINSOCK_DEPRECATED_NO_WARNINGS 
#define _CRT_SECURE_NO_WARNINGS 
#include <iostream>
#include <windows.h>
#include <winsvc.h>
#include <conio.h>
#include <winuser.h>
#include <tchar.h>

#pragma comment(lib,"Advapi32.lib")
#pragma comment(lib ,"User32.lib")
using namespace std;

TCHAR szServiceName[] = L"timeCompensation";    //服务名

SERVICE_STATUS serviceStatus;
SERVICE_STATUS_HANDLE serviceStatusHandle;
// 用于通知服务停止的事件句柄  
HANDLE hStopEvent = NULL;

/*
    @brief :递归创建文件夹
    @param    path : 创建文件夹的最后深路径
    @return     成功返回true ,失败返回false
*/
bool CreateDirectoryRecursive(const std::wstring& path);


/*
    @brief  服务控制主函数,这里实现对服务的控制,当在服务管理器上停止或其它操作时,将会运行此处代码
*/
void WINAPI ServiceCtrlHandler(DWORD controlCode);


/*
    @brief  服务主函数,这在里进行控制对服务控制的注册
*/
void WINAPI ServiceMain(DWORD argc, LPTSTR* argv);

/*
    @brief 程序日志,将内容写入系统日志
*/
void LogEvent(LPCTSTR pFormat, ...);

/*
    @brief  停止windows service 程序并删除程序
    @return 成功则返回true ,失败或者服务不存在则返回false
*/
BOOL Uninstall();

/*
    @brief 判断windows服务程序是否已经安装
    @return 如果已经安装,返回true  ,否则返回false
*/
BOOL IsInstalled();

/*
    @brief    安装windows service 程序
    @return 如果成功安装返回true ,否则返回false
*/
BOOL Install();

/*
    @brief  启动windows service服务程序
    @return 成功启动返回true ,否则返回false
*/
BOOL StartScService();


/*
    @brief 要实现的程序。更新时间,记录文件存在C:/ProgramData/timeUpdate/下的timeCmpensation.txt中,每24小时增加10.064S
*/
void updateTime();




bool CreateDirectoryRecursive(const std::wstring& path) {
    if (path.empty()) {
        return false;
    }

    // 检查路径是否已经存在  
    if (GetFileAttributesW(path.c_str()) != INVALID_FILE_ATTRIBUTES) {
        // 如果路径存在且是目录,返回成功  
        if (FILE_ATTRIBUTE_DIRECTORY & GetFileAttributesW(path.c_str())) {
            return true;
        }
        // 如果路径存在但不是目录,返回失败  
        return false;
    }

    // 拆分路径,找到最后一个\之前的部分(即父目录)  
    size_t found = path.find_last_of(L"\\/");
    if (found == std::wstring::npos) {
        // 如果没有找到\或/,则路径只包含一个目录名  
        return CreateDirectoryW(path.c_str(), NULL);
    }

    // 递归创建父目录  
    std::wstring parentPath = path.substr(0, found);
    if (!CreateDirectoryRecursive(parentPath)) {
        return false;
    }

    // 创建当前目录  
    return CreateDirectoryW(path.c_str(), NULL);
}

/*
void updateTime() {

    CreateDirectoryRecursive(L"C:/ProgramData/timeUpdate"); //递归创建文件夹

    //unsigned long size = 255;
    //WCHAR UserName_b[255];
    //char UserName_e[255];
    //GetUserName(UserName_b, &size);//GetUserName()函数同上
    //WideCharToMultiByte(CP_ACP, 0, UserName_b, -1, &UserName_e[0], 255, nullptr, nullptr);   //将wchar_t * 转换成char *

    char filePath[256];
    snprintf(filePath, 255, "C:/ProgramData/timeUpdate/timeCompensation.txt");

    time_t timep;
    time(&timep); //获取从1970至今过了多少秒,存入time_t类型的timep

    //获取精确到毫秒的时间
    SYSTEMTIME timenow;
    GetLocalTime(&timenow);
    //printf("%I64d秒 , %I64d毫秒\n", timenow.wSecond, timenow.wMilliseconds);


    //下一步llt,是转化成(毫秒级)的Unix时间戳,将tm的秒时间戳*1000加上timenow的毫秒时间戳得到毫秒时间戳
    time_t llt = timep * 1000 + timenow.wMilliseconds;
    FILE* file;
    if (0 != fopen_s(&file, filePath, "r+")) {
        //打不开说明服务刚创建,程序写下时间戳就退出
        if (0 != fopen_s(&file, filePath, "w")) {
            printf("打开文件失败\n");
            return;
        }
        fprintf(file, "%I64d", llt);
        fclose(file);
        return;
    }
    //获取文件内的时间戳
    char buf[64] = {};
    fread_s(buf, sizeof(buf), 1, sizeof(buf), file);
    fclose(file);

    time_t file_times = atoll(buf);

    if (llt - file_times >= 1000 * 60 * 60 * 24) {
        //每超过24小时就执行时间补偿,增加时间补偿 ,24小时/补偿10.064S ,但是获取本机电脑到设置完时间,有1-2毫秒的程序运行时间差,所以多加1毫秒
        time_t add_count = (llt - file_times) / (1000 * 60 * 60 * 24); //计算有多少个24小时
        time_t kllt = (llt - file_times) % (1000 * 60 * 60 * 24); //保存没有被算进去的毫秒数
        time_t llt_new = llt + add_count * (long long)(10064 + 1);   //更新现在的毫秒数
        time_t timestamp = llt_new / 1000;  //更新当前秒数
        time_t Milliseconds = llt_new % 1000;   //计算当前剩余的毫秒数


        tm t;
        _localtime64_s(&t, &timestamp);    //将更新后时间戳转化成标准时间,年份需要加1900,月份加1(0-11)
        timenow.wYear = t.tm_year + 1900;
        timenow.wMonth = t.tm_mon + 1;
        timenow.wDay = t.tm_mday;
        timenow.wHour = t.tm_hour;
        timenow.wMinute = t.tm_min;
        timenow.wSecond = t.tm_sec;
        timenow.wDayOfWeek = t.tm_wday;
        timenow.wMilliseconds = Milliseconds;
        SetLocalTime(&timenow);

        if (0 != fopen_s(&file, filePath, "w")) {
            printf("打开文件失败\n");
            return;
        }
        //更新保留的时间,在每24小时更新后,剩余的不足24小时加入下次计算
        time_t wllt = llt_new - kllt;
        fprintf(file, "%lld", wllt);
        fclose(file);

    }
    

}
*/

void updateTime() {

    CreateDirectoryRecursive(L"C:/ProgramData/timeUpdate"); //递归创建文件夹

    //unsigned long size = 255;
    //WCHAR UserName_b[255];
    //char UserName_e[255];
    //GetUserName(UserName_b, &size);//GetUserName()函数同上
    //WideCharToMultiByte(CP_ACP, 0, UserName_b, -1, &UserName_e[0], 255, nullptr, nullptr);   //将wchar_t * 转换成char *

    char filePath[256];
    snprintf(filePath, 255, "C:/ProgramData/timeUpdate/timeCompensation.txt");

    /*
    1秒 = 1000毫秒
    1毫秒 = 1000 微秒
    1微秒 = 1000 纳秒
    */
    // SYSTEMTIME结构表示的是本地时间,而FILETIME表示的是UTC时间(协调世界时)
    FILETIME ft;
    SYSTEMTIME st;
    GetSystemTimeAsFileTime(&ft); // 获取当前时间,此值表示自 1601 年 1 月 1 日开始以来的 100 纳秒单位数
    FileTimeToLocalFileTime(&ft, &ft);  //GetSystemTimeAsFileTime获取的是UTC的时间 ,而我们这里的是UTC+8 所以要转为本地时间再使用

    // 放入高32位和低32位得到64位,将FILETIME转换为64位整数
    ULARGE_INTEGER ui;
    ui.LowPart = ft.dwLowDateTime;
    ui.HighPart = ft.dwHighDateTime;


    time_t llt = ui.QuadPart;
    FILE* file;
    if (0 != fopen_s(&file, filePath, "r+")) {
        //打不开说明服务刚创建,程序写下时间戳就退出
        if (0 != fopen_s(&file, filePath, "w")) {
            printf("打开文件失败\n");
            return;
        }
        fprintf(file, "%lld", llt);
        fclose(file);
        return;
    }
    //获取文件内的时间戳
    char buf[64] = {};
    fread_s(buf, sizeof(buf), 1, sizeof(buf), file);
    fclose(file);

    time_t file_times = atoll(buf);

    if (llt - file_times >= 864000000000) { 
        //每超过24小时就执行时间补偿,增加时间补偿 ,24小时/补偿10.064S ,但是获取本机电脑到设置完时间
        time_t add_count = (llt - file_times) / 864000000000; //计算超出多少个24小时
        time_t kllt = llt - file_times % 864000000000;
        ui.QuadPart = ui.QuadPart + add_count * 10064 * 10000;   //计算时间增加了多少

        ft.dwLowDateTime = ui.LowPart ;
        ft.dwHighDateTime = ui.HighPart ;
        FileTimeToSystemTime(&ft, &st); //调整时间后将FILETIME转换为SYSTEMTIME

        SetLocalTime(&st);


        if (0 != fopen_s(&file, filePath, "w")) {
            printf("打开文件失败\n");
            return;
        }
        //更新保留的时间,在每24小时更新后,剩余的不足24小时加入下次计算
        time_t wllt = ui.QuadPart - kllt;   
        fprintf(file, "%lld", wllt);
        fclose(file);

    }
}


BOOL IsInstalled()
{
    BOOL bResult = FALSE;

    //打开服务控制管理器
    SC_HANDLE hSCM = ::OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);

    if (hSCM != NULL)
    {
        //打开服务
        SC_HANDLE hService = ::OpenService(hSCM, szServiceName, SERVICE_QUERY_CONFIG);
        if (hService != NULL)
        {
            bResult = TRUE;
            ::CloseServiceHandle(hService);
        }
        ::CloseServiceHandle(hSCM);
    }
    return bResult;
}


BOOL Install()
{
    if (IsInstalled())
        return TRUE;

    //打开服务控制管理器
    SC_HANDLE hSCM = ::OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
    if (hSCM == NULL)
    {
        MessageBox(NULL, L"Couldn't open service manager", szServiceName, MB_OK);
        return FALSE;
    }

    // Get the executable file path
    TCHAR szFilePath[MAX_PATH];
    ::GetModuleFileName(NULL, szFilePath, MAX_PATH);    //
    _tcscat(szFilePath , L" -k runservice");    //加参数,标明服务入口

    //创建服务
    SC_HANDLE hService = ::CreateService(
        hSCM, szServiceName, szServiceName,
        SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS,
        SERVICE_AUTO_START, SERVICE_ERROR_NORMAL,
        szFilePath, NULL, NULL, L"", NULL, NULL);

    if (hService == NULL)
    {
        ::CloseServiceHandle(hSCM);
        MessageBox(NULL, L"Couldn't create service", szServiceName, MB_OK);
        return FALSE;
    }

    ::CloseServiceHandle(hService);
    ::CloseServiceHandle(hSCM);
    return TRUE;
}

BOOL StartScService()
{
    if (!IsInstalled()) 
        return FALSE;

    //打开服务控制管理器
    SC_HANDLE hSCM = ::OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
    if (hSCM == NULL)
    {
        MessageBox(NULL, L"Couldn't open service manager", szServiceName, MB_OK);
        return FALSE;
    }

    SC_HANDLE hService = ::OpenService(hSCM, szServiceName, SERVICE_START);

    if (hService == NULL)
    {
        ::CloseServiceHandle(hSCM);
        MessageBox(NULL, L"Couldn't open service", szServiceName, MB_OK);
        return FALSE;
    }
    BOOL bDelete = ::StartService(hService , NULL ,NULL);
    if (!bDelete) {

        //【1056】-服务的实例已在运行中。
        char buf[255];
        snprintf(buf , 255,"StartService serviceHandle=%p -> fail(%ld)", hService, GetLastError());
        wchar_t wideStr[255] ;

        // 使用MultiByteToWideChar进行转换  
        // 这里使用CP_ACP,它代表ANSI代码页。根据你的具体情况,你可能需要使用不同的代码页  
        MultiByteToWideChar(CP_ACP, 0, buf, strlen(buf), wideStr, 255);

        MessageBox(NULL, wideStr, szServiceName, MB_OK);

    }
    else {
        wchar_t wideStr[255];
        char buf[255];
        snprintf(buf, 255, "StartService serviceHandle=%p -> succ", hService);
        // 使用MultiByteToWideChar进行转换  
        // 这里使用CP_ACP,它代表ANSI代码页。根据你的具体情况,你可能需要使用不同的代码页  
        MultiByteToWideChar(CP_ACP, 0, buf, strlen(buf), wideStr, 255);

        MessageBox(NULL, wideStr, szServiceName, MB_OK);
    }

    ::CloseServiceHandle(hService);
    ::CloseServiceHandle(hSCM);
    return TRUE;
}


void LogEvent(LPCTSTR pFormat, ...)
{
    TCHAR    chMsg[256];
    HANDLE  hEventSource;
    LPTSTR  lpszStrings[1];
    va_list pArg;

    va_start(pArg, pFormat);
    vswprintf_s(chMsg, pFormat, pArg);
    va_end(pArg);

    lpszStrings[0] = chMsg;

    hEventSource = RegisterEventSource(NULL, szServiceName);
    if (hEventSource != NULL)
    {
        ReportEvent(hEventSource, EVENTLOG_INFORMATION_TYPE, 0, 0, NULL, 1, 0, (LPCTSTR*)&lpszStrings[0], NULL);
        DeregisterEventSource(hEventSource);
    }
}


BOOL Uninstall()
{
    if (!IsInstalled())
        return TRUE;

    SC_HANDLE hSCM = ::OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);

    if (hSCM == NULL)
    {
        MessageBox(NULL, L"Couldn't open service manager", szServiceName, MB_OK);
        return FALSE;
    }

    SC_HANDLE hService = ::OpenService(hSCM, szServiceName, SERVICE_STOP | DELETE);

    if (hService == NULL)
    {
        ::CloseServiceHandle(hSCM);
        MessageBox(NULL, L"Couldn't open service", szServiceName, MB_OK);
        return FALSE;
    }
    SERVICE_STATUS status;
    ::ControlService(hService, SERVICE_CONTROL_STOP, &status);

    //删除服务
    BOOL bDelete = ::DeleteService(hService);
    ::CloseServiceHandle(hService);
    ::CloseServiceHandle(hSCM);

    if (bDelete)
        return TRUE;

    LogEvent(szServiceName,L"Service could not be deleted");
    return FALSE;
}



void WINAPI ServiceCtrlHandler(DWORD controlCode)
{
    switch (controlCode)
    {
    case SERVICE_CONTROL_STOP:
        // 设置服务状态为SERVICE_STOPPED_PENDING  
        serviceStatus.dwCurrentState = SERVICE_STOPPED;
        serviceStatus.dwWin32ExitCode = 0;
        SetServiceStatus(serviceStatusHandle, &serviceStatus);

        // 通知服务主循环停止  
        SetEvent(hStopEvent);
        return;
    default:
        break;
    }

}

void WINAPI ServiceMain(DWORD argc, LPTSTR* argv)
{
    // 注册服务控制处理函数
    serviceStatusHandle = RegisterServiceCtrlHandler(szServiceName, ServiceCtrlHandler);

    // 设置服务状态
    serviceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
    serviceStatus.dwCurrentState = SERVICE_RUNNING;
    serviceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;
    serviceStatus.dwWin32ExitCode = 0;
    serviceStatus.dwServiceSpecificExitCode = 0;
    serviceStatus.dwCheckPoint = 0;
    serviceStatus.dwWaitHint = 0;
    SetServiceStatus(serviceStatusHandle, &serviceStatus);

    // 创建一个自动重置的事件,用于通知停止  
    hStopEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
    if (hStopEvent == NULL)
    {
        // 处理错误  
        return;
    }

    // 执行服务的主要逻辑
    if (serviceStatus.dwCurrentState == SERVICE_RUNNING)
    {
        while (1) {
            //以下用于循环函数
            
            updateTime();
            // 检查停止事件是否被设置  
            if (WaitForSingleObject(hStopEvent, 0) == WAIT_OBJECT_0)
            {
                // 停止事件被触发,退出循环  
                break;
            }
            //休眠一小时
            Sleep(1000*60*60);

        }
    
    }
    // 停止服务时的清理逻辑
    // 设置服务状态为停止
    serviceStatus.dwCurrentState = SERVICE_STOPPED;
    SetServiceStatus(serviceStatusHandle, &serviceStatus);
}

int main(int argc, char* argv[]) {
    /* 
    SYSTEMTIME timenow_s ,timenow_e;
    GetLocalTime(&timenow_s);
    updateTime();
    GetLocalTime(&timenow_e);
    printf(" %d年, %d月,%d日,%d时,%d分,%d秒 ,%d毫秒\n",
        timenow_s.wYear, timenow_s.wMonth, timenow_s.wDay, timenow_s.wHour, timenow_s.wMinute, timenow_s.wSecond, timenow_s.wMilliseconds);
    printf(" %d年, %d月,%d日,%d时,%d分,%d秒 ,%d毫秒\n",
        timenow_e.wYear, timenow_e.wMonth, timenow_e.wDay, timenow_e.wHour, timenow_e.wMinute, timenow_e.wSecond, timenow_e.wMilliseconds);
    _getch();
    return 0;
    */

    //GetModuleFileName(NULL, filePath_b, static_cast<DWORD>(256)); //获取当前程序的绝对路径
    //WideCharToMultiByte(CP_ACP, 0, filePath_b, -1, &filePath_e[0], 256, nullptr, nullptr);   //将wchar_t * 转换成char *
    

    
    if (argc == 3) {
        if ( 0 == strcmp(argv[1], "-k")) {
            if (0 == strcmp(argv[2], "runservice")) {
                SERVICE_TABLE_ENTRY serviceTable[] =
                {
                    { szServiceName, ServiceMain },
                    { NULL, NULL }
                };
                // 启动服务控制分派器
                StartServiceCtrlDispatcher(serviceTable);
            }
        }
    }
    else {
        if (IsInstalled()) {
            Uninstall();
        }
        if (!Install()) {
            return 0;
        }
        StartScService();
    }
    return 0;
}

3 遇到的问题

3.1 存储路径错误导致操作文件异常

当初为了想其他程序一样规范,使保存时间戳的文件存储在 "C:\User\用户名" 下,使用 GetUserName 函数获取了用户名,但是它只能返回创建当前进程的用户名,并非真正的当前登录用户名。如果当前进程是服务进程,或者是由服务进程所创建,则GetUserName获得的用户名会是"SYSTEM"。当时找了半天资料都是权限和路径不对什么的,都解决不了,无法写入文件(获取到的不是用户名而是SYSTEM的话没有那个文件夹)无法自动创建。最终选择将保存的时间戳更改保存到 C:\ProgramData 目录下。

3.2 时间的换算

在修改时间时需要的是毫秒级别的,所以使用的是 SYSTEMTIME结构体 ,但是 GetLocalTime(Out LPSYSTEMTIME lpSystemTime);函数获取到的是日期,所以当时使用了time(time_t Time)来获取秒级的时间戳来进行转换,但是这样会显得麻烦,且两次获取的话还是可能会产生误差。所以后面使用了FILETIME结构体,使用GetSystemTimeAsFileTime(Out LPFILETIME lpSystemTimeAsFileTime)函数来获取100纳秒级的时间戳来进行计算。但是它获取到的为1601 年 1 月 1 日开始以来的 100 纳秒单位数,获取的是UTC的时间 。而我们这里的是需要的是本地时间,设置时间是使用的SetLocalTime(In CONST SYSTEMTIME lpSystemTime)函数也是使用本地时间,如果直接使用FileTimeToSystemTime(In CONST FILETIME* lpFileTime, Out LPSYSTEMTIME lpSystemTime)来将修改后的时间戳来转换的话会导致少8小时,所以获取时间戳后需要使用FileTimeToLocalFileTime(In CONST FILETIME* lpFileTime, Out LPFILETIME lpLocalFileTime)函数将时间转换为本地时间再使用。

4.参考文献

本文参考CSDN博主「Jackchenyj」的原创文章《用C/C++创建windows服务程序》 。原文链接:https://blog.csdn.net/chenyujing1234/article/details/8023816

posted @ 2024-06-13 14:43  梦中沉沦  阅读(116)  评论(0)    收藏  举报