I spent some time today tracking down the die-on-restart problem with wxHaskell. 


Here's a simple demo of the problem:

    import Graphics.UI.WX
    main = io >> io where io = start (frame [] >> return ())

(See http://sourceforge.net/tracker/?func=detail&atid=536845&aid=1610984&group_id=73133 .)

In practice, the die-on-restart problem happens commonly when using wxHaskell in ghci.  This behavior is why I stopped using wxHaskell and switched to gtk2hs, though I'd love to change back, since wxHaskell is a more elegant library, and gtk2hs and open don't work together under native os x (and the X-based macports version generates ugly GUIs).

I didn't get the the heart of the problem, though I may have gotten close.  This note records my journey, in the hope that together we can get further.



In Graphics.UI.WX (in the wx package):

    -- | 'start' runs the GUI.
    start :: IO a -> IO ()
    start io
      = run (unitIO io)

In Graphics.UI.WXCore.Types (wxcore):

    -- | Ignore the result of an 'IO' action.
    unitIO :: IO a -> IO ()
    unitIO io
      = do io; return ()

In Graphics.UI.WXCore (wxcore):

    -- | Start the event loop. Takes an initialisation action as argument.
    -- Except for 'run', the functions in the WXH library can only be called
    -- from this intialisation action or from event handlers, or otherwise bad
    -- things will happen :-)
    run :: IO a -> IO ()
    run init
      = do appOnInit (do wxcAppInitAllImageHandlers
                         init
                         return ())
           performGC
           performGC

I guess the unitIO in start is unnecessary, and start == run.

Appartently, the fatal re-initialization happens in appOnInit or wxcAppInitAllImageHandlers, or maybe both.

In Graphics.UI.WXCore.Events:

    ------------------------------------------------------------------------------------------
    -- Application startup
    ------------------------------------------------------------------------------------------
    -- | Installs an init handler and starts the event loop.
    -- Note: the closure is deleted when initialization is complete, and than the Haskell init function
    -- is started.
    appOnInit :: IO () -> IO ()
    appOnInit init
      = do closure  <- createClosure (return () :: IO ()) onDelete (\ev -> return ())   -- run init on destroy !
           progName <- getProgName
           args     <- getArgs
           argv     <- mapM newCWString (progName:args)
           let argc = length argv
           withArray (argv ++ [nullPtr]) $ \cargv -> wxcAppInitializeC closure argc cargv
           mapM_ free argv
      where
        onDelete ownerDeleted
          = init

What's going on here?

wxcAppInitializeC sounds pretty hard core.  In Graphics.UI.WXCore.WxcClassesMZ:

    -- | usage: (@wxcAppInitAllImageHandlers@).
    wxcAppInitAllImageHandlers ::  IO ()
    wxcAppInitAllImageHandlers
      = wx_ELJApp_InitAllImageHandlers
    foreign import ccall "ELJApp_InitAllImageHandlers" wx_ELJApp_InitAllImageHandlers :: IO ()

    -- | usage: (@wxcAppInitializeC closure argc argv@).
    wxcAppInitializeC :: Closure  a -> Int -> Ptr (Ptr CWchar) ->  IO ()
    wxcAppInitializeC closure _argc _argv
      = withObjectPtr closure $ \cobj_closure ->
        wx_ELJApp_InitializeC cobj_closure  (toCInt _argc)  _argv 
    foreign import ccall "ELJApp_InitializeC" wx_ELJApp_InitializeC :: Ptr (TClosure a) -> CInt -> Ptr (Ptr CWchar) -> IO ()

In wxcore-0.11.1.2/wxc/src/ewxw_main.cpp:

    EWXWEXPORT(void, ELJApp_InitializeC) (wxClosure* closure, int _argc, char** _argv)
    {
      char* args[] = { "wxc", NULL };

      initClosure = closure;
      if (_argv == NULL) {
        /* note: wxGTK crashes when argv == NULL */
        _argv = args;
        _argc = 1;
      }
      APPTerminating = 0;
      wxEntry(_argc,_argv);
      APPTerminating = 1;
      /* wxPendingEvents is deleted but not set to NULL -> disaster when restarted from an interpreter */
    #if !defined(WXMAKINGDLL) && !defined(WXUSINGDLL)
      wxPendingEvents = NULL;
    #endif
    #if defined(wxUSE_ODBC) && (wxUSE_ODBC != 0)
      wxDbCloseConnections();
    #endif
    }

(There is also a version for Win32.)

That "disaster" warning sounds relevant.  I didn't know whether the wxPendingEvents NULLing was in active code, so I added some non-compilable code in a new #else branch.  Recompilation succeeded, so I gather that the NULLing is happening.

Next, I added printfs before & after the wxEntry call in ELJApp_InitializeC.  Sure enough, on the second start, wxEntry is called but not returned from.

What's initClosure?  The only use I could found is in wxcore-0.11.1.2/wxc/src/wrapper.cpp:

    /*-----------------------------------------------------------------------------
        The main application
    -----------------------------------------------------------------------------*/
    wxClosure* initClosure = NULL;

    bool ELJApp::OnInit (void)
    {
      wxInitAllImageHandlers();
      initIdleTimer();
      if (initClosure) {
        delete initClosure; /* special: init is only called once with a NULL event */
        initClosure=NULL;
      }
      return true;
    }

I see the closure getting deleted but not getting invoked.  Oh, yeah!  The whole user action has been stuffed into the delete portion of the closure.  The other parts of the closure are inert.


Loose end: there's also ELJApp_InitAllImageHandlers, called by wxHaskell's 'run' (and hence 'start'), defined in wxcore-0.11.1.2/wxc/src/wrapper.cpp:

    EWXWEXPORT(void,ELJApp_InitAllImageHandlers)()
    {
            wxInitAllImageHandlers();
    }

I added printfs before & after the wxInitAllImageHandlers call.  They both showed up in both start attempts, so I think ELJApp_InitAllImageHandlers is not a re-start problem.


Where can we go from here?  For instance, are there solutions for other interpreted settings wxHaskell works, e.g. in Python, Ruby, Lisp or Scheme?

    - Conal