Win VM modifications

Rob Gayvert rtg at rochester.rr.com
Thu Mar 3 17:47:46 UTC 2005


Andreas Raab wrote:

>>> We just tried it, and no, it wouldn't harm regular Squeak at all. 
>>> But it does seem to harm Areithfa Ffenestri support (see 
>>> http://minnow.cc.gatech.edu/squeak/3862) quite severely.
>>
>> That certainly makes sense -- everything but your main window will be 
>> ignored.
>
> Well, it makes no sense whatsoever to me ;-) If wxWindows runs a 
> mainloop I would expect it to drain the app event queue and dispatch 
> all the events (just like the loop in ioProcessEvent does). If it 
> would do so, then clearly Ffenestri should work (since the events 
> would eventually get into the WndProc and passed up to Squeak) so the 
> behavior of wxWindows is truly extraordinary.

Why doesn't this make sense?  If you use
                 PeekMessage(&msg,stWindow,0,0,PM_NOREMOVE))
you're only getting messages for the main Squeak window. And in my wx 
check, I'm only peeking for messages to windows other than the main 
Squeak window.  

> It is really important to understand what a Windows app mainloop 
> really does: If we have, e.g., the standard loop from sqWin32Window.c
>
>  while(PeekMessage(&msg,NULL,0,0,PM_NOREMOVE))
>    {
>      GetMessage(&msg,NULL,0,0);
>      DispatchMessage(&msg);
>    }
>
> then the following happens:
> * PeekMessage: Will see if there are any events for *any* window in 
> the app's event queue without removing it
> * GetMessage: Will fetch the next event from the queue which may be 
> for *any* window
> * DispatchMessage: Will deliver the event to the *appropriate* 
> window's WndProc (which is registered with the window when the window 
> is created)
>
> In other words: A window created by wxWindow *will* receive the event 
> through the above mechanism in its registered WndProc. A mainloop like 
> the above run by wxWindow *should* deliver events to *either* the main 
> Squeak window or any other (e.g., Ffenestri-created) windows. The 
> behavior of wxWindows apparently not adhering to either of the above 
> two is extremely strange!

Well, I'm certainly no Win32 expert, so I can't say whether wxWidgets is 
strange, but its MainLoop does more than Peek/Get/Dispatch.  If you have 
the stomach for it, take a look at the attachment, evtloop.cpp.

If I revert the original ioProcessEvents and turn off my event process, 
events do get delivered to wx windows, but it behaves very badly and 
generally crashes in short order.  And if events destined for wx windows 
are passed on to wxApp::ProcessMessage from ioProcessEvents, it works a 
bit better, but still crashes. The only scheme I've found that works 
consistently is to poll for wx events in a separate Squeak process. I 
don't like it, but it works.

> I would expect that the first version works (wxWindows does get the 
> events synchronously in its WndProc!) *and* that the second version 
> works - wxWindow should deliver the events to the appropriate WndProc, 
> so you should be perfectly able to simply not to run ioProcessEvents 
> *at all* (this would be a good test - if Squeak doesn't work when 
> wxWindows runs the mainloop something is horribly broken).

That would be a good test, but I wouldn't be surprised if the funky 
stuff that goes on in ProcessMessage will screw it up for Squeak.

>> Well, this was the fundamental problem in getting two GUI systems to 
>> work together, when each one wants to have the MainLoop. I'm not 
>> crazy about the current scheme, but here's how it works. As we've 
>> discussed, the Squeak loop is modified to ignore events to non-Squeak 
>> windows. At startup, a process is run that checks every few 
>> milliseconds for an event to a wx-window (ignoring events to the 
>> Squeak window).  When it gets an event, it passes it through the 
>> normal wxWidgets processing. Not pretty, but it works.
>
> How does it check for the events? (can I see the source code?)

You can get all the wxSqueak source from my website, and all of the 
wxWidgets source from wxwidgets.org.
The key part of the check is this:

    if (PeekMessage(&msg,0,0,0,PM_NOREMOVE)) {
        if (msg.hwnd != stWindow){
            GetMessage(&msg,0,0,0);
            if (!((wxSqueakApp*)wxTheApp)->ProcessMessage(&msg)){
                TranslateMessage(&msg);
                DispatchMessage(&msg);
            }

If it weren't for the ProcessMessage() call, this would be a "normal" loop.

>> Initially, I had hoped that I could tap into events after they 
>> arrived on the Smalltalk side, assuming they had been augmented with 
>> window information like your're doing with Areithfa Ffenestri. But 
>> Squeak events are handled asychronously, and so much of the wxWidgets 
>> event processing depends on them being handled synchronously that I 
>> just couldn't get it to work.
>
> I think you have a major bug in your thought process - as far as I can 
> tell (and as far as I know Windows) your windows should get the events 
> just fine (see above explanation). Unless there is some very strange 
> magic in the main loop of wxWindows (do you have the source for the 
> mainloop?) this ought to work with no further modifications.

I hope you're right. It would be much cleaner to handle all events in a 
unified way, and eliminate the horribly inefficient event process in Squeak.




-------------- next part --------------
///////////////////////////////////////////////////////////////////////////////
// Name:        msw/evtloop.cpp
// Purpose:     implements wxEventLoop for MSW
// Author:      Vadim Zeitlin
// Modified by:
// Created:     01.06.01
// RCS-ID:      $Id: evtloop.cpp,v 1.25 2004/07/30 22:54:25 VZ Exp $
// Copyright:   (c) 2001 Vadim Zeitlin <zeitlin at dptmaths.ens-cachan.fr>
// License:     wxWindows licence
///////////////////////////////////////////////////////////////////////////////

// ============================================================================
// declarations
// ============================================================================

// ----------------------------------------------------------------------------
// headers
// ----------------------------------------------------------------------------

#if defined(__GNUG__) && !defined(NO_GCC_PRAGMA)
    #pragma implementation "evtloop.h"
#endif

// For compilers that support precompilation, includes "wx.h".
#include "wx/wxprec.h"

#ifdef __BORLANDC__
    #pragma hdrstop
#endif

#ifndef WX_PRECOMP
    #include "wx/window.h"
    #include "wx/app.h"
#endif //WX_PRECOMP

#include "wx/evtloop.h"

#include "wx/tooltip.h"
#include "wx/except.h"
#include "wx/ptr_scpd.h"

#include "wx/msw/private.h"

#if wxUSE_THREADS
    #include "wx/thread.h"

    // define the array of MSG strutures
    WX_DECLARE_OBJARRAY(MSG, wxMsgArray);

    #include "wx/arrimpl.cpp"

    WX_DEFINE_OBJARRAY(wxMsgArray);
#endif // wxUSE_THREADS

// ----------------------------------------------------------------------------
// helper class
// ----------------------------------------------------------------------------

// this object sets the wxEventLoop given to the ctor as the currently active
// one and unsets it in its dtor
class wxEventLoopActivator
{
public:
    wxEventLoopActivator(wxEventLoop **pActive,
                         wxEventLoop *evtLoop)
    {
        m_pActive = pActive;
        m_evtLoopOld = *pActive;
        *pActive = evtLoop;
    }

    ~wxEventLoopActivator()
    {
        // restore the previously active event loop
        *m_pActive = m_evtLoopOld;
    }

private:
    wxEventLoop *m_evtLoopOld;
    wxEventLoop **m_pActive;
};

// ============================================================================
// wxEventLoop implementation
// ============================================================================

wxEventLoop *wxEventLoopBase::ms_activeLoop = NULL;

// ----------------------------------------------------------------------------
// ctor/dtor
// ----------------------------------------------------------------------------

wxEventLoop::wxEventLoop()
{
    m_shouldExit = false;
    m_exitcode = 0;
}

// ----------------------------------------------------------------------------
// wxEventLoop message processing
// ----------------------------------------------------------------------------

void wxEventLoop::ProcessMessage(WXMSG *msg)
{
    // give us the chance to preprocess the message first
    if ( !PreProcessMessage(msg) )
    {
        // if it wasn't done, dispatch it to the corresponding window
        ::TranslateMessage(msg);
        ::DispatchMessage(msg);
    }
}

bool wxEventLoop::PreProcessMessage(WXMSG *msg)
{
    HWND hwnd = msg->hwnd;
    wxWindow *wndThis = wxGetWindowFromHWND((WXHWND)hwnd);

    // this may happen if the event occured in a standard modeless dialog (the
    // only example of which I know of is the find/replace dialog) - then call
    // IsDialogMessage() to make TAB navigation in it work
    if ( !wndThis )
    {
        // we need to find the dialog containing this control as
        // IsDialogMessage() just eats all the messages (i.e. returns true for
        // them) if we call it for the control itself
        while ( hwnd && ::GetWindowLong(hwnd, GWL_STYLE) & WS_CHILD )
        {
            hwnd = ::GetParent(hwnd);
        }

        return hwnd && ::IsDialogMessage(hwnd, msg) != 0;
    }

#if wxUSE_TOOLTIPS
    // we must relay WM_MOUSEMOVE events to the tooltip ctrl if we want it to
    // popup the tooltip bubbles
    if ( msg->message == WM_MOUSEMOVE )
    {
        wxToolTip *tt = wndThis->GetToolTip();
        if ( tt )
        {
            tt->RelayEvent((WXMSG *)msg);
        }
    }
#endif // wxUSE_TOOLTIPS

    // allow the window to prevent certain messages from being
    // translated/processed (this is currently used by wxTextCtrl to always
    // grab Ctrl-C/V/X, even if they are also accelerators in some parent)
    if ( !wndThis->MSWShouldPreProcessMessage((WXMSG *)msg) )
    {
        return false;
    }

    // try translations first: the accelerators override everything
    wxWindow *wnd;

    for ( wnd = wndThis; wnd; wnd = wnd->GetParent() )
    {
        if ( wnd->MSWTranslateMessage((WXMSG *)msg))
            return true;

        // stop at first top level window, i.e. don't try to process the key
        // strokes originating in a dialog using the accelerators of the parent
        // frame - this doesn't make much sense
        if ( wnd->IsTopLevel() )
            break;
    }

    // now try the other hooks (kbd navigation is handled here): we start from
    // wndThis->GetParent() because wndThis->MSWProcessMessage() was already
    // called above
    for ( wnd = wndThis->GetParent(); wnd; wnd = wnd->GetParent() )
    {
        if ( wnd->MSWProcessMessage((WXMSG *)msg) )
            return true;
    }

    // no special preprocessing for this message, dispatch it normally
    return false;
}

// ----------------------------------------------------------------------------
// wxEventLoop running and exiting
// ----------------------------------------------------------------------------

bool wxEventLoop::IsRunning() const
{
    return ms_activeLoop == this;
}

int wxEventLoop::Run()
{
    // event loops are not recursive, you need to create another loop!
    wxCHECK_MSG( !IsRunning(), -1, _T("can't reenter a message loop") );

    // ProcessIdle() and Dispatch() below may throw so the code here should
    // be exception-safe, hence we must use local objects for all actions we
    // should undo
    wxEventLoopActivator activate(&ms_activeLoop, this);

    // we must ensure that OnExit() is called even if an exception is thrown
    // from inside Dispatch() but we must call it from Exit() in normal
    // situations because it is supposed to be called synchronously,
    // wxModalEventLoop depends on this (so we can't just use ON_BLOCK_EXIT or
    // something similar here)
#if wxUSE_EXCEPTIONS
    for ( ;; )
    {
        try
        {
#endif // wxUSE_EXCEPTIONS

            // this is the event loop itself
            for ( ;; )
            {
                #if wxUSE_THREADS
                    wxMutexGuiLeaveOrEnter();
                #endif // wxUSE_THREADS

                // generate and process idle events for as long as we don't
                // have anything else to do
                while ( !Pending() && (wxTheApp && wxTheApp->ProcessIdle()) )
                    ;

                // if the "should exit" flag is set, the loop should terminate
                // but not before processing any remaining messages so while
                // Pending() returns true, do process them
                if ( m_shouldExit )
                {
                    while ( Pending() )
                        Dispatch();

                    break;
                }

                // a message came or no more idle processing to do, sit in
                // Dispatch() waiting for the next message
                if ( !Dispatch() )
                {
                    // we got WM_QUIT
                    break;
                }
            }

#if wxUSE_EXCEPTIONS
            // exit the outer loop as well
            break;
        }
        catch ( ... )
        {
            try
            {
                if ( !wxTheApp || !wxTheApp->OnExceptionInMainLoop() )
                {
                    OnExit();
                    break;
                }
                //else: continue running the event loop
            }
            catch ( ... )
            {
                // OnException() throwed, possibly rethrowing the same
                // exception again: very good, but we still need OnExit() to
                // be called
                OnExit();
                throw;
            }
        }
    }
#endif // wxUSE_EXCEPTIONS

    return m_exitcode;
}

void wxEventLoop::Exit(int rc)
{
    wxCHECK_RET( IsRunning(), _T("can't call Exit() if not running") );

    m_exitcode = rc;
    m_shouldExit = true;

    OnExit();

    // all we have to do to exit from the loop is to (maybe) wake it up so that
    // it can notice that Exit() had been called
    //
    // in particular, we do *not* use PostQuitMessage() here because we're not
    // sure that WM_QUIT is going to be processed by the correct event loop: it
    // is possible that another one is started before this one has a chance to
    // process WM_QUIT
    ::PostMessage(NULL, WM_NULL, 0, 0);
}

// ----------------------------------------------------------------------------
// wxEventLoop message processing dispatching
// ----------------------------------------------------------------------------

bool wxEventLoop::Pending() const
{
    MSG msg;
    return ::PeekMessage(&msg, 0, 0, 0, PM_NOREMOVE) != 0;
}

bool wxEventLoop::Dispatch()
{
    wxCHECK_MSG( IsRunning(), false, _T("can't call Dispatch() if not running") );

    MSG msg;
    BOOL rc = ::GetMessage(&msg, (HWND) NULL, 0, 0);

    if ( rc == 0 )
    {
        // got WM_QUIT
        return false;
    }

    if ( rc == -1 )
    {
        // should never happen, but let's test for it nevertheless
        wxLogLastError(wxT("GetMessage"));

        // still break from the loop
        return false;
    }

#if wxUSE_THREADS
    wxASSERT_MSG( wxThread::IsMain(),
                  wxT("only the main thread can process Windows messages") );

    static bool s_hadGuiLock = true;
    static wxMsgArray s_aSavedMessages;

    // if a secondary thread owning the mutex is doing GUI calls, save all
    // messages for later processing - we can't process them right now because
    // it will lead to recursive library calls (and we're not reentrant)
    if ( !wxGuiOwnedByMainThread() )
    {
        s_hadGuiLock = false;

        // leave out WM_COMMAND messages: too dangerous, sometimes
        // the message will be processed twice
        if ( !wxIsWaitingForThread() || msg.message != WM_COMMAND )
        {
            s_aSavedMessages.Add(msg);
        }

        return true;
    }
    else
    {
        // have we just regained the GUI lock? if so, post all of the saved
        // messages
        //
        // FIXME of course, it's not _exactly_ the same as processing the
        //       messages normally - expect some things to break...
        if ( !s_hadGuiLock )
        {
            s_hadGuiLock = true;

            size_t count = s_aSavedMessages.Count();
            for ( size_t n = 0; n < count; n++ )
            {
                MSG& msg = s_aSavedMessages[n];
                ProcessMessage(&msg);
            }

            s_aSavedMessages.Empty();
        }
    }
#endif // wxUSE_THREADS

    ProcessMessage(&msg);

    return true;
}




More information about the Wxsqueak mailing list