Niki Spahiev wrote:
Jeff Shannon wrote:
When I did this, I created a COM server that immediately starts a second thread. That second thread then creates my wxApp.
Can i see some of COM server code? My attempt did not work.
I don't have time to try to resolve the problems I mentioned just now, but I can show you the core part of the COM server code. Be warned that I haven't really looked at this code in over a year (and I was pretty new to wxPython then -- this was one of my learning projects). Some of the code is (failed) attempts to work around the exit-issues. (After the COM server is sent an exit command, it requests that the wxApp shut down, but that shutdown doesn't happen until the wxApp receives another event. I've tried to trigger this with wxWakeUpIdle(), with no luck. I don't think I tried playing with wxCallAfter(), though... ) I should also mention that, while the COM server is theoretically destroyed, the host (COM client) application is still running and it seems to cache the COM server (changes to the COM server code don't show up until I close the application and restart it), so there may be a hidden cache reference that's keeping things alive... I'm not enough of an expert on COM to really understand the interplay here -- I just barely know enough about COM to accomplish this much.
This application is designed to show and manipulate (scale, rotate, etc) specific images under control from another application (which is actually a COM-enabled terminal emulator connected to our database host).
import win32com.server
import pythoncom
import threading, Queue
class ImgCOMServer:
_public_methods_ = [ ... ]
_reg_progid_ = "..."
_reg_clsid_ = "..." # GUID
_reg_clstx_ = pythoncom.CLSCTX_LOCAL_SERVER
def __init__(self):
self.inqueue = Queue.Queue()
self.outqueue = Queue.Queue()
self.thread = MyThread(self.inqueue, self.outqueue)
self.thread.setDaemon(1)
self.thread.start()
def SendCmd(self, token):
print "Sending command %s" % token.command
self.inqueue.put(token)
reply = self.outqueue.get()
if reply.command == token.command and reply.ok:
print "Received reply: %s" % reply.result
return reply.result
raise win32com.server.exception.COMException(
'Unable to complete command %s' % token.command)
The 'token' being used in the SendCmd method is simply an instance object (of class Token, defined elsewhere), which has a single mandatory attribute 'command' that contains a string indicating the command name, and various optional attributes carrying any additional information a particular command might need. Once the wxApp has handled the command, it puts the same token back into the outqueue, with an 'ok' flag to indicate success or failure and a 'result' which may or may not be None. This currently executes a single command and then waits for a response -- a more complex program might use command ids and nonblocking queue reads to send multiple simultaneous commands, but I didn't need that and didn't want to mess with the complexity. (IIRC, the Thread.setDaemon() call in __init__() was one of those failed work-arounds for the exiting issue that I mentioned...)
class MyThread(threading.Thread):
def __init__(self, inqueue, outqueue):
threading.Thread.__init__(self, name='ImageViewerThread')
self.inqueue = inqueue
self.outqueue = outqueue
def run(self):
print "Starting thread..."
import ImageApp
self._app = ImageApp.MyApp(0)
self._app.inqueue = self.inqueue
self._app.outqueue = self.outqueue
print "Entering message loop..."
self._app.MainLoop()
print "Message loop exited."
self.WakeUp()
self._app.frame = None
self._app = None
def WakeUp(self):
from time import sleep
sleep(0.25)
self._app.WakeUp()
The WakeUp() method simply asks the wxApp to call wxWakeUpIdle(), in the hopes of triggering an event that would convince my wxFrame to close. That didn't work. Neither did setting the references to my wx objects to None. The various print statements let me use PythonWin's trace collector to keep track of what's happening.
Note that the ImageApp module is the first point where wxPython.wx is imported, and I don't import ImageApp until I'm inside my running (secondary) thread. ImageApp is a pretty simple module, which simply creates a wxApp and sets idle handling for it, and creates a Controller object that manages windows, process commands, and all that junk. (I'm not sure now why I separated this from the wxApp, and would probably consolidate the two now.)
from wxPython.wx import *
import Controller
class MyApp(wxApp):
def OnInit(self):
wxInitAllImageHandlers()
self._controller = Controller.Controller(self)
self.SetTopWindow(self._controller.GetTopWindow())
self.inqueue = None
self.outqueue = None
self.IsClosing = 0
EVT_IDLE(self, self.OnIdle)
return true
def OnIdle(self, event):
if self.IsClosing:
return
event.RequestMore()
if self.inqueue != None:
try:
token = self.inqueue.get_nowait()
self._controller.DispatchCommand(token)
except Queue.Empty:
pass
def WakeUp(self):
print "Waking up idle handling..."
wxWakeUpIdle()
Note that my idle handling is horribly inefficient -- it will spin in circles doing nothing until a command is sent. A better way to do this would be to have the COM server call WakeUp when it sends a command, but the WakeUp was added later (to try to resolve other problems) and I did an incomplete job of retrofitting.
The only relevant part of the Controller object is its Exit() method:
def Exit(self, token):
print "Exiting ImageViewer"
self._app.IsClosing = 1
self.UpdateConfig()
for win in self.Windows:
win.Close(1)
sleep(1)
self._app.ExitMainLoop()
First, it sets a flag on the wxApp so that OnIdle will simply return, instead of asking for more idle events. I update my wxConfig object (to save window positions, etc), then close all windows. The sleep() call was an attempt to give the windows time to close before I shut down the wxApp, but (again) this didn't work. One of the first things I'd try, if I were to start working on this again, would be to use wxCallAfter() to delay the ExitMainLoop() call...
I hope that these code excerpts help. I'd considered making a Wiki page with this information, but didn't want to do that until I'd fixed the problems I was having, and never got a chance to do that. All I ask is that, if anyone *does* figure out how to get this to exit cleanly, that they share the solution with me.