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:
parent
8552ff5789
commit
17e4bd9575
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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" />
|
||||||
|
|
Loading…
Reference in New Issue