Inheriting from ComponentPeer: hasHeavyweightPeer


#1

Hello again!

I am trying to create a plugin system for my recent project. I want the plugins GUI to be displayed inside a Component on the main applications GUI. I wrote two classes to create a tunnel between the main app and the lib, but you can guess that it is not working as I wish, hence my question here.

The problem seems to be a restriction in JUCE code, so I hope someone finds some time to help me out.

I attached an image to explain the idea:
[attachment=0]sketch.gif[/attachment]
The library-gui is coded with JUCE components. These components are attached to a “fake window”: The TunnelPeer - a class inherited from ComponentPeer. This class translates repaint() calls from the GUIs components and passes them on to the main application via the C API.
On the other side, the main application has a TunnelComponent attached to its GUI. This TunnelComponent is “connected” to the TunnelPeer inside the library. It has an image buffer that is painted onto the screen when necessary. Now after the library has called the main application because its GUI needs to be repainted, the main application will eventually do a call to the TunnelPeer to make it repaint the libraries GUI and pass simple Bitmap data back to the main application. This bitmap data is filled inside the TunnelComponents internal buffer and made visible on the screen.
Other GUI related calls (such as Mouse Events) will be treated similar.

This way, I have simple classes on both ends of the tunnel. These behave like normal JUCE classes but are internally connected. So anything drawn on the libraries GUI can be made visible on the main applications GUI through a C interface.

The nice thing is, that any call to “repaint()” from any component inside the libraries GUI will propagate through its parent Components until they reach the TunnelPeer. The TunnelPeer will propagate the message to the main application and from there on upwards until it finally reaches the Window of the main application. So from the library the behavior should actually be the same as if it had its own window.

Now, that does only work partially. The problem is the propagation of this repaint() call from one Component to its parent. Look at this part of the juce::Component implementation[code]void Component::internalRepaintUnchecked (const Rectangle& area, const bool isEntireComponent)
{
if (flags.visibleFlag)
{
if (cachedImage != nullptr)
{
if (isEntireComponent)
cachedImage->invalidateAll();
else
cachedImage->invalidate (area);
}

    if (flags.hasHeavyweightPeerFlag)
    {
        // if component methods are being called from threads other than the message
        // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe.
        CHECK_MESSAGE_MANAGER_IS_LOCKED

        ComponentPeer* const peer = getPeer();

        if (peer != nullptr)
            peer->repaint (area);
    }
    else
    {
        if (parentComponent != nullptr)
            parentComponent->internalRepaint (ComponentHelpers::convertToParentSpace (*this, area));
    }
}

}[/code]

When a Component needs to be repainted, it will call its parent Component, if there is one. And if it has “flags.hasHeavyweightPeerFlag” set, it will try to find a ComponentPeer to call “repaint” on. My own TunnelPeer Component is such a HeavyweightPeer but the flag can only be set by calling “Component::addToDesktop(…)”. Which is clearly not, what I’m trying to do. So basically: The repaint() messages from components inside the library propagate all the way up to the most top-level Component that has been attached to my TunnelPeer. But the heavyweight-Flag is not set (and can’t ever be set except from ComponentPeer itself). Thus it will never call the TunnelPeer and thus the message will never reach the main application.

I know that’s a pretty long explanation and I hope it was clear. Please, if anyone has an idea: What can i do to get that propagation right? Everything else seems to be working…

StrangeMan


#2

Was that a dumb question?
Is there a better way to get a plugins GUI inside the hosts GUI?


#3

http://www.rawmaterialsoftware.com/juce/api/classAudioProcessorEditor.html

Create instance of editor via juce::AudioProcessor::createEditor from your plugin instance, addAndMakeVisible(editor) in parent… Done simply, without tunneling business.


#4

I don’t think that helps… The plugins I use are not VST/AU/RTAS plugins, but something more specialized inside a *.dll. Basically I have a JUCE based application and JUCE code inside a dll. I want the GUI from inside the dll to be visible on the main applications (host) GUI.

My first approach was to create a Component object inside the dll and pass a pointer over to the main application. The application would the call addAndMakeVisible(lib_gui_pointer) on its own GUI. However, I noticed that everything declared as static is not shared between the library and the main application - therefore repainting did not work. I asked here and Jules explained the case for me and told me to use a simple C API to avoid passing objects between the dll and the main application. So this is my second approach. And slowly it appears to me that this approach is wrong, too. I can post some code, if it helps.

thanks,
StrangeMan


#5

In the end, this is actually a question of the best concept for getting the GUI from a dll inside the hosts window. I found it very hard to find useful information on the web, so I tried, what appeared to me as the most obvious: passing a Component object from the dll to the host application. That was wrong, and here is my second idea. But that one seems to be wrong, too.


#6

Perhaps you should take a look at the VST wrapping code as it could provide hints?


#7

StrangeMan, I think the problem is having two copies of Juce - one statically linked into your exe and one statically linked into your plugin DLL. If you put all the Juce code into a separate Juce DLL that your exe and plugin DLL both dynamically link to and make sure you keep all of the Juce code out of your exe and plugin DLL, then you will only have one copy of the Juce static variables and everything should play nice together.


#8

Hello!

@oneish: That’s an interesting idea. However, it would force all plugins to be coded in C++ and with JUCE. Actually I liked the idea, that more languages can be used, once the interface is just a plain C one. However, I will keep that in mind as another possibility! Thank you!

@jrlanglois: I have been scrolling through the VST code several times before, but I somehow did not manage to understand the logic. The OSX part seems relatively “easy”, because there is that NSViewComponent that wraps around a native handle and allows me to pin it onto my existing GUI. (well, that might be wrong, so please correct me if so). However the windows part still looks a bit strange to me. What I understood so far: You usually create a window and get the native handle for it, then you pass that handle on to the plugin (vst or whatever) and make it attach its GUI to that handle.
This is what I found inside the VSTPluginFormat class:

[code]void openPluginWindow()
{
if (isOpen || getWindowHandle() == 0)
return;

    JUCE_VST_LOG ("Opening VST UI: " + plugin.getName());
    isOpen = true;

    ERect* rect = nullptr;
    dispatch (effEditGetRect, 0, 0, &rect, 0);
    dispatch (effEditOpen, 0, 0, getWindowHandle(), 0);

    // do this before and after like in the steinberg example
    dispatch (effEditGetRect, 0, 0, &rect, 0);
    dispatch (effGetProgram, 0, 0, 0, 0); // also in steinberg code

    // Install keyboard hooks
    pluginWantsKeys = (dispatch (effKeysRequired, 0, 0, 0, 0) == 0);

   #if JUCE_WINDOWS
    originalWndProc = 0;
    pluginHWND = GetWindow ((HWND) getWindowHandle(), GW_CHILD);

    if (pluginHWND == 0)
    {
        isOpen = false;
        setSize (300, 150);
        return;
    }

    #pragma warning (push)
    #pragma warning (disable: 4244)

    originalWndProc = (void*) GetWindowLongPtr (pluginHWND, GWLP_WNDPROC);

    if (! pluginWantsKeys)
        SetWindowLongPtr (pluginHWND, GWLP_WNDPROC, (LONG_PTR) vstHookWndProc);

    #pragma warning (pop)

    int w, h;
    RECT r;
    GetWindowRect (pluginHWND, &r);
    w = r.right - r.left;
    h = r.bottom - r.top;

    if (rect != nullptr)
    {
        const int rw = rect->right - rect->left;
        const int rh = rect->bottom - rect->top;

        if ((rw > 50 && rh > 50 && rw < 2000 && rh < 2000 && rw != w && rh != h)
            || ((w == 0 && rw > 0) || (h == 0 && rh > 0)))
        {
            // very dodgy logic to decide which size is right.
            if (abs (rw - w) > 350 || abs (rh - h) > 350)
            {
                SetWindowPos (pluginHWND, 0,
                              0, 0, rw, rh,
                              SWP_NOMOVE | SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER);

                GetWindowRect (pluginHWND, &r);

                w = r.right - r.left;
                h = r.bottom - r.top;

                pluginRefusesToResize = (w != rw) || (h != rh);

                w = rw;
                h = rh;
            }
        }
    }[/code]

Let me try to say what it does:
[list=1][]It checks, if the window it is being displayed in is open and valid.[/]
[]It gets the size information from the plugin[/]
[]It passes the handle of its own window to the plugin (why?)[/]
[]It gets the native handle for the most-top child window of its own window and checks if there was one found, if yes, the plugin seems to have its window created.[/]
[]The rest seems to be logic for rescaling and key/mouse events[/][/list]
So the part that appears strange to me is number 3 and 4. Does the VST plugin create a new window as a child window? All of that does not make much sense to me so far.
I guess I’ll go read some Windows window handling guideline/tutorial to understand that stuff…

Thank you for your answers!
Strange Man

PS: This board helped me a lot so far, but I feel its difficult to give back - I hope to eventually be able to contribute something to the community here …


#9

Here is how I interpret the code reproduced above:

  1. check that the plugin window is NOT open already, but that the window we want it to use is open
  2. ask the plugin how big a window it wants
  3. send the plugin a window handle to use
  4. repeat 2
  5. get a handle for the plugin window’s ultimate parent
  6. replace the plugin parent window’s main processing loop with a new one (I think the new one may reuse the main Juce one, but traps a few specific messages)
  7. try to work out the correct size for the new window etc.

#10

Can you explain that?

The processing loop part looks straight forward - it just filters out some message types and then calls the original loop if necessary.


#11

I guess we need to find the unltimate parent because that’s what the processing loop needs to be associated with.