XInput 和 DirectInput 功能比较

XInput 是一个 API,允许应用程序从 Xbox 控制器接收Windows输入。 本文档介绍 Xbox 控制器的 XInput 和 DirectInput 实现之间的差异,以及如何同时支持 XInput 设备和旧版 DirectInput 设备。

注意

不建议使用旧版 DirectInput,并且 DirectInput 不适用于Windows Microsoft Store应用。

新标准:XInput

XInput 现已可用于游戏开发。 这是 Xbox 和 Windows的新输入标准。 API 通过 DirectX SDK 提供,驱动程序可通过Windows 更新获得。

DirectInput 使用 XInput 有几个优点:

  • XInput 更易于使用,需要比 DirectInput 设置少
  • Xbox 和 Windows 编程都将使用相同的核心 API 集,使编程能够更轻松地转换跨平台
  • 将有一个大型的 Xbox 控制器基础
  • XInput 设备 (也就是说,仅当使用 XInput API 时,Xbox 控制器) 才会具有振动功能
  • Xbox 主机 (发布的未来控制器,即方向盘) 也将适用于Windows

将 Xbox 控制器与 DirectInput 配合使用

Xbox 控制器在 DirectInput 上正确枚举,可与 DirectInputAPIs 一起使用。 但是,DirectInput 实现中缺少 XInput 提供的某些功能:

  • 左右触发器按钮将充当单个按钮,而不是独立
  • 振动效果将不可用
  • 无法查询耳机设备

DirectInput 中的左右触发器的组合设计。 当没有用户与设备交互时,游戏始终假定 DirectInput 设备轴居中。 但是,当触发器未保持时,Xbox 控制器旨在注册最小值,而不是居中。 因此,较旧的游戏会假定用户交互。

解决方案是合并触发器,将一个触发器设置为正方向,另一个触发器设置为负方向,因此没有用户交互指示位于中心的“control”的 DirectInput

若要单独测试触发器值,必须使用 XInput。

XInput 和 DirectInput 并排

仅支持 XInput,游戏将不适用于旧 版 DirectInput 设备。 XInput 无法识别这些设备。

如果希望游戏支持旧 版 DirectInput 设备,则可以并排使用 DirectInput 和 XInput。 枚举 DirectInput 设备时,所有 DirectInput 设备都将正确枚举。 所有 XInput 设备都将显示为 XInput 和 DirectInput 设备,但不应通过 DirectInput 处理它们。 需要确定哪些 DirectInput 设备是旧设备,哪些设备是 XInput 设备,并从 DirectInput 设备的枚举中删除它们。

为此,请将此代码插入 DirectInput 枚举回调:

#include <wbemidl.h>
#include <oleauto.h>

#ifndef SAFE_RELEASE
#define SAFE_RELEASE(p) { if (p) { (p)->Release(); (p) = nullptr; } }
#endif

//-----------------------------------------------------------------------------
// Enum each PNP device using WMI and check each device ID to see if it contains 
// "IG_" (ex. "VID_045E&PID_028E&IG_00").  If it does, then it's an XInput device
// Unfortunately this information can not be found by just using DirectInput 
//-----------------------------------------------------------------------------
BOOL IsXInputDevice( const GUID* pGuidProductFromDirectInput )
{
    IWbemLocator*           pIWbemLocator = nullptr;
    IEnumWbemClassObject*   pEnumDevices = nullptr;
    IWbemClassObject*       pDevices[20] = {};
    IWbemServices*          pIWbemServices = nullptr;
    BSTR                    bstrNamespace = nullptr;
    BSTR                    bstrDeviceID = nullptr;
    BSTR                    bstrClassName = nullptr;
    bool                    bIsXinputDevice = false;
    
    // CoInit if needed
    HRESULT hr = CoInitialize(nullptr);
    bool bCleanupCOM = SUCCEEDED(hr);

    // So we can call VariantClear() later, even if we never had a successful IWbemClassObject::Get().
    VARIANT var = {};
    VariantInit(&var);

    // Create WMI
    hr = CoCreateInstance(__uuidof(WbemLocator),
        nullptr,
        CLSCTX_INPROC_SERVER,
        __uuidof(IWbemLocator),
        (LPVOID*)&pIWbemLocator);
    if (FAILED(hr) || pIWbemLocator == nullptr)
        goto LCleanup;

    bstrNamespace = SysAllocString(L"\\\\.\\root\\cimv2");  if (bstrNamespace == nullptr) goto LCleanup;
    bstrClassName = SysAllocString(L"Win32_PNPEntity");     if (bstrClassName == nullptr) goto LCleanup;
    bstrDeviceID = SysAllocString(L"DeviceID");             if (bstrDeviceID == nullptr)  goto LCleanup;
    
    // Connect to WMI 
    hr = pIWbemLocator->ConnectServer(bstrNamespace, nullptr, nullptr, 0L,
        0L, nullptr, nullptr, &pIWbemServices);
    if (FAILED(hr) || pIWbemServices == nullptr)
        goto LCleanup;

    // Switch security level to IMPERSONATE. 
    hr = CoSetProxyBlanket(pIWbemServices,
        RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, nullptr,
        RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE,
        nullptr, EOAC_NONE);
    if ( FAILED(hr) )
        goto LCleanup;

    hr = pIWbemServices->CreateInstanceEnum(bstrClassName, 0, nullptr, &pEnumDevices);
    if (FAILED(hr) || pEnumDevices == nullptr)
        goto LCleanup;

    // Loop over all devices
    for (;;)
    {
        ULONG uReturned = 0;
        hr = pEnumDevices->Next(10000, _countof(pDevices), pDevices, &uReturned);
        if (FAILED(hr))
            goto LCleanup;
        if (uReturned == 0)
            break;

        for (size_t iDevice = 0; iDevice < uReturned; ++iDevice)
        {
            // For each device, get its device ID
            hr = pDevices[iDevice]->Get(bstrDeviceID, 0L, &var, nullptr, nullptr);
            if (SUCCEEDED(hr) && var.vt == VT_BSTR && var.bstrVal != nullptr)
            {
                // Check if the device ID contains "IG_".  If it does, then it's an XInput device
                // This information can not be found from DirectInput 
                if (wcsstr(var.bstrVal, L"IG_"))
                {
                    // If it does, then get the VID/PID from var.bstrVal
                    DWORD dwPid = 0, dwVid = 0;
                    WCHAR* strVid = wcsstr(var.bstrVal, L"VID_");
                    if (strVid && swscanf_s(strVid, L"VID_%4X", &dwVid) != 1)
                        dwVid = 0;
                    WCHAR* strPid = wcsstr(var.bstrVal, L"PID_");
                    if (strPid && swscanf_s(strPid, L"PID_%4X", &dwPid) != 1)
                        dwPid = 0;

                    // Compare the VID/PID to the DInput device
                    DWORD dwVidPid = MAKELONG(dwVid, dwPid);
                    if (dwVidPid == pGuidProductFromDirectInput->Data1)
                    {
                        bIsXinputDevice = true;
                        goto LCleanup;
                    }
                }
            }
            VariantClear(&var);
            SAFE_RELEASE(pDevices[iDevice]);
        }
    }

LCleanup:
    VariantClear(&var);
    
    if(bstrNamespace)
        SysFreeString(bstrNamespace);
    if(bstrDeviceID)
        SysFreeString(bstrDeviceID);
    if(bstrClassName)
        SysFreeString(bstrClassName);
        
    for (size_t iDevice = 0; iDevice < _countof(pDevices); ++iDevice)
        SAFE_RELEASE(pDevices[iDevice]);

    SAFE_RELEASE(pEnumDevices);
    SAFE_RELEASE(pIWbemLocator);
    SAFE_RELEASE(pIWbemServices);

    if(bCleanupCOM)
        CoUninitialize();

    return bIsXinputDevice;
}


//-----------------------------------------------------------------------------
// Name: EnumJoysticksCallback()
// Desc: Called once for each enumerated joystick. If we find one, create a
//       device interface on it so we can play with it.
//-----------------------------------------------------------------------------
BOOL CALLBACK EnumJoysticksCallback( const DIDEVICEINSTANCE* pdidInstance,
                                     VOID* pContext )
{
    if( IsXInputDevice( &pdidInstance->guidProduct ) )
        return DIENUM_CONTINUE;

     // Device is verified not XInput, so add it to the list of DInput devices

     return DIENUM_CONTINUE;    
}

此代码稍有改进的版本位于旧版 DirectInput Joystick 示例中。

使用 XInput 入门

编程参考