COM with Python?

Gordon McMillan gmcm at hypernet.com
Fri Jul 9 10:41:41 EDT 1999


Mark Hammond wrote:

> Gordon McMillan wrote:

> > Hmm. I've read those posts, and never did find a resolution.
> 
> Well, what I meant was that all the threads Gordon mentions has done
> the topic, as far as I can tell, pretty much to its conclusion with
> the current state of win32com.  I agree the solutions, if
> applicable, still dont really qualify as a "resolution".  Anything
> better will require some win32com changes tho, and these havent been
> made (or even identified)
> 
> Ive never needed to implement a singleton, but my personal solution
> (from someone else I cant recall in those threads) would be to
> implement the functionality in a single Python object and have the
> COM objects delegate to it.  Thus you dont really have a singleton
> COM object (as you are giving off as many as necessary) but by
> keeping the functionality and state in a single Python object you
> are can get the same result (for most cases!)

Well, having just done some testing, let me put a resolution on this: 
the global Python object approach works (which surprised me!), and 
the Running Object Table approach, while it works, seems to have 
stability problems.

To explain: I hadn't realized that the COM plumbing would, when using 
CLSCTX_LOCAL_SERVER, reconnect to a running instance, (so I doubted 
that the global Python object approach would work). It does, and 
while I haven't left one running for 24 hrs yet, it appears to work 
flawlessly - the COM invoked pythonw process goes away as soon as all 
refs go to zero.

The ROT approach (using pythoncom.RegisterActiveObject()) is flaky. 
Sometimes the COM invoked pythonw goes away when it should, sometimes 
it hangs around. Worse, if you leave it running for long periods, it 
seems capable of freezing NT's UI (the machine appears to still be 
alive, but you can't _do_ anything except reboot). I'm more than 
willing to believe that this is NOT Mark's fault.

Attached is Andy's SpamServer, with some mods to allow all clients on 
the same machine to work with the same SpamServer (well, to the same 
list of SpamCans). Which will probably next lead to questions about 
threading models...

- Gordon

-------------- next part --------------
# copyright Andy Robinson 1997
# you may freely modify and reuse this under the same terms
# as the main python licence

# registration stuff modified and expanded by Harri Pasanen
# thanks also to Mark Hammond and Greg Stein

# modified 7/8/99 to demonstrate having all clients on a
# machine work with the same SpamServer data. 
# Gordon McMillan

from win32com.server.exception import COMException
import win32com.server.util
import win32com.client.dynamic
import sys
import pythoncom

_cans = None

class SpamServer:
    _reg_clsid_ = "{EC419091-35FC-11D3-9403-00609736B700}"
    _reg_desc_ = "Python SpamServer GPO"
    _reg_progid_ = "Python.SpamServerGPO"
    _reg_class_spec_ = "spamserver_GPO.SpamServer"
    _reg_clsctx_ = pythoncom.CLSCTX_LOCAL_SERVER
    _public_methods_ = ['AddCan',
                            'GetCanCount',
                            'GetCanAt',
                            'DeleteCan',
                            'GetDescriptionList',
                            'GetCrosstab']
    # you need to explicitly list the public methods for COM to work
    # spelling errors here lead to errors when you call CreateObject
    # from a client
    
    def __init__(self):
        "it keeps a collection of data - start with a sample object"
        global _cans
        if _cans is None:
            _cans = [SpamCan()]
        self.cans = _cans
        
    def GetCanCount(self):
        "return the number of objects held"
        return len(self.cans)
        
    def AddCan(self, contents = 'Spam', type = 'Can', qty = 3):
        "two uses, can just add a generic one, or one you created in a GUI"
        newOne = SpamCan()
        newOne.contents = contents
        newOne.type = type
        newOne.qty = qty
        self.cans.append(newOne) 			
        
    def DeleteCan(self,index):
        del self.cans[index]
        
    def GetCanAt(self, index):
        return win32com.server.util.wrap(self.cans[index])
        
    def GetDescriptionList(self):
            # for a list view
        return map(lambda x:x.GetDescription(), self.cans)
        
    def GetCrosstab(self):
            # some example data for yout to play with
        return [['e.g.','cans','bottles','bags'],
                ['spam',5,4,3],
                ['spinach',0,1,2],
                ['beer',12,4,2]]
        
class SpamCan:
    "just a simple 'packet' of data to play with"
    _public_methods_ = ['GetDescription']
    _public_attrs_ = ['contents','type','qty']
    
    def __init__(self):
        self.contents = 'spam'
        self.type = 'can'
        self.qty = 3
        
    def GetDescription(self):
        return '%d %ss of %s' %(self.qty, self.type, self.contents)
        
# each class needs a class ID.  You can get these by typing:
#	import pythoncom
#	g = pythoncom.CreateGuid()
#	print g
# in the interactive window, then pasting it into your code.
        
def RegisterSpam():
    # register them both - this must be called once for the
    # demo to work
    import win32com.server.register
    win32com.server.register.UseCommandLine(SpamServer)
    
def UnRegisterSpam():
    """ Unregister each server - use this before 
    deleting the server to keep your registr tidy"""
    
    print "Unregistering COM server..."
    from win32com.server.register import UnregisterServer
    UnregisterServer(SpamServer._reg_clsid_ ,
                   "Python.SpamServerGPO")
    print "SpamServer Class unregistered."

    # and finally some test code
    
def TestDirect():
    # does some stuff to verify the class works IN PYTHON
    s = SpamServer()
    print 'Created spam server'
    s.AddCan('Cheese', 'Sandwich', 2)
    print 'added 2 cheese sandwiches'
    s.AddCan('Chimay Rouge', 'Bottle', 6)
    print 'added some strong beer'
    print 'contains %d items:' % (s.GetCanCount())
    for item in s.GetDescriptionList():
        print '\t' + item
    s.DeleteCan(0)
    print 'ditched the beer, we are at work here!'
    print 'Tests passed'
    
def TestCOM():
    try:
        SpamSrv = win32com.client.dynamic.Dispatch("Python.SpamServerGPO")
        print 'Python.SpamServerGPO class created from COM'
    except:
        print "**** - The Python.SpamServer test server is not available"
        return
    print 'cans:', SpamSrv.GetCanCount()
    print 'Adding a cheese sandwich', SpamSrv.AddCan('Cheese', 'Sandwich', 2)
    print 'Adding some beer:', SpamSrv.AddCan('Chimay Rouge', 'Bottle', 6)
    print 'cans: ', SpamSrv.GetCanCount()
    descr = SpamSrv.GetDescriptionList()
    for item in descr:
        print item
    print "The Python.Spamserver COM Server worked OK."
    
    
if __name__=='__main__':
    """If they ran the whole script, register classes.
    Options also given to unregister and to test"""
    import sys
    if "/unreg" in sys.argv:
        UnRegisterSpam()
    elif "/test" in sys.argv:
        print "doing direct tests..."
        TestDirect()
        print "testing COM"
        TestCOM()
    else:
        RegisterSpam()



More information about the Python-list mailing list