Skip to content

Dynamically load XInput library at runtime maybe a better choice #3645

@Demonese

Description

@Demonese

Version/Branch of Dear ImGui:

Version: any
Branch: master

Back-end/Renderer/Compiler/OS

Back-ends: imgui_impl_win32.cpp
Compiler: VS2019
Operating System: any

My Issue/Question:

My application designed to run on multiple Windows versions and load XInput library dynamically. So disable links to XInput library via macros will cause link error.
I rewrite the implement of Win32 XInput on my project. #3646

Screenshots/Video

none

Standalone, minimal, complete and verifiable example: (see #2261)
global datas:

static HMODULE           g_hXInputDLL                                                  = NULL;
static DWORD (__stdcall *g_fXInputGetCapabilities)(DWORD, DWORD, XINPUT_CAPABILITIES*) = NULL;
static DWORD (__stdcall *g_fXInputGetState)(DWORD, XINPUT_STATE*)                      = NULL;
static XINPUT_STATE      g_tXInputState;

init:

ZeroMemory(&g_tXInputState, sizeof(XINPUT_STATE)); // clean it first
const wchar_t* xinput_dll_name[] = {
    L"xinput1_4.dll",   // Windows 8+
    L"xinput1_3.dll",   // DirectX SDK, Windows XP...
    L"xinput9_1_0.dll", // Windows Vista, 7...
};
typedef DWORD (__stdcall *f_XInputGetCapabilities)(DWORD, DWORD, XINPUT_CAPABILITIES*);
typedef DWORD (__stdcall *f_XInputGetState)(DWORD, XINPUT_STATE*);
for (size_t idx = 0; idx < 3; idx += 1)
{
    HMODULE dll = LoadLibraryW(xinput_dll_name[idx]);
    if (dll != NULL)
    {
        g_hXInputDLL = dll;
        g_fXInputGetCapabilities = (f_XInputGetCapabilities)GetProcAddress(dll, "XInputGetCapabilities");
        g_fXInputGetState = (f_XInputGetState)GetProcAddress(dll, "XInputGetState");
        break;
    }
}

shutdown:

g_fXInputGetCapabilities = NULL;
g_fXInputGetState = NULL;
if (g_hXInputDLL)
    FreeLibrary(g_hXInputDLL);
g_hXInputDLL = NULL;
ZeroMemory(&g_tXInputState, sizeof(XINPUT_STATE));

update:

ImGuiIO& io = ImGui::GetIO();
memset(io.NavInputs, 0, sizeof(io.NavInputs));
if ((io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) == 0)
    return;

// Calling XInputGetState() every frame on disconnected gamepads is unfortunately too slow.
// Instead we refresh gamepad availability by calling XInputGetCapabilities() _only_ after receiving WM_DEVICECHANGE.
if (g_WantUpdateHasGamepad)
{
    g_WantUpdateHasGamepad = false;
    XINPUT_CAPABILITIES caps;
    if (g_fXInputGetCapabilities)
        g_HasGamepad = (g_fXInputGetCapabilities(0, XINPUT_FLAG_GAMEPAD, &caps) == ERROR_SUCCESS);
    else
        g_HasGamepad = false;
    if (!g_HasGamepad)
        ZeroMemory(&g_tXInputState, sizeof(XINPUT_STATE)); // clear if no gamepad
}

io.BackendFlags &= ~ImGuiBackendFlags_HasGamepad;
if (g_HasGamepad && g_fXInputGetState)
{
    if (g_fXInputGetState(0, &g_tXInputState) == ERROR_SUCCESS)
    {
        const XINPUT_GAMEPAD& gamepad = g_tXInputState.Gamepad;
        io.BackendFlags |= ImGuiBackendFlags_HasGamepad;
        #define MAP_BUTTON(NAV_NO, BUTTON_ENUM)     { io.NavInputs[NAV_NO] = (gamepad.wButtons & BUTTON_ENUM) ? 1.0f : 0.0f; }
        #define MAP_ANALOG(NAV_NO, VALUE, V0, V1)   { float vn = (float)(VALUE - V0) / (float)(V1 - V0);\
                                                        if (vn > 1.0f) vn = 1.0f;\
                                                        if (vn > 0.0f && io.NavInputs[NAV_NO] < vn) io.NavInputs[NAV_NO] = vn; }
        MAP_BUTTON(ImGuiNavInput_Activate,      XINPUT_GAMEPAD_A);              // Cross / A
        MAP_BUTTON(ImGuiNavInput_Cancel,        XINPUT_GAMEPAD_B);              // Circle / B
        MAP_BUTTON(ImGuiNavInput_Menu,          XINPUT_GAMEPAD_X);              // Square / X
        MAP_BUTTON(ImGuiNavInput_Input,         XINPUT_GAMEPAD_Y);              // Triangle / Y
        MAP_BUTTON(ImGuiNavInput_DpadLeft,      XINPUT_GAMEPAD_DPAD_LEFT);      // D-Pad Left
        MAP_BUTTON(ImGuiNavInput_DpadRight,     XINPUT_GAMEPAD_DPAD_RIGHT);     // D-Pad Right
        MAP_BUTTON(ImGuiNavInput_DpadUp,        XINPUT_GAMEPAD_DPAD_UP);        // D-Pad Up
        MAP_BUTTON(ImGuiNavInput_DpadDown,      XINPUT_GAMEPAD_DPAD_DOWN);      // D-Pad Down
        MAP_BUTTON(ImGuiNavInput_FocusPrev,     XINPUT_GAMEPAD_LEFT_SHOULDER);  // L1 / LB
        MAP_BUTTON(ImGuiNavInput_FocusNext,     XINPUT_GAMEPAD_RIGHT_SHOULDER); // R1 / RB
        MAP_BUTTON(ImGuiNavInput_TweakSlow,     XINPUT_GAMEPAD_LEFT_SHOULDER);  // L1 / LB
        MAP_BUTTON(ImGuiNavInput_TweakFast,     XINPUT_GAMEPAD_RIGHT_SHOULDER); // R1 / RB
        MAP_ANALOG(ImGuiNavInput_LStickLeft,    gamepad.sThumbLX,  -XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE, -32768);
        MAP_ANALOG(ImGuiNavInput_LStickRight,   gamepad.sThumbLX,  +XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE, +32767);
        MAP_ANALOG(ImGuiNavInput_LStickUp,      gamepad.sThumbLY,  +XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE, +32767);
        MAP_ANALOG(ImGuiNavInput_LStickDown,    gamepad.sThumbLY,  -XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE, -32767);
        #undef MAP_BUTTON
        #undef MAP_ANALOG
    }
}

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions