You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Windows服务中CreateProcess启动客户端时截图功能失效问题求助

问题描述

开发了一套由C++客户端和Python服务器组成的远程控制应用,具备获取客户端桌面截图并上传至服务器的功能。直接运行程序时所有功能正常,但通过Windows服务中的CreateProcess接口启动客户端后,仅截图功能无法工作,其余功能均正常。

服务器端获取截图代码

if (cState == State.GETTING_SCREENSHOT):
            message = "SCREENSHOT".encode('utf-8')
            MySend(client_sock , message)
            curr_number = 0
            with open("settings.txt" , 'r') as settings:
                curr_number_str = settings.readline()
                curr_number = int(curr_number_str)

            with open("settings.txt" , 'w') as settings:
                settings.write(str(curr_number + 1))
            
            screenshot_file = str(curr_number) + ".png"
            with open(screenshot_file , 'wb') as file:
                while True:
                    data = MyRecv(client_sock , 40000)
                    data_arr = bytearray(data)
                    if (data_arr[:10] == b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'):
                        continue
                    if (b'ENDOFF' in data):
                        break
                    file.write(data)
            cState = State.INPUT_COMMAND

客户端发送截图代码

case SENDING_SCREENSHOT_IN_PROGRESS:
{
    wchar_t PathToLocalAppDataFolder[MAX_PATH];
    if (FAILED(SHGetFolderPathW(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, PathToLocalAppDataFolder))) {
        LoadMessage("Error in ShGetFolderPathW");
        break;
    }
    std::wstring PathToScreenshot = std::wstring(PathToLocalAppDataFolder) + L"\\1.png";
    if (!SaveScreen(PathToScreenshot.c_str())) {
        LoadMessage("SaveScreen api-func failed");
        break;
    }

    HANDLE hFile = CreateFile(PathToScreenshot.c_str(), GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if (hFile == INVALID_HANDLE_VALUE) {
        LoadMessage("Can not open screenshot file");
        break;
    }

    DWORD dwRead = 1;
    BYTE buffSend[BIG_BUFFLEN];
    while (dwRead) {
        ZeroMemory(buffSend, BIG_BUFFLEN);
        dwRead = 0;
        ReadFile(hFile, buffSend, BIG_BUFFLEN, &dwRead, NULL);
        MySend(ClientSoc, (char*)buffSend, dwRead, 0);
    }
    CloseHandle(hFile);
    char message[SMALL_BUFFLEN] = "ENDOFFILE";
    MySend(ClientSoc, message, BIG_BUFFLEN, 0);

    DeleteFile(PathToScreenshot.c_str());
    cState = WAITING_FOR_INCOMING_COMMAND;
    break;
}

Windows服务启动客户端代码

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <iostream>
#include <shlobj_core.h>
#include <string>
#include "api.h"
#include <fstream>

#pragma comment(lib , "API.lib")
#pragma warning(disable:4996)


wchar_t SERVICE_NAME[] = L"RemoteController";
bool isCreate = FALSE;


SERVICE_STATUS ServiceStatus = { 0 };
SERVICE_STATUS_HANDLE hServiceStatus; //StatusHandle
HANDLE ServiceStopEvent = INVALID_HANDLE_VALUE;


VOID WINAPI ServiceMain(DWORD argc, LPTSTR* argv);
VOID WINAPI ServiceCtrlHandler(DWORD CtrlCode);
DWORD WINAPI ServiceWorkerThreadEntry(LPVOID lpParam);

VOID Install(VOID);
BOOL dirExists(const wchar_t* dir_path);

int main(int argc, char* argv[]) {
    FreeConsole();
    Install();

    SERVICE_TABLE_ENTRY ServiceTable[] = {
        {SERVICE_NAME , (LPSERVICE_MAIN_FUNCTION)ServiceMain} ,
        {NULL , NULL}
    };
    if (StartServiceCtrlDispatcher(ServiceTable) == FALSE) {
        LoadMessage("Service.exe : StartServiceCtrlDIspathcer error");
        return GetLastError();
    }
    return 0;
}


VOID WINAPI ServiceMain(DWORD argc, LPTSTR* argv) {
    DWORD Status = E_FAIL;
    hServiceStatus = RegisterServiceCtrlHandler(
        SERVICE_NAME,
        ServiceCtrlHandler
    );
    if (hServiceStatus == NULL) {
        LoadMessage("Service.exe : RegisterServiceCtrlHandler failed");
        return;
    }
    ZeroMemory(&ServiceStatus, sizeof(ServiceStatus));
    ServiceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS;
    ServiceStatus.dwControlsAccepted = 0;
    ServiceStatus.dwCurrentState = SERVICE_START_PENDING;
    ServiceStatus.dwWin32ExitCode = 0;
    ServiceStatus.dwServiceSpecificExitCode = 0;
    ServiceStatus.dwCheckPoint = 0;

    if (SetServiceStatus(hServiceStatus, &ServiceStatus) == FALSE) {
        LoadMessage("Service.exe : SetServiceStatus failed");
    }

    ServiceStopEvent = CreateEventW(NULL, TRUE, FALSE, L"RemoteControllerEvent");
    if (ServiceStopEvent == NULL) {
        ServiceStatus.dwControlsAccepted = 0;
        ServiceStatus.dwCurrentState = SERVICE_STOPPED;
        ServiceStatus.dwWin32ExitCode = GetLastError();
        ServiceStatus.dwCheckPoint = 1;
        if (SetServiceStatus(hServiceStatus, &ServiceStatus) == FALSE) {
            LoadMessage("Service.exe : SetServiceStatus failed");
            return;
        }
    }

    //start
    ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;
    ServiceStatus.dwCurrentState = SERVICE_RUNNING;
    ServiceStatus.dwWin32ExitCode = 0;
    ServiceStatus.dwCheckPoint = 0;

    if (SetServiceStatus(hServiceStatus, &ServiceStatus) == FALSE) {
        LoadMessage("Service.exe : SetServiceStatus failed");
    }
    HANDLE hThread = CreateThread(NULL, 0, ServiceWorkerThreadEntry, NULL, 0, NULL);
    WaitForSingleObject(hThread, INFINITE);

    CloseHandle(ServiceStopEvent);
    ServiceStatus.dwControlsAccepted = 0;
    ServiceStatus.dwCurrentState = SERVICE_STOPPED;
    ServiceStatus.dwWin32ExitCode = 0;
    ServiceStatus.dwCheckPoint = 3;

    if (!SetServiceStatus(hServiceStatus, &ServiceStatus) == FALSE) {
        LoadMessage("Service.exe : SetServiceStatus failed");
    }
    return;
}


VOID WINAPI ServiceCtrlHandler(DWORD CtrlCode) {
    switch (CtrlCode) {
    case SERVICE_CONTROL_STOP:
        if (ServiceStatus.dwCurrentState != SERVICE_RUNNING) break;

        ServiceStatus.dwControlsAccepted = 0;
        ServiceStatus.dwCurrentState = SERVICE_STOP_PENDING;
        ServiceStatus.dwWin32ExitCode = 0;
        ServiceStatus.dwCheckPoint = 4;
        if (SetServiceStatus(hServiceStatus, &ServiceStatus) == FALSE) {
            LoadMessage("Service.exe : SetServiceStatus failed");
            return;
        }
        SetEvent(ServiceStopEvent);
    default:
        break;
    }
}


BOOL dirExists(const wchar_t* dir_path) {
    DWORD dwAttributes = GetFileAttributes(dir_path);
    if (dwAttributes == INVALID_FILE_ATTRIBUTES) return FALSE;
    if (dwAttributes & FILE_ATTRIBUTE_DIRECTORY) return TRUE;
    return FALSE;
}


VOID Install(VOID) {

    SC_HANDLE schSCManager;
    SC_HANDLE schService;
    wchar_t curr_path[MAX_PATH];
    if (!GetModuleFileName(NULL, curr_path, MAX_PATH)) {
        LoadMessage("Service.exe : GetModuleFilename failed");
        return;
    }
    schSCManager = OpenSCManager(
        NULL, NULL, SC_MANAGER_ALL_ACCESS
    );
    if (schSCManager == NULL) {
        LoadMessage("Service.exe : OpenSCManager failed[with SC_MANAGER_ALL_ACCESS]");
        return;
    }
    schService = CreateService(
        schSCManager, SERVICE_NAME, SERVICE_NAME, SERVICE_ALL_ACCESS,
        SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS, SERVICE_AUTO_START, SERVICE_ERROR_NORMAL,
        curr_path, NULL, NULL, NULL, NULL, NULL);
    if (schService == NULL) {
        LoadMessage("Service.exe : CreateService failed"); 
        CloseServiceHandle(schSCManager);
        return;
    }

    if (StartService(schService , 0 , NULL) == 0){
        LoadMessage("Service.exe : Start service failed");
        CloseServiceHandle(schSCManager);
        CloseServiceHandle(schService);
        return;
    }
    
    LoadMessage("\tService instaled");
    CloseServiceHandle(schSCManager);
    CloseServiceHandle(schService);
}

DWORD WINAPI ServiceWorkerThreadEntry(LPVOID lpParam) {
    if (isCreate) {
        return ERROR_SUCCESS;
    }
    isCreate = TRUE;
    wchar_t ProgramToRun[MAX_PATH];
    ZeroMemory(ProgramToRun , sizeof(ProgramToRun));
    SHGetFolderPath(NULL, CSIDL_PROGRAM_FILES, NULL, 0, ProgramToRun);
    std::wstring tmp = std::wstring(ProgramToRun) + L"\\RemoteController\\MainClient.exe";
    STARTUPINFO si;
    ZeroMemory(&si, sizeof(si));
    si.cb = sizeof(si);
    PROCESS_INFORMATION pi;
    CreateProcess(tmp.c_str(), NULL, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi);
    return ERROR_SUCCESS;
}
原因分析

Windows服务默认运行在会话0,而用户的交互式桌面运行在会话1及以上。通过服务直接用CreateProcess启动的客户端会继承服务的会话0环境,无法访问用户的交互式桌面——截图功能需要调用系统API访问当前用户的显示设备和桌面窗口,因此会失败;而网络通信等不需要桌面交互的功能不受会话环境影响,所以能正常工作。

另外,服务中设置的SERVICE_INTERACTIVE_PROCESS仅允许服务在会话0显示UI,依然无法跨会话访问用户的交互式桌面。

解决方案

1. 核心思路:切换到用户会话启动客户端

获取当前登录用户的会话ID,通过CreateProcessAsUser()在用户会话中启动客户端,让客户端运行在能访问用户桌面的环境里。

2. 修改服务启动客户端的代码

替换原ServiceWorkerThreadEntry函数中的CreateProcess逻辑,示例代码如下:

DWORD WINAPI ServiceWorkerThreadEntry(LPVOID lpParam) {
    if (isCreate) {
        return ERROR_SUCCESS;
    }
    isCreate = TRUE;

    // 获取当前活跃的用户会话ID
    DWORD dwSessionId = WTSGetActiveConsoleSessionId();
    if (dwSessionId == 0xFFFFFFFF) {
        LoadMessage("Failed to get active session ID");
        return ERROR_FAILURE;
    }

    HANDLE hToken = NULL;
    // 获取会话对应的用户令牌
    if (!WTSQueryUserToken(dwSessionId, &hToken)) {
        LoadMessage("WTSQueryUserToken failed");
        return ERROR_FAILURE;
    }

    wchar_t ProgramToRun[MAX_PATH];
    ZeroMemory(ProgramToRun, sizeof(ProgramToRun));
    SHGetFolderPath(NULL, CSIDL_PROGRAM_FILES, NULL, 0, ProgramToRun);
    std::wstring tmp = std::wstring(ProgramToRun) + L"\\RemoteController\\MainClient.exe";

    STARTUPINFO si;
    ZeroMemory(&si, sizeof(si));
    si.cb = sizeof(si);
    si.lpDesktop = L"winsta0\\default"; // 指定用户的默认桌面
    PROCESS_INFORMATION pi;

    // 在用户会话中启动客户端
    if (!CreateProcessAsUser(
        hToken,
        tmp.c_str(),
        NULL,
        NULL,
        NULL,
        FALSE,
        CREATE_NEW_CONSOLE,
        NULL,
        NULL,
        &si,
        &pi
    )) {
        LoadMessage("CreateProcessAsUser failed");
        CloseHandle(hToken);
        return ERROR_FAILURE;
    }

    // 清理资源
    CloseHandle(hToken);
    CloseHandle(pi.hProcess);
    CloseHandle(pi.hThread);
    return ERROR_SUCCESS;
}

3. 补充:添加服务权限

CreateProcessAsUser需要服务具备SE_ASSIGNPRIMARYTOKEN_NAME和SE_INCREASE_QUOTA_NAME权限,需在服务启动时添加权限控制:

BOOL EnablePrivilege(LPCTSTR lpszPrivilege, BOOL bEnablePrivilege) {
    TOKEN_PRIVILEGES tp;
    HANDLE hToken;
    LUID luid;

    if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
        return FALSE;

    if (!LookupPrivilegeValue(NULL, lpszPrivilege, &luid)) {
        CloseHandle(hToken);
        return FALSE;
    }

    tp.PrivilegeCount = 1;
    tp.Privileges[0].Luid = luid;
    tp.Privileges[0].Attributes = bEnablePrivilege ? SE_PRIVILEGE_ENABLED : 0;

    if (!AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), (PTOKEN_PRIVILEGES)NULL, (PDWORD)NULL)) {
        CloseHandle(hToken);
        return FALSE;
    }

    CloseHandle(hToken);
    return TRUE;
}

在ServiceMain函数中启动工作线程前调用:

EnablePrivilege(SE_ASSIGNPRIMARYTOKEN_NAME, TRUE);
EnablePrivilege(SE_INCREASE_QUOTA_NAME, TRUE);

4. 额外注意事项

  • 确保客户端程序所在目录具备写入权限,避免截图保存失败;若放在Program Files下,需给目录添加普通用户的写入权限,或改为保存到用户的AppData目录。
  • 若系统存在多用户登录,需根据需求选择目标会话ID,上述代码默认获取当前活跃控制台的会话。

内容的提问来源于stack exchange,提问作者Егоров АРТЕМ

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 03:45:28