wxPython app hanging, not ending MainLoop

Hello,

I have a problem with my wxPython based application. The application hangs and does not leave the MainLoop.

Let me first describe my setup. I have a windows MFC c++ application that calls a python program via the COM interface. This python program is a win32 COM server. This program does have a need for a GUI frontend based
on wxPython. In order to separate this GUI from my main c++/Python application I use the python multiprocessing.Process class to create a separate python process in which I run my wx.App based application. Below you can find my simplified code.

If I use ‘python.exe’ instead of ‘pythonw.exe’ in the
multiprocessing.set_executable statement, the wx.App based application terminates fine, i.e. its OnExit handler is called and the MainLoop finishes as expected. However, if I
use ‘pythonw.exe’ instead, this is not the case anymore. Neither OnExit is called nor is the MainLoop finished. However, the frame OnCloseFrame handler is still called. I did try to use Destroy() instead of Skip() but that doesn’t work either.

So my question is, do anyone has an explanation why the gui is hanging when I use ‘pythonw.exe’. Additionally, do you know a measure with which I can solve my issue.

Best,

Johannes

GuiProcess runs function creation_fun in its own process. Here it is function StartNumber

class GuiProcess(multiprocessing.Process):

def init(self, inQueue, outQueue, creation_fun, process_name = ‘GuiProcess’, *args, **kwargs):

self._app = None

self._inQueue = inQueue

self._outQueue = outQueue

self._creation_fun = creation_fun

multiprocessing.set_executable(os.path.join(dirPython,‘pythonw.exe’))

multiprocessing.Process.init(self, name = process_name)

self._args = tuple(args)

self._kwargs = dict(kwargs)

self.daemon = True

def run(self):

self._app = self._creation_fun(app_data, self._inQueue, self._outQueue, *self._args, **self._kwargs)

GuiApp uses helper GuiProcess to call function creation_fun in a new process

class GuiApp():

def init(self, data, creation_fun, callbacks, *args, **kwargs):

self.data = data

self.callbacks = callbacks

self.inQueue = multiprocessing.Queue() # push data into gui process

self.outQueue = multiprocessing.Queue() # receive data from gui process

self.gui_process = GuiProcess(self.inQueue, self.outQueue, creation_fun, ‘GuiProcess’, *args, **kwargs)

self.gui_process.start()

class StartNumberFrame(wx.Frame):

def init( self, msgDict, data, inQueue, outQueue, *args, **kwargs ):

self.Bind( wx.EVT_CLOSE, self.OnCloseFrame )

def OnCloseFrame( self, event ):

event.Skip()

class StartNumberApp(wx.App):

def init(self, msgDict, data, inQueue, outQueue):

wx.App.init(self, False)

def OnInit(self):

parent = GetCADdyParent()

frame = StartNumber.StartNumberFrame(self.msgDict, self.data, self.inQueue, self.outQueue, parent)

frame.Show(True)

self.SetTopWindow(frame)

return True

def OnExit(self):

This function creates a wx.App based instance and starts its main loop.

def StartNumber(data, inQueue, outQueue, *args, **kwargs):

app = StartNumberApp(msgDict, data, inQueue, outQueue)

app.MainLoop()

The main python application started from my MFC windows C++ program via COM

class ActRenumber(ActionBase):

def init(self, …):

self.app_start_number = GuiApp(…,StartNumber, …) # GuiApp takes function StartNumber which is finally responsible for the creation of the wxApp application run in a separate
process.

Hi,

I would like to give some additional informations. Maybe someone does have an idea with these :slight_smile:

1. I do have the problem with wxPython 3.0.2 on windows 64 bit, i.e. the pythonw.exe is not terminated after closing the frame of my wx.App based python application. I do have to manually remove it from the windows task manager.

2. As already noted, if I do use python.exe instead of pythonw.exe I do not see any problem, i.e the main loop of the wx.App program is terminated correctly.

3. The problem does not show up with version wxPython 3.0.0.

4. I use the following code do get the parent for my wx.Frame from the foreign MFC application:

def GetCADdyParent():
     parent = None

     from CADdy.Application import CheckApplic
     if CheckApplic():
         try:
             from CADdy.Application import GetApplic
             applic = GetApplic()
             uiManager = applic.UIManager()

             # Get the native window handle from the main non wx
             # application.
             w = uiManager.MainWindowHandle

             parent = wx.Window_FromHWND(None, w)

         except:
             pass

     return parent

5. If I do not call the GetCADdyParent function, i.e. if I do initialize my frame with no parent the wx.App OnExit() handler is called and the main loop is terminated correctly.

6. The following style flags are used on initialization of my frame:
     wx.DEFAULT_FRAME_STYLE
     wx.FRAME_FLOAT_ON_PARENT
     wx.FRAME_NO_TASKBAR
     wx.RESIZE_BORDER
     wx.SYSTEM_MENU
     wx.TAB_TRAVERSAL

7. It does not make any difference with respect of leaving the main loop, whether I use the following two style flags:

     wx.FRAME_FLOAT_ON_PARENT
     wx.FRAME_NO_TASKBAR

What could be the reason for the not ending wx.App main loop?
What is the exact termination process?
What I mean is, what is the function call order that finally leads to wx.Frame.OnExit() and terminates the wx.App.mainLoop()?
What could prohibit this termination process?
What are the C++ sources that I have to look into in order to get some insight into the termination process?

I would really appreciate some help with this problem.

Best,
Johannes

Some reasons a wxPython application may not fully exit properly is:

. not all top level windows are destroyed

. not all system resources are released such as stdio, timers, gdc's,
network stuff, etc.

. not all threads are properly closed or they are in a deadlock

. or you are processing wxPython generated events outside of the same
process as the wxApp wxPython process - often the wxPython process expects
to be the "main" or top-most process.

Did you tell us previously that the parent window for your frame is
a window handle from a different process? That’s a very unusual
thing to do. The net result of this is to link the two message
queues together in ways that wxPython is almost certainly not
prepared to handle. In particular, the window destruction chain is
known to be delicate in this case. If you get a clue when the app
is going to terminate, the suggestion is that you break the
parent/child relationship before destroying the windows. That
separates the message queues again.
Are you running your own main loop, or are you relying on the MFC
application to do the dispatching for you?

···

Johannes wrote:

4. I use the following code do get the parent for my wx.Frame from the foreign MFC application:
-- Tim Roberts, Providenza & Boekelheide, Inc.

timr@probo.com

Hello Tim,

> Did you tell us previously that the parent window for
> your frame is a window handle from a different process?

I use wx.Window_FromHWND(None, hwnd_of_foreign_app_window) as the parent of my wx.Frame.
What I would like to archieve here is, that the wxPython application, running in its own process, stay on top of my main MFC application.

> That's a very unusual thing to do.

How else can you bring your wxPython application always staying on top of another window?

> The net result of this is to link the two message queues
> together in ways that wxPython is almost certainly not
> prepared to handle.

Hmm, but it works great with the exception of the mainLoop termination and that only if I use pythonw.exe instead of python.exe. Also if I use wxPython 3.0.0 I do not see this problem.

Below, I have condensed my setup so that you can see what I have done.

self.gui_process = GuiProcess(..., StartNumber, ...)
self.gui_process.start()

def StartNumber(...):
    app = StartNumberApp(...)
    app.MainLoop()

class GuiProcess(multiprocessing.Process):
     # creation_fun is def StartNumber
     def __init__(self, ..., creation_fun, ...):
         self._creation_fun = creation_fun

         # with python.exe instead, everything works correctly!
         multiprocessing.set_executable('pythonw.exe')
         multiprocessing.Process.__init__(self, ...)

         self.daemon = True

     def run(self):
         self._app = self._creation_fun(...)

class StartNumberFrame(wx.Frame):
     def __init__( self, parent, ... ):
         wx.Frame.__init__ ( self, parent, ..., style = wx.DEFAULT_FRAME_STYLE|wx.FRAME_FLOAT_ON_PARENT|wx.FRAME_NO_TASKBAR )
         self.Bind( wx.EVT_CLOSE, self.OnCloseFrame )
         ...
     def OnCloseFrame( self, event ):
         ...
         event.Skip()

class StartNumberApp(wx.App):
     def __init__(self, ...):
         wx.App.__init__(self, False)

    def OnInit(self):
         parent = GetCADdyParent()

         frame = StartNumber.StartNumberFrame(..., parent)
         frame.Show(True)

         self.SetTopWindow(frame)

        return True

     def OnExit(self):
         # After closing the frame OnExit is
         # not called if used pythonw.exe in multiprocessing.set_executable,
         # but called if used python.exe instead
         ...

def GetCADdyParent():
     parent = None

     from CADdy.Application import CheckApplic
     if CheckApplic():
         try:
             from CADdy.Application import GetApplic
             applic = GetApplic()
             uiManager = applic.UIManager()

             # Get the window handle (HWND) of the main window
             # of the MFC based C++ application.
             hwnd = uiManager.MainWindowHandle
             parent = wx.Window_FromHWND(None, hwnd)
         except:
             pass

     return parent

Alternatively, I have tried the following implementation path in oder to use DissociateHandle() on my frame after creation. But it does not solve the termination problem. Otherwise it works like my original solution.

class StartNumberApp(wx.App):
     def OnInit(self):
         ...
         frame = StartNumber.StartNumberFrame.create(self.msgDict, self.data, self.inQueue, self.outQueue, None)
         return True

class ForeignFrame(wx.Frame):
     _top_hwnd = None

     def __init__(self, parent, id=-1, title="", pos=(-1, -1), size=(-1, -1), name='frame', resize=True, style=wx.DEFAULT_FRAME_STYLE|wx.FRAME_FLOAT_ON_PARENT|wx.FRAME_NO_TASKBAR):
         wx.Frame.__init__(self, parent, id, title, pos, size, style)

         self.panel = None

         self.initialPosOffset = pos
         self._runningModal = False

         if (not resize):
             wx.Frame.ToggleWindowStyle(self, wx.RESIZE_BORDER)

         self.Bind(wx.EVT_CLOSE, self.on_close)

     @classmethod
     def create(cls, *args, **kwargs):
         app = wx.GetApp()

         if (app == None):
             app = wx.App(redirect=False)

         topHandle = ForeignFrame._get_top_hwnd()

         preFrame = wx.PreFrame()
         preFrame.AssociateHandle(topHandle)
         preFrame.PostCreate(preFrame)

         app.SetTopWindow(preFrame)

         try:
             frame = cls(preFrame, *args, **kwargs)
             frame.Show(True)
         except:
             frame = None

         preFrame.DissociateHandle()

         return frame

     @staticmethod
     def _get_top_hwnd():
         if ForeignFrame._top_hwnd is not None:
             return ForeignFrame._top_hwnd

         hwnd = None

         from CADdy.Application import CheckApplic
         if CheckApplic():
             try:
                 from CADdy.Application import GetApplic
                 applic = GetApplic()
                 uiManager = applic.UIManager()
                 hwnd = uiManager.MainWindowHandle
             except:
                 pass

         return hwnd

     def on_close(self, evt):
         self.Show(False)
         self.Destroy()

> If you get a clue when the app is going to terminate,
> the suggestion is that you break the parent/child
> relationship before destroying the windows.
> That separates the message queues again.

Ok, that is a good hint and it solved the termination problem. I added a parent.RemoveChild(self) call to the OnCloseFrame handler just before the call of event.Skip().

class StartNumberFrame(wx.Frame):
     def __init__( self, parent, ... ):
         wx.Frame.__init__ ( self, parent, ..., style = wx.DEFAULT_FRAME_STYLE|wx.FRAME_FLOAT_ON_PARENT|wx.FRAME_NO_TASKBAR )
         self.Bind( wx.EVT_CLOSE, self.OnCloseFrame )
         ...
     def OnCloseFrame( self, event ):
         ...
         parent = self.GetParent()
         if parent is not None:
             parent.RemoveChild(self)
         event.Skip()

Thank you for taking your time.

Best,
Johannes