InputCommon: Fix slow startup on some Windows systems

Rewrite GetXInputGUIDS to use SetupAPI instead of WMI Queries. When
using a language pack where the system language and user/program
language differ, Windows starts taking a VERY long time (10+ seconds)
to complete Queries for Win32_PNPEntity objects (it's probably
translating every single string since it transfers every single one
from the WMI server into memory in the program).

Fixes Issue 9744.
This commit is contained in:
EmptyChaos 2016-08-10 23:30:36 +00:00
parent 8552ff5789
commit 17e4bd9575
4 changed files with 64 additions and 124 deletions

View File

@ -23,14 +23,11 @@ void InitJoystick(IDirectInput8* const idi8, HWND hwnd)
idi8->EnumDevices(DI8DEVCLASS_GAMECTRL, DIEnumDevicesCallback, (LPVOID)&joysticks,
DIEDFL_ATTACHEDONLY);
std::vector<DWORD> xinput_guids;
GetXInputGUIDS(&xinput_guids);
std::unordered_set<DWORD> xinput_guids = GetXInputGUIDS();
for (DIDEVICEINSTANCE& joystick : joysticks)
{
// skip XInput Devices
if (std::find(xinput_guids.begin(), xinput_guids.end(), joystick.guidProduct.Data1) !=
xinput_guids.end())
if (xinput_guids.count(joystick.guidProduct.Data1))
{
continue;
}

View File

@ -2,131 +2,81 @@
// Licensed under GPLv2+
// Refer to the license.txt file included.
// This function is contained in a separate file because WbemIdl.h pulls in files which break on
// /Zc:strictStrings, so this compilation unit is compiled without /Zc:strictStrings.
#include <WbemIdl.h>
#include <Windows.h>
#include <cwchar>
#include <unordered_set>
#include <vector>
#define SAFE_RELEASE(p) \
{ \
if (p) \
{ \
(p)->Release(); \
(p) = nullptr; \
} \
}
// clang-format off
#include <Windows.h>
#include <SetupAPI.h>
// clang-format on
namespace ciface
{
namespace DInput
{
//-----------------------------------------------------------------------------
// Modified some MSDN code to get all the XInput device GUID.Data1 values in a vector,
// faster than checking all the devices for each DirectInput device, like MSDN says to do
//-----------------------------------------------------------------------------
void GetXInputGUIDS(std::vector<DWORD>* guids)
// Code for enumerating hardware devices that use the XINPUT device driver.
// The MSDN recommended code suffers from massive performance problems when using language packs,
// if the system and user languages differ then WMI Queries become incredibly slow (multiple
// seconds). This is more or less equivalent and much faster.
std::unordered_set<DWORD> GetXInputGUIDS()
{
IWbemLocator* pIWbemLocator = nullptr;
IEnumWbemClassObject* pEnumDevices = nullptr;
IWbemClassObject* pDevices[20] = {0};
IWbemServices* pIWbemServices = nullptr;
BSTR bstrNamespace = nullptr;
BSTR bstrDeviceID = nullptr;
BSTR bstrClassName = nullptr;
DWORD uReturned = 0;
VARIANT var;
HRESULT hr;
static const GUID s_GUID_devclass_HID = {
0x745a17a0, 0x74d3, 0x11d0, {0xb6, 0xfe, 0x00, 0xa0, 0xc9, 0x0f, 0x57, 0xda}};
std::unordered_set<DWORD> guids;
// CoInit if needed
hr = CoInitialize(nullptr);
bool bCleanupCOM = SUCCEEDED(hr);
// Enumerate everything under the "Human Interface Devices" tree in the Device Manager
// NOTE: Some devices show up multiple times due to sub-devices, we rely on the set to
// prevent duplicates.
HDEVINFO setup_enum = SetupDiGetClassDevsW(&s_GUID_devclass_HID, nullptr, nullptr, DIGCF_PRESENT);
if (setup_enum == INVALID_HANDLE_VALUE)
return guids;
// 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.
CoSetProxyBlanket(pIWbemServices, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, nullptr,
RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, nullptr, EOAC_NONE);
hr = pIWbemServices->CreateInstanceEnum(bstrClassName, 0, nullptr, &pEnumDevices);
if (FAILED(hr) || pEnumDevices == nullptr)
goto LCleanup;
// Loop over all devices
while (true)
std::vector<wchar_t> buffer(128);
SP_DEVINFO_DATA dev_info;
dev_info.cbSize = sizeof(SP_DEVINFO_DATA);
for (DWORD i = 0; SetupDiEnumDeviceInfo(setup_enum, i, &dev_info); ++i)
{
// Get 20 at a time
hr = pEnumDevices->Next(10000, 20, pDevices, &uReturned);
if (FAILED(hr) || uReturned == 0)
break;
for (UINT iDevice = 0; iDevice < uReturned; ++iDevice)
// Need to find the size of the data and set the buffer appropriately
DWORD buffer_size = 0;
while (!SetupDiGetDeviceRegistryPropertyW(setup_enum, &dev_info, SPDRP_HARDWAREID, nullptr,
reinterpret_cast<BYTE*>(buffer.data()),
static_cast<DWORD>(buffer.size()), &buffer_size))
{
// 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(strVid, L"VID_%4X", &dwVid) != 1)
dwVid = 0;
WCHAR* strPid = wcsstr(var.bstrVal, L"PID_");
if (strPid && swscanf(strPid, L"PID_%4X", &dwPid) != 1)
dwPid = 0;
if (buffer_size > buffer.size())
buffer.resize(buffer_size);
else
break;
}
if (GetLastError() != ERROR_SUCCESS)
continue;
// Compare the VID/PID to the DInput device
DWORD dwVidPid = MAKELONG(dwVid, dwPid);
guids->push_back(dwVidPid);
// bIsXinputDevice = true;
}
}
SAFE_RELEASE(pDevices[iDevice]);
// HARDWAREID is a REG_MULTI_SZ
// There are multiple strings separated by NULs, the list is ended by an empty string.
for (std::size_t j = 0; buffer[j]; j += std::wcslen(&buffer[j]) + 1)
{
// XINPUT devices have "IG_xx" embedded in their IDs which is what we look for.
if (!std::wcsstr(&buffer[j], L"IG_"))
continue;
unsigned int vid = 0;
unsigned int pid = 0;
// Extract Vendor and Product IDs for matching against DirectInput's device list.
wchar_t* pos = std::wcsstr(&buffer[j], L"VID_");
if (!pos || !std::swscanf(pos, L"VID_%4X", &vid))
continue;
pos = std::wcsstr(&buffer[j], L"PID_");
if (!pos || !std::swscanf(pos, L"PID_%4X", &pid))
continue;
guids.insert(MAKELONG(vid, pid));
break;
}
}
LCleanup:
if (bstrNamespace)
SysFreeString(bstrNamespace);
if (bstrDeviceID)
SysFreeString(bstrDeviceID);
if (bstrClassName)
SysFreeString(bstrClassName);
for (UINT iDevice = 0; iDevice < 20; iDevice++)
SAFE_RELEASE(pDevices[iDevice]);
SAFE_RELEASE(pEnumDevices);
SAFE_RELEASE(pIWbemLocator);
SAFE_RELEASE(pIWbemServices);
if (bCleanupCOM)
CoUninitialize();
SetupDiDestroyDeviceInfoList(setup_enum);
return guids;
}
}
}
#undef SAFE_RELEASE

View File

@ -5,12 +5,12 @@
#pragma once
#include <Windows.h>
#include <vector>
#include <unordered_set>
namespace ciface
{
namespace DInput
{
void GetXInputGUIDS(std::vector<DWORD>* guids);
std::unordered_set<DWORD> GetXInputGUIDS();
}
}

View File

@ -41,14 +41,7 @@
<ClCompile Include="ControllerInterface\DInput\DInput.cpp" />
<ClCompile Include="ControllerInterface\DInput\DInputJoystick.cpp" />
<ClCompile Include="ControllerInterface\DInput\DInputKeyboardMouse.cpp" />
<ClCompile Include="ControllerInterface\DInput\XInputFilter.cpp">
<!--Disable /Zc:strictStrings so that wbem headers may compile-->
<!--
This is somewhat gross as it doesn't dynamically remove the option,
I really hope the issue is fixed in next VS release :(
-->
<AdditionalOptions>/Zo /Zc:inline /Zc:rvalueCast /volatile:iso</AdditionalOptions>
</ClCompile>
<ClCompile Include="ControllerInterface\DInput\XInputFilter.cpp" />
<ClCompile Include="ControllerInterface\ExpressionParser.cpp" />
<ClCompile Include="ControllerInterface\ForceFeedback\ForceFeedbackDevice.cpp" />
<ClCompile Include="ControllerInterface\XInput\XInput.cpp" />