SourceForge has been redesigned. Learn more.
Close

#594 InvokeTypes error using win32com v2.17

open
nobody
win32 (141)
5
2012-05-11
2012-05-11
Mike Fox
No

I think we have found a bug in win32com 2.17. We are using it with Python 2.7.2. We are calling a proprietary DLL named PATE, which provides APIs to a database named Enovia from the Dassault corporation. The Enovia client is linked to a CAD program named CATIA so we must conjure objects of both CATIA and Enovia as shown in the code snippet below. The strApplicationID is a password administered by my company and has nothing to do with the PATE DLL itself. Rather, PATE should pass the strApplicationID to the Enovia server, which should authenticate the session.

import win32com.client, win32com.client.gencache, win32com.client.selecttlb
strPATECLSID = '{9BB3CB79-7098-47D0-83A2-F8F56D236DC8}'
strPATEclassCLSID = '{9A1F3C8E-46B7-4CFA-A820-E4256ECD53B5}'

self.objCATIA = win32com.client.GetActiveObject('CATIA.Application')
win32com.client.gencache.EnsureModule(strPATECLSID, 0, 0, 0)
self.objBase= win32com.client.gencache.GetClassForCLSID(strPATEclassCLSID)
self.__objPATECOM = self.objBase(self.objCATIA)
strApplicationID = <secret>
try:
self.__objPATECOM.SetApplicationID(strApplicationID)
except (pywintypes.com_error), _e:
print "Connection error: %s" %(_e)
return False
====================================================================
When we use win32com 2.16 the code above works fine but when we use win32com 2.17 it throws the error message shown below.

File testKBE_PATECAKE.py, line 367 in ConnectToENOVIA
self.__objPATECOM.SetApplicationID(strApplicationID)
File "C:\Python27\lib\site-packages\win32com\gen_py\9BB3CB79 -7098-47D0-83A2-F8
F56D236DC8x0x0x0.py", line 457 in SetApplicationID
return self._oleobj_.InvokeTypes(1610940417, LCID, 1, (24, 0), ((16396, 1),)
, iAID
File "C:\Python27\lib\site-packages\win32com\client\dynamic.py", line 516 in
__getattr__
raise AttributeError("%s.%s" % (self._username_, attr))
AttributeEror: CATIA.Application.InvokeTypes

Discussion

  • Mark Hammond

    Mark Hammond - 2012-05-12

    I suspect the problem is that previously you have makepy support generated for the 'CATIA.Application' object which should make things work. The problem is that when you do:

    self.__objPATECOM = self.objBase(self.objCATIA)

    The DispatchBaseClass.__init__ function in win32com\client\__init__.py checks to see if the object is itself a makepy generated object and will work correctly if it is.

    Even though, this really is a bug as we should handle that situation. A fix would probably be to change the line in DispatchBaseClass.__init__ from:

    elif isinstance(oobj, DispatchBaseClass):
    to
    elif hasattr(oobj, "_oleobj_"):

     
  • Mike Fox

    Mike Fox - 2012-05-16

    To Mark Hammond:
    I don't think the problem is caused by a pre-existing wrapper in the gen_py folder as you suggested in your comment. When I uninstalled version 2.17 of pywin32 and installed version 2.16 on the same PC (with the contents of the gen_py folder unchanged) I was able to call the SetApplicationID method without it throwing an error.

     
  • Mark Hammond

    Mark Hammond - 2012-05-16

    Can you please modify your test code to add:

    print self.objCATIA

    After creating it, and tell me what it prints in both the failing and working cases?

     
  • Mike Fox

    Mike Fox - 2012-05-30

    Sorry for the delay. I added print statements after lines 4, 6, and 7 (in the code shown in my original post). With version 2.16 (pywin32-216.win-amd64-py2.7) I get the following correct output:
    self.objCATIA= <COMObject CATIA.Application>
    self.objBase= win32com.gen_py.9BB3CB79-7098-47D0-83A2-F8F56D236DC8x0x0x0.PATECOM
    self.__objPATECOM= <win32com.gen_py.CATIA V5 BOEPATEAutomationInterfaces Object
    Library.PATECOM instance at 0x44540104>

    Then I ran the same sript with version 2.17 (pywin32-217.win-amd64-py2.7) and got the following:
    self.objCATIA= <COMObject CATIA.Application>
    self.objBase= win32com.gen_py.9BB3CB79-7098-47D0-83A2-F8F56D236DC8x0x0x0.PATECOM
    self.__objPATECOM= <win32com.gen_py.CATIA V5 BOEPATEAutomationInterfaces Object
    Library.PATECOM instance at 0x44540104>
    Traceback (moste recent call last):
    File testKBE_PATECAKE.py, line 370 in ConnectToENOVIA
    self.__objPATECOM.SetApplicationID(strApplicationID)
    File "C:\Python27\lib\site-packages\win32com\gen_py\9BB3CB79 -7098-47D0-83A2-F8
    F56D236DC8x0x0x0.py", line 457 in SetApplicationID
    return self._oleobj_.InvokeTypes(1610940417, LCID, 1, (24, 0), ((16396, 1),), iAID
    File "C:\Python27\lib\site-packages\win32com\client\dynamic.py", line 516 in __getattr__
    raise AttributeError("%s.%s" % (self._username_, attr))
    AttributeEror: CATIA.Application.InvokeTypes

     
  • Mark Hammond

    Mark Hammond - 2012-05-30

    It looks like the genpy idea I had was wrong - the objects are the same in both examples.

    What should happen is that the line:

    self.__objPATECOM = self.objBase(self.objCATIA)

    end up calling DispatchBaseClass.__init__ which is in win32com\client\__init__.py. What *should* happen, and appears to be happening in 216 is it should enter the branch:

    elif isinstance(oobj, DispatchBaseClass):
    try:
    oobj = oobj._oleobj_.QueryInterface(self.CLSID, pythoncom.IID_IDispatch) # Must be a valid COM instance
    except pythoncom.com_error, details:
    import winerror
    # Some stupid objects fail here, even tho it is _already_ IDispatch!!??
    # Eg, Lotus notes.
    # So just let it use the existing object if E_NOINTERFACE
    if details.hresult != winerror.E_NOINTERFACE:
    raise
    oobj = oobj._oleobj_

    In the working case it just does the QI so oobj ends up as a "raw" IDispatch. In your failing 217 scenario, it seems to be either not hitting that first condition at all, or enters that block but hits the "except pythoncom.com_error" block.

    You might want to instrument that code with prints to try and (a) confirm I'm correct in the working case and (b) work out how it fails in the failing case (ie, whether the 'if' statement simply isn't entered at all or it hits the exception).

     
  • Mike Fox

    Mike Fox - 2012-06-01

    I put some print statements into the DispatchBaseClass.__init__ method as you suggested. That method checks whether the oobj == None, then checks whether oobj is an instance of DispatchBaseClass. Both tests fail so the QueryInterface method is never called and the raw oobj is put into the DispatchBaseClass.__dict__

    When I printed the raw oobj (that was input to the __init__ method) it printed
    <PyIDispatch at 0x000000002371440 with obj at 0x000000000048E258>

    If I change the 6th line of the __init__ method from
    elif isinstance(oobj, DispatchBaseClass):
    to
    else:
    the code runs without throwing any errors. What does that mean?

     
  • Mark Hammond

    Mark Hammond - 2012-06-03

    What I really don't understand still is why it works in an earlier version, when best I can tell, the types of all the objects and that code block are all the same...