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:
parent
ffcb7cefb1
commit
1f966a4cc1
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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; }
|
||||
|
||||
|
|
Loading…
Reference in New Issue