Using multiple GameInput API versions in one application

Each GameInput API version, v0 through v4, defines its own set of COM interfaces. The GameInput runtime implements every version on the same underlying objects, so a single object, such as a device, has a v0 interface, a v4 interface, and an interface for each version in between. To switch an object from one version to another, call QueryInterface on it.

This lets you:

  • Add v4 features to an application that's built on an earlier API version, such as v0, without migrating the whole application at once.
  • Call SDKs, middleware, or engine plug-ins that were built against an earlier version of GameInput, even when the rest of your application uses v4.

Supported API versions

A GameInput runtime supports every API version that's lower than or equal to its own major version. For example, with the GameInput 4.0 runtime installed, every GameInput object implements the v0, v1, v2, v3, and v4 interfaces, and your application can use any combination of them.

Installed runtime API versions that you can use
4.x v0, v1, v2, v3, v4
3.x v0, v1, v2, v3
2.x v0, v1, v2
1.x v0, v1

If you request an API version that's newer than the installed runtime, QueryInterface and GameInputInitialize fail with E_NOINTERFACE. The highest API version that your application uses determines the minimum runtime version that it requires.

On PC, the installed runtime is the GameInput redistributable or a runtime that you deploy with your game. For more information, see GameInput NuGet package and GameInput sideloading. On XBOX, the runtime is included with the GDK, and you can also deploy a newer runtime with your game. For more information, see GameInput on XBOX consoles.

Set up your project

For new implementations, use the headers and GameInput.lib from the latest Microsoft.GameInput NuGet package. In the package, <GameInput.h> is the latest header, and each earlier header is in a versioned folder, such as <v0/GameInput.h> or <v2/GameInput.h>. The library exports GameInputInitialize, which creates GameInput for any API version that you request.

Libraries from earlier releases, such as the one in the GDK, also continue to work. For more information, see Keep an earlier library.

Include the v0 header

Starting with v1, each GameInput header declares its types in a versioned namespace, such as GameInput::v4. The v0 header declares its types in the global namespace instead. To use the v0 header together with the latest header, put the following boilerplate in a header of your own, such as GameInputV0.h:

// 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>
}

This header works as follows:

  • It includes the latest <GameInput.h> first. This declares the latest API in its own namespace, and includes the Windows and COM headers that the v0 header depends on, so the v0 header doesn't include them again inside the namespace.
  • The forward declarations make the typedefs in the v0 header declare new interfaces in GameInput::v0, instead of referring to interfaces that the latest header already declared.
  • Including <v0/GameInput.h> inside the namespace moves every v0 type, enumeration, and constant into GameInput::v0, such as GameInput::v0::IGameInput and GameInput::v0::GameInputKindGamepad.

In your v0 source files, include GameInputV0.h instead of <GameInput.h>, and add using namespace GameInput::v0; so that the existing code can keep using unqualified names. Any source file that includes GameInputV0.h can also use the latest API by its full name, such as GameInput::v4::IGameInput.

Example: Add v4 features to a v0 application

You don't need to rewrite your v0 code to start using v4 features. Keep the v0 code in its own source files, and put new v4 code in separate source files. Pass GameInput objects between the two as IUnknown*, and have each side call QueryInterface for the version that it needs.

Existing v0 code

The following v0 code creates GameInput and reads the gamepad. Compared to the original v0 code, only three things change:

  • It includes GameInputV0.h instead of <GameInput.h>.
  • It creates GameInput by calling GameInputInitialize with the v0 IID, because the latest library doesn't export the v0 GameInputCreate function.
  • It calls the new v4 code when the A button is pressed.
// 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();
    }
}

New v4 code

The new source file includes the v4 header and calls QueryInterface on the device that the v0 code passes in. It then changes the color of the controller's center light with IGameInputDevice::SetLightingColor and IGameInputDevice::ApplyLightingColors, which are new in v4. If the device doesn't have a center light, the call is ignored.

// 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();
    }
}

If QueryInterface fails, the installed runtime doesn't support v4. Your v0 code keeps working, and your application can skip the v4 features.

When all of your code uses v4, you can remove GameInputV0.h and the QueryInterface calls.

Keep an earlier library

If you continue to link a library from an earlier release, such as the one in the GDK, your existing v0 code doesn't need to change at all. Keep including the v0 header from that release and calling GameInputCreate. That call is forwarded to the installed runtime, so when the runtime is 4.0 or later, the object that it returns also implements the v4 interfaces, and the new v4 code shown earlier works without changes.

Earlier libraries don't export GameInputInitialize. Get v4 interfaces only by calling QueryInterface, and don't call the v4 GameInputCreate, which calls GameInputInitialize.

Rules for switching between versions

Interfaces from different API versions have the same names, such as IGameInput and IGameInputDevice, but they're different types with different methods. A pointer to a v0 interface isn't a pointer to a v4 interface, even though both refer to the same object.

Important

Casting isn't the same as calling QueryInterface. Always use QueryInterface to get an interface for a different API version. Never use static_cast, reinterpret_cast, or a C-style cast to convert between versions. A cast compiles, but calling a method through the converted pointer can call the wrong method, which leads to undefined behavior.

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));

Also keep these rules in mind:

  • Don't include another GameInput header after the v0 header. The v0 header defines its error codes, such as GAMEINPUT_E_DEVICE_DISCONNECTED, as macros, and those macros break the declarations in any GameInput header that's included after it. GameInputV0.h follows this rule by including the latest header first.
  • Returned objects use the version that you called. For example, GetDevice on a v4 reading returns a v4 device, and a device callback that's registered with the v4 IGameInput receives v4 devices. Call QueryInterface before you pass these objects to code that uses another version.
  • Each QueryInterface call adds a reference. Release the returned pointer when you no longer need it, just like any other COM interface pointer.
  • Check the result. When you request a newer API version, a failure means that the installed runtime doesn't support that version.
  • The v0 types in GameInputV0.h are separate C++ types. GameInput::v0::IGameInput and the global IGameInput from <v0/GameInput.h> alone describe the same interface, but C++ treats them as different types. If existing code or another library declares functions with the global v0 types, call those functions from a source file that includes only <v0/GameInput.h>.

Call SDKs that use an earlier API version

Some SDKs, middleware, and engine plug-ins are built against a specific GameInput API version and expect GameInput interfaces of that version. Your application can use a newer version and still call these components. Call QueryInterface to get the version that the component expects, and pass that pointer.

Put the call in a source file that includes the same GameInput header that the component uses. In the following example, ThirdPartySdk.h declares ThirdPartySdkInitialize(GameInput::v2::IGameInput*), and the rest of the application passes its GameInput object as IUnknown*:

// 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;
}

The same approach works in the other direction. If a component gives your application a GameInput object of an earlier version, call QueryInterface on it to get the v4 interface.

Because every version is implemented by the same object, the SDK and your application share devices, readings, and state. The SDK's API version must also be lower than or equal to the installed runtime version.

See also

GameInput API versioning
GameInput NuGet package
GameInput sideloading
GameInputInitialize
GameInput API reference