[python-win32] How to write a COM Server implementing interfaces from type lib?

Jan Wedel Jan.Wedel at ettex.de
Fri Apr 20 11:45:38 CEST 2012


Hi,

sorry for the delay but we were and still are running short on time in 
this project so I eventually switched to comtypes (which has a lot other 
inconveniences compared to pythoncom but at least it did work with my 
special use case).

Nonetheless, I already tried your suggestion but unfortunately it didn't 
work. This is the output of the code at the end of this mail using the 
typelib I had problems with:

"""
GUID before:  {00000000-0000-0000-0000-000000000000}
GUID after:  {2E6664B0-69B5-4620-8CC4-9A3F790DED73}
Traceback (most recent call last):
  File "record-test.py", line 19, in <module>
    status = pythoncom.GetRecordFromTypeInfo(info)
pywintypes.com_error: (-2147024809, 'Falscher Parameter.', None, None)
"""

Additionally, I have submitted my patched version of policy.py to 
support multiple typelibs.

Pythoncom is really nice to use since it manages all the ctypes pointer 
stuff which in comtypes you have handle by yourself. But as I already 
explained, I needed to make a decision and by fighting with pointers to 
pointers to structs in python (!!!), I was able to get my COM server 
running.

Anyways, thank you a lot for your help in the past.

//Jan

"""
import time
import pywintypes
import pythoncom

# Load typelibs
TL_OPC_DA = 
pythoncom.LoadRegTypeLib('{3B540B51-0378-4551-ADCC-EA9B104302BF}', 3, 0, 
0)

status = None

for i in range(TL_OPC_DA.GetTypeInfoCount()):
	 info = TL_OPC_DA.GetTypeInfo(i);
	 if info.GetDocumentation(-1)[0] == "tagOPCSERVERSTATUS":
		 print "GUID before: ", info.GetTypeAttr()[0]
		 cti = info.QueryInterface(pythoncom.IID_ICreateTypeInfo)
		 cti.SetGuid(pythoncom.CreateGuid())
		 print "GUID after: ", info.GetTypeAttr()[0]
		 status = pythoncom.GetRecordFromTypeInfo(info)
		 print "GOT", status

		 break
		 
status.ftStartTime = pywintypes.Time(self.start_time)
status.ftCurrentTime = pywintypes.Time(time.time())
status.ftLastUpdateTime = pywintypes.Time(self.last_update_time)
status.dwServerState = ServerState.RUNNING
status.dwGroupCount = len(self.groups)
status.dwBandWidth = self.band_width
status.wMajorVersion = MAJOR_VERSION
status.wMinorVersion = MINOR_VERSION
status.wBuildNumber = BUILD_NUMBER
status.wReserved = 0
status.szVendorInfo = VENDOR_INFO

print "Done!"
"""


> -----Ursprüngliche Nachricht-----
> Von: Mark Hammond [mailto:mhammond at skippinet.com.au]
> Gesendet: Freitag, 30. März 2012 07:30
> An: Jan Wedel
> Cc: python-win32 at python.org
> Betreff: Re: [python-win32] How to write a COM Server implementing 
interfaces
> from type lib?
> 
> It seems I *can* make this work with the pythoncom test object.
> 
> I created a structure with no GUID in a TLB with a name 
StructWithoutUUID.
> 
> Then, I used the code at the end of this message as a quick hack to 
try out
> the SetGuid and the output is:
> 
> """
> as expected, failed to create the record normally: The structure
> 'StructWithoutUUID' is not defined in module '<module 
'win32com.gen_py.6BCD...
> GUID before:  {00000000-0000-0000-0000-000000000000}
> GUID after:  {EFC72B2F-84D1-4474-8D91-F59928A9C13E}
> GOT com_struct(int_value=0, str_value=u'') """
> 
> So maybe you could check the test code you used - eg, make sure the 
ITypeInfo
> you are using is the one you expect etc.
> 
> I probably *could* hack this into win32com to do this magically behind 
the
> scenes, but I'm not really sure it is worthwhile - most typelibs with
> structures seems to have the GUID set correctly.  Let's see if it gets 
you any
> further without any other gross hacks, and if it does, I'll rethink 
that.
> 
> The code I used to test is:
> """
> import sys
> import win32com.client
> import pythoncom
> 
> ob = win32com.client.Dispatch("PyCOMTest.PyCOMTest")
> module = sys.modules[ob.__class__.__module__]
> tlb = pythoncom.LoadRegTypeLib(module.CLSID, module.MajorVersion,
> module.MinorVersion, module.LCID)
> 
> try:
>      rec = win32com.client.Record("StructWithoutUUID", ob)
>      print "Hrm - this should have failed!"
> except ValueError, exc:
>      print "as expected, failed to create the record normally:", exc
> 
> for i in range(tlb.GetTypeInfoCount()):
>      info = tlb.GetTypeInfo(i);
>      if info.GetDocumentation(-1)[0] == "StructWithoutUUID":
>          print "GUID before: ", info.GetTypeAttr()[0]
>          cti = info.QueryInterface(pythoncom.IID_ICreateTypeInfo)
>          cti.SetGuid(pythoncom.CreateGuid())
>          print "GUID after: ", info.GetTypeAttr()[0]
>          rec = pythoncom.GetRecordFromTypeInfo(info)
>          print "GOT", rec
>          rec.int_value = 123
> 
>          break
> """
> 
> Cheers,
> 
> Mark
> 
> 
> On 27/03/2012 7:06 PM, Jan Wedel wrote:
> > I tried the following from the console:
> >
> >  >>> import pythoncom
> >
> > Get the type lib:
> >
> >  >>> TL_OPC_DA =
> > pythoncom.LoadRegTypeLib('{3B540B51-0378-4551-ADCC-EA9B104302BF}', 
3,
> > 0, 0)  >>> TL_OPC_DA <PyITypeLib at 0x02BB2B98 with obj at 
0x004D3390>
> >
> > Index 8 is the record I try to create:
> >
> >  >>> TL_OPC_DA.GetTypeInfo(8)
> > <PyITypeInfo at 0x02BB2BB0 with obj at 0x004D4804>  >>> recinfo =
> > TL_OPC_DA.GetTypeInfo(8)
> >
> > Cast to ICreateTypeInfo:
> >
> >  >>> icti = recinfo.QueryInterface(pythoncom.IID_ICreateTypeInfo)
> >  >>> icti
> > <PyICreateTypeInfo at 0x02BB2BC8 with obj at 0x004D4800>
> >
> > Set random GUID:
> >
> >  >>> icti.SetGuid(pythoncom.CreateGuid())
> >
> > BAM! :
> >
> >  >>> pythoncom.GetRecordFromTypeInfo(icti)
> > Traceback (most recent call last):
> >    File "<stdin>", line 1, in <module>
> > pywintypes.com_error: (-2147024809, 'Falscher Parameter.', None, 
None)
> >
> > Try another cast because I wasn't sure that GetRecordFromTypeInfo
> > supports/checks the ICreateTypeInfo interface.
> >
> >  >>> iti = icti.QueryInterface(pythoncom.IID_ITypeInfo)
> >  >>> iti
> > <PyITypeInfo at 0x02BB2BF8 with obj at 0x004D4804>
> >
> > BAM! :
> >
> >  >>> pythoncom.GetRecordFromTypeInfo(iti)
> > Traceback (most recent call last):
> >    File "<stdin>", line 1, in <module>
> > pywintypes.com_error: (-2147024809, 'Falscher Parameter.', None, 
None)
> >
> > Unfortunately, as you can see, it doesn't work. Do you know if there
> > is a way to get a more verbose error message or some COM debugging
> > console to know what COM is actually complaining about? I mean, I'm
> > pretty sure that it works as you assumed (Like COM tries to get the
> > GUID from the TypeInfo and then calls GetRecordFromGuid which would
> > explain why setting a GUID still doesnt work) but just to make sure, 
you
> know?
> >
> > //Jan
> >
> > Am 27.03.2012 00:24, schrieb Mark Hammond:
> >> On 26/03/2012 8:51 PM, Jan Wedel wrote:
> >>>> I'm confident the E_INVALIDARG error is coming from COM itself.  
It
> >>>
> >>>> wouldn't surprise me at all to find GetRecordInfoFromTypeInfo
> >>>> simply
> >>>
> >>>> uses the type info to fetch the GUID of the record then calls the
> >>>
> >>>> ...FromGuids method.
> >>>
> >>>
> >>>
> >>> Yeah, maybe. I just tried to find some explanation and found this:
> >>>
> >>> http://www.autohotkey.com/forum/topic84172.html
> >>>
> >>>
> >>>
> >>> This thread seems to be related and presents a solution. As far as 
I
> >>> understand, COM is complaining because the UUID is missing and in
> >>> this solution a random GUID is created and attached to the 
typeinfo...
> >>
> >> That's interesting and might well work.  It could all be done from
> >> pure Python as ICreateTypeInfo is exposed.
> >>
> >>>> It might be possible to write all the info needed to the 
generated
> >>>> file,
> >>>
> >>>> but it would probably take a fair bit of work.  If you look later
> >>>> in
> >>>
> >>>> PyRecord.cpp, all the "set attribute" and "get attribute" code is
> >>>
> >>>> implemented by way of the IRecordInfo interface - eg, 
GetFieldNoCopy.
> >>>
> >>>> It might be possible to steal the code from comtypes, but I'm not
> >>>
> >>>> familiar with that.
> >>>
> >>>
> >>>
> >>> The question is, is it necessaey to use the C++ part at all? Would 
it be
> >>>   possible to just keep the Record stuff in pure python? Why would
> >>> it be necessary to dynamically loade the TypeInfo from a DLL that
> >>> most likely will not change that often?
> >>
> >> The problem is the code that knows what "something =
> >> aRecord.someAtribute" does.  It needs to know how to move the 
memory
> >> around so the right thing happens - that is what GetFieldNoCopy 
does.
> >>
> >>> The thing is, I'm currently working on a customer project and
> >>> unfortunately I need this stuff getting to work soon. I would 
really
> >>> appreciate you applying a fix to the C++ layer of pythoncom if
> >>> possible at all. But I also understand that you can't spend all 
your
> >>> spare time on that problem.
> >>
> >> I'd be happy to apply a fix if I knew what the fix was.  From pure
> >> Python, you might be able to:
> >>
> >> * load the typelib
> >> * find the type info for the guid.
> >> * QI the type info for ICreateTypeInfo.
> >> * Set the GUID to some random GUID.
> >> * Call pythoncom.GetRecordFromTypeInfo with the modified typeinfo.
> >> * Use the result object as normal.
> >>
> >>> So, if it's not easy for you to fix this, could you tell me what
> >>> would be necessary to fix or work-around this? My "vision" is to
> >>> manually create a static definition of all relevant structs 
somewhere
> which might
> >>>   be just laborious work.
> >>
> >> The problem is how that static info is actually used rather than 
the
> >> generation of it.  Some code will need to use that static info to
> >> support getting and setting attributes on the record.
> >>
> >>> My problem is that I don't know much about the internals of COM. 
Is
> >>> it possible to write a pure python class that has the correct
> >>> attributes and methods so I can return an instance of this class
> >>> from a COM method of my server and the receiver and COM itself 
would
> >>> be able to interpret it as the correct struct or does it require
> >>> some native code?
> >>
> >> It would almost certainly require some native (or ctypes) code to 
do
> >> it all statically without an IRecordInfo interface to perform the
> >> heavy lifting.
> >>
> >> I'll try and find time to use one of the pywin32 test objects to 
see
> >> if this can work by dynamically setting a GUID, but it is unlikely 
to
> >> happen for a few days.
> >>
> >> Cheers,
> >>
> >> Mark
> >>>
> >>>
> >>>
> >>> //Jan
> >>>
> >>> ----- Originalnachricht -----
> >>> Von: "Mark Hammond" <mhammond at skippinet.com.au>
> >>> Gesendet: Sam, 24.3.2012 00:53
> >>> An: "Jan Wedel" <Jan.Wedel at ettex.de>
> >>> Betreff: Re: AW: AW: AW: AW: [python-win32] How to write a COM
> >>> Server implementing interfaces from type lib?
> >>>
> >>> I'm confident the E_INVALIDARG error is coming from COM itself.  
It
> >>> wouldn't surprise me at all to find GetRecordInfoFromTypeInfo 
simply
> >>> uses the type info to fetch the GUID of the record then calls the
> >>> ...FromGuids method.
> >>>
> >>> It might be possible to write all the info needed to the generated
> >>> file, but it would probably take a fair bit of work.  If you look
> >>> later in PyRecord.cpp, all the "set attribute" and "get attribute"
> >>> code is implemented by way of the IRecordInfo interface - eg,
> GetFieldNoCopy.
> >>> It might be possible to steal the code from comtypes, but I'm not
> >>> familiar with that.
> >>>
> >>> Cheers,
> >>>
> >>> Mark
> >>>
> >>> On 24/03/2012 2:39 AM, Jan Wedel wrote:
> >>>>> Obviously this is a "dynamic" process - other languages/tools 
etc
> >>>>> are likely to use these struct definitions at compile time
> >>>>
> >>>> What about writing all necessary information into the generated
> >>>> type lib files like comtypes is doing it? Or do you need to have
> >>>> some native objects?
> >>>>
> >>>>> (...)it might be worth poking around the MS docs and see if they
> >>>>> offer any way to get an IRecordInfo given just the typelib info
> >>>>> and the struct name, as that seems the only info we have at the
> >>>>> time we need it.
> >>>>>     If we can find something I'll try and add the support and 
send
> >>>>> you a custom built pythoncomxx.dll.
> >>>>
> >>>> There is some modification of the Record method necessary. In my
> >>>> server I pass an object created by GetModuleForTypelib(...) to 
the
> >>>> Record() method. Inside, I check if its a nodule. If yes, I load
> >>>> the typelib using pythoncom.LoadRegTypeLib(...) (don't know if 
its
> >>>> already cached somewhere, but it was my quick and dirty attempt).
> >>>> I've checked the MS doc and found a second method to retrieve the
> >>>> Record object. Using the returned library object, I used
> >>>> pythoncom.GetRecordFromTypeInfo(tlib.GetTypeInfo(8)) to retrieve
> >>>> the Record from the type info object instead of 
GetRecordFromGuids.
> >>>>
> >>>> The "8" is hard coded for testing and is the library index of the
> >>>> Record. If this idea could work, it's probably worth to add the
> >>>> index to the "RecordMap" when generating the file so we have an
> >>>> alternative look-up key instead of the GUID.
> >>>>
> >>>> However, it doesn't work either and gives the following result:
> >>>>
> >>>> Traceback (most recent call last):
> >>>>     File "C:\Program Files
> >>>> (x86)\Python\lib\site-packages\win32com\universal.py", line 179, 
in
> >>>> dispatch
> >>>>       retVal = ob._InvokeEx_(meth.dispid, 0, meth.invkind, args,
> >>>> None, None)
> >>>>     File "C:\Program Files
> >>>> (x86)\Python\lib\site-packages\win32com\server\policy.py", line
> >>>> 346, in _InvokeEx_
> >>>>       return self._invokeex_(dispid, lcid, wFlags, args, kwargs,
> >>>> serviceProvider)
> >>>>     File "C:\Program Files
> >>>> (x86)\Python\lib\site-packages\win32com\server\policy.py", line
> >>>> 650, in _invokeex_
> >>>>       return func(*args)
> >>>>     File "C:\temp\opc\PyOPCComServer.py", line 241, in GetStatus
> >>>>       status = Record("tagOPCSERVERSTATUS", GEN_TL_OPC_DA)
> >>>>     File "C:\Program Files
> >>>> (x86)\Python\lib\site-packages\win32com\client\__init__.py", line
> >>>> 403, in Record
> >>>>       return pythoncom.GetRecordFromTypeInfo(tlib.GetTypeInfo(8))
> >>>> com_error: (-2147024809, 'Falscher Parameter.', None, None)
> >>>>
> >>>> Which means E_INVALIDARG. I've checked the source code of
> >>>> PyRecord.cpp and it says "This function will fail if the 
specified
> >>>> type info does not have a guid defined".
> >>>>
> >>>> I don't know if this is a COM or PythonCom limitation... If it's
> >>>> the latter, I would really appreciate fixing the C++ code.
> >>>> Otherwise, it might help, having the whole definition inside the
> >>>> generate python file as explained above.
> >>>>
> >>>> Thanks a lot for your help!
> >>>>
> >>>> //Jan
> >>>>
> >>>>
> >>>> ----- Originalnachricht -----
> >>>> Von: "Mark Hammond"<mhammond at skippinet.com.au>
> >>>> Gesendet: Fre, 3/23/2012 2:43pm
> >>>> An: "Jan Wedel"<Jan.Wedel at ettex.de>
> >>>> Betreff: Re: AW: AW: AW: [python-win32] How to write a COM Server
> >>>> implementing interfaces from type lib?
> >>>>
> >>>> So - looking at the source, the win32com.client.Record object
> >>>> attempts to look up both the tlb guid and the record guid based 
on
> >>>> the name using that RecordMap dict we talked about.  From there, 
we
> >>>> should wind up in win32com\src\PyRecord.cpp, where we use the
> >>>> typelib info and the struct GUID to call the COM function
> >>>> GetRecordInfoFromGuids(), which takes those GUIDs and returns an
> >>>> IRecordInfo interface that tells us all the struct element names
> >>>> and types etc.
> >>>>
> >>>> Obviously this is a "dynamic" process - other languages/tools etc
> >>>> are likely to use these struct definitions at compile time, which
> >>>> may explain why they have a NULL guid - they assume people wont
> >>>> need to look them up at runtime using GetRecordInfoFromGuids.  It
> >>>> is late and I must hit bed, but it might be worth poking around 
the
> >>>> MS docs and see if they offer any way to get an IRecordInfo given
> >>>> just the typelib info and the struct name, as that seems the only
> >>>> info we have at the time we need it.
> >>>>     If we can find something I'll try and add the support and 
send
> >>>> you a custom built pythoncomxx.dll.
> >>>>
> >>>> Cheers,
> >>>>
> >>>> Mark
> >>>>
> >>>> On 24/03/2012 12:06 AM, Jan Wedel wrote:
> >>>>> I tried to manually add the Records to the generated file to see
> >>>>> if patching genpy.py would solve the problem:
> >>>>>
> >>>>> RecordMap = {
> >>>>>        u'tagOPCITEMVQT': 
'{00000000-0000-0000-0000-000000000000}',
> >>>>>        u'tagOPCSERVERSTATE': 
'{00000000-0000-0000-0000-000000000000}',
> >>>>>        u'tagOPCSERVERSTATUS':
> >>>>> '{00000000-0000-0000-0000-000000000000}',
> >>>>> }
> >>>>>
> >>>>> The client calls the GetStatus() method of my server which is
> >>>>> implemented as follows:
> >>>>>
> >>>>>        def GetStatus(self):
> >>>>>            """Returns the current server status"""
> >>>>>            print "GetStatus()"
> >>>>>
> >>>>>            # return None
> >>>>>            status = Record("tagOPCSERVERSTATUS", self)
> >>>>>
> >>>>>            status.ftStartTime = pywintypes.Time(self.start_time)
> >>>>>            status.ftCurrentTime = pywintypes.Time(time.time())
> >>>>>            status.ftLastUpdateTime =
> >>>>> pywintypes.Time(self.last_update_time)
> >>>>>            status.dwServerState = ServerState.RUNNING
> >>>>>            status.dwGroupCount = len(self.groups)
> >>>>>            status.dwBandWidth = self.band_width
> >>>>>            status.wMajorVersion = MAJOR_VERSION
> >>>>>            status.wMinorVersion = MINOR_VERSION
> >>>>>            status.wBuildNumber = BUILD_NUMBER
> >>>>>            status.wReserved = 0
> >>>>>            status.szVendorInfo = VENDOR_INFO
> >>>>>
> >>>>>            return status
> >>>>>
> >>>>> with the following result:
> >>>>>
> >>>>> GetStatus()
> >>>>> pythoncom error: Failed to call the universal dispatcher
> >>>>>
> >>>>> Traceback (most recent call last):
> >>>>>      File "C:\Program Files
> >>>>> (x86)\Python\lib\site-packages\win32com\universal.py",line 179, 
in
> >>>>> dispatch
> >>>>>        retVal = ob._InvokeEx_(meth.dispid, 0, meth.invkind, 
args,
> >>>>> None, None)
> >>>>>      File "C:\Program Files
> >>>>> (x86)\Python\lib\site-packages\win32com\server\policy.py", line
> >>>>> 346, in _InvokeEx_
> >>>>>        return self._invokeex_(dispid, lcid, wFlags, args, 
kwargs,
> >>>>> serviceProvider)
> >>>>>      File "C:\Program Files
> >>>>> (x86)\Python\lib\site-packages\win32com\server\policy.py", line
> >>>>> 650, in _invokeex_
> >>>>>        return func(*args)
> >>>>>      File "C:\temp\opc\PyOPCComServer.py", line 234, in 
GetStatus
> >>>>>        status = Record("tagOPCSERVERSTATUS", self)
> >>>>>      File "C:\Program Files
> >>>>> (x86)\Python\lib\site-packages\win32com\client\__init__.py", 
line
> >>>>> 399, in Record
> >>>>>        object = gencache.EnsureDispatch(object)
> >>>>>      File "C:\Program Files
> >>>>> (x86)\Python\lib\site-packages\win32com\client\gencache.py", 
line
> >>>>> 529, in EnsureDispatch
> >>>>>        disp = win32com.client.Dispatch(prog_id)
> >>>>>      File "C:\Program Files
> >>>>> (x86)\Python\lib\site-packages\win32com\client\__init__.py", 
line
> >>>>> 96, in Dispatch
> >>>>>        return __WrapDispatch(dispatch, userName, resultCLSID,
> >>>>> typeinfo, clsctx=clsctx)
> >>>>>      File "C:\Program Files
> >>>>> (x86)\Python\lib\site-packages\win32com\client\__init__.py", 
line
> >>>>> 43, in __WrapDispatch
> >>>>>        return dynamic.Dispatch(dispatch, userName, WrapperClass,
> >>>>> typeinfo, clsctx=clsctx)
> >>>>>      File "C:\Program Files
> >>>>> (x86)\Python\lib\site-packages\win32com\client\dynamic.py", line
> >>>>> 122, in Dispatch
> >>>>>        typeinfo = IDispatch.GetTypeInfo()
> >>>>> AttributeError: EttexOPCServer instance has no attribute 
'GetTypeInfo'
> >>>>>
> >>>>> pythoncom error: Failed to call the universal dispatcher
> >>>>>
> >>>>> Traceback (most recent call last):
> >>>>>      File "C:\Program Files
> >>>>> (x86)\Python\lib\site-packages\win32com\universal.py",
> >>>>> line 195, in dispatch
> >>>>>        WriteFromOutTuple(retVal, meth._gw_out_args, argPtr)
> >>>>> TypeError: The VARIANT type is unknown (0x4024).
> >>>>>
> >>>>> If found an example of how to create a Record. However, it does
> >>>>> use client COM and passes the object generated by Dispatch() to
> >>>>> the Record constructor. What object should I pass on the server 
side?
> >>>>> Obviously "self" doesn't work...
> >>>>>
> >>>>> Then I tried to return None instead which should leave the 
output
> >>>>> arguments untouch, but I only get the "TypeError: The VARIANT 
type
> >>>>> is unknown (0x4024)." message. The 0x4024 is 16420 and can be
> >>>>> found in the vtable definition of the generated file:
> >>>>>
> >>>>> IOPCServer_vtables_ = [
> >>>>> (...)
> >>>>>        (( u'GetStatus' , u'ppServerStatus' , ), 1610678275,
> >>>>> (1610678275, (), [ (16420, 2, None, None) , ], 1 , 1 , 4 , 0 , 
24
> >>>>> , (3, 0, None, None) , 0 , )),
> >>>>> (...)
> >>>>>
> >>>>> I was asking myself how does this 16420 from the vtable gets
> >>>>> translated into a "Ptr Ptr tagOPCSERVERSTATUS"? How does the
> >>>>> record definition know what fields it contains? Is is probably
> >>>>> necessary to add some more information to the Record such as 
this
> >>>>> number, e.g? Or does the internal COM libs uses the TypeLib
> >>>>> information to resolve it? It tried to look into the source of
> >>>>> this type error but the WriteFromOutTuple method is somewhere 
inside the
> pythoncom dll...
> >>>>>
> >>>>> I'm really stuck here...
> >>>>>
> >>>>> //Jan
> >>>>>
> >>>>>
> >>>>>
> >>>>> ----- Originalnachricht -----
> >>>>> Von: "Mark Hammond"<mhammond at skippinet.com.au>
> >>>>> Gesendet: Fre, 3/23/2012 12:11pm
> >>>>> An: "Jan Wedel"<Jan.Wedel at ettex.de>
> >>>>> Betreff: Re: AW: AW: [python-win32] How to write a COM Server
> >>>>> implementing interfaces from type lib?
> >>>>>
> >>>>> I haven't got the code in front of me, but in that place it 
would
> >>>>> probably be OK to use the name.  I'm slightly worried about the
> >>>>> "global"
> >>>>> resolution though - there's probably code that can find a record
> >>>>> given just the GUID and without regard for which tlb it was
> >>>>> defined in (ie, so the name itself need not be unique).  In that
> >>>>> case, a tuple of (typelib_guid, name) would probably be OK.
> >>>>>
> >>>>> Mark
> >>>>>
> >>>>> On 23/03/2012 7:54 PM, Jan Wedel wrote:
> >>>>>> After I wrote my last mail, I did some further research on the
> >>>>>> problem. In genpy.py where the python file is generated for the
> >>>>>> type lib, I found this code:
> >>>>>>
> >>>>>>        print>>   stream, 'RecordMap = {'
> >>>>>>         for record in recordItems.itervalues():
> >>>>>>             if str(record.clsid) == pythoncom.IID_NULL:
> >>>>>>                 print>>   stream, "\t###%s: %s, # Typedef 
disabled
> >>>>>> because it doesn't have a non-null GUID" % 
(repr(record.doc[0]),
> >>>>>> repr(str(record.clsid)))
> >>>>>>             else:
> >>>>>>                 print>>   stream, "\t%s: %s," %
> >>>>>> (repr(record.doc[0]), repr(str(record.clsid)))
> >>>>>>         print>>   stream, "}"
> >>>>>>
> >>>>>> I've checked the typelib with COMView, and all records, modules
> >>>>>> and enums defined have the UUID {00000-...00000} assigned. Then
> >>>>>> I've checked some random other type libs I've found on my
> >>>>>> computer and it seems that they are always using {0000...0000}. 
I
> >>>>>> don't know much about COM but I guess this must be OK. The type
> >>>>>> lib I'm using is made by the OPC foundation and there must be a
> >>>>>> numerous COM clients using it so it can't be that wrong, can 
it?
> >>>>>>
> >>>>>> The question is, is it possible to create a Record object in my
> >>>>>> server class and return it without the need of having a unique
> >>>>>> UUID assignedand without the need of beeing defined by genyp?
> >>>>>>
> >>>>>> If that doesn't work, I might also try to patch the genpy.py, 
but
> >>>>>> it seems as if you rely on the CLSID being unique. in the
> >>>>>> BuildOleItemsFromType method, there is the following code:
> >>>>>>
> >>>>>>           elif infotype == pythoncom.TKIND_RECORD or infotype 
==
> >>>>>> pythoncom.TKIND_UNION:
> >>>>>>             newItem = RecordItem(info, attr, doc)
> >>>>>>             recordItems[newItem.clsid] = newItem
> >>>>>>
> >>>>>> Because all records have the same clsid, they get overwritten 
by
> >>>>>> each other. There probably must be some other identification
> >>>>>> mechanism. Either we use an index or the record name itself...
> >>>>>> But then COM methods must be aware of these type in input and
> >>>>>> output parameters...
> >>>>>>
> >>>>>> Do have any suggestion on how to proceed (patch/work-around)?
> >>>>>>
> >>>>>> Thanks!
> >>>>>>
> >>>>>> //Jan
> >>>>>>
> >>>>>>
> >>>>>> ----- Originalnachricht -----
> >>>>>> Von: "Mark Hammond"<mhammond at skippinet.com.au>
> >>>>>> Gesendet: Fre, 3/23/2012 12:30am
> >>>>>> An: "Jan Wedel"<Jan.Wedel at ettex.de>
> >>>>>> Cc: python-win32 at python.org
> >>>>>> Betreff: Re: AW: [python-win32] How to write a COM Server
> >>>>>> implementing interfaces from type lib?
> >>>>>>
> >>>>>> On 23/03/2012 3:54 AM, Jan Wedel wrote:
> >>>>>>> I've actually managed to patch the policy.py to allow multiple
> >>>>>>> typelibraries.
> >>>>>>>
> >>>>>>> Instead of having a definition for interfaces, type library 
id,
> >>>>>>> version etc i've build this into one tuple. I've created a new
> >>>>>>> attribute that can have multiple of these tuples. The head of 
my
> >>>>>>> server class now looks like this:
> >>>>>>>
> >>>>>>>
> >>>>>>> class EttexOPCServer: _reg_progid_ = "Ettex.OPC.Automation"
> >>>>>>> _reg_desc_ = "ettex OPC DA Server" _reg_clsid_ =
> >>>>>>> "{13B51E8D-4BC2-4DED-8D4E-4614692F88E6}" _reg_catids_ = [
> >>>>>>> '{63D5F432-CFE4-11D1-B2C8-0060083BA1FB}' ]
> >>>>>>>
> >>>>>>> _typelib_interfaces_ = [
> >>>>>>> ("{3B540B51-0378-4551-ADCC-EA9B104302BF}",
> >>>>>>> 3, 0, 0, [ 'IOPCServer', 'IOPCItemProperties', ] ),
> >>>>>>> ("{B28EEDB1-AC6F-11D1-84D5-00608CB8A7E9}", 1, 0, 0, [
> >>>>>>> 'IOPCCommon', 'IConnectionPointContainer' ] ), ] _reg_clsctx_ 
=
> >>>>>>> pythoncom.CLSCTX_LOCAL_SERVER
> >>>>>>>
> >>>>>>> I had to patch three locations in policy.py so far and I would
> >>>>>>> be happy to send you my changes if you like.
> >>>>>>
> >>>>>> That would be great - a patch on sourceforge would be best.
> >>>>>> However, it
> >>>>>> might be better to wait until we are sure it is working OK :)
> >>>>>>
> >>>>>>
> >>>>>>> However it still doesn't
> >>>>>>> work but I'm not sure if I have missed something in my patch 
or
> >>>>>>> if it is a general problem in my server code or a bug in the
> >>>>>>> framework.
> >>>>>>>
> >>>>>>> At least, the client successfully creates the object and tries
> >>>>>>> to call a method of the interface but I get the following 
debug
> output:
> >>>>>>>
> >>>>>>> GetStatus() pythoncom error: Failed to call the universal
> >>>>>>> dispatcher
> >>>>>>>
> >>>>>>> Traceback (most recent call last): File "C:\Program Files
> >>>>>>> (x86)\Python\lib\site-packages\win32com\universal.py", line 
195,
> >>>>>>> in dispatch WriteFromOutTuple(retVal, meth._gw_out_args, 
argPtr)
> >>>>>>> TypeError: The VARIANT type is unknown (0x4024). pythoncom 
error:
> >>>>>>> Unexpected gateway error
> >>>>>>>
> >>>>>>> Traceback (most recent call last): File "C:\Program Files
> >>>>>>> (x86)\Python\lib\site-packages\win32com\universal.py", line 
195,
> >>>>>>> in dispatch WriteFromOutTuple(retVal, meth._gw_out_args, 
argPtr)
> >>>>>>> TypeError: The VARIANT type is unknown (0x4024). 
GetErrorString
> >>>>>>> -2147467259 0 pythoncom error: Failed to call the universal
> >>>>>>> dispatcher
> >>>>>>>
> >>>>>>> (...)
> >>>>>>>
> >>>>>>> I've hat a lookat the definition of GetStatus. It requires a
> >>>>>>> pointer to a pointer of type "tagOPCSERVERSTATUS" which is a
> >>>>>>> record definition in the type library. But when I look at what
> >>>>>>> has been generated by makepy, the record map looks pretty 
empty to me:
> >>>>>>>
> >>>>>>> RecordMap = { u'tagOPCITEMVQT':
> >>>>>>> '{00000000-0000-0000-0000-000000000000}', }
> >>>>>>>
> >>>>>>> The type lib defines 10 records! I tried to import the typelib
> >>>>>>> using comtypes and get that generated (excerpt):
> >>>>>>>
> >>>>>>> class tagOPCSERVERSTATUS(Structure): pass
> >>>>>>>
> >>>>>>> # values for enumeration 'tagOPCSERVERSTATE' 
OPC_STATUS_RUNNING
> >>>>>>> = 1 OPC_STATUS_FAILED = 2 OPC_STATUS_NOCONFIG = 3
> >>>>>>> OPC_STATUS_SUSPENDED =
> >>>>>>> 4 OPC_STATUS_TEST = 5 OPC_STATUS_COMM_FAULT = 6
> >>>>>>> tagOPCSERVERSTATE = c_int # enum tagOPCSERVERSTATUS._fields_ = 
[
> >>>>>>> ('ftStartTime', _FILETIME), ('ftCurrentTime', _FILETIME),
> >>>>>>> ('ftLastUpdateTime', _FILETIME), ('dwServerState',
> >>>>>>> tagOPCSERVERSTATE), ('dwGroupCount', c_ulong), ('dwBandWidth',
> >>>>>>> c_ulong), ('wMajorVersion', c_ushort), ('wMinorVersion',
> >>>>>>> c_ushort), ('wBuildNumber', c_ushort), ('wReserved', 
c_ushort),
> >>>>>>> ('szVendorInfo', WSTRING), ]
> >>>>>>>
> >>>>>>> Is there a bug in makepy that prevents creating these records?
> >>>>>>
> >>>>>> It would seem so :)
> >>>>>>
> >>>>>>> If
> >>>>>>> yes, can I fix it or can I manually change the generated file 
to
> >>>>>>> support the records?
> >>>>>>
> >>>>>> I'm really not sure - you probably need to look at the existing
> >>>>>> Record tests.  The "PyCOMTest" test object (which is in the
> >>>>>> source tree and needs you to build it using MSVC) has some
> >>>>>> structs for testing.
> >>>>>>
> >>>>>>
> >>>>>>> If not, how would I create and return such a pointer of a
> >>>>>>> pointer in pythoncom? Is comtypes compatible with pythoncom so 
I
> >>>>>>> could use the comtypes generated files?
> >>>>>>
> >>>>>> Unfortunately there isn't any compatibility between the 2.
> >>>>>>
> >>>>>> Cheers,
> >>>>>>
> >>>>>> Mark
> >>>>>>>
> >>>>>>> Thanks!
> >>>>>>>
> >>>>>>> //Jan
> >>>>>>>
> >>>>>>> ----- Originalnachricht ----- Von: "Jan
> >>>>>>> Wedel"<Jan.Wedel at ettex.de>
> >>>>>>> Gesendet: Don, 3/22/2012 1:51pm An: mhammond at skippinet.com.au 
Cc:
> >>>>>>> python-win32 at python.org Betreff: Re: [python-win32] How to 
write
> >>>>>>> a COM Server implementing interfaces from type lib?
> >>>>>>>
> >>>>>>> Hi Mark,
> >>>>>>>
> >>>>>>> thanks for your reply.
> >>>>>>>
> >>>>>>>> That's E_FAIL which is pretty generic.  Doesn't sound like a
> >>>>>>>> simple failure to QI for the correct interface.
> >>>>>>>
> >>>>>>> Yeah. The client says something like "Unknown Error" which 
makes
> >>>>>>> it hard to tell what the problem actually is.
> >>>>>>>
> >>>>>>>> win32com should be able to do this given there is a tlb - see
> >>>>>>>> the "pippo" samples.  Further, using the debug facilities and
> >>>>>>>> win32traceutil, you should be able to see the creation of the
> >>>>>>>> object, the QIs on the object and any methods actually 
called.
> >>>>>>>> But as mentioned, I doubt it is a QI failure.
> >>>>>>>
> >>>>>>> Yeah, there is a type library (OPC DA). Actually there are 
even
> >>>>>>> two type libraries, each of them contains two Interface which 
my
> >>>>>>> server needs to implement.
> >>>>>>>
> >>>>>>> I've had a look into policy.py and found that:
> >>>>>>>
> >>>>>>> def _build_typeinfos_(self): # Can only ever be one for now.
> >>>>>>> (...)
> >>>>>>>
> >>>>>>> which means using _typelib_guid_ allows only one type lib,
> >>>>>>> right? I could try to patch your code or do you think there is 
a
> >>>>>>> general problem that would make it impossible having more than
> >>>>>>> one typelib in your framework?
> >>>>>>>
> >>>>>>>> Have you tried contacting the author of the object?
> >>>>>>>
> >>>>>>> I am the author... I only use the 3rd party typelibs. I am
> >>>>>>> writing the server that must support the interfaces so that
> >>>>>>> other 3rd party clients can access the server component.
> >>>>>>>
> >>>>>>> I can reproduce the error using python the COMView tool trying
> >>>>>>> to instanciate the Server. The problem is, that I don't see 
why.
> >>>>>>> I've enabled debugging mode when registering the server. When
> >>>>>>> using one of the 3rd party clients, rhe python trace collector
> >>>>>>> shows the
> >>>>>>> following:
> >>>>>>>
> >>>>>>> Object with win32trace dispatcher created (object=None) 
Entering
> >>>>>>> constructor in<PyOPCComServer.EttexOPCServer instance at
> >>>>>>> 0x0213B058>._QueryInterface_ with unsupported IID
> >>>>>>> {00000003-0000-0000-C000-000000000046}
> >>>>>>> ({00000003-0000-0000-C000-000000000046})
> >>>>>>> in<PyOPCComServer.EttexOPCServer instance at
> >>>>>>> 0x0213B058>._QueryInterface_ with unsupported IID
> >>>>>>> {0000001B-0000-0000-C000-000000000046}
> >>>>>>> ({0000001B-0000-0000-C000-000000000046})
> >>>>>>> in<PyOPCComServer.EttexOPCServer instance at
> >>>>>>> 0x0213B058>._QueryInterface_ with unsupported IID
> >>>>>>> {00000018-0000-0000-C000-000000000046}
> >>>>>>> ({00000018-0000-0000-C000-000000000046})
> >>>>>>> in<PyOPCComServer.EttexOPCServer instance at
> >>>>>>> 0x0213B058>._QueryInterface_ with unsupported IID
> >>>>>>> {4C1E39E1-E3E3-4296-AA86-EC938D896E92}
> >>>>>>> ({4C1E39E1-E3E3-4296-AA86-EC938D896E92})
> >>>>>>> in<PyOPCComServer.EttexOPCServer instance at
> >>>>>>> 0x0213B058>._InvokeEx_-AddConnection(1, 0) [1,0,None] 
Connection
> >>>>>>> added. Active connections: 1 in<PyOPCComServer.EttexOPCServer
> >>>>>>> instance at 0x0213B058>._InvokeEx_-ReleaseConnection(1, 0, 1)
> >>>>>>> [1,0,None] Connection released. Active connections: 0
> >>>>>>>
> >>>>>>> The QI calls are standard COM calls, not application specific
> >>>>>>> (AFAIK). You can see the "Entering constructor" message which 
is
> >>>>>>> in my python constructor of the server. Just for fun, I
> >>>>>>> implemented the IExternalConnection interface to see if the 
methods
> are called.
> >>>>>>> "Connection added. ..." is my output. But I can't see an 
further
> >>>>>>> requests that shows what the problem is. Are there any calls 
in
> >>>>>>> pythoncom that could fail and does not give debug output?
> >>>>>>>
> >>>>>>> My pythoncom server starts like that:
> >>>>>>>
> >>>>>>> class EttexOPCServer: _reg_progid_ = "Ettex.OPC.Automation"
> >>>>>>> _reg_desc_ = "ettex OPC DA Server" _reg_clsid_ =
> >>>>>>> "{13B51E8D-4BC2-4DED-8D4E-4614692F88E6}" _reg_catids_ = [
> >>>>>>> '{63D5F432-CFE4-11D1-B2C8-0060083BA1FB}' ] _com_interfaces_ = 
[
> >>>>>>> '{00000019-0000-0000-C000-000000000046}', # 
IExternalConnection
> >>>>>>> (Is it really mandatory?)
> >>>>>>> '{39C13A4D-011E-11D0-9675-0020AFD8ADB3}', # IOPCServer
> >>>>>>> '{F31DFDE2-07B6-11D2-B2D8-0060083BA1FB}', # IOPCCommon
> >>>>>>> '{39C13A72-011E-11D0-9675-0020AFD8ADB3}', # IOPCItemProperties
> >>>>>>> '{B196B284-BAB4-101A-B69C-00AA00341D07}'  #
> >>>>>>> IConnectionPointContainer ] _typelib_guid_ =
> >>>>>>> "{3B540B51-0378-4551-ADCC-EA9B104302BF}" _typelib_version_ = 
(3,
> >>>>>>> 0) _reg_clsctx_ = pythoncom.CLSCTX_LOCAL_SERVER
> >>>>>>>
> >>>>>>> _public_methods_ =  [ 'Connect', 'Disconnect', 
'GetErrorString'
> >>>>>>> ] ## IExternalConnection methods _public_methods_ += [
> >>>>>>> 'AddConnection', 'ReleaseConnection' ]
> >>>>>>>
> >>>>>>> 
################################################################
> >>>>>>> # ## COM Class Attributes
> >>>>>>> 
################################################################
> >>>>>>> # _public_attrs_ = [ 'ClientName', 'OPCGroups' ]
> >>>>>>>
> >>>>>>> 
################################################################
> >>>>>>> # ## COM Class Read-Only Attributes
> >>>>>>> 
################################################################
> >>>>>>> # _readonly_attrs_ = [ 'OPCGroups' ]
> >>>>>>>
> >>>>>>> I don't have all interface methods implemented at the time but 
I
> >>>>>>> guess that doesn't matter as long as not all type libs have 
been
> >>>>>>> loaded, does it?
> >>>>>>>
> >>>>>>> If its not possible with pythoncom, is it possible with 
comtypes?
> >>>>>>> I've tried that as well with nearly the same result ("Unknown
> >>>>>>> error"):
> >>>>>>>
> >>>>>>> # OPC Data Access 3.00 Type Library opc_da_tl =
> >>>>>>> comtypes.GUID("{3B540B51-0378-4551-ADCC-EA9B104302BF}") # OPC
> >>>>>>> Common 1.10 Type Library opc_com_tl =
> >>>>>>> comtypes.GUID("{B28EEDB1-AC6F-11D1-84D5-00608CB8A7E9}")
> >>>>>>>
> >>>>>>> GetModule((opc_da_tl, 3, 0)) GetModule((opc_com_tl, 1, 0))
> >>>>>>>
> >>>>>>> import comtypes.gen.OPCDA as OpcDa import comtypes.gen.OPCCOMN
> >>>>>>> as OpcCommon
> >>>>>>>
> >>>>>>> class EttexOPCServer2(OpcCommon.IOPCCommon,
> >>>>>>> OpcCommon.IConnectionPointContainer, OpcDa.IOPCServer,
> >>>>>>> OpcDa.IOPCItemProperties): _reg_progid_ = 
"Ettex.OPC.Automation2"
> >>>>>>> _reg_desc_ = "ettex OPC DA Server 2" _reg_novers_progid_ =
> >>>>>>> _reg_progid_ _reg_clsid_ = 
"{80A2B8F7-792E-43F4-95F8-CD6BB4B413AD}"
> >>>>>>> _reg_catids_ = [ '{63D5F432-CFE4-11D1-B2C8-0060083BA1FB}' ]
> >>>>>>> _reg_clsctx_ = comtypes.CLSCTX_LOCAL_SERVER _regcls_ =
> >>>>>>> comtypes.server.localserver.REGCLS_MULTIPLEUSE
> >>>>>>>
> >>>>>>> _com_interfaces_ = [OpcCommon.IOPCCommon,
> >>>>>>> OpcCommon.IConnectionPointContainer, OpcDa.IOPCServer,
> >>>>>>> OpcDa.IOPCItemProperties]
> >>>>>>>
> >>>>>>> I don't know what to do. Is there anything more I can do to
> >>>>>>> debug to find out WHAT exactly causes the error? Because as 
you
> >>>>>>> can see, the pythoncom debug output doesn't show this error 
that
> >>>>>>> is returned by the client.
> >>>>>>>
> >>>>>>> Thanks a lot!
> >>>>>>>
> >>>>>>> //Jan
> >>>>>>>
> >>>>>>> ----- Originalnachricht ----- Von: "Mark
> >>>>>>> Hammond"<skippy.hammond at gmail.com>   Gesendet: Don, 3/22/2012
> >>>>>>> 12:29pm
> >>>>>>> An: "Jan Wedel"<Jan.Wedel at ettex.de>   Cc: 
python-win32 at python.org
> >>>>>>> Betreff: Re: [python-win32] How to write a COM Server
> >>>>>>> implementing interfaces from type lib?
> >>>>>>>
> >>>>>>> On 22/03/2012 2:53 AM, Jan Wedel wrote:
> >>>>>>>> Hi,
> >>>>>>>>
> >>>>>>>> I'm currently having trouble to write a COM-Server that has
> >>>>>>>> some special requirements: - It needs to derive from IUnknown 
-
> >>>>>>>> It needs to implement multiple interface from two different
> >>>>>>>> proprietary typelibs (dlls) - It needs to implement a custom
> >>>>>>>> category
> >>>>>>>>
> >>>>>>>> At first I started with pythoncom. I used the attribute
> >>>>>>>> _reg_catids_ to specify the category and _com_interfaces_ to
> >>>>>>>> specify the interfaces I want to implement. The client
> >>>>>>>> (proprietary 3rd party sw, no source) sees the server but
> >>>>>>>> throws some 0x80004005 error on CoCreateInstance.
> >>>>>>>
> >>>>>>> That's E_FAIL which is pretty generic.  Doesn't sound like a
> >>>>>>> simple failure to QI for the correct interface.
> >>>>>>>
> >>>>>>> win32com should be able to do this given there is a tlb - see
> >>>>>>> the "pippo" samples.  Further, using the debug facilities and
> >>>>>>> win32traceutil, you should be able to see the creation of the
> >>>>>>> object, the QIs on the object and any methods actually called.
> >>>>>>> But as mentioned, I doubt it is a QI failure.
> >>>>>>>
> >>>>>>>> I was hoping that I can just tell the COM dispatcher, "yes, I
> >>>>>>>> have these interface implemented" and implement the methods
> >>>>>>>> without really having the interface classes available.
> >>>>>>>>
> >>>>>>>> I read, that pythoncom can only create components that use
> >>>>>>>> IDispatch so I guess the _com_interfaces_ idea won't work, 
will
> >>>>>>>> it?
> >>>>>>>
> >>>>>>> As above, you should be able to fully implement them so long 
as
> >>>>>>> makepy has been run.
> >>>>>>>
> >>>>>>>> Then I did some further research and found comtypes. I tried 
to
> >>>>>>>> write a server stub again. I used GetModule to load the type
> >>>>>>>> library containing the Interfaces, importing the generated
> >>>>>>>> interface classes and let the main server class extend these
> >>>>>>>> interfaces.
> >>>>>>>>
> >>>>>>>> The first problem was, that comtypes did not support the
> >>>>>>>> _reg_catids_ attribute or anything similar so I had to add 
the
> >>>>>>>> Implemented Categories key manually to the registry. Then, I
> >>>>>>>> was able to see the server through the client, which 
obviously
> >>>>>>>> filters by categories, but it still shows the same error as 
before.
> >>>>>>>>
> >>>>>>>> So, what is the correct/best way to implement a server that
> >>>>>>>> needs to implement custom interfaces and categories? Or is it
> >>>>>>>> possible at all using python?
> >>>>>>>
> >>>>>>> The fact you get the same error there implies something else 
is
> >>>>>>> going wrong, but it is impossible to guess what.  Have you 
tried
> >>>>>>> contacting the author of the object?
> >>>>>>>
> >>>>>>> Mark
> >>>>>>>
> >>>>>>>>
> >>>>>>>> Thanks!
> >>>>>>>>
> >>>>>>>> //Jan
> >>>>>>>>
> >>>>>>>>
> >>>>>>>>
> >>>>>>>> _______________________________________________ python-win32
> >>>>>>>> mailing list python-win32 at python.org
> >>>>>>>> http://mail.python.org/mailman/listinfo/python-win32
> >>>>>>>
> >>>>>>> _______________________________________________ python-win32
> >>>>>>> mailing list python-win32 at python.org
> >>>>>>> http://mail.python.org/mailman/listinfo/python-win32
> >>>>>>
> >>>>>
> >>>>
> >>>>
> >>>
> >>
> >>
> >
> 
> 




More information about the python-win32 mailing list