> ## 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。

每個 GameInput API 版本 (v0 到 v4) 都定義了自己的一組 COM 介面。GameInput 執行階段會在相同的基礎物件上實作每個版本，因此單一物件 (例如裝置) 具有 v0 介面、v4 介面，以及介於兩者之間每個版本的介面。若要將物件從一個版本切換到另一個版本，請在該物件上呼叫 [QueryInterface](https://docs.xbox.com/zh-TW/windows/win32/api/unknwn/nf-unknwn-iunknown-queryinterface\(refiid_void\))。

這可讓您：

* 將 v4 功能新增至以較早 API 版本 (例如 v0) 建置的應用程式，而不需一次移轉整個應用程式。
* 呼叫以較早版本 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-TW/reference/input/gameinput/functions/gameinputinitialize.md) 會失敗並傳回 `E_NOINTERFACE`。您的應用程式所使用的最高 API 版本，決定了它所需的最低執行階段版本。

在 PC 上，已安裝的執行階段是 GameInput 可轉散發套件，或您隨遊戲部署的執行階段。如需詳細資訊，請參閱 [GameInput NuGet 套件](https://docs.xbox.com/zh-TW/build/core-features/common/input/overviews/input-nuget.md)和 [GameInput 側載](https://docs.xbox.com/zh-TW/build/core-features/common/input/overviews/input-agility-redistributable.md)。在 XBOX 上，執行階段隨附於 GDK，而您也可以隨遊戲部署較新的執行階段。如需詳細資訊，請參閱 [XBOX 主機上的 GameInput](https://docs.xbox.com/zh-TW/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-TW/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 自己的命名空間中宣告該 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` 的原始程式檔，也都可以透過完整名稱 (例如 `GameInput::v4::IGameInput`) 使用最新的 API。

## 範例：將 v4 功能新增至 v0 應用程式

您不需要重寫 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-TW/reference/input/gameinput/interfaces/igameinputdevice/methods/igameinputdevice_setlightingcolor.md) 和 [IGameInputDevice::ApplyLightingColors](https://docs.xbox.com/zh-TW/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 介面，且不要呼叫會呼叫 `GameInputInitialize` 的 v4 `GameInputCreate`。

## 在版本之間切換的規則

不同 API 版本的介面具有相同的名稱 (例如 `IGameInput` 和 `IGameInputDevice`)，但它們是具有不同方法的不同類型。即使 v0 介面的指標和 v4 介面的指標參考的是相同的物件，v0 介面的指標也不是 v4 介面的指標。

> [!NOTE]
>
> 轉換 (cast) 與呼叫 `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-TW/build/core-features/common/input/overviews/input-versioning.md)\
[GameInput NuGet 套件](https://docs.xbox.com/zh-TW/build/core-features/common/input/overviews/input-nuget.md)\
[GameInput 側載](https://docs.xbox.com/zh-TW/build/core-features/common/input/overviews/input-agility-redistributable.md)\
[GameInputInitialize](https://docs.xbox.com/zh-TW/reference/input/gameinput/functions/gameinputinitialize.md)\
[GameInput API 參考](https://docs.xbox.com/zh-TW/reference/input/gameinput/gameinput_members.md)


## Related topics

- [GameInput API 版本控制](https://docs.xbox.com/zh-TW/build/core-features/common/input/overviews/input-versioning.md)
- [GameInputInitialize](https://docs.xbox.com/zh-TW/reference/input/gameinput/functions/gameinputinitialize.md)
- [XBOX 主機上的 GameInput](https://docs.xbox.com/zh-TW/build/core-features/common/input/overviews/input-xbox.md)
- [GameInputCreate](https://docs.xbox.com/zh-TW/reference/input/gameinput/functions/gameinputcreate.md)
- [IGameInputDevice::GetDeviceId](https://docs.xbox.com/zh-TW/reference/input/gameinput/interfaces/igameinputdevice/methods/igameinputdevice_getdeviceid.md)
