Last active
April 4, 2026 14:02
-
-
Save CookiePLMonster/250787d4bb8c0a831e3b9d7840cadad4 to your computer and use it in GitHub Desktop.
DirectInput 3-7 test app, for testing dinputto8
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #define NOMINMAX | |
| #include <Windows.h> | |
| #include <iostream> | |
| #pragma comment(lib, "dxguid.lib") | |
| #define DIRECTINPUT_VERSION 0x700 | |
| #include <dinput.h> | |
| #include <wil/com.h> | |
| #include <wil/win32_helpers.h> | |
| #define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN | |
| #include "doctest.h" | |
| static BOOL WINAPI DIEnumDevicesCallback(LPCDIDEVICEINSTANCE lpddi, LPVOID pvRef) | |
| { | |
| GUID* pGuid = static_cast<GUID*>(pvRef); | |
| *pGuid = lpddi->guidInstance; | |
| return DIENUM_STOP; | |
| } | |
| #if 0 | |
| static BOOL WINAPI DIEnumDeviceObjectsCallback(LPCDIDEVICEOBJECTINSTANCE lpddoi, LPVOID pvRef) | |
| { | |
| uint32_t* pEnumCount = static_cast<uint32_t*>(pvRef); | |
| std::wcout << (*pEnumCount)++ << ". dwOfs=" << static_cast<int32_t>(lpddoi->dwOfs) << ", tszName=" << lpddoi->tszName << ", dwType=" << std::hex << lpddoi->dwType << std::dec << std::endl; | |
| return DIENUM_CONTINUE; | |
| } | |
| HRESULT TestVersion(decltype(DirectInputCreateW) func, DWORD version) | |
| { | |
| std::cout << "Testing version 0x" << std::hex << version << ":" << std::endl; | |
| std::cout << "Enumerating objects WITHOUT data format set" << std::endl; | |
| { | |
| uint32_t enumCount = 1; | |
| dinputDevice->EnumObjects(DIEnumDeviceObjectsCallback, &enumCount, DIDFT_ALL); | |
| std::cout << std::endl; | |
| } | |
| { | |
| DIOBJECTDATAFORMAT joystick[] = { | |
| { &GUID_ZAxis, 0, DIDFT_AXIS|DIDFT_ANYINSTANCE, 0 }, | |
| { &GUID_XAxis, 12, DIDFT_AXIS|DIDFT_ANYINSTANCE, 0 }, | |
| { nullptr, DIJOFS_BUTTON(5), DIDFT_BUTTON|DIDFT_ANYINSTANCE, 0 }, | |
| { nullptr, DIJOFS_BUTTON(7), DIDFT_BUTTON|DIDFT_ANYINSTANCE, 0 }, | |
| { nullptr, DIJOFS_BUTTON(3), DIDFT_BUTTON|DIDFT_ANYINSTANCE, 0 }, | |
| }; | |
| const DIDATAFORMAT format = { | |
| sizeof(DIDATAFORMAT), | |
| sizeof(DIOBJECTDATAFORMAT), | |
| DIDF_ABSAXIS, | |
| sizeof(DIJOYSTATE), | |
| std::size(joystick), | |
| joystick | |
| }; | |
| RETURN_IF_FAILED(dinputDevice->SetDataFormat(&format)); | |
| } | |
| std::cout << "Enumerating objects WITH data format set" << std::endl; | |
| { | |
| uint32_t enumCount = 1; | |
| dinputDevice->EnumObjects(DIEnumDeviceObjectsCallback, &enumCount, DIDFT_ALL); | |
| std::cout << std::endl; | |
| } | |
| return S_OK; | |
| } | |
| #endif | |
| TEST_CASE("IDirectInput") | |
| { | |
| // Common setup | |
| wil::unique_hmodule dinputLib(LoadLibrary(TEXT("dinput"))); | |
| REQUIRE_NE(dinputLib, nullptr); | |
| auto pDirectInputCreate = GetProcAddressByFunctionDeclaration(dinputLib.get(), DirectInputCreateW); | |
| REQUIRE_NE(pDirectInputCreate, nullptr); | |
| auto version = GENERATE(0x300, 0x500, 0x50a, 0x5b2, 0x602, 0x61A, 0x700); | |
| wil::com_ptr<IDirectInput> dinput; | |
| REQUIRE_UNARY(SUCCEEDED(pDirectInputCreate(GetModuleHandle(nullptr), version, dinput.put(), nullptr))); | |
| // Test body | |
| SUBCASE("version") | |
| { | |
| wil::com_ptr<IDirectInput2> dinput2; | |
| wil::com_ptr<IDirectInput7> dinput7; | |
| REQUIRE_NOTHROW(dinput.copy_to(IID_IDirectInput2, dinput2.put_void())); | |
| REQUIRE_NOTHROW(dinput.copy_to(IID_IDirectInput7, dinput7.put_void())); | |
| CHECK_EQ(dinput, dinput2); | |
| CHECK_EQ(dinput, dinput7); | |
| CHECK_EQ(dinput.try_copy<IUnknown>(), dinput2.try_copy<IUnknown>()); | |
| CHECK_EQ(dinput.try_copy<IUnknown>(), dinput7.try_copy<IUnknown>()); | |
| } | |
| SUBCASE("charset") | |
| { | |
| wil::com_ptr<IDirectInputA> dinputA; | |
| wil::com_ptr<IDirectInputW> dinputW; | |
| REQUIRE_NOTHROW(dinput.copy_to(IID_IDirectInputA, dinputA.put_void())); | |
| REQUIRE_NOTHROW(dinput.copy_to(IID_IDirectInputW, dinputW.put_void())); | |
| // They don't -need- to be equal | |
| WARN_NE(static_cast<IUnknown*>(dinputA.get()), static_cast<IUnknown*>(dinputW.get())); | |
| CHECK_EQ(dinputA.try_copy<IUnknown>(), dinputW.try_copy<IUnknown>()); | |
| } | |
| } | |
| TEST_CASE("IDirectInputDevice") | |
| { | |
| // Common setup | |
| wil::unique_hmodule dinputLib(LoadLibrary(TEXT("dinput"))); | |
| REQUIRE_NE(dinputLib, nullptr); | |
| auto pDirectInputCreate = GetProcAddressByFunctionDeclaration(dinputLib.get(), DirectInputCreateW); | |
| REQUIRE_NE(pDirectInputCreate, nullptr); | |
| auto version = GENERATE(0x300, 0x500, 0x50a, 0x5b2, 0x602, 0x61A, 0x700); | |
| CHECK(FAILED(pDirectInputCreate(GetModuleHandle(nullptr), version, nullptr, nullptr))); | |
| wil::com_ptr<IDirectInput> dinput; | |
| REQUIRE(SUCCEEDED(pDirectInputCreate(GetModuleHandle(nullptr), version, dinput.put(), nullptr))); | |
| REQUIRE(dinput != nullptr); | |
| GUID instanceGuid(GUID_NULL); | |
| REQUIRE_UNARY(SUCCEEDED(dinput->EnumDevices(DIDEVTYPE_MOUSE, DIEnumDevicesCallback, &instanceGuid, DIEDFL_ATTACHEDONLY))); | |
| REQUIRE_NE(instanceGuid, GUID_NULL); | |
| SUBCASE("CreateDevice") | |
| { | |
| { | |
| CHECK(FAILED(dinput->CreateDevice(instanceGuid, nullptr, nullptr))); | |
| wil::com_ptr<IDirectInputDevice> dinputDevice; | |
| CHECK(dinput->CreateDevice(GUID_NULL, dinputDevice.put(), nullptr) == DIERR_DEVICENOTREG); | |
| } | |
| wil::com_ptr<IDirectInputDevice> dinputDevice; | |
| REQUIRE_UNARY(SUCCEEDED(dinput->CreateDevice(instanceGuid, dinputDevice.put(), nullptr))); | |
| // Test body | |
| // Versions | |
| { | |
| wil::com_ptr<IDirectInputDevice2> dinputDevice2; | |
| wil::com_ptr<IDirectInputDevice7> dinputDevice7; | |
| REQUIRE_NOTHROW(dinputDevice.copy_to(IID_IDirectInputDevice2, dinputDevice2.put_void())); | |
| REQUIRE_NOTHROW(dinputDevice.copy_to(IID_IDirectInputDevice7, dinputDevice7.put_void())); | |
| CHECK_EQ(dinputDevice, dinputDevice2); | |
| CHECK_EQ(dinputDevice, dinputDevice7); | |
| CHECK_EQ(dinputDevice.try_copy<IUnknown>(), dinputDevice2.try_copy<IUnknown>()); | |
| CHECK_EQ(dinputDevice.try_copy<IUnknown>(), dinputDevice7.try_copy<IUnknown>()); | |
| } | |
| // Charsets | |
| { | |
| wil::com_ptr<IDirectInputDeviceA> dinputDeviceA; | |
| wil::com_ptr<IDirectInputDeviceW> dinputDeviceW; | |
| REQUIRE_NOTHROW(dinputDevice.copy_to(IID_IDirectInputDeviceA, dinputDeviceA.put_void())); | |
| REQUIRE_NOTHROW(dinputDevice.copy_to(IID_IDirectInputDeviceW, dinputDeviceW.put_void())); | |
| // They don't -need- to be equal | |
| WARN_NE(static_cast<IUnknown*>(dinputDeviceA.get()), static_cast<IUnknown*>(dinputDeviceW.get())); | |
| CHECK_EQ(dinputDeviceA.try_copy<IUnknown>(), dinputDeviceW.try_copy<IUnknown>()); | |
| } | |
| } | |
| SUBCASE("CreateDeviceEx") | |
| { | |
| wil::com_ptr<IDirectInput7> dinput7; | |
| REQUIRE_NOTHROW(dinput.query_to(IID_IDirectInput7W, dinput7.put_void())); | |
| // Both should be possible to create with the same A/W call | |
| SUBCASE("ANSI") | |
| { | |
| wil::com_ptr<IDirectInputDevice7A> dinputDeviceA; | |
| CHECK(SUCCEEDED(dinput7->CreateDeviceEx(instanceGuid, IID_IDirectInputDevice7A, dinputDeviceA.put_void(), nullptr))); | |
| } | |
| SUBCASE("Unicode") | |
| { | |
| wil::com_ptr<IDirectInputDevice7W> dinputDeviceW; | |
| CHECK(SUCCEEDED(dinput7->CreateDeviceEx(instanceGuid, IID_IDirectInputDevice7W, dinputDeviceW.put_void(), nullptr))); | |
| } | |
| } | |
| } | |
| TEST_CASE("DirectInputCreate - Wrong versions") | |
| { | |
| // Common setup | |
| wil::unique_hmodule dinputLib(LoadLibrary(TEXT("dinput"))); | |
| REQUIRE_NE(dinputLib, nullptr); | |
| auto pDirectInputCreate = GetProcAddressByFunctionDeclaration(dinputLib.get(), DirectInputCreateW); | |
| REQUIRE_NE(pDirectInputCreate, nullptr); | |
| wil::com_ptr<IDirectInputW> dinput; | |
| CHECK(pDirectInputCreate(GetModuleHandle(nullptr), 0, dinput.put(), nullptr) == DIERR_NOTINITIALIZED); | |
| CHECK(pDirectInputCreate(GetModuleHandle(nullptr), 0x6FF, dinput.put(), nullptr) == DIERR_BETADIRECTINPUTVERSION); | |
| CHECK(pDirectInputCreate(GetModuleHandle(nullptr), 0x701, dinput.put(), nullptr) == DIERR_OLDDIRECTINPUTVERSION); | |
| } | |
| TEST_CASE("DirectInputCreateEx") | |
| { | |
| // Common setup | |
| wil::unique_hmodule dinputLib(LoadLibrary(TEXT("dinput"))); | |
| REQUIRE_NE(dinputLib, nullptr); | |
| auto pDirectInputCreateEx = GetProcAddressByFunctionDeclaration(dinputLib.get(), DirectInputCreateEx); | |
| REQUIRE_NE(pDirectInputCreateEx, nullptr); | |
| auto version = GENERATE(0x300, 0x500, 0x50a, 0x5b2, 0x602, 0x61A, 0x700); | |
| wil::com_ptr<IDirectInputA> dinputA; | |
| CHECK(SUCCEEDED(pDirectInputCreateEx(GetModuleHandle(nullptr), version, IID_IDirectInputA, dinputA.put_void(), nullptr))); | |
| wil::com_ptr<IDirectInputW> dinputW; | |
| CHECK(SUCCEEDED(pDirectInputCreateEx(GetModuleHandle(nullptr), version, IID_IDirectInputW, dinputW.put_void(), nullptr))); | |
| wil::com_ptr<IUnknown> dinputUnk; | |
| CHECK(pDirectInputCreateEx(GetModuleHandle(nullptr), version, IID_PPV_ARGS(dinputUnk.put()), nullptr) == E_NOINTERFACE); | |
| } | |
| TEST_CASE("DirectInputCreateEx - Wrong versions") | |
| { | |
| // Common setup | |
| wil::unique_hmodule dinputLib(LoadLibrary(TEXT("dinput"))); | |
| REQUIRE_NE(dinputLib, nullptr); | |
| auto pDirectInputCreateEx = GetProcAddressByFunctionDeclaration(dinputLib.get(), DirectInputCreateEx); | |
| REQUIRE_NE(pDirectInputCreateEx, nullptr); | |
| wil::com_ptr<IDirectInputA> dinputA; | |
| CHECK(pDirectInputCreateEx(GetModuleHandle(nullptr), 0, IID_IDirectInputA, dinputA.put_void(), nullptr) == DIERR_NOTINITIALIZED); | |
| CHECK(pDirectInputCreateEx(GetModuleHandle(nullptr), 0x6FF, IID_IDirectInputA, dinputA.put_void(), nullptr) == DIERR_BETADIRECTINPUTVERSION); | |
| CHECK(pDirectInputCreateEx(GetModuleHandle(nullptr), 0x701, IID_IDirectInputA, dinputA.put_void(), nullptr) == DIERR_OLDDIRECTINPUTVERSION); | |
| } | |
| template <REFCLSID C, REFIID I, typename T> | |
| struct ClassFactoryTemplate { | |
| static constexpr REFCLSID clsid = C; | |
| static constexpr REFIID iid = I; | |
| using type = T; | |
| }; | |
| TEST_CASE_TEMPLATE("ClassFactory", T, ClassFactoryTemplate<CLSID_DirectInput, IID_IDirectInput, IDirectInput>, | |
| ClassFactoryTemplate<CLSID_DirectInputDevice, IID_IDirectInputDevice, IDirectInputDevice>) | |
| { | |
| wil::unique_hmodule dinputLib(LoadLibrary(TEXT("dinput"))); | |
| REQUIRE_NE(dinputLib, nullptr); | |
| auto pGetClassObject = GetProcAddressByFunctionDeclaration(dinputLib.get(), DllGetClassObject); | |
| REQUIRE_NE(pGetClassObject, nullptr); | |
| { | |
| CHECK(FAILED(pGetClassObject(T::clsid, IID_IUnknown, nullptr))); | |
| wil::com_ptr<IUnknown> unknown; | |
| CHECK(SUCCEEDED(pGetClassObject(T::clsid, IID_PPV_ARGS(unknown.put())))); | |
| CHECK(unknown != nullptr); | |
| // Passing the DInput IID to GetClassObject directly should fail | |
| wil::com_ptr<typename T::type> di; | |
| di.attach((typename T::type*)1); | |
| CHECK(pGetClassObject(T::clsid, T::iid, reinterpret_cast<void**>(di.addressof())) == E_NOINTERFACE); | |
| CHECK(di.detach() == nullptr); | |
| } | |
| wil::com_ptr<IClassFactory> classFactory; | |
| CHECK(SUCCEEDED(pGetClassObject(T::clsid, IID_PPV_ARGS(classFactory.put())))); | |
| REQUIRE(classFactory != nullptr); | |
| { | |
| wil::com_ptr<IUnknown> createdObject1; | |
| CHECK(SUCCEEDED(classFactory->CreateInstance(nullptr, IID_PPV_ARGS(createdObject1.put())))); | |
| CHECK(createdObject1 != nullptr); | |
| } | |
| { | |
| wil::com_ptr<typename T::type> createdObject2; | |
| CHECK(SUCCEEDED(classFactory->CreateInstance(nullptr, T::iid, createdObject2.put_void()))); | |
| CHECK(createdObject2 != nullptr); | |
| } | |
| { | |
| wil::com_ptr<IStream> invalidObject; | |
| invalidObject.attach((IStream*)1); // Poison the pointer to check if it nulls | |
| CHECK(classFactory->CreateInstance(nullptr, IID_PPV_ARGS(invalidObject.addressof())) == E_NOINTERFACE); | |
| CHECK(invalidObject.detach() == nullptr); | |
| } | |
| CHECK(FAILED(classFactory->CreateInstance(nullptr, IID_IUnknown, nullptr))); | |
| } | |
| TEST_CASE("ClassFactory Initialize") | |
| { | |
| wil::unique_hmodule dinputLib(LoadLibrary(TEXT("dinput"))); | |
| REQUIRE_NE(dinputLib, nullptr); | |
| auto pGetClassObject = GetProcAddressByFunctionDeclaration(dinputLib.get(), DllGetClassObject); | |
| REQUIRE_NE(pGetClassObject, nullptr); | |
| SUBCASE("DInput") | |
| { | |
| wil::com_ptr<IClassFactory> factory; | |
| REQUIRE(SUCCEEDED(pGetClassObject(CLSID_DirectInput, IID_PPV_ARGS(factory.put())))); | |
| REQUIRE(factory != nullptr); | |
| // bad outer | |
| { | |
| wil::com_ptr<IUnknown> aggregated; | |
| CHECK(factory->CreateInstance(factory.get(), IID_PPV_ARGS(aggregated.put())) == CLASS_E_NOAGGREGATION); | |
| } | |
| wil::com_ptr<IDirectInput7> dinput7; | |
| REQUIRE(SUCCEEDED(factory->CreateInstance(nullptr, IID_IDirectInput7, dinput7.put_void()))); | |
| REQUIRE(dinput7 != nullptr); | |
| CHECK(SUCCEEDED(dinput7->Initialize(GetModuleHandle(nullptr), 0x700))); | |
| } | |
| SUBCASE("DInputDevice") | |
| { | |
| wil::com_ptr<IClassFactory> factory; | |
| REQUIRE(SUCCEEDED(pGetClassObject(CLSID_DirectInputDevice, IID_PPV_ARGS(factory.put())))); | |
| REQUIRE(factory != nullptr); | |
| // bad outer | |
| { | |
| wil::com_ptr<IUnknown> aggregated; | |
| CHECK(factory->CreateInstance(factory.get(), IID_PPV_ARGS(aggregated.put())) == CLASS_E_NOAGGREGATION); | |
| } | |
| wil::com_ptr<IDirectInputDevice7> dinputDevice7; | |
| REQUIRE(SUCCEEDED(factory->CreateInstance(nullptr, IID_IDirectInputDevice7, dinputDevice7.put_void()))); | |
| REQUIRE(dinputDevice7 != nullptr); | |
| CHECK(SUCCEEDED(dinputDevice7->Initialize(GetModuleHandle(nullptr), 0x700, GUID_SysMouse))); | |
| } | |
| } | |
| TEST_CASE("ClassFactory Effects") | |
| { | |
| wil::unique_hmodule dinputLib(LoadLibrary(TEXT("dinput"))); | |
| REQUIRE_NE(dinputLib, nullptr); | |
| auto pGetClassObject = GetProcAddressByFunctionDeclaration(dinputLib.get(), DllGetClassObject); | |
| REQUIRE_NE(pGetClassObject, nullptr); | |
| auto effect = GENERATE(GUID_ConstantForce, GUID_RampForce, GUID_Square, GUID_Sine, GUID_Triangle, GUID_SawtoothUp, GUID_SawtoothDown, | |
| GUID_Spring, GUID_Damper, GUID_Inertia, GUID_Friction, GUID_CustomForce); | |
| // It should be *impossible* to create effects this way | |
| wil::com_ptr<IClassFactory> classFactory; | |
| classFactory.attach((IClassFactory*)1); // Poison the pointer to check if it nulls | |
| CHECK(pGetClassObject(effect, IID_PPV_ARGS(classFactory.addressof())) == CLASS_E_CLASSNOTAVAILABLE); | |
| CHECK(classFactory.detach() == nullptr); | |
| } | |
| TEST_CASE("ClassFactory Wrong interfaces") | |
| { | |
| wil::unique_hmodule dinputLib(LoadLibrary(TEXT("dinput"))); | |
| REQUIRE_NE(dinputLib, nullptr); | |
| auto pGetClassObject = GetProcAddressByFunctionDeclaration(dinputLib.get(), DllGetClassObject); | |
| REQUIRE_NE(pGetClassObject, nullptr); | |
| wil::com_ptr<IClassFactory> dinputFactory; | |
| CHECK(SUCCEEDED(pGetClassObject(CLSID_DirectInput, IID_PPV_ARGS(dinputFactory.put())))); | |
| REQUIRE(dinputFactory != nullptr); | |
| wil::com_ptr<IDirectInputDevice7A> dinputDevice; | |
| CHECK(dinputFactory->CreateInstance(nullptr, IID_IDirectInputDevice7A, dinputDevice.put_void()) == E_NOINTERFACE); | |
| wil::com_ptr<IClassFactory> dinputDeviceFactory; | |
| CHECK(SUCCEEDED(pGetClassObject(CLSID_DirectInputDevice, IID_PPV_ARGS(dinputDeviceFactory.put())))); | |
| REQUIRE(dinputDeviceFactory != nullptr); | |
| wil::com_ptr<IDirectInput7A> dinput; | |
| CHECK(dinputDeviceFactory->CreateInstance(nullptr, IID_IDirectInput7A, dinput.put_void()) == E_NOINTERFACE); | |
| } | |
| TEST_CASE("DirectInput refcounts") | |
| { | |
| wil::unique_hmodule dinputLib(LoadLibrary(TEXT("dinput"))); | |
| REQUIRE_NE(dinputLib, nullptr); | |
| auto pDirectInputCreateEx = GetProcAddressByFunctionDeclaration(dinputLib.get(), DirectInputCreateEx); | |
| REQUIRE_NE(pDirectInputCreateEx, nullptr); | |
| wil::com_ptr<IDirectInput7> dinput; | |
| REQUIRE(SUCCEEDED(pDirectInputCreateEx(GetModuleHandle(nullptr), 0x700, IID_IDirectInput7, dinput.put_void(), nullptr))); | |
| REQUIRE(dinput != nullptr); | |
| SUBCASE("DInput") | |
| { | |
| CHECK(dinput.detach()->Release() == 0); | |
| } | |
| SUBCASE("DInputDevice") | |
| { | |
| GUID instanceGuid(GUID_NULL); | |
| REQUIRE_UNARY(SUCCEEDED(dinput->EnumDevices(DIDEVTYPE_MOUSE, DIEnumDevicesCallback, &instanceGuid, DIEDFL_ATTACHEDONLY))); | |
| REQUIRE_NE(instanceGuid, GUID_NULL); | |
| wil::com_ptr<IDirectInputDevice7> dinputDevice; | |
| REQUIRE_UNARY(SUCCEEDED(dinput->CreateDeviceEx(instanceGuid, IID_IDirectInputDevice7, dinputDevice.put_void(), nullptr))); | |
| dinput.reset(); | |
| CHECK(dinputDevice.detach()->Release() == 0); | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment