Fix potential crash on exit if certain native resources weren't disposed

Finalizers in SpeexResampler and QuickNes assume that the underlying native dll still exists when they run, as they have to in order to successfully clean up leaked memory.  This is not true if those resolvers, which were stored as static fields themselves, had been finalized, which can only happen on app exit (because the static fields were readonly and always kept their value).  If a SpeexResampler or QuickNes was never disposed and itself lasted all the way to app exit, then the order of these two finalizers would be unspecified and a crash could happen.

In normal circumstances, this was only observable in DS core because apparently Suuper couldn't copy paste properly and missed the dispose part, but that was already fixed in 129d454a67.
This commit is contained in:
nattthebear 2020-06-19 23:10:56 -04:00
parent ffcb7cefb1
commit 1f966a4cc1
3 changed files with 20 additions and 8 deletions

View File

@ -25,11 +25,22 @@ namespace BizHawk.Common
});
private IntPtr _p;
private bool _eternal;
public DynamicLibraryImportResolver(string dllName)
/// <summary>
///
/// </summary>
/// <param name="dllName"></param>
/// <param name="eternal">
/// If true, the DLL will never be unloaded, even by god. Use this when you need lifetime semantics similar to [DllImport]
/// </param>
public DynamicLibraryImportResolver(string dllName, bool eternal = false)
{
static string ResolveFilePath(string orig) => orig[0] == '/' ? orig : asdf.Value.Select(dir => dir + orig).FirstOrDefault(File.Exists) ?? orig;
_p = OSTailoredCode.LinkedLibManager.LoadOrThrow(OSTailoredCode.IsUnixHost ? ResolveFilePath(dllName) : dllName);
_eternal = eternal;
if (eternal)
GC.SuppressFinalize(this);
}
public IntPtr GetProcAddrOrZero(string entryPoint) => OSTailoredCode.LinkedLibManager.GetProcAddrOrZero(_p, entryPoint);
@ -38,7 +49,8 @@ namespace BizHawk.Common
private void DisposeHelper()
{
if (_p == IntPtr.Zero) return; // already freed
if (_eternal || _p == IntPtr.Zero)
return;
OSTailoredCode.LinkedLibManager.FreeByPtr(_p);
_p = IntPtr.Zero;
}

View File

@ -15,11 +15,11 @@ namespace BizHawk.Emulation.Common
public class SpeexResampler : IDisposable, ISoundProvider
{
private static readonly LibSpeexDSP NativeDSP;
private static readonly IImportResolver NativeDLL;
static SpeexResampler()
{
NativeDLL = new DynamicLibraryImportResolver(OSTailoredCode.IsUnixHost ? "libspeexdsp.so.1" : "libspeexdsp.dll");
NativeDSP = BizInvoker.GetInvoker<LibSpeexDSP>(NativeDLL, CallingConventionAdapters.Native);
var resolver = new DynamicLibraryImportResolver(
OSTailoredCode.IsUnixHost ? "libspeexdsp.so.1" : "libspeexdsp.dll", eternal: true);
NativeDSP = BizInvoker.GetInvoker<LibSpeexDSP>(resolver, CallingConventionAdapters.Native);
}
// to accept an ISyncSoundProvider input

View File

@ -27,8 +27,9 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES
{
static QuickNES()
{
Resolver = new DynamicLibraryImportResolver($"libquicknes{(OSTailoredCode.IsUnixHost ? ".dll.so.0.7.0" : ".dll")}");
QN = BizInvoker.GetInvoker<LibQuickNES>(Resolver, CallingConventionAdapters.Native);
var resolver = new DynamicLibraryImportResolver(
$"libquicknes{(OSTailoredCode.IsUnixHost ? ".dll.so.0.7.0" : ".dll")}", eternal: true);
QN = BizInvoker.GetInvoker<LibQuickNES>(resolver, CallingConventionAdapters.Native);
QN.qn_setup_mappers();
}
@ -81,7 +82,6 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES
}
static readonly LibQuickNES QN;
static readonly DynamicLibraryImportResolver Resolver;
public IEmulatorServiceProvider ServiceProvider { get; }