Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

COM servers from within a Python service (on Win2k)

1 view
Skip to first unread message

Andrew Bennetts

unread,
Dec 4, 2001, 10:21:27 PM12/4/01
to
I've searched Google, and haven't found any answers to this...

Is it possible for a Python service to also be a COM server? The
obvious reason to do this would be to control the service via the COM
interface.

The only answers that I've found have been along the lines of "Get the
service to create seperate COM server when it starts, and use a named
pipe to have the COM server communicate with the service", which is
rather hacky. I don't see why it shouldn't be possible to just have the
COM server in the pythonservice.exe process.

Can anyone show me how to make this work?

Regards,

-Andrew.

Duncan Booth

unread,
Dec 5, 2001, 3:57:26 AM12/5/01
to
Andrew Bennetts <andrew-p...@puzzling.org> wrote in
news:mailman.1007522528...@python.org:

> I've searched Google, and haven't found any answers to this...
>
> Is it possible for a Python service to also be a COM server? The
> obvious reason to do this would be to control the service via the COM
> interface.

Yes it is possible.

>
> Can anyone show me how to make this work?
>

I have emailed you a working example. It is a minimal service which
contains a COM server.

--
Duncan Booth dun...@rcp.co.uk
int month(char *p){return(124864/((p[0]+p[1]-p[2]&0x1f)+1)%12)["\5\x8\3"
"\6\7\xb\1\x9\xa\2\0\4"];} // Who said my code was obscure?

Kragen Sitaker

unread,
Dec 5, 2001, 5:03:45 AM12/5/01
to
Duncan Booth <dun...@NOSPAMrcp.co.uk> writes:
> Andrew Bennetts <andrew-p...@puzzling.org> wrote in
> news:mailman.1007522528...@python.org:
> > Can anyone show me how to make this work?
>
> I have emailed you a working example. It is a minimal service which
> contains a COM server.

Can you post it?

Syver Enstad

unread,
Dec 5, 2001, 10:11:10 AM12/5/01
to
Kragen Sitaker <kra...@pobox.com> writes:

Yess, yess, preciousss, post for me too. Gollum.

--

Vennlig hilsen

Syver Enstad

Duncan Booth

unread,
Dec 5, 2001, 10:49:14 AM12/5/01
to
Syver Enstad <syver-e...@online.no> wrote in news:u4rn5v...@online.no:

Strange, last time this was discussed nobody was very interested.
Split up on the begin/end lines. The only thing to watch is that
you must use DCOMCNFG.EXE to set the default security to include
access by your userid otherwise access to the COM object will be
denied. Then run servicetest.py to check it all works.
The most likely problem will be some of the long lines wrapping.

Dont forget to change the guid if you use this for anything.
BTW, I suspect the QS_ALLEVENTS value should actually be QS_ALLINPUT.

---- begin readme.txt ----
This is a simple NT/2000 service that exposes a COM object.

servicetest.py runs unit tests including installing and removing the
service. It leaves the service installed and running on completion.

Alternatively install the service with:
service.py install

You must start the service to register the COM object:
service.py start

Stopping the service (service.py stop) does not uninstall the COM
object. You must remove the service (service.py remove) to unregister
the COM object.

When the service is running it writes stdout and stderr to
logfile.txt, but these may be buffered so output won't necessarily
appear immediately.

Creating the exposed object will start the service. Destroying the
object doesn't stop the service (although it could be made to do this
quite easily).

To change the object that is exposed, edit COMNAME in service.py.
---- end readme.txt ----
---- begin servicetest.py ----
import unittest,weakref,types,os,sys,time,re
import service
import win32service, win32serviceutil,pywintypes
import win32com.client
from win32service import SERVICE_STOPPED, SERVICE_START_PENDING, SERVICE_STOP_PENDING, \
SERVICE_RUNNING, SERVICE_CONTINUE_PENDING, SERVICE_PAUSE_PENDING, SERVICE_PAUSED

SERVICENAME = service.MyService._svc_name_
COMNAME = service.COMOBJECT._reg_progid_

SERVICE_STATES = {
SERVICE_STOPPED: "Stopped",
SERVICE_START_PENDING: "Starting",
SERVICE_STOP_PENDING: "Stopping",
SERVICE_RUNNING: "Running",
SERVICE_CONTINUE_PENDING: "Continuing",
SERVICE_PAUSE_PENDING: "Pausing",
SERVICE_PAUSED: "Paused",
}

class ServiceStatus:
def __init__(self):
self.serviceType, self.currentState, self.controlsAccepted, \
self.win32ExitCode, self.serviceSpecificExitCode, self.checkPoint, \
self.waitHint = win32serviceutil.QueryServiceStatus(SERVICENAME)

# Uninstalling a service is asynchronous, so after uninstalling we might
# get a service status. If so sleep a bit and try again.
def isServiceInstalled(expected=None):
def installed():
try:
s = ServiceStatus()
except pywintypes.error, (code, fn, msg):
if code==1060: return 0 # Not installed.
raise
return 1

for i in range(10):
inst = installed()
if expected is None or inst==expected:
break
time.sleep(0.05)
return inst


def serviceState():
"""Returns service state SERVICE_STOPPED, SERVICE_RUNNING, or SERVICE_PAUSED.
If the service is changing state it waits up to 10 seconds for the state change to complete."""
maxtime = time.time() + 10
while time.time() < maxtime:
s = ServiceStatus() # Raises exception if not installed.

if s.currentState in (SERVICE_START_PENDING, SERVICE_STOP_PENDING, \
SERVICE_CONTINUE_PENDING, SERVICE_PAUSE_PENDING):
time.sleep(0.05)
continue
return s.currentState

def commandWithOutput(command, expected):
import tempfile
tmpfile = tempfile.mktemp()
try:
cmdLine = '"%s" %s>%s' % (sys.executable, command , tmpfile)
os.system(cmdLine)
output = open(tmpfile, "r").read()
if not output.find(expected) >= 0:
print "Expected:",`expected`
print "Got:",`output`
assert output.find(expected) >= 0
finally:
os.unlink(tmpfile)

def serviceStart():
if not serviceState()==SERVICE_RUNNING:
commandWithOutput("service.py start", "Starting service %s\n" % SERVICENAME)
assert serviceState()==SERVICE_RUNNING

def serviceInstall():
if not isServiceInstalled():
commandWithOutput("service.py install", "\nService installed\n")
assert isServiceInstalled(1), "Service should now be installed"

def serviceStop():
if serviceState() != SERVICE_STOPPED:
commandWithOutput("service.py stop", "Stopping service %s\n" % SERVICENAME)
assert serviceState()==SERVICE_STOPPED

def serviceRemove():
if isServiceInstalled(1):
commandWithOutput("service.py remove", "Removing service %s\n" % SERVICENAME)
assert not isServiceInstalled(0), "Service should now be removed"

class ServiceTestCase(unittest.TestCase):
def checkInstallation(self):
"""Test service install/start/stop/remove"""
serviceInstall()
serviceStart()
serviceStop()
serviceRemove()
# After removing the service, the COM object should not exist
self.assertRaises(pywintypes.com_error, win32com.client.Dispatch, COMNAME)

class ComTestCase(unittest.TestCase):
"""Test the COM object in the service."""
# N.B. Because it takes a comparatively long time to install and remove the service
# the setUp installs it, but the tearDown leaves it installed.
# Individual tests start and stop the service as required.
def setUp(self):
if not isServiceInstalled():
OUT = "\nService installed\n"
commandWithOutput("service.py install", OUT)
assert isServiceInstalled(1), "Service should now be installed"

def checkServiceAutoStarts(self):
# The COM object isn't registered until we start the service,
# so make sure service has been started at least once
serviceStart()
serviceStop()
assert serviceState()==SERVICE_STOPPED
obj = win32com.client.Dispatch(COMNAME)
assert serviceState()==SERVICE_RUNNING

def checkComMethods(self):
serviceStop()
serviceStart()
obj = win32com.client.Dispatch(COMNAME)
assert obj.Hello("World")=="Hello World"
assert obj.Hello("Test2")=="Hello Test2"
assert obj.globalCalls()==2
assert obj.noCalls==2
del obj
obj = win32com.client.Dispatch(COMNAME)
assert obj.Hello("World")=="Hello World"
assert obj.globalCalls()==3
assert obj.noCalls==1
del obj
serviceStop()
serviceStart()
obj = win32com.client.Dispatch(COMNAME)
assert obj.Hello("World")=="Hello World"
assert obj.globalCalls()==1
assert obj.noCalls==1


def suite():
suite = unittest.makeSuite(ServiceTestCase, 'check')
suite.addTest(unittest.makeSuite(ComTestCase, 'check'))
return suite

allTests = suite()

if __name__=='__main__':
if len(sys.argv) > 1 and sys.argv[1] == 'gui':
import unittestgui
unittestgui.main("servicetest.allTests")
else:
verbose = 1
if '-v' in sys.argv or '--verbose' in sys.argv:
verbose += 1
runner = unittest.TextTestRunner(verbosity=verbose)
runner.run(allTests)

---- end servicetest.py ----
---- begin comobject.py ----
import sys
import pythoncom, win32api, time, win32con

globalCalls = 0

class HelloWorld:
_reg_clsctx_ = pythoncom.CLSCTX_LOCAL_SERVER
_reg_class_spec_ = "comobject.HelloWorld"

# Don't copy this: generate a new guid with pythoncom.CreateGuid()
_reg_clsid_ = "{8196223B-4BC8-4B91-A9E8-3FFA0A72FD5A}"
_reg_desc_ = "Python Test COM Server"
_reg_progid_ = "Python.TestServer"
_public_methods_ = ['Hello', 'globalCalls']
_public_attrs_ = ['softspace', 'noCalls']
_readonly_attrs_ = ['noCalls']

def __init__(self):
self.softspace = 1
self.noCalls = 0

def globalCalls(self):
return globalCalls

def Hello(self, who):
global globalCalls
self.noCalls = self.noCalls + 1
globalCalls += 1
# insert "softspace" number of spaces
msg = "Hello" + " " * self.softspace + str(who)
service.LOG("Message: %s" % msg)
return msg

def registerCOMservice(servicename, classes=HelloWorld):
try:
import win32com.server.register
register = win32com.server.register
register.RegisterClasses(classes)
# Now complete the registration for a service.
clsid = classes._reg_clsid_
keyNameRoot = "CLSID\\%s" % str(clsid)
register._set_subkeys(keyNameRoot, { 'AppId': str(clsid)} )

# The serviceparameter gets passed to us when started for COM.
Appidkeys = { "LocalService": servicename, "ServiceParameters": "LocalServiceStartup"}
register._set_subkeys("AppID\\%s" % clsid, Appidkeys)
except:
service.LOG("registerCOMservice", sys.exc_info())

def unregisterCOMservice(classes=HelloWorld):
import win32com.server.register
win32com.server.register.UnregisterClasses(classes)

# So that we can get at the logger
# This has to come after the class definition of the COM object.
import service
---- end comobject.py ----
---- begin service.py ----
# Very small service written in Python

import win32serviceutil
import win32service
import win32event, win32api

import time, sys, os
import comobject
import pythoncom, win32api, time, win32con

try:
p = os.path
LOGFILE = p.join(p.dirname(p.abspath(__file__)), "logfile.txt")
except NameError:
LOGFILE = "logfile.txt"

# This is the class we wish to make available via COM.
COMOBJECT = comobject.HelloWorld

def log_time():
"""Return a simple time string without spaces suitable for logging
"""
return ("%4.4d-%2.2d-%2.2dT%2.2d:%2.2d:%2.2d"
% time.gmtime(time.time())[:6])

class Logger:
def __init__(self, filename=LOGFILE):
self.logfile = open(filename, "w+t")
sys.stderr = sys.stdout = self.logfile
import os
print os.getcwd()

def log(self, summary, error=None):
time = log_time()
self.logfile.write("%s: %s\n" % (time, summary))
if error:
try:
self.logfile.write(format_exception(
error[0], error[1], error[2],
trailer='\n', limit=100))
except:
self.logfile.write("%s: %s\n" % error[:2])
self.logfile.flush()

def __call__(self, summary, error=None):
self.log(summary, error)

class MyService(win32serviceutil.ServiceFramework):
_svc_name_ = "PythonCOMService"
_svc_display_name_ = "Python COM service"
def __init__(self, *args):
try:
self.log = Logger()
global LOG
LOG = self.log
self.log("Service starting, args=%s" % args)
win32serviceutil.ServiceFramework.__init__(self, *args)
self.ReportServiceStatus(win32service.SERVICE_START_PENDING)

# Create an event which we will use to wait on.
# The service stop request will set this event.
self.hWaitStop = win32event.CreateEvent(None, 0, 0, None)

#comobject.registerCOMservice(self._svc_name_, self.log, COMOBJECT)
self.classes = [COMOBJECT._reg_clsid_]

except:
self.log("Exception during service start", error=sys.exc_info())

def SvcStop(self):
try:
# Tell the SCM we are starting the stop process.
self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
win32event.SetEvent(self.hWaitStop)
except:
self.log("SvcStop()", sys.exc_info())

# Stop the COM thread
#win32api.PostThreadMessage(self.threadId, win32con.WM_QUIT, 0, 0);

def SvcDoRun(self):
# Runs a COM loop
self.log("Running")
try:
self.ReportServiceStatus(win32service.SERVICE_START_PENDING)
sys.coinit_flags = 2
from win32com.server import factory
self.threadId = win32api.GetCurrentThreadId()
pythoncom.CoInitialize()
infos = factory.RegisterClassFactories(self.classes)
pythoncom.CoResumeClassObjects()
self.ReportServiceStatus(win32service.SERVICE_RUNNING)

while 1:
res = win32event.MsgWaitForMultipleObjects((self.hWaitStop,), 0, 2000, win32event.QS_ALLEVENTS)
if res == win32event.WAIT_OBJECT_0:
break # Must be terminating

pythoncom.PumpWaitingMessages()

factory.RevokeClassFactories(infos)
pythoncom.CoUninitialize()
except:
self.log("SvcDoRun()", sys.exc_info())

self.log("Service stopped")
self.ReportServiceStatus(win32service.SERVICE_STOPPED)

if __name__=='__main__':
# If we are removing the service, also remove the COM registration.
if "remove" in sys.argv:
comobject.unregisterCOMservice(COMOBJECT)
elif 'install' in sys.argv:
comobject.registerCOMservice(MyService._svc_name_, COMOBJECT)

win32serviceutil.HandleCommandLine(MyService)
---- end service.py ----

Laura Creighton

unread,
Dec 5, 2001, 11:03:50 AM12/5/01
to
Please make a recipe of it and post it to the Activestate Cookbook.
The only hard thing about that is logging in the first time.

Laura Creighton

0 new messages