[SOLVED] Exception closing app that implements AudioDeviceManager


#1

I have added the following AudioSettings.h to my project, so that I can launch an audio device manager window:

#include "../JuceLibraryCode/JuceHeader.h"

class AudioSettings
{
    SharedResourcePointer<AudioDeviceManager> sharedAudioDeviceManager;
public:
    //static AudioDeviceManager& getSharedAudioDeviceManager()
    //{
    //    static AudioDeviceManager* sharedAudioDeviceManager;
    //    if (sharedAudioDeviceManager == nullptr)
    //    {
    //        sharedAudioDeviceManager = new AudioDeviceManager();
    //        sharedAudioDeviceManager->initialise(2, 2, 0, true, String(), 0);
    //    }
    //    return *sharedAudioDeviceManager;
    //}
    ScopedPointer<AudioDeviceSelectorComponent> settings;
    ScopedPointer<DialogWindow> dw;

    void launch()
    {
        //auto& deviceManager = getSharedAudioDeviceManager();
        
        sharedAudioDeviceManager->initialise(
            0,            // numInputChannelsNeeded
            2,            // numOutputChannelsNeeded
            nullptr,    // XML
            true,        // selectDefaultDeviceOnFailure
            String(),    // preferredDefaultDeviceName
            0            // preferredSetupOptions
            );
        // Present an audio settings dialog box.
        settings = new AudioDeviceSelectorComponent(*sharedAudioDeviceManager,
            0, 0, // min/max inputs
            2, 2, // min/max outputs
            true, // showMidiInputOptions
            true, // showMidiOutputSelector
            true, // showChannelsAsStereoPairs
            true  // hideAdvancedOptions
            );
        settings->setSize(640, 480);

        DialogWindow::LaunchOptions options;
        options.content.setOwned(settings);
        options.dialogTitle = "Audio Settings";
        options.dialogBackgroundColour = Colours::lightgrey;
        options.escapeKeyTriggersCloseButton = true;
        options.useNativeTitleBar = true;
        options.resizable = true;
        
        dw = options.launchAsync();
        dw->centreWithSize(450, 250);
    }
};

However when I quit my app, I get an exception here:

#ifndef JUCE_CONTAINERDELETEPOLICY_H_INCLUDED
#define JUCE_CONTAINERDELETEPOLICY_H_INCLUDED
//==============================================================================
/**
    Used by container classes as an indirect way to delete an object of a
    particular type.
    The generic implementation of this class simply calls 'delete', but you can
    create a specialised version of it for a particular class if you need to
    delete that type of object in a more appropriate way.
    @see ScopedPointer, OwnedArray
*/
template <typename ObjectType>
struct ContainerDeletePolicy
{
    static void destroy (ObjectType* object)
    {
        // If the line below triggers a compiler error, it means that you are using
        // an incomplete type for ObjectType (for example, a type that is declared
        // but not defined). This is a problem because then the following delete is
        // undefined behaviour. The purpose of the sizeof is to capture this situation.
        // If this was caused by a ScopedPointer to a forward-declared type, move the
        // implementation of all methods trying to use the ScopedPointer (e.g. the destructor
        // of the class owning it) into cpp files where they can see to the definition
        // of ObjectType. This should fix the error.
        ignoreUnused (sizeof (ObjectType));

        delete object; // <-- Exception thrown at 0x00D8C2A8 in Trainer.exe: 0xC0000005: Access violation reading location 0xDDDDDDDD.
    }
};

#endif   // JUCE_CONTAINERDELETEPOLICY_H_INCLUDED

Here is the main component:

#include "AudioSettings.h"


class MainContentComponent : public AudioAppComponent, private Timer
{
private:
    ScopedPointer<AudioSettings> settings;

public:
    MainContentComponent()
    {
        :
        settings = new AudioSettings(); // <-- if I remove these, it quits ok!
        settings->launch();

    ~MainContentComponent()
    {
        shutdownAudio(); // <-- error callstack includes here!
    }

I've read the comment associated with the exception, and I think my code avoids the case outlined: my AudioSettings is defined before I use  ScopedPointer<AudioSettings>.

What is going wrong?

π


#2

What happens if you add the following line at the beginning of MainContentComponent's destructor?

settings = nullptr;


#3

It makes no difference!


#4

I think it might be because you have  SharedResourcePointer within a ScopedPointer? 

I would also try Timur's suggestion but within the AudioSettings class, as you also have a ScopedPointer named settings which may need to be set to null for it to be deleted in the destructor. 


#5

No luck.

 I can code around the SharedResourcePointer as follows:


class AudioSettings
{
    //SharedResourcePointer<AudioDeviceManager> sharedAudioDeviceManager;
public:
    static AudioDeviceManager& getSharedAudioDeviceManager()
    {
        static AudioDeviceManager* sharedAudioDeviceManager;
        if (sharedAudioDeviceManager == nullptr)
        {
            sharedAudioDeviceManager = new AudioDeviceManager();
            sharedAudioDeviceManager->initialise(2, 2, 0, true, String(), 0);
        }
        return *sharedAudioDeviceManager;
    }

... but the same exception occurs.


#6

Ok I have something half working now.

Creating a fresh audio app, I add the following file:

//    Settings.h

#include "../JuceLibraryCode/JuceHeader.h"

class AudioSettings
{
public:
    //SharedResourcePointer<AudioDeviceManager> sharedAudioDeviceManager;
    static AudioDeviceManager& getSharedAudioDeviceManager()
    {
        static AudioDeviceManager* sharedAudioDeviceManager;
        if (sharedAudioDeviceManager == nullptr)
        {
            sharedAudioDeviceManager = new AudioDeviceManager();
            sharedAudioDeviceManager->initialise(
                0,            // numInputChannelsNeeded
                2,            // numOutputChannelsNeeded
                nullptr,    // XML
                true,        // selectDefaultDeviceOnFailure
                String(),    // preferredDefaultDeviceName
                0            // preferredSetupOptions
                );
        }
        return *sharedAudioDeviceManager;
    }
    void launchWindow()
    {
        auto& sharedAudioDeviceManager = getSharedAudioDeviceManager();
        AudioDeviceSelectorComponent* settingsPane = new AudioDeviceSelectorComponent(
                sharedAudioDeviceManager,
                0, 0, // min/max inputs
                2, 2, // min/max outputs
                true, // showMidiInputOptions
                true, // showMidiOutputSelector
                true, // showChannelsAsStereoPairs
                true  // hideAdvancedOptions
                );
        settingsPane->setSize(640, 480);

        DialogWindow::LaunchOptions options;
        options.content.setOwned( settingsPane );
        options.dialogTitle = "Audio Settings";
        options.dialogBackgroundColour = Colours::lightgrey;
        options.escapeKeyTriggersCloseButton = true;
        options.useNativeTitleBar = true;
        options.resizable = true;

        DialogWindow* dialogWindow = options.launchAsync();
        dialogWindow->centreWithSize(450, 250);
    }
};

I also modify the main component's .cpp:

// MainComponent.cpp

#include "Settings.h"

class MainContentComponent   : public AudioAppComponent
{

private:
    ScopedPointer<AudioSettings> settings;

public:
    MainContentComponent()
    { 
        setSize (800, 600);

        // specify the number of input and output channels that we want to open
        setAudioChannels (2, 2);

        settings = new AudioSettings();
        settings->launchWindow();                    
    }

This works correctly, only now it triggers a jassert telling me I am leaking.

However, I can't see how to avoid the leak.

I am creating four objects:

static AudioDeviceManager* sharedAudioDeviceManager;
AudioDeviceSelectorComponent* settingsPane = ...
DialogWindow::LaunchOptions options;
DialogWindow* dialogWindow = options.launchAsync();

If I inspect the definition for setOwned...

options.content.setOwned( settingsPane );

I learn that options is now taking ownership of settingsPane.

Digging into launchAsync, it appears that it takes care of deleting dialogWindow when the dialog is closed.

Which means it must be either sharedAudioDeviceManageror or options that is leaking.

How to proceed?

π


#7

Finally I have clean code.

It makes logical sense that the dialog window should hold on to the options object that spawned it, which just leaves the audio settings object, which was clearly leaking.

The following creates a shared AudioSettings object tied to the lifetime of the main content component that contains a separate device manager for input and output.

As I am using a separate manager for input and output, it no longer makes sense to derive from AudioAppComponent (which contains a single device manager handling both input and output).

So as I move forwards extending this, I will have to replicate functionality from that class.

π

MainComponent.cpp

// MainComponent.cpp
#include "../JuceLibraryCode/JuceHeader.h"
#include "Settings.h"
class MainContentComponent   : public Component,  Button::Listener
{
private:
    SharedResourcePointer<AudioSettings> sharedAudioSettings;
    TextButton micButton, spkrButton;
public:
    MainContentComponent()
    {
        setSize (800, 600);
        addAndMakeVisible(&micButton);
        micButton.setButtonText("Mic");
        micButton.addListener(this);
        micButton.setColour(TextButton::buttonColourId, Colours::red);
        addAndMakeVisible(&spkrButton);
        spkrButton.setButtonText("Spkr");
        spkrButton.addListener(this);
        spkrButton.setColour(TextButton::buttonColourId, Colours::green);
    }
    void buttonClicked(Button* button) {
        if(button == &micButton) sharedAudioSettings->launchInputWindow();
        if (button == &spkrButton) sharedAudioSettings->launchOutputWindow();
    }
    void paint (Graphics& g) override {
        g.fillAll (Colours::darkgrey);
    }
    void resized() override {
        spkrButton.setBounds(20, 70, getWidth()-40, 20);
        micButton.setBounds(20, 100, getWidth()-40, 20);
    }
private:
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainContentComponent)
};
Component* createMainContentComponent() { 
    return new MainContentComponent(); 
}

Settings.h

// Settings.h
#ifndef SETTINGS_H_INCLUDED
#define SETTINGS_H_INCLUDED
#include "../JuceLibraryCode/JuceHeader.h"
class AudioSettings
{
private:
    ScopedPointer<AudioDeviceManager> pInputDeviceManager;
    ScopedPointer<AudioDeviceManager> pOutputDeviceManager;
    void launch(AudioDeviceManager* pDeviceManager, int inputs, int outputs);
public:
    const AudioDeviceManager& getInputDeviceManager();
    const AudioDeviceManager& getOutputDeviceManager();
    AudioSettings();
    
    void launchInputWindow();
    void launchOutputWindow();
};
#endif  // SETTINGS_H_INCLUDED

Settings.cpp

// Settings.cpp
#include "Settings.h"
const AudioDeviceManager&  AudioSettings::getInputDeviceManager() {
    return *pInputDeviceManager;
}
const AudioDeviceManager&  AudioSettings::getOutputDeviceManager() {
    return *pOutputDeviceManager;
}
AudioSettings::AudioSettings() 
{
    pInputDeviceManager = new AudioDeviceManager();
    pOutputDeviceManager = new AudioDeviceManager();
    pInputDeviceManager->initialise(
        1,            // numInputChannelsNeeded
        0,            // numOutputChannelsNeeded
        nullptr,    // XML
        true,        // selectDefaultDeviceOnFailure
        String(),    // preferredDefaultDeviceName
        0            // preferredSetupOptions
        );
    pOutputDeviceManager->initialise(
        0,            // numInputChannelsNeeded
        2,            // numOutputChannelsNeeded
        nullptr,    // XML
        true,        // selectDefaultDeviceOnFailure
        String(),    // preferredDefaultDeviceName
        0            // preferredSetupOptions
        );
}
void AudioSettings::launchInputWindow() {
    launch(pInputDeviceManager, 1, 0);
}
void AudioSettings::launchOutputWindow() {
    launch(pOutputDeviceManager, 0, 2);
}
void AudioSettings::launch(AudioDeviceManager* pDeviceManager, int inputs, int outputs)
{
    AudioDeviceSelectorComponent* settingsPane = new AudioDeviceSelectorComponent(
        *pDeviceManager,
        inputs, inputs,        // min/max inputs
        outputs, outputs,    // min/max outputs
        inputs>0,            // showMidiInputOptions
        outputs>0,            // showMidiOutputSelector
        true,                // showChannelsAsStereoPairs
        true                // showAdvancedOptions
        );
    settingsPane->setSize(640, 480);

    DialogWindow::LaunchOptions options;
    options.content.setOwned(settingsPane);
    options.dialogTitle = "Audio Settings";
    options.dialogBackgroundColour = Colours::lightgrey;
    options.escapeKeyTriggersCloseButton = true;
    options.useNativeTitleBar = true;
    options.resizable = true;

    DialogWindow* dialogWindow = options.launchAsync();
    dialogWindow->centreWithSize(450, 250);
}