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

Windows服务中调用GetRawInputDeviceList返回0的问题咨询

我来帮你拆解这个问题——其实Windows服务里调用GetRawInputDevices()不是完全不可行,但确实有Windows安全模型带来的限制,咱们一步步说清楚:

为什么服务里调用会返回0?

Windows服务默认运行在会话0隔离环境中,而Raw Input输入设备是和用户登录会话(通常是会话1及以上)绑定的。会话0是系统级会话,没有关联任何用户的物理输入设备上下文,所以默认调用GetRawInputDevices()自然会返回0;而你的非服务程序运行在当前登录用户的会话里,能正常访问设备,所以可以枚举到5-6个设备。

可行吗?怎么调整?

是可行的,但需要让你的设备枚举代码运行在用户会话中,而不是服务所在的会话0。有两种方案:

方案1:允许服务与桌面交互(测试可用,生产不推荐)

从Windows Vista开始,微软就不建议使用这个选项,因为会带来安全风险,但测试环境可以快速验证:

  • 打开服务管理器,找到你的服务,右键→属性→登录选项卡,勾选“允许服务与桌面交互”
  • 重启服务后,调用GetRawInputDevices()应该就能获取到设备列表了

方案2:启动用户会话子进程(生产推荐)

更好的做法是让服务在检测到用户登录时,启动一个运行在用户会话的子进程,在子进程里执行设备枚举。核心是用WTSGetActiveConsoleSessionId()获取活跃用户会话ID,再用CreateProcessAsUser()在该会话中启动程序。

服务端核心代码(启动子进程)

#include <windows.h>
#include <Wtsapi32.h>
#pragma comment(lib, "Wtsapi32.lib")

BOOL LaunchDeviceEnumeratorInUserSession() {
    // 获取当前活跃控制台会话ID(用户登录的会话)
    DWORD sessionId = WTSGetActiveConsoleSessionId();
    if (sessionId == 0xFFFFFFFF) {
        return FALSE; // 无活跃用户会话
    }

    // 获取用户会话的令牌
    HANDLE hUserToken = NULL;
    if (!WTSQueryUserToken(sessionId, &hUserToken)) {
        return FALSE;
    }

    STARTUPINFO si = {0};
    PROCESS_INFORMATION pi = {0};
    si.cb = sizeof(STARTUPINFO);
    // 指定用户会话的默认桌面
    si.lpDesktop = L"winsta0\\default";

    // 启动设备枚举子进程,替换成你的子程序路径
    BOOL success = CreateProcessAsUser(
        hUserToken,
        L"C:\\YourPath\\DeviceEnumerator.exe",
        NULL,
        NULL,
        NULL,
        FALSE,
        0,
        NULL,
        NULL,
        &si,
        &pi
    );

    // 清理资源
    CloseHandle(hUserToken);
    if (pi.hProcess) CloseHandle(pi.hProcess);
    if (pi.hThread) CloseHandle(pi.hThread);

    return success;
}

子进程代码(设备枚举,和非服务程序逻辑一致)

#include <windows.h>
#include <iostream>
#include <vector>

void EnumerateRawInputDevices() {
    UINT deviceCount = 0;
    // 先获取设备总数
    if (!GetRawInputDevices(NULL, &deviceCount, sizeof(RAWINPUTDEVICELIST))) {
        std::cout << "GetRawInputDevices failed, error code: " << GetLastError() << std::endl;
        return;
    }

    // 分配内存存储设备列表
    std::vector<RAWINPUTDEVICELIST> devices(deviceCount);
    if (!GetRawInputDevices(devices.data(), &deviceCount, sizeof(RAWINPUTDEVICELIST))) {
        std::cout << "GetRawInputDevices failed, error code: " << GetLastError() << std::endl;
        return;
    }

    std::cout << "Found " << deviceCount << " raw input devices:" << std::endl;
    for (UINT i = 0; i < deviceCount; ++i) {
        char deviceName[256] = {0};
        UINT nameSize = sizeof(deviceName);
        // 获取设备名称
        if (GetRawInputDeviceInfoA(devices[i].hDevice, RIDI_DEVICENAME, deviceName, &nameSize)) {
            std::cout << "Device " << i + 1 << ": " << deviceName << std::endl;
        }
    }

    // 这里添加把设备信息发送到服务端的逻辑
}

int main() {
    EnumerateRawInputDevices();
    return 0;
}
什么时候完全不可行?

如果你的服务必须严格运行在会话0,不能启动子进程,也不允许开启“与桌面交互”,那确实没办法获取用户的输入设备——这是Windows会话隔离机制的设计目的,防止服务被恶意利用来窃取用户输入或访问用户会话资源。

内容的提问来源于stack exchange,提问作者TOESTER

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 08:35:16