Make sure DirectInput objects are disposed properly. Also includes a better fix for thread safety issues (#722).

This commit is contained in:
J.D. Purcell 2017-04-08 11:49:04 -04:00
parent 5446a636ba
commit a2aba7e3c2
5 changed files with 165 additions and 96 deletions

View File

@ -1,5 +1,4 @@
using System; using System;
using System.Linq;
using System.Collections.Generic; using System.Collections.Generic;
using SlimDX; using SlimDX;
using SlimDX.DirectInput; using SlimDX.DirectInput;
@ -10,50 +9,77 @@ namespace BizHawk.Client.EmuHawk
{ {
// ********************************** Static interface ********************************** // ********************************** Static interface **********************************
static DirectInput dinput; private static readonly object _syncObj = new object();
public static List<GamePad> Devices; private static readonly List<GamePad> _devices = new List<GamePad>();
private static DirectInput _dinput;
public static void Initialize() public static void Initialize()
{ {
if (dinput == null) lock (_syncObj)
dinput = new DirectInput();
Devices = new List<GamePad>();
foreach (DeviceInstance device in dinput.GetDevices(DeviceClass.GameController, DeviceEnumerationFlags.AttachedOnly))
{ {
Console.WriteLine("joydevice: {0} `{1}`", device.InstanceGuid, device.ProductName); Cleanup();
if (device.ProductName.Contains("XBOX 360")) _dinput = new DirectInput();
continue; // Don't input XBOX 360 controllers into here; we'll process them via XInput (there are limitations in some trigger axes when xbox pads go over xinput)
var joystick = new Joystick(dinput, device.InstanceGuid); foreach (DeviceInstance device in _dinput.GetDevices(DeviceClass.GameController, DeviceEnumerationFlags.AttachedOnly))
joystick.SetCooperativeLevel(GlobalWin.MainForm.Handle, CooperativeLevel.Background | CooperativeLevel.Nonexclusive);
foreach (DeviceObjectInstance deviceObject in joystick.GetObjects())
{ {
if ((deviceObject.ObjectType & ObjectDeviceType.Axis) != 0) Console.WriteLine("joydevice: {0} `{1}`", device.InstanceGuid, device.ProductName);
joystick.GetObjectPropertiesById((int)deviceObject.ObjectType).SetRange(-1000, 1000);
}
joystick.Acquire();
GamePad p = new GamePad(device.InstanceName, device.InstanceGuid, joystick); if (device.ProductName.Contains("XBOX 360"))
Devices.Add(p); continue; // Don't input XBOX 360 controllers into here; we'll process them via XInput (there are limitations in some trigger axes when xbox pads go over xinput)
var joystick = new Joystick(_dinput, device.InstanceGuid);
joystick.SetCooperativeLevel(GlobalWin.MainForm.Handle, CooperativeLevel.Background | CooperativeLevel.Nonexclusive);
foreach (DeviceObjectInstance deviceObject in joystick.GetObjects())
{
if ((deviceObject.ObjectType & ObjectDeviceType.Axis) != 0)
joystick.GetObjectPropertiesById((int)deviceObject.ObjectType).SetRange(-1000, 1000);
}
joystick.Acquire();
GamePad p = new GamePad(device.InstanceName, device.InstanceGuid, joystick);
_devices.Add(p);
}
}
}
public static IEnumerable<GamePad> EnumerateDevices()
{
lock (_syncObj)
{
foreach (var device in _devices)
{
yield return device;
}
} }
} }
public static void UpdateAll() public static void UpdateAll()
{ {
foreach (var device in Devices.ToList()) lock (_syncObj)
device.Update(); {
foreach (var device in _devices)
{
device.Update();
}
}
} }
public static void CloseAll() public static void Cleanup()
{ {
if (Devices != null) lock (_syncObj)
{ {
foreach (var device in Devices) foreach (var device in _devices)
{
device.joystick.Dispose(); device.joystick.Dispose();
Devices.Clear(); }
_devices.Clear();
if (_dinput != null)
{
_dinput.Dispose();
_dinput = null;
}
} }
} }

View File

@ -1,7 +1,6 @@
using System; using System;
using System.Linq;
using System.Runtime.InteropServices;
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.InteropServices;
using SlimDX.XInput; using SlimDX.XInput;
#pragma warning disable 169 #pragma warning disable 169
@ -13,9 +12,9 @@ namespace BizHawk.Client.EmuHawk
{ {
// ********************************** Static interface ********************************** // ********************************** Static interface **********************************
public static List<GamePad360> Devices = new List<GamePad360>(); private static readonly object _syncObj = new object();
private static readonly List<GamePad360> _devices = new List<GamePad360>();
static bool IsAvailable; private static readonly bool _isAvailable = IsAvailable();
[DllImport("kernel32", SetLastError = true, EntryPoint = "GetProcAddress")] [DllImport("kernel32", SetLastError = true, EntryPoint = "GetProcAddress")]
static extern IntPtr GetProcAddressOrdinal(IntPtr hModule, IntPtr procName); static extern IntPtr GetProcAddressOrdinal(IntPtr hModule, IntPtr procName);
@ -43,9 +42,8 @@ namespace BizHawk.Client.EmuHawk
public XINPUT_GAMEPAD Gamepad; public XINPUT_GAMEPAD Gamepad;
} }
public static void Initialize() private static bool IsAvailable()
{ {
IsAvailable = false;
try try
{ {
//some users wont even have xinput installed. in order to avoid spurious exceptions and possible instability, check for the library first //some users wont even have xinput installed. in order to avoid spurious exceptions and possible instability, check for the library first
@ -70,35 +68,60 @@ namespace BizHawk.Client.EmuHawk
//don't remove this code. it's important to catch errors on systems with broken xinput installs. //don't remove this code. it's important to catch errors on systems with broken xinput installs.
//(probably, checking for the library was adequate, but lets not get rid of this anyway) //(probably, checking for the library was adequate, but lets not get rid of this anyway)
var test = new SlimDX.XInput.Controller(UserIndex.One).IsConnected; var test = new SlimDX.XInput.Controller(UserIndex.One).IsConnected;
IsAvailable = true; return true;
} }
} }
catch { } catch { }
if (!IsAvailable) return; return false;
}
//now, at this point, slimdx may be using one xinput, and we may be using another public static void Initialize()
//i'm not sure how slimdx picks its dll to bind to. {
//i'm not sure how troublesome this will be lock (_syncObj)
//maybe we should get rid of slimdx for this altogether {
_devices.Clear();
var c1 = new Controller(UserIndex.One); if (!_isAvailable)
var c2 = new Controller(UserIndex.Two); return;
var c3 = new Controller(UserIndex.Three);
var c4 = new Controller(UserIndex.Four);
if (c1.IsConnected) Devices.Add(new GamePad360(0,c1)); //now, at this point, slimdx may be using one xinput, and we may be using another
if (c2.IsConnected) Devices.Add(new GamePad360(1,c2)); //i'm not sure how slimdx picks its dll to bind to.
if (c3.IsConnected) Devices.Add(new GamePad360(2,c3)); //i'm not sure how troublesome this will be
if (c4.IsConnected) Devices.Add(new GamePad360(3,c4)); //maybe we should get rid of slimdx for this altogether
var c1 = new Controller(UserIndex.One);
var c2 = new Controller(UserIndex.Two);
var c3 = new Controller(UserIndex.Three);
var c4 = new Controller(UserIndex.Four);
if (c1.IsConnected) _devices.Add(new GamePad360(0, c1));
if (c2.IsConnected) _devices.Add(new GamePad360(1, c2));
if (c3.IsConnected) _devices.Add(new GamePad360(2, c3));
if (c4.IsConnected) _devices.Add(new GamePad360(3, c4));
}
}
public static IEnumerable<GamePad360> EnumerateDevices()
{
lock (_syncObj)
{
foreach (var device in _devices)
{
yield return device;
}
}
} }
public static void UpdateAll() public static void UpdateAll()
{ {
if(IsAvailable) lock (_syncObj)
foreach (var device in Devices.ToList()) {
foreach (var device in _devices)
{
device.Update(); device.Update();
}
}
} }
// ********************************** Instance Members ********************************** // ********************************** Instance Members **********************************

View File

@ -135,6 +135,14 @@ namespace BizHawk.Client.EmuHawk
Instance = new Input(); Instance = new Input();
} }
public static void Cleanup()
{
#if WINDOWS
KeyInput.Cleanup();
GamePad.Cleanup();
#endif
}
public enum InputEventType public enum InputEventType
{ {
Press, Release Press, Release
@ -338,9 +346,8 @@ namespace BizHawk.Client.EmuHawk
//FloatValues.Clear(); //FloatValues.Clear();
//analyze xinput //analyze xinput
for (int i = 0; i < GamePad360.Devices.Count; i++) foreach (var pad in GamePad360.EnumerateDevices())
{ {
var pad = GamePad360.Devices[i];
string xname = "X" + (pad.PlayerNumber) + " "; string xname = "X" + (pad.PlayerNumber) + " ";
for (int b = 0; b < pad.NumButtons; b++) for (int b = 0; b < pad.NumButtons; b++)
HandleButton(xname + pad.ButtonName(b), pad.Pressed(b)); HandleButton(xname + pad.ButtonName(b), pad.Pressed(b));
@ -355,10 +362,10 @@ namespace BizHawk.Client.EmuHawk
} }
//analyze joysticks //analyze joysticks
for (int i = 0; i < GamePad.Devices.Count; i++) foreach (var item in GamePad.EnumerateDevices().Select((n, i) => new { Device = n, Index = i }))
{ {
var pad = GamePad.Devices[i]; var pad = item.Device;
string jname = "J" + (i + 1) + " "; string jname = "J" + (item.Index + 1) + " ";
for (int b = 0; b < pad.NumButtons; b++) for (int b = 0; b < pad.NumButtons; b++)
HandleButton(jname + pad.ButtonName(b), pad.Pressed(b)); HandleButton(jname + pad.ButtonName(b), pad.Pressed(b));

View File

@ -6,50 +6,68 @@ namespace BizHawk.Client.EmuHawk
{ {
public static class KeyInput public static class KeyInput
{ {
private static DirectInput dinput; private static readonly object _syncObj = new object();
private static Keyboard keyboard; private static readonly List<KeyEvent> _eventList = new List<KeyEvent>();
private static KeyboardState state = new KeyboardState(); private static DirectInput _dinput;
private static Keyboard _keyboard;
public static void Initialize() public static void Initialize()
{ {
if (dinput == null) lock (_syncObj)
dinput = new DirectInput(); {
Cleanup();
if (keyboard == null || keyboard.Disposed) _dinput = new DirectInput();
keyboard = new Keyboard(dinput);
keyboard.SetCooperativeLevel(GlobalWin.MainForm.Handle, CooperativeLevel.Background | CooperativeLevel.Nonexclusive); _keyboard = new Keyboard(_dinput);
keyboard.Properties.BufferSize = 8; _keyboard.SetCooperativeLevel(GlobalWin.MainForm.Handle, CooperativeLevel.Background | CooperativeLevel.Nonexclusive);
_keyboard.Properties.BufferSize = 8;
}
} }
static List<KeyEvent> EmptyList = new List<KeyEvent>(); public static void Cleanup()
static List<KeyEvent> EventList = new List<KeyEvent>(); {
lock (_syncObj)
{
if (_keyboard != null)
{
_keyboard.Dispose();
_keyboard = null;
}
if (_dinput != null)
{
_dinput.Dispose();
_dinput = null;
}
}
}
public static IEnumerable<KeyEvent> Update() public static IEnumerable<KeyEvent> Update()
{ {
EventList.Clear(); lock (_syncObj)
if (keyboard.Acquire().IsFailure)
return EmptyList;
if (keyboard.Poll().IsFailure)
return EmptyList;
for (; ; )
{ {
var events = keyboard.GetBufferedData(); _eventList.Clear();
if (Result.Last.IsFailure)
return EventList;
if (events.Count == 0)
break;
foreach (var e in events)
{
foreach (var k in e.PressedKeys)
EventList.Add(new KeyEvent { Key = k, Pressed = true });
foreach (var k in e.ReleasedKeys)
EventList.Add(new KeyEvent { Key = k, Pressed = false });
}
}
return EventList; if (_keyboard == null || _keyboard.Acquire().IsFailure || _keyboard.Poll().IsFailure)
return _eventList;
for (; ; )
{
var events = _keyboard.GetBufferedData();
if (Result.Last.IsFailure || events.Count == 0)
break;
foreach (var e in events)
{
foreach (var k in e.PressedKeys)
_eventList.Add(new KeyEvent { Key = k, Pressed = true });
foreach (var k in e.ReleasedKeys)
_eventList.Add(new KeyEvent { Key = k, Pressed = false });
}
}
return _eventList;
}
} }
public struct KeyEvent public struct KeyEvent
@ -57,6 +75,5 @@ namespace BizHawk.Client.EmuHawk
public Key Key; public Key Key;
public bool Pressed; public bool Pressed;
} }
} }
} }

View File

@ -231,9 +231,7 @@ namespace BizHawk.Client.EmuHawk
GlobalWin.Sound = null; GlobalWin.Sound = null;
} }
GlobalWin.GL.Dispose(); GlobalWin.GL.Dispose();
#if WINDOWS Input.Cleanup();
GamePad.CloseAll();
#endif
} }
else else
{ // Display error message windows { // Display error message windows
@ -305,9 +303,7 @@ namespace BizHawk.Client.EmuHawk
GlobalWin.Sound = null; GlobalWin.Sound = null;
} }
GlobalWin.GL.Dispose(); GlobalWin.GL.Dispose();
#if WINDOWS Input.Cleanup();
GamePad.CloseAll();
#endif
} }
} }