Skip to content

Instantly share code, notes, and snippets.

@CookiePLMonster
Last active April 4, 2026 14:02
Show Gist options
  • Select an option

  • Save CookiePLMonster/250787d4bb8c0a831e3b9d7840cadad4 to your computer and use it in GitHub Desktop.

Select an option

Save CookiePLMonster/250787d4bb8c0a831e3b9d7840cadad4 to your computer and use it in GitHub Desktop.
DirectInput 3-7 test app, for testing dinputto8
#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