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

Mark Hammond mhammond at skippinet.com.au
Fri Mar 30 07:30:03 CEST 2012


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