Juce as an embedded plugin (aka shared library)


#1

i’m doing some test using juce as a plugin (not matter which type), so i compiled juce as a shared library. In order to make things work, i discovered we need to create a message thread for processing when the shared library is loaded, and delete it when it gets unloaded.
If i do like this:

[code]#include “juce.h”

//================================================================================================
BEGIN_JUCE_NAMESPACE
extern void juce_callAnyTimersSynchronously();
extern Display* display;
extern bool juce_postMessageToSystemQueue (void* message);
END_JUCE_NAMESPACE

//================================================================================================
class SharedMessageThread : public Thread
{
public:
SharedMessageThread()
: Thread (T(“VstMessageThread”))
{
startThread (7);
}

~SharedMessageThread()
{
    signalThreadShouldExit();

    const int quitMessageId = 0xfffff321;
    Message* const m = new Message (quitMessageId, 1, 0, 0);

    if (! juce_postMessageToSystemQueue (m))
        delete m;

    while (isThreadRunning ());

    clearSingletonInstance();
}

void run()
{
    MessageManager* const messageManager = MessageManager::getInstance();

    const int originalThreadId = messageManager->getCurrentMessageThread();
    
    if (originalThreadId != getThreadId())
        messageManager->setCurrentMessageThread (getThreadId());

    printf ("SharedMessageThread::run started > %d > %d \n", originalThreadId, getThreadId());

    while (! threadShouldExit()
            && messageManager->dispatchNextMessage())
    {
    }

    printf ("SharedMessageThread::run finished \n");

    if (originalThreadId != getThreadId())
        messageManager->setCurrentMessageThread (originalThreadId);

    printf ("SharedMessageThread::run cleanup \n");
}

// can we have more than one message thread ? NO !
juce_DeclareSingleton (SharedMessageThread, false)

};

juce_ImplementSingleton (SharedMessageThread);

//================================================================================================
void exportedMain () asm (“exportedMain”);
void exportedMain ()
{
initialiseJuce_GUI();
SharedMessageThread::getInstance();

DocumentWindow* c = 0;

{
    const MessageManagerLock mml;

    c = new DocumentWindow (T("xxx"), Colours::white, DocumentWindow::allButtons);
    c->centreWithSize (400, 300);
    c->setVisible (true);
}

Thread::sleep (2000);

{    
    const MessageManagerLock mml;

    c->setVisible (false);
    deleteAndZero (c);
}

Thread::sleep (500);

SharedMessageThread::deleteInstance();
shutdownJuce_GUI();

}

//================================================================================================
attribute((constructor)) void myPluginInit() // this is called when the library is unloaded
{
printf (“myPluginInit > %d \n”, Thread::getCurrentThreadId());
}

attribute((destructor)) void myPluginFini() // this is called when the library is unloaded
{
printf (“myPluginFini 1 > %d \n”, Thread::getCurrentThreadId());
}

[/code]

and i try to loadLibrary, executing the exportedMain function, all is working as expected, showing a white window, desappearing after some time and deallocating stuff.

But this doesn’t work if the exportedMain returns an object which lifetime cannot be guaranteed to finish upon its execution (think in the case of a VST plugin, which gets returned from the exported main function):

[code] // …

//================================================================================================
void exportedMain () asm (“exportedMain”);
void exportedMain ()
{
DocumentWindow* c = 0;

{
    const MessageManagerLock mml;

    c = new DocumentWindow (T("xxx"), Colours::white, DocumentWindow::allButtons);
    c->centreWithSize (400, 300);
    c->setVisible (true);
}

Thread::sleep (2000);

{    
    const MessageManagerLock mml;

    c->setVisible (false);
    deleteAndZero (c);
}

Thread::sleep (500);

}

//================================================================================================
attribute((constructor)) void myPluginInit() // this is called when the library is unloaded
{
printf (“myPluginInit > %d \n”, Thread::getCurrentThreadId());

initialiseJuce_GUI();
SharedMessageThread::getInstance();

}

attribute((destructor)) void myPluginFini() // this is called when the library is unloaded
{
printf (“myPluginFini 1 > %d \n”, Thread::getCurrentThreadId());

SharedMessageThread::deleteInstance();
shutdownJuce_GUI();

}

[/code]

this should be working, but i’m getting a segmentation fault upon deletion (this is the call stacktrace):

myPluginFini 2 > 
*** glibc detected *** ../bin/load_debug: corrupted double-linked list: 0x08086f60 ***
======= Backtrace: =========
/lib/libc.so.6[0xb7dea227]
/lib/libc.so.6[0xb7debb57]
/lib/libc.so.6(cfree+0x7a)[0xb7debe2a]
/home/kraken/Projects/juce_break/bin/libdll_debug.so(_ZN4juce6StringD1Ev+0x29)[0xb7be9da9]
/home/kraken/Projects/juce_break/bin/libdll_debug.so(_ZN4juce16FreeTypeFontFace13FontNameIndexD1Ev+0x11)[0xb7c2a965]
/home/kraken/Projects/juce_break/bin/libdll_debug.so(_ZN4juce16FreeTypeFontFaceD1Ev+0x36)[0xb7c2a99e]
/home/kraken/Projects/juce_break/bin/libdll_debug.so(_ZN4juce10OwnedArrayINS_16FreeTypeFontFaceENS_20DummyCriticalSectionEE5clearEb+0x58)[0xb7c2b3be]
/home/kraken/Projects/juce_break/bin/libdll_debug.so(_ZN4juce10OwnedArrayINS_16FreeTypeFontFaceENS_20DummyCriticalSectionEED1Ev+0x19)[0xb7c2b437]
/home/kraken/Projects/juce_break/bin/libdll_debug.so(_ZN4juce17FreeTypeInterfaceD0Ev+0x61)[0xb7c2b93b]
/home/kraken/Projects/juce_break/bin/libdll_debug.so(_ZN4juce17DeletedAtShutdown9deleteAllEv+0xae)[0xb7bf0ccc]
/home/kraken/Projects/juce_break/bin/libdll_debug.so(_ZN4juce16shutdownJuce_GUIEv+0x16)[0xb7beec0a]
/home/kraken/Projects/juce_break/bin/libdll_debug.so(_Z12myPluginFiniv+0x41)[0xb7be835f]
/home/kraken/Projects/juce_break/bin/libdll_debug.so[0xb7be839a]
/home/kraken/Projects/juce_break/bin/libdll_debug.so[0xb7be8248]
/home/kraken/Projects/juce_break/bin/libdll_debug.so(_fini+0x18)[0xb7cf8a1c]
/lib/ld-linux.so.2[0xb7fdc2e1]
/lib/ld-linux.so.2[0xb7fdcbf6]
/lib/libdl.so.2[0xb7d82cb4]
/lib/ld-linux.so.2[0xb7fd75dd]
/lib/libdl.so.2[0xb7d8304b]
/lib/libdl.so.2(dlclose+0x2a)[0xb7d82ce4]
../bin/load_debug[0x8057a45]
../bin/load_debug(__gxx_personality_v0+0x30c)[0x804a9b4]
/lib/libc.so.6(__libc_start_main+0xe0)[0xb7d9bfc8]
../bin/load_debug(__gxx_personality_v0+0x1b9)[0x804a861]

i don’t know why is leaking. i’ve tried doing the same with other libraries (exported like this) and there is no apparent problem. what can be ? i need to sort this out !!!

ps. the code for loading the function back is:

#include "juce.h"

typedef void (*EXPORTEDFUNC)();

int main ()
{
    initialiseJuce_NonGUI();

    void* dll = Process::loadDynamicLibrary ("/home/kraken/Projects/juce_break/bin/libdll_debug.so");

    EXPORTEDFUNC ex = (EXPORTEDFUNC) Process::getProcedureEntryPoint (dll, "exportedMain");

    if (ex)
        ex ();

    Process::freeDynamicLibrary (dll);

    shutdownJuce_NonGUI();

    return 1;
}

#2

Must be an allocator problem - creating an object in one dll and deleting it in another… Sorry, too busy to look into it for you right now though!


#3

mmmh pity, you will sort this out a day ? (if i don’t find a neat solution in the meanwhile)


#4

Have a look at the way the win32 dll stuff declares special new/delete operators to avoid allocator problems…


#5

you mean this ?

//==============================================================================

  // Win32 DLL (release) versions..



  // For the DLL, we'll define some functions in the DLL that will be used for allocation - that

  // way all juce calls in the DLL and in the host API will all use the same allocator.

  extern JUCE_API void* juce_Malloc (const int size);

  extern JUCE_API void* juce_Calloc (const int size);

  extern JUCE_API void* juce_Realloc (void* const block, const int size);

  extern JUCE_API void juce_Free (void* const block);



  /** This should be used instead of calling malloc directly. */

  #define juce_malloc(numBytes)                 juce::juce_Malloc (numBytes)

  /** This should be used instead of calling calloc directly. */

  #define juce_calloc(numBytes)                 juce::juce_Calloc (numBytes)

  /** This should be used instead of calling realloc directly. */

  #define juce_realloc(location, numBytes)      juce::juce_Realloc (location, numBytes)

  /** This should be used instead of calling free directly. */

  #define juce_free(location)                   juce::juce_Free (location)



  #define juce_UseDebuggingNewOperator \

    static void* operator new (size_t sz)           { void* const p = juce_malloc ((int) sz); return (p != 0) ? p : ::operator new (sz); } \

    static void* operator new (size_t sz, void* p)  { return ::operator new (sz, p); } \

    static void operator delete (void* p)           { juce_free (p); }

uhmmm thanx :confused:


#6

ah i’ve discovered that the problem is only if i load a juce plugin from a juce host, with juce compiled statically in both (and use the approach of the shared library constructor/destructor).

maybe there is a name clash of the exported symbols ?


#7

could be - I’m not sure about linux, but certainly on the mac you get name clashes like this. Try making sure only the required symbols are exported.


#8

ok, i think i will leave this for a while, at least now i’ve modified my classes so i can use it the way i’ve done it in the VstWrapper, so there isn’t any problem so far.

Thanx for helping !


#9