Skip to content

Commit

Permalink
Release native dll when shutting down a VST plugin to ensure resource…
Browse files Browse the repository at this point in the history
…s get released on right thread - fixes AV on vvvv shutdown

Also fixes a ref count leak when passing parameter changes to the audio processor
  • Loading branch information
azeno committed Nov 4, 2024
1 parent 064201d commit 4b01217
Show file tree
Hide file tree
Showing 12 changed files with 108 additions and 83 deletions.
6 changes: 3 additions & 3 deletions src/AGainFilter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public AGainFilter()
Module.TryCreate("C:\\Users\\elias\\source\\repos\\vst3sdk\\build\\VST3\\Debug\\again.vst3", out var module);
var factory = module.Factory;

ComWrappers cw = Utils.comWrappers;
ComWrappers cw = VstWrappers.Instance;

var info = module.Factory.ClassInfos[0];
component = factory.CreateInstance<IComponent>(info.ID);
Expand Down Expand Up @@ -163,8 +163,8 @@ unsafe void Process(AudioBufferStereo audioBufferStereo)
NumOutputs = 1,
Inputs = &inputs,
Outputs = &outputs,
InputParameterChanges = inputParameterChanges.GetComPtr(in IParameterChanges.Guid),
OutputParameterChanges = outputParameterChanges.GetComPtr(in IParameterChanges.Guid)
InputParameterChanges = inputParameterChanges.ComInterfacePtr,
OutputParameterChanges = outputParameterChanges.ComInterfacePtr
};

processor.process(in processData);
Expand Down
6 changes: 3 additions & 3 deletions src/EffectHost.Audio.cs
Original file line number Diff line number Diff line change
Expand Up @@ -117,9 +117,9 @@ private unsafe void DoFillBuffers(float[][] buffers, int offset, int numSamples)
NumOutputs = audioOutputBusses.Length,
Inputs = audioInputBuffers,
Outputs = audioOutputBuffers,
InputParameterChanges = (inputParameterChanges ?? s_noChanges).GetComPtr(in IParameterChanges.Guid),
OutputParameterChanges = outputParameterChanges.GetComPtr(in IParameterChanges.Guid),
InputEvents = inputEvents.GetComPtr(in IEventList.Guid),
InputParameterChanges = (inputParameterChanges ?? s_noChanges).ComInterfacePtr,
OutputParameterChanges = outputParameterChanges.ComInterfacePtr,
InputEvents = inputEvents.ComInterfacePtr,
//OutputEvents = outputEvents.GetComPtr(in IEventList.Guid),
ProcessContext = new nint(&processContext)
};
Expand Down
25 changes: 3 additions & 22 deletions src/EffectHost.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.ObjectPool;
using Sanford.Multimedia.Midi;
using System.Collections.Concurrent;
using System.Collections.Immutable;
Expand All @@ -21,26 +20,6 @@ namespace VL.Audio.VST;
using StatePin = Pin<IChannel<PluginState>>;
using AudioPin = Pin<IReadOnlyList<AudioSignal>>;

sealed class ParameterChangesPool : DefaultObjectPool<ParameterChanges>
{
public static readonly ParameterChangesPool Default = new();

public ParameterChangesPool() : base(new Policy())
{
}

sealed class Policy : PooledObjectPolicy<ParameterChanges>
{
public override ParameterChanges Create() => new ParameterChanges();

public override bool Return(ParameterChanges obj)
{
obj.Clear();
return true;
}
}
}

[GeneratedComClass]
internal partial class EffectHost : FactoryBasedVLNode, IVLNode, IComponentHandler, IComponentHandler2, IDisposable
{
Expand All @@ -56,6 +35,7 @@ internal partial class EffectHost : FactoryBasedVLNode, IVLNode, IComponentHandl

private readonly NodeContext nodeContext;
private readonly ILogger logger;
private readonly Module module;
private readonly PlugProvider plugProvider;
private readonly IComponent component;
private readonly IAudioProcessor processor;
Expand Down Expand Up @@ -102,7 +82,7 @@ public EffectHost(NodeContext nodeContext, IVLNodeDescription nodeDescription, s

NodeDescription = nodeDescription;

Module.TryCreate(modulePath, out var module);
module = Module.Create(modulePath);
plugProvider = PlugProvider.Create(module.Factory, info, s_context)!;
component = plugProvider.Component;
processor = (IAudioProcessor)component;
Expand Down Expand Up @@ -203,6 +183,7 @@ public void Dispose()
component.setActive(false);

plugProvider.Dispose();
module.Dispose();
}
}

Expand Down
25 changes: 25 additions & 0 deletions src/ParameterChangesPool.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using Microsoft.Extensions.ObjectPool;
using VL.Lib.Reactive;
using VST3.Hosting;

namespace VL.Audio.VST;

sealed class ParameterChangesPool : DefaultObjectPool<ParameterChanges>
{
public static readonly ParameterChangesPool Default = new();

public ParameterChangesPool() : base(new Policy())
{
}

sealed class Policy : PooledObjectPolicy<ParameterChanges>
{
public override ParameterChanges Create() => new ParameterChanges();

public override bool Return(ParameterChanges obj)
{
obj.Clear();
return true;
}
}
}
6 changes: 2 additions & 4 deletions src/Utils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ namespace VL.Audio.VST;

static class Utils
{
public static readonly ComWrappers comWrappers = new StrategyBasedComWrappers();

public static void ReleaseComObject(this object obj)
{
if (obj is ComObject com && IsUniqueInstance(com))
Expand All @@ -31,9 +29,9 @@ public static nint GetComPtr(this object? obj, in Guid guid)
if (obj is null)
return default;

var pUnk = comWrappers.GetOrCreateComInterfaceForObject(obj, CreateComInterfaceFlags.None);
var pUnk = VstWrappers.Instance.GetOrCreateComInterfaceForObject(obj, CreateComInterfaceFlags.None);
Marshal.QueryInterface(pUnk, in guid, out var pInt);
// TODO: Marshal.Release(pUnk) ?
Marshal.Release(pUnk);
return pInt;
}

Expand Down
2 changes: 1 addition & 1 deletion src/VST3/Hosting/AttributeList.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
namespace VST3.Hosting;

[GeneratedComClass]
sealed partial class AttributeList : IAttributeList
sealed partial class AttributeList : VstObject<IAttributeList>, IAttributeList
{
record BinaryBlob(nint Data, uint SizeInBytes);

Expand Down
2 changes: 1 addition & 1 deletion src/VST3/Hosting/EventList.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
namespace VST3.Hosting;

[GeneratedComClass]
internal partial class EventList : IEventList
internal partial class EventList : VstObject<IEventList>, IEventList
{
private readonly List<Event> events = new List<Event>();

Expand Down
19 changes: 3 additions & 16 deletions src/VST3/Hosting/HostApp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
namespace VST3.Hosting;

[GeneratedComClass]
sealed partial class HostApp : IHostApplication, IPlugInterfaceSupport
sealed partial class HostApp : VstObject<IHostApplication>, IHostApplication, IPlugInterfaceSupport
{
private readonly HashSet<Guid> supportedInterfaces;

Expand All @@ -16,25 +16,12 @@ public HostApp(IEnumerable<Type> supportedInterfaces)
nint IHostApplication.createInstance(Guid cid, Guid _iid)
{
if (cid == typeof(IMessage).GUID && _iid == typeof(IMessage).GUID)
{
var comObject = VstWrappers.Instance.GetOrCreateComInterfaceForObject(new Message(), CreateComInterfaceFlags.None);
return GetInterfacePointerFromComInterface(ref _iid, comObject);
}
return new Message();

if (cid == typeof(IAttributeList).GUID && _iid == typeof(IAttributeList).GUID)
{
var comObject = VstWrappers.Instance.GetOrCreateComInterfaceForObject(new AttributeList(), CreateComInterfaceFlags.None);
return GetInterfacePointerFromComInterface(ref _iid, comObject);
}
return new AttributeList();

throw new NotImplementedException();

static nint GetInterfacePointerFromComInterface(ref Guid _iid, nint comObject)
{
Marshal.QueryInterface(comObject, ref _iid, out var interfacePtr);
Marshal.Release(comObject);
return interfacePtr;
}
}

void IHostApplication.getName(Span<char> name)
Expand Down
2 changes: 1 addition & 1 deletion src/VST3/Hosting/Message.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
namespace VST3.Hosting;

[GeneratedComClass]
sealed partial class Message : IMessage
sealed partial class Message : VstObject<IMessage>, IMessage
{
private string? id;
private AttributeList? attributeList;
Expand Down
70 changes: 46 additions & 24 deletions src/VST3/Hosting/Module.cs
Original file line number Diff line number Diff line change
@@ -1,17 +1,12 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace VST3.Hosting;

class Module : IDisposable
sealed class Module : IDisposable
{
private static Dictionary<string, Module?> s_modules = new(StringComparer.OrdinalIgnoreCase);

public static List<string> GetModulePaths()
{
// https://steinbergmedia.github.io/vst3_dev_portal/pages/Technical+Documentation/Locations+Format/Plugin+Locations.html
Expand Down Expand Up @@ -123,12 +118,28 @@ static string GetArchString()
const string Arm64XWin = "arm64x-win";

public static unsafe bool TryCreate(string path, [NotNullWhen(true)] out Module? module)
{
lock (s_modules)
{
if (!s_modules.TryGetValue(path, out module))
s_modules[path] = module = CreateInternal(path);
module?.AddRef();
return module != null;
}
}

public static unsafe Module Create(string path)
{
if (TryCreate(path, out var module))
return module;
throw new Exception($"Failed to create module for {path}");
}

private static unsafe Module? CreateInternal(string path)
{
nint modulePtr;
bool isBundle;

module = null;

if (Directory.Exists(path))
{
isBundle = true;
Expand All @@ -144,23 +155,22 @@ public static unsafe bool TryCreate(string path, [NotNullWhen(true)] out Module?
}

if (modulePtr == default)
return false;
return null;

if (!NativeLibrary.TryGetExport(modulePtr, "GetPluginFactory", out var getPluginFactoryAddress))
return false;
return null;

if (NativeLibrary.TryGetExport(modulePtr, "InitDll", out var initDllAddress))
{
var dllEntry = (delegate* unmanaged[Cdecl]<bool>)initDllAddress;
if (!dllEntry())
return false;
return null;
}

var factoryProc = (delegate* unmanaged[Cdecl]<nint>)getPluginFactoryAddress;
var pluginFactoryPtr = factoryProc();
var pluginFactory = VstWrappers.Instance.CreateObjectForComInstance<IPluginFactory>(pluginFactoryPtr);
module = new Module(modulePtr, Path.GetFileName(path), path, isBundle, new PluginFactory(pluginFactory));
return true;
return new Module(modulePtr, Path.GetFileName(path), path, isBundle, new PluginFactory(pluginFactory));

static nint LoadAsDll(string path)
{
Expand All @@ -177,6 +187,7 @@ static nint LoadAsPackage(string path, string archString)
}

private readonly nint module;
private int refCount;

private Module(nint module, string name, string filePath, bool isBundle, PluginFactory factory)
{
Expand All @@ -193,16 +204,27 @@ private Module(nint module, string name, string filePath, bool isBundle, PluginF
public PluginFactory Factory { get; }
public bool IsBundle { get; }

public unsafe void Dispose()
{
Factory.Dispose();
private void AddRef() => Interlocked.Increment(ref refCount);

if (NativeLibrary.TryGetExport(module, "ExitDll", out var exitDllAddress))
private unsafe void Release()
{
if (Interlocked.Decrement(ref refCount) == 0)
{
var exitDll = (delegate* unmanaged[Cdecl]<bool>)exitDllAddress;
exitDll();
}
lock (s_modules)
{
s_modules.Remove(FilePath);
Factory.Dispose();

NativeLibrary.Free(module);
if (NativeLibrary.TryGetExport(module, "ExitDll", out var exitDllAddress))
{
var exitDll = (delegate* unmanaged[Cdecl]<bool>)exitDllAddress;
exitDll();
}

NativeLibrary.Free(module);
}
}
}

public void Dispose() => Release();
}
10 changes: 2 additions & 8 deletions src/VST3/Hosting/ParameterChanges.cs
Original file line number Diff line number Diff line change
@@ -1,19 +1,13 @@
using System.Collections.Concurrent;
using System.Collections.Immutable;
using System.Runtime.InteropServices.Marshalling;
using System.Runtime.InteropServices.Marshalling;

namespace VST3.Hosting;

[GeneratedComClass]
sealed partial class ParameterChanges : IParameterChanges
sealed partial class ParameterChanges : VstObject<IParameterChanges>, IParameterChanges
{
private readonly List<ParameterValueQueue> queues = new();
private int usedQueueCount;

public ParameterChanges()
{
}

public ParameterValueQueue AddParameterData(in uint id, out int index)
{
for (var i = 0; i < usedQueueCount; i++)
Expand Down
18 changes: 18 additions & 0 deletions src/VST3/Hosting/VstObject.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using VL.Audio.VST;

namespace VST3.Hosting;

internal abstract class VstObject<TComInterface>
{
private static readonly Guid comInterfaceGuid = typeof(TComInterface).GUID;
private readonly nint comInterfacePtr;

public VstObject()
{
comInterfacePtr = this.GetComPtr(comInterfaceGuid);
}

public nint ComInterfacePtr => comInterfacePtr;

public static implicit operator nint(VstObject<TComInterface> obj) => obj.ComInterfacePtr;
}

0 comments on commit 4b01217

Please sign in to comment.