Hi Tim,

I wasn't aware Thomas was away, thanks for letting me know. He just suggested to me the other week that I should join and start a discussion on here about the patch.

In regards to use cases, I guess the reason why I want this functionality is for the main project I am working on, which is the NVDA screen reader, at
http://www.nvda-project.org/

In that project we use comtypes very heavily, for MSAA, SAPI, RichEdit controls, etc. But for our support for MS Word and Excel etc, we currently use pywin32. Reason being that with comtypes the COM interfaces are just way too large and take way too long to generate.

Pywin32 does a great job with dynamic dispatch, though its just annoying having to use two Python COM implementations in the same project.

So this is why I would like to improve comtypes dynamic dispatch support.

I have also updated the patch, again, now fixing a few little issues with the non-typeinfo dynamic dispatch support, and more importantly, I have added some more tests to test_dyndispatch.py which tests many of my code changes using MSAA objects.

Mick





Tim Golden wrote:
Michael Curran wrote:
Hi all,

I have attached a new version of my dynamic dispatch patch.

This time, if an IDispatch interface does not support ITypeInfo, then
dynamic dispatch falls back to using the old GetIDsOfNames etc.

This is so far mainly there for the Mozilla Gecko toolkit, as its
IDispatch with MSAA is hardly complete.

Just so you don't think everyone's ignoring you... I'm certainly
interested in the patches, altho' without any specific use-case
in mind. Are you aware, tho', that Thomas Heller posted a few
days back to say he was away and incommunicado for two or three
weeks? He's obviously best placed to comment on things.

TJG

-------------------------------------------------------------------------
Sponsored by: SourceForge.net Community Choice Awards: VOTE NOW!
Studies have shown that voting for your favorite open source project,
along with a healthy diet, reduces your potential for chronic lameness
and boredom. Vote Now at http://www.sourceforge.net/community/cca08
_______________________________________________
comtypes-users mailing list
comtypes-users@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/comtypes-users
Index: comtypes/test/test_dyndispatch.py
===================================================================
--- comtypes/test/test_dyndispatch.py   (revision 64719)
+++ comtypes/test/test_dyndispatch.py   (working copy)
@@ -1,7 +1,14 @@
 import unittest
 from comtypes.client import CreateObject
-from comtypes.client.dynamic import _Dispatch
+from comtypes.client.dynamic import _Dispatch, MethodCaller
+import comtypes
+comtypes.disableCodeGeneration=True
+from ctypes import POINTER, byref, oledll, windll
+from comtypes.automation import IDispatch
 
+#MSAA constants
+NAVDIR_FIRSTCHILD=7
+
 class Test(unittest.TestCase):
     def test(self):
         d = CreateObject("MSScriptControl.ScriptControl")
@@ -27,5 +34,38 @@
         self.failUnlessEqual(result[4], 96)
         self.assertRaises(IndexError, lambda: result[5])
 
+class Test_MSAA(unittest.TestCase):
+
+    def setUp(self):
+        pdisp=POINTER(IDispatch)()
+        
oledll.oleacc.AccessibleObjectFromWindow(windll.user32.GetDesktopWindow(),-4,byref(IDispatch._iid_),byref(pdisp))
+        self.desktopObject=_Dispatch(pdisp)
+
+    def tearDown(self):
+        del self.desktopObject
+
+    def test_OptionalArgsPropgetIsMethod(self):
+        member=self.desktopObject.accName
+        self.failUnless(isinstance(member,MethodCaller),msg="property with 
optional arguments must be treeted as a method")
+
+    def test_noArgsPropgetIsProperty(self):
+        member=self.desktopObject.accFocus
+        self.failIf(isinstance(member,MethodCaller),msg="property with no 
arguments must not be treeted as a method")
+
+    def test_callOptionalArgsPropgetWithArgs(self):
+        res=self.desktopObject.accName(0)
+        self.failUnless(isinstance(res,basestring),msg="should return a 
string")
+
+    def test_callOptionalArgsPropgetWithNoArgs(self):
+        res=self.desktopObject.accName()
+        self.failUnless(isinstance(res,basestring),msg="should return a 
string")
+
+    def test_IterateByDirectIEnumVARIANTInterface(self):
+        childCounter=0
+        for child in self.desktopObject:
+            childCounter+=1
+            self.failUnless(isinstance(child,_Dispatch),msg="Child must be a 
dynamic Dispatch")
+        self.failUnless(childCounter>0,msg="number of children must be greater 
than 0")
+
 if __name__ == "__main__":
     unittest.main()
Index: comtypes/__init__.py
===================================================================
--- comtypes/__init__.py        (revision 64719)
+++ comtypes/__init__.py        (working copy)
@@ -2,6 +2,9 @@
 
 __version__ = "0.5.0a"
 
+disableCodeGeneration=False #Set to true to not allow generating interface 
modules and to use dynamic IDispatch instead
+
+
 from ctypes import *
 from _ctypes import COMError
 from comtypes import partial
Index: comtypes/client/_generate.py
===================================================================
--- comtypes/client/_generate.py        (revision 64719)
+++ comtypes/client/_generate.py        (working copy)
@@ -197,6 +197,8 @@
                 if file_:
                     file_.close()
 
+    if comtypes.disableCodeGeneration:
+        raise RuntimeError("Not allowed to generate module")
     # generate the module since it doesn't exist or is out of date
     from comtypes.tools.tlbparser import generate_module
     if comtypes.client.gen_dir is None:
Index: comtypes/client/__init__.py
===================================================================
--- comtypes/client/__init__.py (revision 64719)
+++ comtypes/client/__init__.py (working copy)
@@ -122,13 +122,16 @@
         punk.QueryInterface(comtypes.IUnknown, typeattr.guid)
     except comtypes.COMError, details:
         logger.debug("Does not implement default interface, returning dynamic 
object")
-        return comtypes.client.dynamic.Dispatch(punk)
+        return 
dynamic.Dispatch(punk.QueryInterface(comtypes.automation.IDispatch))
 
     itf_name = tinfo.GetDocumentation(-1)[0] # interface name
     tlib = tinfo.GetContainingTypeLib()[0] # typelib
 
     # import the wrapper, generating it on demand
-    mod = GetModule(tlib)
+    try:
+        mod = GetModule(tlib)
+    except:
+        return 
dynamic.Dispatch(punk.QueryInterface(comtypes.automation.IDispatch))
     # Python interface class
     interface = getattr(mod, itf_name)
     logger.debug("Implements default interface from typeinfo %s", interface)
@@ -144,9 +147,13 @@
     #
     # Could the above code, as an optimization, check that QI works,
     # *before* generating the wrapper module?
-    result = punk.QueryInterface(interface)
-    logger.debug("Final result is %s", result)
-    return result
+    try:
+        result = punk.QueryInterface(interface)
+        logger.debug("Final result is %s", result)
+        return result
+    except:
+        return 
dynamic.Dispatch(punk.QueryInterface(comtypes.automation.IDispatch))
+
 # backwards compatibility:
 wrap = GetBestInterface
 
Index: comtypes/client/dynamic.py
===================================================================
--- comtypes/client/dynamic.py  (revision 64719)
+++ comtypes/client/dynamic.py  (working copy)
@@ -34,28 +34,28 @@
         self._obj = _obj
         
     def __call__(self, *args):
-        return self._obj._comobj.Invoke(self._id, *args)
+        return self._obj._comobj.Invoke(self._id, 
*args,**dict(_invkind=comtypes.automation.DISPATCH_METHOD|comtypes.automation.DISPATCH_PROPERTYGET))
 
-    def __getitem__(self, *args):
-        return self._obj._comobj.Invoke(self._id, *args,
-                                        
**dict(_invkind=comtypes.automation.DISPATCH_PROPERTYGET))
+class _Dispatch(object):
+    # Expose methods and properties via fully dynamic dispatch
 
-    def __setitem__(self, *args):
-        if _is_object(args[-1]):
-            self._obj._comobj.Invoke(self._id, *args,
-                                        
**dict(_invkind=comtypes.automation.DISPATCH_PROPERTYPUTREF))
-        else:
-            self._obj._comobj.Invoke(self._id, *args,
-                                        
**dict(_invkind=comtypes.automation.DISPATCH_PROPERTYPUT))
+    __memberCache={} #key is tuple of guid,memberName and value is funcDesc
 
-class _Dispatch(object):
-    # Expose methods and properties via fully dynamic dispatch
     def __init__(self, comobj):
         self.__dict__["_comobj"] = comobj
         self.__dict__["_ids"] = {} # Tiny optimization: trying not to use 
GetIDsOfNames more than once
+        try:
+            typeInfo=self._comobj.GetTypeInfo(0)
+            self.__dict__['_iid']=typeInfo.GetTypeAttr().guid
+            
self.__dict__['_typeComp']=typeInfo.QueryInterface(comtypes.typeinfo.ITypeComp)
+        except:
+            pass
 
     def __enum(self):
-        e = self._comobj.Invoke(-4) # DISPID_NEWENUM
+        try:
+            e = self._comobj.Invoke(-4) # DISPID_NEWENUM
+        except:
+            e=self._comobj
         return e.QueryInterface(comtypes.automation.IEnumVARIANT)
 
     def __getitem__(self, index):
@@ -68,45 +68,53 @@
             raise IndexError, "index out of range"
         return item
 
+    def __call__(self,*args):
+        return self._comobj.Invoke(0, 
*args,**dict(_invkind=comtypes.automation.DISPATCH_PROPERTYGET|comtypes.automation.DISPATCH_METHOD))
+
     def QueryInterface(self, *args):
         "QueryInterface is forwarded to the real com object."
         return self._comobj.QueryInterface(*args)
 
     def __getattr__(self, name):
-##        tc = 
self._comobj.GetTypeInfo(0).QueryInterface(comtypes.typeinfo.ITypeComp)
-##        dispid = tc.Bind(name)[1].memid
-        dispid = self._ids.get(name)
-        if not dispid:
-            dispid = self._comobj.GetIDsOfNames(name)[0]
-            self._ids[name] = dispid
-        
-        flags = comtypes.automation.DISPATCH_PROPERTYGET
-        try:
-            result = self._comobj.Invoke(dispid, _invkind=flags)
-        except COMError, (hresult, text, details):
-            if hresult in ERRORS_BAD_CONTEXT:
-                result = MethodCaller(dispid, self)
-                self.__dict__[name] = result
-            else: raise
-        except: raise
-        
-        return result
+        if hasattr(self,'_iid'):
+            memberKey=(self._iid,name)
+            if not memberKey in self.__memberCache:
+                
self.__memberCache[memberKey]=memberDesc=self._typeComp.Bind(name)[1]
+            else:
+                memberDesc=self.__memberCache[memberKey]
+            if memberDesc.invkind==comtypes.automation.DISPATCH_PROPERTYGET 
and memberDesc.cParams==0 and memberDesc.cParamsOpt==0:
+                result = self._comobj.Invoke(memberDesc.memid, 
_invkind=comtypes.automation.DISPATCH_PROPERTYGET)
+            else:
+                result = MethodCaller(memberDesc.memid, self)
+            return result        
+        else:
+            memid=self._ids.get(name,None)
+            if memid is None:
+                self._ids[name]=memid=self.GetIDsOfNames(name)[0]
+            return MethodCaller(memid,self)
 
     def __setattr__(self, name, value):
-        dispid = self._ids.get(name)
-        if not dispid:
-            dispid = self._comobj.GetIDsOfNames(name)[0]
-            self._ids[name] = dispid
+        if hasattr(self,'_iid'):
+            memberKey=(self._iid,name)
+            if not memberKey in self.__memberCache:
+                
self.__memberCache[memberKey]=memberDesc=self._typeComp.Bind(name)[1]
+            else:
+                memberDesc=self.__memberCache[memberKey]
+            memid=memberDesc.memid
+        else:
+            memid=self._ids.get(name,None)
+            if memid is None:
+                self._ids[name]=memid=self.GetIDsOfNames(name)[0]
         # First try propertyput, if that fails with
-        # DISP_E_MEMBERNOTFOUND then try propertyputref
+            # DISP_E_MEMBERNOTFOUND then try propertyputref
         flags = comtypes.automation.DISPATCH_PROPERTYPUT
         try:
-            return self._comobj.Invoke(dispid, value, _invkind=flags)
+            return self._comobj.Invoke(memid, value, _invkind=flags)
         except COMError, (hresult, text, details):
             if hresult == hres.DISP_E_MEMBERNOTFOUND: pass
             else: raise
         flags = comtypes.automation.DISPATCH_PROPERTYPUTREF
-        return self._comobj.Invoke(dispid, value, _invkind=flags)
+        return self._comobj.Invoke(memid, value, _invkind=flags)
 
     def __iter__(self):
         return _Collection(self.__enum())
-------------------------------------------------------------------------
Sponsored by: SourceForge.net Community Choice Awards: VOTE NOW!
Studies have shown that voting for your favorite open source project,
along with a healthy diet, reduces your potential for chronic lameness
and boredom. Vote Now at http://www.sourceforge.net/community/cca08
_______________________________________________
comtypes-users mailing list
comtypes-users@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/comtypes-users

Reply via email to