This document summarizes whether GameInputSharp.Core meets its goal of being a comprehensive C# wrapper for GameInput (game and desktop input) and identifies potential problems or bugs for developers who use it.
API alignment with Microsoft docs: Initialization, enumeration, device info layout, and COM method signatures align with the official GameInput API. See API_ALIGNMENT.md for current alignment (entry point, device kind filter, display name handling) and a checklist when changing interop or adding APIs. Historical changes are in CHANGELOG.md.
Security and safety: For risks (DLL loading, buffer handling, callbacks) and a hardening plan, see SECURITY.md.
| Area | Coverage | Notes |
|---|---|---|
| Initialization | ✓ | Dynamic load of GameInput.dll / GameInputRedist.dll; graceful failure (empty devices) when runtime missing. |
| Device enumeration | ✓ | Blocking enumeration via RegisterDeviceCallback + dispatcher; gamepad, keyboard, mouse; full device info (GetDeviceInfo), stable ID, and input mapper (axis/button mapping). |
| Gamepad state | ✓ | GetCurrentReading + GetGamepadStateFromReading; buttons, triggers, thumbsticks. |
| Mouse state | ✓ | GetCurrentReading + GetMouseStateFromReading; buttons, positions, wheel. |
| Keyboard state | ✓ | GetKeyCountFromReading + GetKeyStateFromReading; scan code, code point, virtual key, dead key. |
| Basic haptics | ✓ | SetRumbleState (left/right rumble). |
| Advanced haptics | ✓ | CreateForceFeedbackEffect (constant effect), motor index 0–7, envelope/magnitude. |
| Device callbacks | ✓ | Async device connect/disconnect via RegisterDeviceCallback + DispatchCallbacks; event + queue. |
| Lifecycle | ✓ | Release of devices, readings, callbacks, GCHandles; ObjectDisposedException on use-after-dispose. |
| API area | Exposed | Notes |
|---|---|---|
| IGameInput: GetCurrentTimestamp | ✓ | GameInputManager.GetCurrentTimestamp() |
| FindDeviceFromId / FindDeviceFromPlatformString | ✓ | FindDeviceFromId(byte[]), FindDeviceFromPlatformString(string) |
| SetFocusPolicy | ✓ | SetFocusPolicy(uint) |
| CreateAggregateDevice / DisableAggregateDevice | ✓ | CreateAggregateDevice(uint, out byte[]), DisableAggregateDevice(byte[]) |
| GetNextReading / GetPreviousReading | ✓ | GameInputInterop (caller must release reading) |
| IGameInputDevice: GetDeviceStatus | ✓ | GamepadDevice/KeyboardDevice/MouseDevice.GetDeviceStatus() |
| GetHapticInfo | ✓ | GamepadDevice.GetHapticInfo() → HapticInfo |
| SetForceFeedbackMotorGain / IsForceFeedbackMotorPoweredOn | ✓ | On GamepadDevice |
| GetExtraAxisCount/GetExtraButtonCount, GetExtraAxisIndexes/GetExtraButtonIndexes | ✓ | On all devices (gamepad, keyboard, mouse) |
| DirectInputEscape, CreateInputMapper, CreateRawDeviceReport, SendRawDeviceOutput | ✓ | On all devices |
| RegisterReadingCallback | ✓ | RegisterReadingCallback(device, inputKind), ReadingCallback event, pump via DispatchCallbacks |
| RegisterSystemButtonCallback, RegisterKeyboardLayoutCallback | ✓ | RegisterSystemButtonCallback, RegisterKeyboardLayoutCallback, SystemButtonPressed, KeyboardLayoutChanged events |
| Readings: GetSensorsState | ✓ | GetCurrentSensorsState(GamepadDevice) |
| GetArcadeStickState, GetFlightStickState, GetRacingWheelState | ✓ | GetCurrentArcadeStickState, etc. |
| GetReadingTimestamp / GetReadingInputKind | ✓ | GameInputManager.GetReadingTimestamp(reading), GameInputInterop for raw |
Verdict: The wrapper exposes the full v3 GameInput API surface used by the C# layer for typical scenarios (devices, readings, callbacks, haptics, raw device, focus, aggregation). The small gaps below are optional/low-level and do not block normal use.
Comparison is against the current (v3) GameInput API as documented in the GDK and used by the Microsoft.GameInput NuGet (GameInput::v3). The older v0 interface (different COM IIDs) exposes additional device methods (e.g. GetBatteryState, AcquireExclusiveRawDeviceAccess, PowerOff); this wrapper does not implement the v0 interface.
| C++ API | Exposed in C#? | Notes |
|---|---|---|
| IGameInputDispatcher::OpenWaitHandle | ✓ | CreateDispatcherWaitHandle() returns DispatcherWaitHandle with SafeWaitHandle; wait on it then call DispatchCallbacks. |
| IGameInputReading::GetDevice | ✓ | GetDeviceFromReading(reading); caller must release the returned device pointer. |
| IGameInputReading::GetControllerAxisCount / GetControllerAxisState | ✓ | GetControllerAxisCount(reading), GetControllerAxisState(reading, float[]). |
| IGameInputReading::GetControllerButtonCount / GetControllerButtonState | ✓ | GetControllerButtonCount(reading), GetControllerButtonState(reading, uint[]). |
| IGameInputReading::GetControllerSwitchCount / GetControllerSwitchState | ✓ | GetControllerSwitchCount(reading), GetControllerSwitchState(reading, int[]). |
| IGameInputReading::GetRawReport | ✓ | GetRawReportFromReading(reading, out IntPtr); do not release, valid only for reading lifetime. |
| IGameInput::StopCallback | ✓ | StopCallback(callbackToken); tokens from RegisterReadingCallback(..., out token) etc. |
All v3 reading and dispatcher APIs listed above are exposed. Callback registration overloads return a token for use with StopCallback(token) (fire-and-forget stop) or UnregisterCallback(token) (wait for completion).
These v3 APIs are either not exposed or only partially exposed. They are optional for typical game/desktop input; add if you need full parity for advanced scenarios.
| C++ API / area | Exposed in C#? | Notes |
|---|---|---|
| IGameInputMapper (methods) | ✓ | CreateInputMapper() returns an InputMapper wrapper (dispose when done). All mapper methods are exposed: GetGamepadAxisMappingInfo, GetGamepadButtonMappingInfo, GetFlightStickAxisMappingInfo, GetFlightStickButtonMappingInfo, GetRacingWheelAxisMappingInfo, GetRacingWheelButtonMappingInfo, GetArcadeStickButtonMappingInfo. Use AxisMappingInfo and ButtonMappingInfo for results. |
| IGameInputForceFeedbackEffect (as object) | ✓ | GamepadDevice.CreateForceFeedbackEffect returns a ForceFeedbackEffect wrapper (Start/Pause/Stop, SetGain, State, MotorIndex). All 11 effect kinds are exposed: Constant, Ramp, SineWave, SquareWave, TriangleWave, SawtoothUpWave, SawtoothDownWave, Spring, Friction, Damper, Inertia. Use ForceFeedbackConstantParams, ForceFeedbackRampParams, ForceFeedbackPeriodicParams, or ForceFeedbackConditionParams with the appropriate overload. Dispose the effect when done. |
| GameInputDeviceInfo (full struct) | ✓ | IInputDevice.GetDeviceInfo() returns a DeviceInfo instance with vendorId, productId, revisionNumber, usage, hardwareVersion, firmwareVersion, deviceId/deviceRootId bytes, deviceFamily, supportedInput, supportedRumbleMotors, supportedSystemButtons, containerId, displayName, pnpPath, Has* flags for nested info (keyboard/mouse/gamepad/forceFeedbackMotorInfo/inputReportInfo/outputReportInfo), and ForceFeedbackMotorCount/InputReportCount/OutputReportCount. |
| IGameInputRawDeviceReport (interface) | ✓ | CreateRawDeviceReport() returns a RawDeviceReport wrapper (dispose when done). The wrapper exposes GetReportInfo(), GetRawDataSize(), GetRawData(byte[]), SetRawData(byte[]), and UnsafePointer. Use device.SendRawDeviceOutput(report) to send; the report is not disposed by that call. |
| Typed state from a reading handle | ✓ | GameInputManager exposes GetGamepadStateFromReading(reading), GetMouseStateFromReading(reading), GetKeyboardStateFromReading(reading, maxKeys), GetSensorsStateFromReading(reading), GetArcadeStickStateFromReading(reading), GetFlightStickStateFromReading(reading), and GetRacingWheelStateFromReading(reading). Use these in a reading callback to obtain typed state from ReadingCallbackEventArgs.Reading without polling. |
-
Device callback and device identity
DeviceCallbackEventArgsexposesDeviceId(same value asIInputDevice.DeviceId) so callers can correlate without re-enumerating. Re-enumeration remains an option if you need the full device list after a change. -
GetDevices() returns new wrappers every time Each call to
GetDevices()runs a new enumeration and returns new wrapper instances (new refs to the same physical devices). If a developer caches “the gamepad” from an earlier call and later callsGetDevices()again and disposes only the new list, the old cached wrapper may still hold a ref; if they then dispose that cached wrapper, they release one ref. There is no double-release of the same pointer, but identity is not stable across calls (two differentGamepadDeviceinstances can represent the same physical device). UseDeviceIdto correlate, or TryGetDeviceByDeviceId(deviceId) to obtain a wrapper for a known ID (e.g. fromDeviceCallbackEventArgs.DeviceId) without holding a long-lived reference.
-
Thread safety
GameInputManagerclass remarks and USAGE.md state that the manager is not thread-safe; use from a single thread (e.g. game loop) or synchronize externally. Do not callDisposefrom inside aDeviceCallbackorReadingCallback.- Device callbacks are invoked from the thread that calls
DispatchCallbacks(dispatcher thread). If handlers do heavy work or touch UI, they may need to marshal to the correct thread.
-
Dispose race with device callbacks On
GameInputManager.Dispose(), we unregister the callback then free theGCHandle. A callback could theoretically still run (e.g. one already in the dispatcher). The static callback usestry/catchand checkshandle.Target; if the handle is freed, behavior depends onGCHandle.FromIntPtrandTarget. Unregistering first minimizes the window; documenting “do not call Dispose from inside a callback” and “call DispatchCallbacks only from one thread” reduces risk. -
Silent failures in interop Several interop helpers swallow exceptions (
catch { }orcatch { return false; }), e.g.SetRumble,PlayForceFeedbackConstant,GetDeviceInfoFromPtr. Failures (e.g. device disconnected) can appear as no-ops. Mitigated: when anILoggeris passed toGameInputManager, the wrapper logs key failure paths (e.g.RegisterReadingCallback/RegisterSystemButtonCallback/RegisterKeyboardLayoutCallbackreturning false,GetGamepadStateFromReadingfailing despite a valid reading). Check return values where provided and useGameInputManager(ILogger)for diagnostics.
-
Gamepad button bitmask
GamepadState.Buttonsis a rawuintbitmask. The library provides GameInputGamepadButtons (e.g.A,B,DPadUp) for testing bits. See MAPPING_REFERENCE.md andGamepadState/InputStateXML for usage. -
Reading timestamp
GameInputManager.GetReadingTimestamp(reading)returns the reading’s timestamp in microseconds (for latency or replay). Use withGetGamepadStateFromReading/GetMouseStateFromReading/ etc. when processing a reading from a callback.
- Ownership and disposal: USAGE.md states that the caller owns device wrappers from
GetDevices()and must dispose them; it also covers thread safety and callback rules. - DeviceCallbackEventArgs: Event args include
DeviceId(same asIInputDevice.DeviceId); use TryGetDeviceByDeviceId(deviceId) to get a wrapper for a known ID. - Device wrappers:
GamepadDevice,KeyboardDevice, andMouseDevicehave finalizers that release the native pointer ifDispose()is not called. - Thread safety and disposal: Manager class remarks and USAGE.md state that the manager is not thread-safe, to use a single thread or external sync, and not to call
Disposefrom inside a callback. - Reading timestamp:
GameInputManager.GetReadingTimestamp(reading)returns the reading’s timestamp in microseconds for use with state-from-reading APIs. - Logging:
GameInputManager(ILogger)is supported; the wrapper logs init/device-wrap failures and, when a logger is set, key registration and state-extraction failures (Debug level).
- Goal: GameInputSharp.Core is comprehensive for its scope: game/desktop input, haptics (basic and advanced, up to 8 locations per device), reading and device callbacks, sensors/motion, raw device reports, and native aggregate devices. Remaining Core gaps are optional v3 APIs (see §1.1) and the separate v0 device interface (e.g. GetBatteryState, exclusive raw access).
- Risks: The main developer-facing risks are mitigated: (1) device identity via
DeviceCallbackEventArgs.DeviceIdand TryGetDeviceByDeviceId; (2) thread safety and callback rules in class remarks and USAGE.md; (3) disposal ownership documented; (4) reading timestamp via GetReadingTimestamp(reading); (5) optional logging for key failure paths whenGameInputManager(ILogger)is used. Device wrappers have finalizers to avoid ref leaks.