> ## Documentation Index
> Fetch the complete documentation index at: https://docs.xbox.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 在一个应用程序中使用多个 GameInput API 版本

> 了解如何使用 QueryInterface 在同一应用程序中组合使用不同的 GameInput API 版本，例如 v0 和 v4。

从 v0 到 v4 的每个 GameInput API 版本都定义了自己的一组 COM 接口。GameInput 运行时在相同的底层对象上实现所有版本，因此单个对象（例如设备）同时具有 v0 接口、v4 接口以及介于两者之间的每个版本的接口。若要将对象从一个版本切换到另一个版本，请对其调用 [QueryInterface](https://docs.xbox.com/zh-CN/windows/win32/api/unknwn/nf-unknwn-iunknown-queryinterface\(refiid_void\))。

这使你可以：

* 向基于较早 API 版本（例如 v0）构建的应用程序添加 v4 功能，而无需一次性迁移整个应用程序。
* 调用针对较早版本 GameInput 构建的 SDK、中间件或引擎插件，即使应用程序的其余部分使用 v4。

## 支持的 API 版本

GameInput 运行时支持低于或等于其自身主版本号的每个 API 版本。例如，安装 GameInput 4.0 运行时后，每个 GameInput 对象都会实现 v0、v1、v2、v3 和 v4 接口，你的应用程序可以使用它们的任意组合。

| 已安装的运行时 | 可以使用的 API 版本 |
| - | - |
| 4.x | v0, v1, v2, v3, v4 |
| 3.x | v0, v1, v2, v3 |
| 2.x | v0, v1, v2 |
| 1.x | v0, v1 |

如果你请求的 API 版本比已安装的运行时更新，`QueryInterface` 和 [GameInputInitialize](https://docs.xbox.com/zh-CN/reference/input/gameinput/functions/gameinputinitialize.md) 会失败并返回 `E_NOINTERFACE`。应用程序使用的最高 API 版本决定了它所需的最低运行时版本。

在 PC 上，已安装的运行时是 GameInput 可再发行组件或你随游戏部署的运行时。有关详细信息，请参阅 [GameInput NuGet 包](https://docs.xbox.com/zh-CN/build/core-features/common/input/overviews/input-nuget.md)和 [GameInput 旁加载](https://docs.xbox.com/zh-CN/build/core-features/common/input/overviews/input-agility-redistributable.md)。在 XBOX 上，运行时包含在 GDK 中，你也可以随游戏部署更新的运行时。有关详细信息，请参阅 [XBOX 主机上的 GameInput](https://docs.xbox.com/zh-CN/build/core-features/common/input/overviews/input-xbox.md)。

## 设置项目

对于新的实现，请使用最新 [Microsoft.GameInput](https://www.nuget.org/packages/Microsoft.GameInput) NuGet 包中的头文件和 `GameInput.lib`。在该包中，`<GameInput.h>` 是最新的头文件，每个较早的头文件都位于带版本号的文件夹中，例如 `<v0/GameInput.h>` 或 `<v2/GameInput.h>`。该库导出 [GameInputInitialize](https://docs.xbox.com/zh-CN/reference/input/gameinput/functions/gameinputinitialize.md)，它会为你请求的任何 API 版本创建 GameInput。

较早版本中的库（例如 GDK 中的库）也仍可正常工作。有关详细信息，请参阅[保留较早的库](#keep-an-earlier-library)。

### 包含 v0 头文件

从 v1 开始，每个 GameInput 头文件都在带版本号的命名空间（例如 `GameInput::v4`）中声明其类型。而 v0 头文件在全局命名空间中声明其类型。若要将 v0 头文件与最新头文件一起使用，请将以下样板代码放入你自己的头文件中，例如 `GameInputV0.h`：

```cpp theme={null}
// GameInputV0.h: declares the v0 API in the GameInput::v0 namespace
#pragma once

#include <windows.h>
#include <GameInput.h>

// The v0 header isn't namespaced; its dependencies are already included above.
namespace GameInput::v0
{
    // Prevent the legacy typedefs from finding interfaces declared by the latest header.
    struct IGameInput;
    struct IGameInputReading;
    struct IGameInputDevice;
    struct IGameInputDispatcher;
    struct IGameInputForceFeedbackEffect;
    struct IGameInputRawDeviceReport;

    #include <v0/GameInput.h>
}
```

此头文件的工作方式如下：

* 它首先包含最新的 `<GameInput.h>`。这会在其自身的命名空间中声明最新的 API，并包含 v0 头文件所依赖的 Windows 和 COM 头文件，因此 v0 头文件不会在命名空间内再次包含它们。
* 前向声明使 v0 头文件中的 typedef 在 `GameInput::v0` 中声明新接口，而不是引用最新头文件已声明的接口。
* 在命名空间内包含 `<v0/GameInput.h>` 会将每个 v0 类型、枚举和常量移入 `GameInput::v0`，例如 `GameInput::v0::IGameInput` 和 `GameInput::v0::GameInputKindGamepad`。

在 v0 源文件中，请包含 `GameInputV0.h` 而不是 `<GameInput.h>`，并添加 `using namespace GameInput::v0;`，以便现有代码可以继续使用非限定名称。任何包含 `GameInputV0.h` 的源文件也可以通过完整名称使用最新的 API，例如 `GameInput::v4::IGameInput`。

## 示例：向 v0 应用程序添加 v4 功能

你无需重写 v0 代码即可开始使用 v4 功能。将 v0 代码保留在其自己的源文件中，并将新的 v4 代码放入单独的源文件中。在两者之间以 `IUnknown*` 形式传递 GameInput 对象，并让每一方针对其所需的版本调用 `QueryInterface`。

### 现有的 v0 代码

以下 v0 代码创建 GameInput 并读取游戏手柄。与原始 v0 代码相比，只有三处更改：

* 它包含 `GameInputV0.h` 而不是 `<GameInput.h>`。
* 它通过使用 v0 IID 调用 `GameInputInitialize` 来创建 GameInput，因为最新的库不导出 v0 `GameInputCreate` 函数。
* 按下 A 按钮时，它会调用新的 v4 代码。

```cpp theme={null}
// LegacyInput.cpp: existing code that uses the v0 API
#include "GameInputV0.h"

using namespace GameInput::v0;

// Implemented in NewInputFeatures.cpp.
void SetControllerColor(IUnknown* device);

IGameInput* g_gameInput = nullptr;

HRESULT InitializeInput()
{
    return GameInput::v4::GameInputInitialize(__uuidof(IGameInput), reinterpret_cast<void**>(&g_gameInput));
}

void UpdateGamepad()
{
    IGameInputReading* reading = nullptr;
    if (SUCCEEDED(g_gameInput->GetCurrentReading(GameInputKindGamepad, nullptr, &reading)))
    {
        GameInputGamepadState state;
        reading->GetGamepadState(&state);

        if (state.buttons & GameInputGamepadA)
        {
            IGameInputDevice* device = nullptr;
            reading->GetDevice(&device);
            SetControllerColor(device);
            device->Release();
        }

        reading->Release();
    }
}
```

### 新的 v4 代码

新的源文件包含 v4 头文件，并对 v0 代码传入的设备调用 `QueryInterface`。然后，它使用 v4 中新增的 [IGameInputDevice::SetLightingColor](https://docs.xbox.com/zh-CN/reference/input/gameinput/interfaces/igameinputdevice/methods/igameinputdevice_setlightingcolor.md) 和 [IGameInputDevice::ApplyLightingColors](https://docs.xbox.com/zh-CN/reference/input/gameinput/interfaces/igameinputdevice/methods/igameinputdevice_applylightingcolors.md) 更改控制器中心灯的颜色。如果设备没有中心灯，则会忽略该调用。

```cpp theme={null}
// NewInputFeatures.cpp: new code that uses the v4 API
#include <windows.h>
#include <v4/GameInput.h>

using namespace GameInput::v4;

void SetControllerColor(IUnknown* legacyDevice)
{
    IGameInputDevice* device = nullptr;

    // Fails with E_NOINTERFACE if the installed runtime is older than 4.0.
    if (SUCCEEDED(legacyDevice->QueryInterface(IID_PPV_ARGS(&device))))
    {
        device->SetLightingColor({ 0, 255, 0 }, GameInputLightingGroupCenterLight);
        device->ApplyLightingColors();
        device->Release();
    }
}
```

如果 `QueryInterface` 失败，则表示已安装的运行时不支持 v4。你的 v0 代码会继续正常工作，应用程序可以跳过 v4 功能。

当所有代码都使用 v4 时，你可以删除 `GameInputV0.h` 和 `QueryInterface` 调用。

### 保留较早的库

如果你继续链接较早版本中的库（例如 GDK 中的库），则现有的 v0 代码完全无需更改。继续包含该版本中的 v0 头文件并调用 `GameInputCreate` 即可。该调用会转发给已安装的运行时，因此当运行时为 4.0 或更高版本时，它返回的对象也会实现 v4 接口，前面所示的新 v4 代码无需更改即可正常工作。

较早的库不导出 `GameInputInitialize`。请仅通过调用 `QueryInterface` 获取 v4 接口，并且不要调用 v4 `GameInputCreate`，因为它会调用 `GameInputInitialize`。

## 在版本之间切换的规则

不同 API 版本中的接口具有相同的名称（例如 `IGameInput` 和 `IGameInputDevice`），但它们是具有不同方法的不同类型。指向 v0 接口的指针并不是指向 v4 接口的指针，即使两者引用的是同一个对象。

> [!NOTE]
>
> 强制转换与调用 `QueryInterface` 不同。始终使用 `QueryInterface` 获取其他 API 版本的接口。切勿使用 `static_cast`、`reinterpret_cast` 或 C 样式强制转换在版本之间进行转换。强制转换可以通过编译，但通过转换后的指针调用方法可能会调用错误的方法，从而导致未定义的行为。

```cpp theme={null}
GameInput::v0::IGameInputDevice* legacyDevice = /* ... */;

// Wrong: compiles, but calls the wrong methods at run time.
auto badDevice = reinterpret_cast<GameInput::v4::IGameInputDevice*>(legacyDevice);

// Correct: asks the object for its v4 interface.
GameInput::v4::IGameInputDevice* device = nullptr;
HRESULT hr = legacyDevice->QueryInterface(IID_PPV_ARGS(&device));
```

还请牢记以下规则：

* **不要在 v0 头文件之后包含其他 GameInput 头文件。** v0 头文件将其错误代码（例如 `GAMEINPUT_E_DEVICE_DISCONNECTED`）定义为宏，这些宏会破坏在其之后包含的任何 GameInput 头文件中的声明。`GameInputV0.h` 通过首先包含最新头文件来遵循此规则。
* **返回的对象使用你所调用的版本。** 例如，对 v4 读取调用 `GetDevice` 会返回 v4 设备，而通过 v4 `IGameInput` 注册的设备回调会收到 v4 设备。在将这些对象传递给使用其他版本的代码之前，请先调用 `QueryInterface`。
* **每次调用 `QueryInterface` 都会添加一个引用。** 与任何其他 COM 接口指针一样，不再需要返回的指针时请将其释放。
* **检查结果。** 当你请求较新的 API 版本时，失败意味着已安装的运行时不支持该版本。
* **`GameInputV0.h` 中的 v0 类型是单独的 C++ 类型。** `GameInput::v0::IGameInput` 与单独使用 `<v0/GameInput.h>` 时的全局 `IGameInput` 描述的是同一个接口，但 C++ 将它们视为不同的类型。如果现有代码或其他库使用全局 v0 类型声明函数，请从仅包含 `<v0/GameInput.h>` 的源文件中调用这些函数。

## 调用使用较早 API 版本的 SDK

某些 SDK、中间件和引擎插件是针对特定 GameInput API 版本构建的，并要求使用该版本的 GameInput 接口。你的应用程序可以使用较新的版本，同时仍然调用这些组件。调用 `QueryInterface` 获取组件所需的版本，然后传递该指针。

请将该调用放在包含与组件所用相同 GameInput 头文件的源文件中。在以下示例中，`ThirdPartySdk.h` 声明了 `ThirdPartySdkInitialize(GameInput::v2::IGameInput*)`，应用程序的其余部分以 `IUnknown*` 形式传递其 GameInput 对象：

```cpp theme={null}
// SdkBridge.cpp: calls an SDK that was built against the v2 API
#include <windows.h>
#include <v2/GameInput.h>
#include "ThirdPartySdk.h"

HRESULT InitializeThirdPartySdk(IUnknown* gameInputObject)
{
    GameInput::v2::IGameInput* gameInput = nullptr;
    HRESULT hr = gameInputObject->QueryInterface(IID_PPV_ARGS(&gameInput));
    if (SUCCEEDED(hr))
    {
        ThirdPartySdkInitialize(gameInput);
        gameInput->Release();
    }

    return hr;
}
```

同样的方法也适用于反方向。如果组件向你的应用程序提供较早版本的 GameInput 对象，请对其调用 `QueryInterface` 以获取 v4 接口。

由于每个版本都由同一个对象实现，因此 SDK 和你的应用程序共享设备、读取和状态。SDK 的 API 版本也必须低于或等于已安装的运行时版本。

## 另请参阅

[GameInput API 版本管理](https://docs.xbox.com/zh-CN/build/core-features/common/input/overviews/input-versioning.md)\
[GameInput NuGet 包](https://docs.xbox.com/zh-CN/build/core-features/common/input/overviews/input-nuget.md)\
[GameInput 旁加载](https://docs.xbox.com/zh-CN/build/core-features/common/input/overviews/input-agility-redistributable.md)\
[GameInputInitialize](https://docs.xbox.com/zh-CN/reference/input/gameinput/functions/gameinputinitialize.md)\
[GameInput API 参考](https://docs.xbox.com/zh-CN/reference/input/gameinput/gameinput_members.md)


## Related topics

- [GameInput API 版本管理](https://docs.xbox.com/zh-CN/build/core-features/common/input/overviews/input-versioning.md)
- [GameInputInitialize](https://docs.xbox.com/zh-CN/reference/input/gameinput/functions/gameinputinitialize.md)
- [XBOX 主机上的 GameInput](https://docs.xbox.com/zh-CN/build/core-features/common/input/overviews/input-xbox.md)
- [GameInputCreate](https://docs.xbox.com/zh-CN/reference/input/gameinput/functions/gameinputcreate.md)
- [IGameInput::EnableBackgroundInput](https://docs.xbox.com/zh-CN/reference/input/gameinput/interfaces/igameinput/methods/igameinput_enablebackgroundinput.md)
