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.

Mick

Michael Curran wrote:
Hi all,

I am one of the core developers for the NVDA screen reader at
http://www.nvda-project.org/

We use comtypes very heavily in our project, mostly for access to MSAA,
RichEdit, MSHTML, and SAPI speech synthesizers.

However, for access to such apps as MS Word, MS Excel etc, we tend to
use pywin32, for the single reason that comtypes interfaces for these
apps can take so long to generate, plus pywin32 has very good dynamic
IDispatch support.

Pywin32 is great, but it is annoying switching between python COM
implementations, we would rather just use one or the other.

This is the reason why I have created a patch for latest comtypes trunk,
which improves its support for dynamic IDispatch, making it now possible
to fully depend only on dynamic IDispatch, with out having to generate
any interfaces when going from one IDispatch object to another etc.

Firstly, a variable called 'disableCodeGeneration' has been added to the
base comtypes package. If you do not wish for any COM interfaces to be
generated by comtypes if it has to, set this to True. It is False by
default.

comtypes.client._generate._createWrapper now throws an exception, if
disableCodeGeneration is True and it finally gets to the point where it
must generate some interfaces (they didn't exist already or they are out
of date).

comtypes.client.GetBestInterface now tries returning a dynamic.Dispatch
object if at any time there is an error. e.g. the interface couldn't be
found or created etc.

So with just these changes, if you have no already generated interfaces,
and you make a dynamic Dispatch object some how, now if any of its
methods or properties return IDispatch objects, rather than them being
smartly wrapped in real generated interfaces, dynamic Dispatch objects
are just returned instead.

So the rule is: with disableCodeGeneration set to True, if the interface
already exists it will use it, but if it doesn't and it supports
IDispatch, it will use dynamic Dispatch, rather than trying to generate
proper interfaces.

I also made some changes to comtypes.client.dynamic._Dispatch, so that
it works nicer with properties and methods.

It now makes use of ITypeComp, using Bind to get IDs and invKind info
from a name. It also caches as much information as it can to make sure
its as fast as possible.

Note that information is cached by its guid and name, on the actual
dynamic Dispatch class, so that multiple uses of the same IDispatch type
stay just as fast.

So now, the only time an IDispatch property is treeted as a property
(i.e. doesn't have to be called like a function) is if its invkind is
propget and it has no params or optional params. Otherwise the method
caller is used instead.

examples in MS Word are:
range.text is a property
range.collapse() is a method
examples in MSAA are:
pacc.accName() or pacc.accName(0) The property has an optional argument
so therefore it has to be treeted as a method.
pacc.accFocus is a property with no arguments at all, so it can be
treeted as a property.

__call__ has also been added to _Dispatch, so that the IDispatch's
default method can be called, by calling the dynamic Dispatch object
like a function.
An example of this is an MS Word paragraphs object, calling
paragraphs(1) will now work.

Also __enum now tries querying directly to IEnumVARIANT, if it fails to
call _enum. This now means that MSAA objects that have an IEnumVARIANT
as as one of their interfaces will now work pythonicly as a list/iterater.

This patch makes dynamic._Dispatch rely on the fact that the IDispatch
object has ITypeInfo via GetTypeInfo(0). The question i have is is this
true for all IDispatch objects? or should we still support the more
basic GetIdsOfNames and then just treet all properties as methods, if
ITypeComp can't be found?

A completely different approach to this could be where if a dynamic
Dispatch object calls invoke, and invoke returns an IDispatch, then we
should wrap it as a dynamic Dispatch object rather than getting its best
interface. However, I am not sure if this could currently be at all
possible in comtypes as we would need to be able to detect the fact that
invoke was called from a dynamic Dispatch object, as its result gets
wrapped... at a very low level. I think for now, allowing the user to
refuse code generation better suits the pattern of use.

What are people's thoughts on this?

Mick


Index: comtypes/__init__.py
===================================================================
--- comtypes/__init__.py        (revision 64620)
+++ 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 64620)
+++ 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 64620)
+++ 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 64620)
+++ 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,48 @@
             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:
+            return MethodCaller(self._ids.get(name,lambda name: 
self.GetIDsOfNames(name)[0]),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,lambda name: 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