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, idi8->EnumDevices(DI8DEVCLASS_GAMECTRL, DIEnumDevicesCallback, (LPVOID)&joysticks,
DIEDFL_ATTACHEDONLY); DIEDFL_ATTACHEDONLY);
std::vector<DWORD> xinput_guids; std::unordered_set<DWORD> xinput_guids = GetXInputGUIDS();
GetXInputGUIDS(&xinput_guids);
for (DIDEVICEINSTANCE& joystick : joysticks) for (DIDEVICEINSTANCE& joystick : joysticks)
{ {
// skip XInput Devices // skip XInput Devices
if (std::find(xinput_guids.begin(), xinput_guids.end(), joystick.guidProduct.Data1) != if (xinput_guids.count(joystick.guidProduct.Data1))
xinput_guids.end())
{ {
continue; continue;
} }

View File

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

View File

@ -5,12 +5,12 @@
#pragma once #pragma once
#include <Windows.h> #include <Windows.h>
#include <vector> #include <unordered_set>
namespace ciface namespace ciface
{ {
namespace DInput 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\DInput.cpp" />
<ClCompile Include="ControllerInterface\DInput\DInputJoystick.cpp" /> <ClCompile Include="ControllerInterface\DInput\DInputJoystick.cpp" />
<ClCompile Include="ControllerInterface\DInput\DInputKeyboardMouse.cpp" /> <ClCompile Include="ControllerInterface\DInput\DInputKeyboardMouse.cpp" />
<ClCompile Include="ControllerInterface\DInput\XInputFilter.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\ExpressionParser.cpp" /> <ClCompile Include="ControllerInterface\ExpressionParser.cpp" />
<ClCompile Include="ControllerInterface\ForceFeedback\ForceFeedbackDevice.cpp" /> <ClCompile Include="ControllerInterface\ForceFeedback\ForceFeedbackDevice.cpp" />
<ClCompile Include="ControllerInterface\XInput\XInput.cpp" /> <ClCompile Include="ControllerInterface\XInput\XInput.cpp" />