Several leaking objects with AudioPluginInstance editor


#1

Hi, when opening the following class on demand, everything works fine until i close the application, where several leaking object warnings arise. I have tried several approaches but i am not able to close the app without warnings. Could somebody please tell me how to open such a window with an AudioPluginInstance’s editor correctly in order not to see a huge amount of leaking objects at the end? Thank you very much.

class PresetsListComponent::SynthWindow  : public DocumentWindow
{
public:
    SynthWindow (PresetsListComponent& owner_, const int synthId)
    : DocumentWindow ("", Colours::lightgrey, DocumentWindow::allButtons),
      owner (owner_)
    {
        audioDeviceManager.initialiseWithDefaultDevices(2, 2);
        
        graph = new AudioProcessorGraph();
        player = new AudioProcessorPlayer();
        rescanMidiDevices();
        buildBasicGraph();
        
        pluginFormatManager = new AudioPluginFormatManager();
        pluginFormatManager->addDefaultFormats();
        
        Synth* synth = AudioController::getInstance().getSynthForId(synthId);
        if (synth != nullptr) {
            
            KnownPluginList plist;
            OwnedArray<juce::PluginDescription> pluginDescriptions;
            juce::VSTPluginFormat format;
            plist.scanAndAddFile(synth->pluginPath, true, pluginDescriptions, format);
            if (pluginDescriptions.size() > 0) {
                String msg;
                instance = pluginFormatManager->createPluginInstance(*pluginDescriptions[0], 0, 0, msg);
                AudioProcessorGraph::Node* newPluginNode = graph->addNode(instance, NodeId::Plugin);
                
                // add midi in to plugin node
                graph->addConnection(
                                     graph->getNodeForId(NodeId::MidiIn)->nodeId, AudioProcessorGraph::midiChannelIndex,
                                     newPluginNode->nodeId, AudioProcessorGraph::midiChannelIndex);
                
                // Add pluginNode to audio out in stereo
                graph->addConnection(newPluginNode->nodeId, 0, graph->getNodeForId(NodeId::AudioOut)->nodeId, 0);
                graph->addConnection(newPluginNode->nodeId, 1, graph->getNodeForId(NodeId::AudioOut)->nodeId, 1);
                
                player->setProcessor(graph);
                audioDeviceManager.addAudioCallback(player);
                
                editor = instance->createEditor();
                auto bc = editor->getConstrainer();
                editor->setBounds(0, 0, bc->getMinimumWidth(), bc->getMinimumHeight());
                
                Component* comp = new Component();
                comp->addAndMakeVisible(editor);
                comp->setBounds(0, 0, bc->getMinimumWidth(), bc->getMinimumHeight());
                
                setContentOwned(comp, true);
                setResizable(false, false);
                setVisible (true);
            }
        }
    }
    
    ~SynthWindow()
    {
        audioDeviceManager.removeAudioCallback(player);
        graph->suspendProcessing(true);
        graph->clear();
        graph->releaseResources();
        
        instance = nullptr;
        editor->deleteAllChildren();
        editor = nullptr;
        //deleteAllChildren();
        //clearContentComponent();
    }
    
    void closeButtonPressed()
    {
        owner.synthWindow = nullptr;
    }
    
private:
    enum NodeId {
        MidiIn   = 55,
        Plugin   = 66,
        AudioOut = 77
    };
  
    PresetsListComponent& owner;
    AudioPluginInstance* instance;
    AudioProcessorEditor* editor;
    AudioDeviceManager audioDeviceManager;
    ScopedPointer<AudioProcessorGraph> graph;
    ScopedPointer<AudioProcessorPlayer> player;
    ScopedPointer<AudioPluginFormatManager> pluginFormatManager;
}

The warnings (with different VST plugins loaded) are:
*** Leaked objects detected: 1 instance(s) of class VSTPluginWindow
JUCE Assertion failure in juce_LeakedObjectDetector.h:97
*** Leaked objects detected: 1 instance(s) of class ComponentBoundsConstrainer
JUCE Assertion failure in juce_LeakedObjectDetector.h:97
*** Leaked objects detected: 1 instance(s) of class Component
JUCE Assertion failure in juce_LeakedObjectDetector.h:97
*** Leaked objects detected: 1 instance(s) of class MouseCursor
JUCE Assertion failure in juce_LeakedObjectDetector.h:97


#2

Some things seem suspicous to me:

createPluginInstance returns a new instance, that you need to clean up afterwards, so better use instead:

ScopedPointer<AudioPluginInstance> instance;

The same is true for

Last but not least, could it be, that you are also leaking the whole PresetsListComponent::SynthWindow?

You will find out by adding to the private scope:

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PresetsListComponent::SynthWindow)

HTH


#3

Thank you Daniel. The version you see is just one of many i tried.
When i use a ScopedPointer for the editor and close the application the whole thing crashes:

Pure virtual function called!
abort() called

Thread 0 Crashed:: Juce Message Thread Dispatch queue: com.apple.main-thread
0 libsystem_kernel.dylib 0x00007fffc0e52dd6 __pthread_kill + 10
1 libsystem_pthread.dylib 0x00007fffc0f3e787 pthread_kill + 90
2 libsystem_c.dylib 0x00007fffc0db8420 abort + 129
3 libc++abi.dylib 0x00007fffbf91385a abort_message + 266
4 libc++abi.dylib 0x00007fffbf9371b2 __cxa_pure_virtual + 18
5 com.yourcompany.JuceGui 0x000000010c792645 juce::VSTPluginWindow::closePluginWindow() + 149 (juce_VSTPluginFormat.cpp:2436)
6 com.yourcompany.JuceGui 0x000000010c7924b7 juce::VSTPluginWindow::~VSTPluginWindow() + 55 (juce_VSTPluginFormat.cpp:2046)
7 com.yourcompany.JuceGui 0x000000010c7913a5 juce::VSTPluginWindow::~VSTPluginWindow() + 21 (juce_VSTPluginFormat.cpp:2057)
8 com.yourcompany.JuceGui 0x000000010c7913c9 juce::VSTPluginWindow::~VSTPluginWindow() + 25 (juce_VSTPluginFormat.cpp:2045)
9 com.yourcompany.JuceGui 0x000000010c576dff juce::ContainerDeletePolicyjuce::AudioProcessorEditor::destroy(juce::AudioProcessorEditor*) + 63 (juce_ContainerDeletePolicy.h:61)
10 com.yourcompany.JuceGui 0x000000010c577224 juce::ScopedPointerjuce::AudioProcessorEditor::~ScopedPointer() + 20 (juce_ScopedPointer.h:109)
11 com.yourcompany.JuceGui 0x000000010c5766d5 juce::ScopedPointerjuce::AudioProcessorEditor::~ScopedPointer() + 21 (juce_ScopedPointer.h:109)

For that version i added comp like this:
setContentNonOwned(comp, true);

The statement JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PresetsListComponent::SynthWindow) was set, i just didn’t copy the whole class.


#4

Ok got that.

The crash seems to me that two objects own the editor, so it is crucial to make sure everything is only owned once.
I am not sure, it might be related to setContentOwned(comp, true);. As you said, it is in flux, maybe you hand over ownership there? In the posted version it is the unowned comp instance, but who knows…

I think you can get rid of the whole comp thing and add the editor directly as content?

Also, if the window now owns the editor, either remove the editor pointer completely, or if you need a pointer outside the construction, consider to use a SafePointer instead. The drawback is you need to check if it is not nullptr before using it each time.

A trick with ScopedPointers I often do is, use them even inside functions:

ScopedPointer<AudioPluginEditor> editor = instance->createEditor();
// even if you add a return here later, you don't need to cleanup. Only as example...
if (somethingWentWrong)
    return;

// then hand over the ownership:
setContentOwned (editor.release(), true);

This seems also inconsistent:

Either instance and editor are ScopedPointers, then they are deleted by that statement. Or like in your posted version if they are raw pointers, this statement has no effect, because it’s the last place to see these variables before they are gone.

Good luck


#5

Hello Daniel,

thank you very much for your tips. I got it finally working! Now i can close and reopen SynthWindow objects for different synths and close them or the whole application without any crash or memory leaks anymore.
Here is my solution:

class PresetsListComponent::SynthWindow  : public DocumentWindow
{
public:
    SynthWindow (PresetsListComponent& owner_, const int synthId)
    : DocumentWindow ("", Colours::lightgrey, DocumentWindow::allButtons),
      owner (owner_)
    {
        audioDeviceManager = new AudioDeviceManager();
        audioDeviceManager->initialiseWithDefaultDevices(2, 2);
        
        graph = new AudioProcessorGraph();
        player = new AudioProcessorPlayer();
        
        rescanMidiDevices();
        buildBasicGraph();
        
        pluginFormatManager = new AudioPluginFormatManager();
        pluginFormatManager->addDefaultFormats();
        
        ScopedPointer<Synth> synth = AudioController::getInstance().getSynthForId(synthId);
        if (synth != nullptr) {
            
            KnownPluginList plist;
            OwnedArray<juce::PluginDescription> pluginDescriptions;
            juce::VSTPluginFormat format;
            plist.scanAndAddFile(synth->pluginPath, true, pluginDescriptions, format);
            if (pluginDescriptions.size() > 0) {
                String msg;
                AudioPluginInstance* instance = pluginFormatManager->createPluginInstance(*pluginDescriptions[0], 0, 0, msg);
                AudioProcessorGraph::Node* newPluginNode = graph->addNode(instance, NodeId::Plugin);
                
                // add midi in to plugin node
                graph->addConnection(
                                     graph->getNodeForId(NodeId::MidiIn)->nodeId, AudioProcessorGraph::midiChannelIndex,
                                     newPluginNode->nodeId, AudioProcessorGraph::midiChannelIndex);
                
                // Add pluginNode to audio out in stereo
                graph->addConnection(newPluginNode->nodeId, 0, graph->getNodeForId(NodeId::AudioOut)->nodeId, 0);
                graph->addConnection(newPluginNode->nodeId, 1, graph->getNodeForId(NodeId::AudioOut)->nodeId, 1);
                
                player->setProcessor(graph);
                audioDeviceManager->addAudioCallback(player);
                
                setContentOwned(instance->createEditor(), true);
                setResizable(false, false);
                setVisible (true);
            }
        }
    }
    
    ~SynthWindow()
    {
        audioDeviceManager->removeAudioCallback(player);
        clearContentComponent();
    }
    
    void closeButtonPressed()
    {
        owner.synthWindow = nullptr;
    }
}

All member variables (like audioDeviceManager) are ScopedPointers and the editor is added now directly as owned content.
Works like a charm, thanks again!
Regards
ricochet