I've hit a wall with this one. I need to create a python based com server, package it as a windows exe and deploy it on windows. It has to have a "full" interface - because the consumer requires idispatch and a specific interfce to function. Now I have created the com server and have it running under the interpreter and it functions flawlessly with my picky client. However, when packaging as an EXE - its an localserver - I get an error in the log when the system tries to instantiate it (even from a vbs script). So here's everyting. I searched high and low in the itnernet and it looks like an import problem but I don't know how to import my own python object for the localserver to use.
This is python 2.7 with pywin32 extensions installed.
So first - the IDL I created for the server:
imtg.idl
// This file will be processed by the MIDL tool to
// produce the type library (imtg.tlb) and marshalling code.
import "oaidl.idl";
import "ocidl.idl";
[
object,
uuid(4fafbb23-6a38-4613-b93b-68ea66c67043),
dual,
helpstring("IImtGroupApp Interface"),
pointer_default(unique)
]
interface IImtGroupApp : IDispatch
{
[id(1), helpstring("method EchoString")] HRESULT EchoString([in] BSTR in1, [out, retval] BSTR *vals);
[id(2), helpstring("method AddNumbers")] HRESULT AddNumbers([in] long in1, [in] long in2, [out, retval] long *vali);
};
[
uuid(d665e9d0-71a9-4e23-a1b4-abe3376d5c58),
version(1.0),
helpstring("ImtGroup 1.0 Type Library")
]
library IMTGROUPLib
{
importlib("stdole32.tlb");
importlib("stdole2.tlb");
importlib("msado15.dll");
[
uuid(ced66424-93fb-4307-9062-7bee76d3d8eb),
helpstring("ImtGroupApp Class")
]
coclass ImtGroupApp {
[default] interface IImtGroupApp;
};
};
Next the Python code - now this gets a bit tricky because when I distribute this I don't want to create the .tlb - so I'm not distributing the .idy - just make sure you have the .tbl to register. Use an as admin cmd prompt if necessary.
imtg_server.py
import sys, os
import pythoncom
import win32com
import winerror
# importers check was old py2exe current uses frozen
if hasattr(sys, 'frozen'):
# we are running as py2exe-packed executable
print "is an exe"
pythoncom.frozen = 1
else:
print "not an exe"
class CImtg:
_reg_clsctx_ = pythoncom.CLSCTX_LOCAL_SERVER
#
# COM declarations
#
_reg_clsid_ = "{24c0e3fe-58e7-4485-87dc-9f9e823b85e1}"
_reg_desc_ = "IMTGroup Python test object"
_reg_progid_ = "ImtGroup.Test"
if hasattr(sys, 'frozen'):
# In the py2exe-packed version, specify the module.class
# to use. In the python script version, python is able
# to figure it out itself.
_reg_class_spec_ = "__main__.CImtg"
print "set reg_class_spec"
print _reg_class_spec_
###
### Link to typelib - uuid matches uuid for type library in idl file
_typelib_guid_ = '{d665e9d0-71a9-4e23-a1b4-abe3376d5c58}'
_typelib_version_ = 1, 0
_com_interfaces_ = ['IImtGroupApp']
def __init__(self):
### initialize something here if necessary
### The item below is not used in this example
self.MyProp1 = 10
def EchoString(self,in1):
return "Echoing " + in1
def AddNumbers(self, in1, in2):
return in1 + in2
def BuildTypelib():
from distutils.dep_util import newer
this_dir = os.path.dirname(__file__)
idl = os.path.abspath(os.path.join(this_dir, "imtg.idl"))
tlb=os.path.splitext(idl)[0] + '.tlb'
if os.path.isfile(idl):
# test for idl - if no idl don't create tlb assume its there
# Comment below for building exe as we will have type library
if newer(idl, tlb):
print "Compiling %s" % (idl,)
rc = os.system ('midl "%s"' % (idl,))
if rc:
raise RuntimeError("Compiling MIDL failed!")
# Can't work out how to prevent MIDL from generating the stubs.
# just nuke them
for fname in "dlldata.c imtg_i.c imtg_p.c imtg.h".split():
os.remove(os.path.join(this_dir, fname))
print "Registering %s" % (tlb,)
tli=pythoncom.LoadTypeLib(tlb)
pythoncom.RegisterTypeLib(tli,tlb)
def UnregisterTypelib():
k = CImtg
try:
pythoncom.UnRegisterTypeLib(k._typelib_guid_,
k._typelib_version_[0],
k._typelib_version_[1],
0,
pythoncom.SYS_WIN32)
print "Unregistered typelib"
except pythoncom.error, details:
if details[0]==winerror.TYPE_E_REGISTRYACCESS:
pass
else:
raise
if __name__=='__main__':
print "checking frozen"
if hasattr(sys, 'frozen'):
# running as packed executable
if '--unregister' in sys.argv or '--register' in sys.argv:
if '--unregister' in sys.argv:
# Unregister the type-libraries.
UnregisterTypelib()
import win32com.server.register
win32com.server.register.UseCommandLine(CImtg)
else:
# Build and register the type-libraries.
BuildTypelib()
import win32com.server.register
win32com.server.register.UseCommandLine(CImtg)
else:
import win32com.server
from win32com.server import localserver
print "starting the server"
localserver.main()
else:
if '--unregister' in sys.argv:
# Unregister the type-libraries.
UnregisterTypelib()
import win32com.server.register
win32com.server.register.UseCommandLine(CImtg)
else:
if '--register' in sys.argv:
# Build and register the type-libraries.
BuildTypelib()
import win32com.server.register
win32com.server.register.UseCommandLine(CImtg)
Next the setup for py2exe
I had to add the funky import of the modulefinder because win32com.shell wasn't included in the packaged executable
setup_imtg.py
# This setup script builds a single-file Python inprocess COM server.
#
import modulefinder
import win32com, sys
for p in win32com.__path__[1:]:
modulefinder.AddPackagePath("win32com",p)
for extra in ["win32com.shell"]:
__import__(extra)
m = sys.modules[extra]
for p in m.__path__[1:]:
modulefinder.AddPackagePath(extra, p)
from distutils.core import setup
import py2exe
import sys
# If run without args, build executables, in quiet mode.
if len(sys.argv) == 1:
sys.argv.append("py2exe")
sys.argv.append("-q")
class Target:
def __init__(self, **kw):
self.__dict__.update(kw)
# for the versioninfo resources
self.name = "IMTG Server"
################################################################
# pywin32 COM pulls in a lot of stuff which we don't want or need.
CImtg = Target(
description = "Sample COM server",
# what to build. For COM servers, the module name (not the
# filename) must be specified!
modules = ["imtg_server"],
# we only want the inproc server.
)
excludes = ["pywin", "pywin.debugger", "pywin.debugger.dbgcon",
"pywin.dialogs", "pywin.dialogs.list"]
options = {
"bundle_files": 1, # create singlefile exe
"compressed": 1, # compress the library archive
"excludes": excludes,
"dll_excludes": ["w9xpopen.exe"] # we don't need this
}
setup(
options = {"py2exe": options},
zipfile = None, # append zip-archive to the executable.
com_server = [CImtg]
)
When you run the generated EXE you can register with
imtg_server --register
but you won't see abou output
--unregister unregisters
You can use this vbs file to test it.
t.vbs
dim MD
set MD = CreateObject("ImtGroup.Test")
dim response
response = MD.EchoString("Really")
MsgBox(response)
When you run there will be a .log created that looks like this:
pythoncom error: ERROR: server.policy could not create an instance.
Traceback (most recent call last):
File "win32com\server\policy.pyc", line 136, in CreateInstance
File "win32com\server\policy.pyc", line 194, in _CreateInstance_
File "win32com\server\policy.pyc", line 727, in call_func
File "win32com\server\policy.pyc", line 717, in resolve_func
AttributeError: 'module' object has no attribute 'CImtg'
pythoncom error: Unexpected gateway error
Traceback (most recent call last):
File "win32com\server\policy.pyc", line 136, in CreateInstance
File "win32com\server\policy.pyc", line 194, in _CreateInstance_
File "win32com\server\policy.pyc", line 727, in call_func
File "win32com\server\policy.pyc", line 717, in resolve_func
AttributeError: 'module' object has no attribute 'CImtg'
pythoncom error: CPyFactory::CreateInstance failed to create instance. (80004005)
So what I need is to get around this error. This class is certainly in my object. I'm concerned that the value I have specified in my server as:
_reg_class_spec_ = "__main__.CImtg"
Is incorrect. The main may refer to the wrapped exe and not my own server which has no main specified. I did try to create main as well with no better results. I just don't know how py2exe proxies the classes. I tried using my file name imtg_server.CImtg and that fails with module not found. I tried just CImtg and that fails. I tried using variations of win32com and pythoncom - but it just doesn't do it. What I have seems "right" so maybe I need an additional reg tag or something? Any help is greatly appreciated. Thank you.
I finally figured this out. I'm hoping it will help someone else struggling with this.
This is a problem with the import. The way py2exe packages this it just can't find the right pieces when running as the com server. What I'm posting below works.
So the first piece is that the setup needs to look like this. NOTICE the addition of "packages" in the options, where we include my com server. This is important because we are going to change the __reg_class_spec__
to point to this explicitly.
So the full revised setup_imtg.py
# This setup script builds a single-file Python inprocess COM server.
#
import modulefinder
import win32com, sys
for p in win32com.__path__[1:]:
modulefinder.AddPackagePath("win32com",p)
for extra in ["win32com.shell"]:
__import__(extra)
m = sys.modules[extra]
for p in m.__path__[1:]:
modulefinder.AddPackagePath(extra, p)
from distutils.core import setup
import py2exe
import sys
# If run without args, build executables, in quiet mode.
if len(sys.argv) == 1:
sys.argv.append("py2exe")
sys.argv.append("-q")
class Target:
def __init__(self, **kw):
self.__dict__.update(kw)
# for the versioninfo resources
self.name = "Python TestServer"
################################################################
# pywin32 COM pulls in a lot of stuff which we don't want or need.
CImtg = Target(
description = "Sample COM Server",
# what to build. For COM servers, the module name (not the
# filename) must be specified!
modules = ["imtg_server"],
# we only want the inproc server.
)
excludes = ["pywin", "pywin.debugger", "pywin.debugger.dbgcon",
"pywin.dialogs", "pywin.dialogs.list"]
options = {
"bundle_files": 1, # create singlefile exe
"packages": ["imtg_server"],
"compressed": 1, # compress the library archive
"excludes": excludes,
"dll_excludes": ["w9xpopen.exe"] # we don't need this
}
setup(
options = {"py2exe": options},
zipfile = None, # append zip-archive to the executable.
com_server = [CImtg]
)
Now then I changed the server code to specify the _reg_class_spec_
to point to imtg_server.CImtg.
But that's not all! We were doing this from the code to start the com server. We don't need to do that! The initial sample I started with used py2exe and built a CONSOLE application and not a com_server, but py2exe DOES build a com server so we do not need to start it again - so this code gets removed:
import win32com.server
from win32com.server import localserver
print "starting the server"
localserver.main()
But as they say - that's not ALL! Since we included the package itself and thats what we are executing the "name" isn't __main__
instead it's imtg_server!
So there's a check for that in here in addition to main in case you are developing but not deploying. MOST of the code now is to determine which way you are running and to check and run appropriately because there's ONE MORE THING! Getting the directory where we are running for building the typelib from the IDL or for registering the typelib appends the name of the exe to the path - where when running the interpreter does not! So I had to strip that off.
Remember when you run the exe the print statements are suppressed so don't worry when you register and nothing is displayed. If there's a problem a .log file will be created.
Here's the working code for imtg_server.py -- enjoy
import sys, os
import pythoncom
import win32com
import winerror
# importers check was old py2exe current uses frozen
if hasattr(sys, 'frozen'):
# we are running as py2exe-packed executable
print "is an exe"
pythoncom.frozen = 1
else:
print "not an exe"
class CImtg:
_reg_clsctx_ = pythoncom.CLSCTX_LOCAL_SERVER
#
# COM declarations
#
_reg_clsid_ = "{24c0e3fe-58e7-4485-87dc-9f9e823b85e1}"
_reg_desc_ = "IMTGroup Python test object"
_reg_progid_ = "ImtGroup.Test"
if hasattr(sys, 'frozen'):
# In the py2exe-packed version, specify the module.class
# to use. In the python script version, python is able
# to figure it out itself.
_reg_class_spec_ = "imtg_server.CImtg"
print "set reg_class_spec"
print _reg_class_spec_
###
### Link to typelib - uuid matches uuid for type library in idl file
_typelib_guid_ = '{d665e9d0-71a9-4e23-a1b4-abe3376d5c58}'
_typelib_version_ = 1, 0
_com_interfaces_ = ['IImtGroupApp']
def __init__(self):
### initialize something here if necessary
### The item below is not used in this example
self.MyProp1 = 10
def EchoString(self,in1):
return "Echoing " + in1
def AddNumbers(self, in1, in2):
return in1 + in2
def BuildTypelib():
from distutils.dep_util import newer
this_dir = os.path.dirname(__file__)
# when running as a exe this directory includes the exe name
if this_dir.endswith('imtg_server.exe'):
this_dir = this_dir[:-15]
idl = os.path.abspath(os.path.join(this_dir, "imtg.idl"))
tlb=os.path.splitext(idl)[0] + '.tlb'
if os.path.isfile(idl):
# test for idl - if no idl don't create tlb assume its there
# Comment below for building exe as we will have type library
if newer(idl, tlb):
print "Compiling %s" % (idl,)
rc = os.system ('midl "%s"' % (idl,))
if rc:
raise RuntimeError("Compiling MIDL failed!")
# Can't work out how to prevent MIDL from generating the stubs.
# just nuke them
for fname in "dlldata.c imtg_i.c imtg_p.c imtg.h".split():
os.remove(os.path.join(this_dir, fname))
print "Registering %s" % (tlb,)
tli=pythoncom.LoadTypeLib(tlb)
pythoncom.RegisterTypeLib(tli,tlb)
def UnregisterTypelib():
k = CImtg
try:
pythoncom.UnRegisterTypeLib(k._typelib_guid_,
k._typelib_version_[0],
k._typelib_version_[1],
0,
pythoncom.SYS_WIN32)
print "Unregistered typelib"
except pythoncom.error, details:
if details[0]==winerror.TYPE_E_REGISTRYACCESS:
pass
else:
raise
if __name__=='__main__' or __name__ =='imtg_server':
print "checking frozen"
if hasattr(sys, 'frozen'):
# running as packed executable
if '--unregister' in sys.argv or '--register' in sys.argv:
if '--unregister' in sys.argv:
# Unregister the type-libraries.
UnregisterTypelib()
import win32com.server.register
win32com.server.register.UseCommandLine(CImtg)
else:
# Build and register the type-libraries.
BuildTypelib()
import win32com.server.register
win32com.server.register.UseCommandLine(CImtg)
else:
if '--unregister' in sys.argv:
# Unregister the type-libraries.
UnregisterTypelib()
import win32com.server.register
win32com.server.register.UseCommandLine(CImtg)
else:
if '--register' in sys.argv:
# Build and register the type-libraries.
BuildTypelib()
import win32com.server.register
win32com.server.register.UseCommandLine(CImtg)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With