# Message loop outside a JUCE app or plugin

**URL:** <https://forum.juce.com/t/message-loop-outside-a-juce-app-or-plugin/66078>\
**Category:** General JUCE discussion\
**Created:** [May 14, 2025, 12:34pm UTC](https://forum.juce.com/t/message-loop-outside-a-juce-app-or-plugin/66078 "2025-05-14T12:34:34Z")\
**Posts on this page:** 17\
**Page:** 1

<div class="post-metadata">

**Author:** ![BenCello](https://avatars.discourse-cdn.com/v4/letter/b/dc4da7/32.png) [@BenCello](https://forum.juce.com/u/BenCello)\
**Post date:** [May 14, 2025, 12:34pm UTC](https://forum.juce.com/t/message-loop-outside-a-juce-app-or-plugin/66078/1 "2025-05-14T12:34:34Z")

</div>

Hello Jucers,  
I would like to run what is strictly need to receive OSC messages _outside_ a JUCE app or plugin. The goal is to run behaviour-driven unit tests of classes with the Catch2 framework.

I understand that to get OSC message to my class, I will need the Message system of JUCE. So I wanted to run that “by hand” with a call to `juce::MessageManager::getInstance()` and `juce::MessageManager::getInstance()->runDispatchLoop()` in its own thead. But this does not seem to work… The thread seems to be dying, I don’t know exactly why yet.

Using the `START_JUCE_APPLICATION` macro is not an option either because it uses an `main` function and Catch2 frameworks needs its own `main` also…

So here is my question: what is strictly needed around (instances, classes, threads) to receive OSC messages with the `OSCReceiver` class and how to start this “by hand” outside a JUCE app or plugin?

Thank’s for any pointers!

Best,

Benjamin

PS: I have already found this thread: [Using Catch2 but need a MessageLoop](https://forum.juce.com/t/using-catch2-but-need-a-messageloop/41380)  
And put the `juce::ScopedJuceInitialiser_GUI` in the Catch2 init and/or in my TEST\_CASE but this does not seem to make any difference…

PPS: testing the `JUCE_ASSERT_MESSAGE_MANAGER_EXISTS` before my test does not stop/fails (so MessageManager should exist… ?), so what am I missing??

---

<div class="post-metadata">

**Author:** ![anthony-nicholls](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.juce.com/anthony-nicholls/32/1246_2.png) [@anthony-nicholls](https://forum.juce.com/u/anthony-nicholls)\
**Post date:** [May 14, 2025, 1:15pm UTC](https://forum.juce.com/t/message-loop-outside-a-juce-app-or-plugin/66078/2 "2025-05-14T13:15:02Z")

</div>

I’ve done this in the past, at the time the best method I found was to create an Application. Importantly it had to be an application not just an executable. On macOS you can then call the binary of that application just like any executable, and for Windows it shouldn’t really make a difference. Then I ran catch2 on its own thread rather than trying to manually run the JUCE message thread. I think if you inherit from `JuceApplication` you get the `initialise()` virtual function which gives the command line args that you can pass to catch 2 and start it up in it’s own thread (I recall I may have been very sneaky and passed the arguments in a different way as I really wanted access to argc and argv which JUCE didn’t give direct access to). That way the message thread and the catch2 thread can run side by side.

The problem I found was that I could never get `[NSApp run]` inside `runDispatchLoop()` to continuously run. Maybe you’ll have better luck though, it was a few years ago (before I joined the JUCE team) that I done it.

I do have some initial ideas on some internal branches for mocking the message manager to handle this kind of thing in tests but there is a lot to consider and I’m not likely to get back to that any time soon.

---

<div class="post-metadata">

**Author:** ![BenCello](https://avatars.discourse-cdn.com/v4/letter/b/dc4da7/32.png) [@BenCello](https://forum.juce.com/u/BenCello)\
**Post date:** [May 14, 2025, 2:57pm UTC](https://forum.juce.com/t/message-loop-outside-a-juce-app-or-plugin/66078/3 "2025-05-14T14:57:21Z")

</div>

Hi @anthony-nicholls  
Thank’s for your answer!  
Instead of running `runDispatchLoop`, I can run `runDispatchLoopUntil` as described in this post: [Suggested improvements in runDispathLoopUntil() on OSX](https://forum.juce.com/t/suggested-improvements-in-rundispathloopuntil-on-osx/36412)  
But even without a `0` argument, I still don’t get messages… And I don’t know why.

Debugging with XCode I think I get some events but I don’t know what happens next…

---

<div class="post-metadata">

**Author:** ![BenCello](https://avatars.discourse-cdn.com/v4/letter/b/dc4da7/32.png) [@BenCello](https://forum.juce.com/u/BenCello)\
**Post date:** [May 14, 2025, 3:13pm UTC](https://forum.juce.com/t/message-loop-outside-a-juce-app-or-plugin/66078/4 "2025-05-14T15:13:36Z")

</div>

OOOoooh!  
I got it working, provided that I call `juce::MessageManager::getInstance()->runDispatchLoopUntil(TIMEOUT);` in the same thread as the test.  
I have difficulties understanding why but that should actually be usable and pretty handy for tests as I can also test if the classes are reacting to an OSC message in the correct timing (there are some Timer involved)!

For some others, maybe, sometime, here is my code:

```auto
  SECTION ("network loopback")
  {
    TestOscReceiver::MessageBreak connect ("/plugin/connect");
    auto oscListener = async(TestOscReceiver::runBreak, ref(connect));
    testConnectionManager.discover(); // should send an `/plugin/connect xxxxx` message with xxxxx begin a socket port number
    int pluginPort = oscListener.get();

    auto fullmessage = oscPrefix+"/targetList";
    
    TestOscReceiver::MessageCount targetList (fullmessage.c_str());

    oscListener = async(launch::async, TestOscReceiver::runTimeout, ref(targetList), TIMEOUT);
    TestOscSender::LocalMessageSender sender(pluginPort, "/plugin/connected", uuid);
    juce::MessageManager::getInstance()->runDispatchLoopUntil(TIMEOUT);
    // testConnectionManager should react to the "/plugin/connnected uuid" message by sending a "/.../targetList" message
    int rcount = oscListener.get();

    CHECK(rcount > 0);
  }

```

`testConnectionManager` is an instance of the class I am testing.  
`TestOscReceiver::MessageBreak` is an asynchronous OSC receiver made with OSCPack ([GitHub - RossBencina/oscpack: Automatically exported from code.google.com/p/oscpack](https://github.com/RossBencina/oscpack)) and `TestOscSender::LocalMessageSender` is an OSC message sender to 127.0.0.1 also using OSCPack.

---

<div class="post-metadata">

**Author:** ![anthony-nicholls](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.juce.com/anthony-nicholls/32/1246_2.png) [@anthony-nicholls](https://forum.juce.com/u/anthony-nicholls)\
**Post date:** [May 14, 2025, 3:20pm UTC](https://forum.juce.com/t/message-loop-outside-a-juce-app-or-plugin/66078/5 "2025-05-14T15:20:45Z")

</div>

I would discourage using runDispatchLoopUntil as it means enabling modal loops.

Is there a reason you can’t run Catch2 in another thread like I suggested? I’ve done this to run tests exactly like you describe and I found it to work very well. If you need anything in the Catch2 thread to run on the message thread you can use `messageManager::callSync()` but I suggest only using it when you have to.

Just seen that you have it working now, that’s great although I would probably still discourage `runDispatchLoopUntil()`.

---

<div class="post-metadata">

**Author:** ![BenCello](https://avatars.discourse-cdn.com/v4/letter/b/dc4da7/32.png) [@BenCello](https://forum.juce.com/u/BenCello)\
**Post date:** [May 14, 2025, 3:30pm UTC](https://forum.juce.com/t/message-loop-outside-a-juce-app-or-plugin/66078/6 "2025-05-14T15:30:58Z")

</div>

Thank’s again!

Yes, I’ve seen that `runDispatchLoopUntil()` is forcing to use modal loops. For now, I have no idea what that means…

Running Catch2 in another thread is not a problem. But what I would like to avoid is having a JUCE Application to test classes that may be used/extracted at some point from the JUCE environment.  
But reading again what you wrote, I am not sure if you suggested to have some “dummy app” just to have JUCE running and then having Catch2 somehow running “separated” from that… Is that what you are saying?  
Or are you suggesting to build a JUCE App which would itself call the `Catch::Session().run` ?  
Anyway, I will continue digging and refactoring if needed along the way.

What are the drawbacks of enabling modal loops? (What are those… ?)

---

<div class="post-metadata">

**Author:** ![eyalamir](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.juce.com/eyalamir/32/2978_2.png) [@eyalamir](https://forum.juce.com/u/eyalamir)\
**Post date:** [May 14, 2025, 4:02pm UTC](https://forum.juce.com/t/message-loop-outside-a-juce-app-or-plugin/66078/7 "2025-05-14T16:02:30Z")

</div>

For the context of unit tests with Catch2, I did the following:

1. The testing executable is a JUCE executable (loaded with CMake with `juce_add_console_app`)

2. I have my own main (not the Catch2-supplied main) that initialized the message thread with `juce::ScopedJuceInitialiser_GUI` before calling `Catch::Session().run(argc, argv)`  
(You can also place that in individual tests if you want to)

3. I enable modal loops (only for the testing app!) and use runDispatchLoopUntil(1) to wait for message thread events during the tests.

---

<div class="post-metadata">

**Author:** ![anthony-nicholls](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.juce.com/anthony-nicholls/32/1246_2.png) [@anthony-nicholls](https://forum.juce.com/u/anthony-nicholls)\
**Post date:** [May 14, 2025, 4:58pm UTC](https://forum.juce.com/t/message-loop-outside-a-juce-app-or-plugin/66078/8 "2025-05-14T16:58:41Z")

</div>

It would be a JUCE App that calls `Catch::Session().run()` on a separate thread, once the tests have finished the thread would call `JUCEApplicationBase::quit()` to notify the app to stop the message loop. But to be clear you could still invoke your app from the command line, you wouldn’t need to implement a UI.

I’ve probably overlooked something and the below code is untested but I think it would be roughly as simple as this (but get JUCE to add an app target from CMake or the Projucer)…

```auto
class MyTestApp : public juce::JUCEApplication
{
public:
    void initialise (const juce::String& commandLine) override
    {
        juce::Thread::launch ([&]
        {
            Catch::Session().run (/* pass the args from commandLine here */);
            quit();
        });
    }

    void shutdown() override {}
    const juce::String getApplicationName() override { return "MyTestApp"; }
    const juce::String getApplicationVersion() override { return "1.0"; }
};

START_JUCE_APPLICATION (MyTestApp)

```

> [@BenCello](#):
>
> What are the drawbacks of enabling modal loops? (What are those… ?)

In your specific use case it may actually work very well for you. They are generally used to implement some blocking dialog that still allows the message loop to run, here is a waring in the JUCE docs about modal loops

> Modal loops are a dangerous construct because things that happen during the events that they dispatch could affect the state of objects which are currently in use somewhere on the stack, so when the loop finishes and the stack unwinds, horrible problems can occur. This is especially bad in plugins, where the host may choose to delete the plugin during runModalLoop(), so that when it returns, the entire DLL could have been unloaded from memory! Also, some OSes deliberately make it impossible to run modal loops (e.g. Android)

In your case you’re in full control, you’re unlikely to hit these kinds of issues.

One other thing to note is that any testing with `runDispatchLoopUntil()` is minimal from our end as it’s generally discouraged.

Other issues I see

- You’ll always have to wait at least 1ms even if the code executes in much less time
- You might have to estimate how long something will take, for example if there are multiple messages that need to be processed on the message thread and one of them takes a long time. If you over estimate, as far as I can tell you’ll always be running the loop for that length of to time even if you’ve actually completed the task in a much shorter space of time (maybe you could call `runDispatchLoopUntil (1)` in a loop with some check in-between to see if the condition has already been met).

None of the above seems like a deal breaker though.

---

<div class="post-metadata">

**Author:** ![eyalamir](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.juce.com/eyalamir/32/2978_2.png) [@eyalamir](https://forum.juce.com/u/eyalamir)\
**Post date:** [May 14, 2025, 5:55pm UTC](https://forum.juce.com/t/message-loop-outside-a-juce-app-or-plugin/66078/9 "2025-05-14T17:55:22Z")

</div>

> [@anthony-nicholls](#):
>
> One other thing to note is that any testing with `runDispatchLoopUntil()` is minimal from our end as it’s generally discouraged.

It would be awesome if the JUCE team can offer a solution for that without modal loops, it’s really the only solution for testing.

Calling tests from another thread is generally not possible in most JUCE code, especially UI related code - there are asserts all over the place if you do that.

---

<div class="post-metadata">

**Author:** ![anthony-nicholls](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.juce.com/anthony-nicholls/32/1246_2.png) [@anthony-nicholls](https://forum.juce.com/u/anthony-nicholls)\
**Post date:** [May 14, 2025, 6:58pm UTC](https://forum.juce.com/t/message-loop-outside-a-juce-app-or-plugin/66078/10 "2025-05-14T18:58:35Z")

</div>

I have plans for a better solution, but to address the assertions when you’re running on another thread run anything that needs to run on the message thread inside a call to MessageManager::callSync(). That’s basically what we were doing in my previous job (although callSync didn’t exist so we rolled our own).

---

<div class="post-metadata">

**Author:** ![BenCello](https://avatars.discourse-cdn.com/v4/letter/b/dc4da7/32.png) [@BenCello](https://forum.juce.com/u/BenCello)\
**Post date:** [May 15, 2025, 7:27am UTC](https://forum.juce.com/t/message-loop-outside-a-juce-app-or-plugin/66078/11 "2025-05-15T07:27:23Z")

</div>

Hi there,

@eyalamir : that’s almost what I do, except for the `juce_add_console_app` that I do not use. I’m doing it with the CMake standard `add_executable`. It works smoothly because overall, I only need 2 JUCE modules to run the tests. I’m guessing that if you have a lot of them and/or need JUCE UI, then `juce_add_console_app` is a much simpler option.

Reading along the forum post, I believe there are more project using this methodology for testing with Catch2!

---

<div class="post-metadata">

**Author:** ![BenCello](https://avatars.discourse-cdn.com/v4/letter/b/dc4da7/32.png) [@BenCello](https://forum.juce.com/u/BenCello)\
**Post date:** [May 15, 2025, 7:33am UTC](https://forum.juce.com/t/message-loop-outside-a-juce-app-or-plugin/66078/12 "2025-05-15T07:33:26Z")

</div>

@anthony-nicholls: thank’s for the kickstart of JUCE app running Catch2. I will give it a go and see how that pan out for me.

One point that is important for this project is that the OSC interoperability has to be rock-solid. That’s also why I am formatting OSC tests message with _another_ library than JUCE’s. Even if it shouldn’t, there are different ways to format and encapsulate OSC messages with/out sender’s IP, with/out bundles, with/out timetags, with/out padding aso and I need to be sure that any message from any sender is correctly received and interpreted by our code!  
Hence usage of Catch2/OSCPack/Netif… for the tests even though they are dependencies that are not used for the final production.

---

<div class="post-metadata">

**Author:** ![anthony-nicholls](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.juce.com/anthony-nicholls/32/1246_2.png) [@anthony-nicholls](https://forum.juce.com/u/anthony-nicholls)\
**Post date:** [May 15, 2025, 7:58am UTC](https://forum.juce.com/t/message-loop-outside-a-juce-app-or-plugin/66078/13 "2025-05-15T07:58:20Z")

</div>

I don’t see any of that causing any issues for the technique I’m suggesting above. If you already have those libraries working happily alongside JUCE then I wouldn’t expect a different thread to make a difference.

---

<div class="post-metadata">

**Author:** ![BenCello](https://avatars.discourse-cdn.com/v4/letter/b/dc4da7/32.png) [@BenCello](https://forum.juce.com/u/BenCello)\
**Post date:** [May 15, 2025, 9:32am UTC](https://forum.juce.com/t/message-loop-outside-a-juce-app-or-plugin/66078/14 "2025-05-15T09:32:48Z")

</div>

Yes, no, I agree! I wasn’t mentioning this with regards to the App solution that you recommend… I am confident that it should not make any difference indeed.

Thanks again @anthony-nicholls

---

<div class="post-metadata">

**Author:** ![BenCello](https://avatars.discourse-cdn.com/v4/letter/b/dc4da7/32.png) [@BenCello](https://forum.juce.com/u/BenCello)\
**Post date:** [May 15, 2025, 12:38pm UTC](https://forum.juce.com/t/message-loop-outside-a-juce-app-or-plugin/66078/15 "2025-05-15T12:38:27Z")

</div>

Yep! That works 🙂  
Just missing the constructor & destructor in your class definition…  
Then, my test code runs unchanged except for the call to `runDispatchLoopUntil()` that I don’t need anymore. And so I don’t need the modal loops to be enable.  
That’s a clean enough solution for me! Thanks

---

<div class="post-metadata">

**Author:** ![anthony-nicholls](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.juce.com/anthony-nicholls/32/1246_2.png) [@anthony-nicholls](https://forum.juce.com/u/anthony-nicholls)\
**Post date:** [May 15, 2025, 1:50pm UTC](https://forum.juce.com/t/message-loop-outside-a-juce-app-or-plugin/66078/16 "2025-05-15T13:50:07Z")

</div>

Glad it’s worked for you. I’ve just noticed looking back at my example that it doesn’t capture the return value which will be very important when running your tests! I think that can easily be achieved be passing the result of `Catch::Session().run()` to `setApplicationReturnValue()` before calling `quit()`.

---

<div class="post-metadata">

**Author:** ![BenCello](https://avatars.discourse-cdn.com/v4/letter/b/dc4da7/32.png) [@BenCello](https://forum.juce.com/u/BenCello)\
**Post date:** [May 15, 2025, 1:54pm UTC](https://forum.juce.com/t/message-loop-outside-a-juce-app-or-plugin/66078/17 "2025-05-15T13:54:06Z")

</div>

Yes, indeed, I was about to post that at the moment for future readers…  
I had to make it so:

```auto
const int result = Catch::Session().run(/* pass the args from commandLine here */);
setApplicationReturnValue (result);
quit();

```

In particular, to use it in a CI context.  
Thx
