home *** CD-ROM | disk | FTP | other *** search
/ PC World 2002 May / PCWorld_2002-05_cd.bin / Software / TemaCD / activepython / ActivePython-2.1.1.msi / Python21_win32com_client_dynamic.py < prev    next >
Encoding:
Python Source  |  2001-07-26  |  18.2 KB  |  483 lines

  1. """Support for dynamic COM client support.
  2.  
  3. Introduction
  4.  Dynamic COM client support is the ability to use a COM server without
  5.  prior knowledge of the server.  This can be used to talk to almost all
  6.  COM servers, including much of MS Office.
  7.  
  8.  In general, you should not use this module directly - see below.
  9.  
  10. Example
  11.  >>> import win32com.client
  12.  >>> xl = win32com.client.Dispatch("Excel.Application")
  13.  # The line above invokes the functionality of this class.
  14.  # xl is now an object we can use to talk to Excel.
  15.  >>> xl.Visible = 1 # The Excel window becomes visible.
  16.  
  17. """
  18. import traceback
  19. import string
  20. import new
  21.  
  22. import pythoncom
  23. import winerror
  24. import build
  25.  
  26. from types import StringType, IntType, TupleType, ListType
  27. from pywintypes import UnicodeType, IIDType
  28.  
  29. import win32com.client # Needed as code we eval() references it.
  30. from win32com.client import NeedUnicodeConversions
  31.  
  32. debugging=0            # General debugging
  33. debugging_attr=0    # Debugging dynamic attribute lookups.
  34.  
  35. LCID = 0x0
  36.  
  37. # These errors generally mean the property or method exists,
  38. # but can't be used in this context - eg, property instead of a method, etc.
  39. # Used to determine if we have a real error or not.
  40. ERRORS_BAD_CONTEXT = [
  41.     winerror.DISP_E_MEMBERNOTFOUND,
  42.     winerror.DISP_E_BADPARAMCOUNT,
  43.     winerror.DISP_E_PARAMNOTOPTIONAL,
  44.     winerror.DISP_E_TYPEMISMATCH,
  45.     winerror.E_INVALIDARG,
  46. ]
  47.  
  48. def debug_print(*args):
  49.     if debugging:
  50.         for arg in args:
  51.             print arg,
  52.         print
  53.  
  54. def debug_attr_print(*args):
  55.     if debugging_attr:
  56.         for arg in args:
  57.             print arg,
  58.         print
  59.  
  60. # get the dispatch type in use.
  61. dispatchType = pythoncom.TypeIIDs[pythoncom.IID_IDispatch]
  62. iunkType = pythoncom.TypeIIDs[pythoncom.IID_IUnknown]
  63. _StringOrUnicodeType=[StringType, UnicodeType]
  64. _GoodDispatchType=[StringType,IIDType,UnicodeType]
  65. _defaultDispatchItem=build.DispatchItem
  66.  
  67. def _GetGoodDispatch(IDispatch, clsctx = pythoncom.CLSCTX_SERVER):
  68.     if type(IDispatch) in _GoodDispatchType:
  69.         try:
  70.             IDispatch = pythoncom.connect(IDispatch)
  71.         except pythoncom.ole_error:
  72.             IDispatch = pythoncom.CoCreateInstance(IDispatch, None, clsctx, pythoncom.IID_IDispatch)
  73.     return IDispatch
  74.  
  75. def _GetGoodDispatchAndUserName(IDispatch,userName,clsctx):
  76.     if userName is None:
  77.         if type(IDispatch) in _StringOrUnicodeType:
  78.             userName = IDispatch
  79.         else:
  80.             userName = "<unknown>"
  81.     return (_GetGoodDispatch(IDispatch, clsctx), userName)
  82.  
  83. def Dispatch(IDispatch, userName = None, createClass = None, typeinfo = None, UnicodeToString=NeedUnicodeConversions, clsctx = pythoncom.CLSCTX_SERVER):
  84.     IDispatch, userName = _GetGoodDispatchAndUserName(IDispatch,userName,clsctx)
  85.     if createClass is None:
  86.         createClass = CDispatch
  87.     lazydata = None
  88.     try:
  89.         if typeinfo is None:
  90.             typeinfo = IDispatch.GetTypeInfo()
  91.         try:
  92.             #try for a typecomp
  93.             typecomp = typeinfo.GetTypeComp()
  94.             lazydata = typeinfo, typecomp
  95.         except pythoncom.com_error:
  96.             pass
  97.     except pythoncom.com_error:
  98.         typeinfo = None
  99.     olerepr = MakeOleRepr(IDispatch, typeinfo, lazydata)
  100.     return createClass(IDispatch, olerepr, userName,UnicodeToString, lazydata)
  101.  
  102. def MakeOleRepr(IDispatch, typeinfo, typecomp):
  103.     olerepr = None
  104.     if typeinfo is not None:
  105.         try:
  106.             attr = typeinfo.GetTypeAttr()
  107.             # If the type info is a special DUAL interface, magically turn it into
  108.             # a DISPATCH typeinfo.
  109.             if attr[5] == pythoncom.TKIND_INTERFACE and attr[11] & pythoncom.TYPEFLAG_FDUAL:
  110.                 # Get corresponding Disp interface;
  111.                 # -1 is a special value which does this for us.
  112.                 href = typeinfo.GetRefTypeOfImplType(-1);
  113.                 typeinfo = typeinfo.GetRefTypeInfo(href)
  114.                 attr = typeinfo.GetTypeAttr()
  115.             if typecomp is None:
  116.                 olerepr = build.DispatchItem(typeinfo, attr, None, 0)
  117.             else:
  118.                 olerepr = build.LazyDispatchItem(attr, None)
  119.         except pythoncom.ole_error:
  120.             pass
  121.     if olerepr is None: olerepr = build.DispatchItem()
  122.     return olerepr
  123.  
  124. def DumbDispatch(IDispatch, userName = None, createClass = None,UnicodeToString=NeedUnicodeConversions, clsctx=pythoncom.CLSCTX_SERVER):
  125.     "Dispatch with no type info"
  126.     IDispatch, userName = _GetGoodDispatchAndUserName(IDispatch,userName,clsctx)
  127.     if createClass is None:
  128.         createClass = CDispatch
  129.     return createClass(IDispatch, build.DispatchItem(), userName,UnicodeToString)
  130.  
  131. class CDispatch:
  132.     def __init__(self, IDispatch, olerepr, userName =  None, UnicodeToString=NeedUnicodeConversions, lazydata = None):
  133.         if userName is None: userName = "<unknown>"
  134.         self.__dict__['_oleobj_'] = IDispatch
  135.         self.__dict__['_username_'] = userName
  136.         self.__dict__['_olerepr_'] = olerepr
  137.         self.__dict__['_mapCachedItems_'] = {}
  138.         self.__dict__['_builtMethods_'] = {}
  139.         self.__dict__['_enum_'] = None
  140.         self.__dict__['_unicode_to_string_'] = UnicodeToString
  141.         self.__dict__['_lazydata_'] = lazydata
  142.  
  143.     def __call__(self, *args):
  144.         "Provide 'default dispatch' COM functionality - allow instance to be called"
  145.         if self._olerepr_.defaultDispatchName:
  146.             invkind, dispid = self._find_dispatch_type_(self._olerepr_.defaultDispatchName)
  147.         else:
  148.             invkind, dispid = pythoncom.DISPATCH_METHOD | pythoncom.DISPATCH_PROPERTYGET, pythoncom.DISPID_VALUE
  149.         if invkind is not None:
  150.             allArgs = (dispid,LCID,invkind,1) + args
  151.             return self._get_good_object_(apply(self._oleobj_.Invoke,allArgs),self._olerepr_.defaultDispatchName,None)
  152.         raise TypeError, "This dispatch object does not define a default method"
  153.  
  154.     def __nonzero__(self):
  155.         return 1 # ie "if object:" should always be "true" - without this, __len__ is tried.
  156.         # _Possibly_ want to defer to __len__ if available, but Im not sure this is
  157.         # desirable???
  158.  
  159.     def __repr__(self):
  160.         return "<COMObject %s>" % (self._username_)
  161.  
  162.     def __str__(self):
  163.         # __str__ is used when the user does "print object", so we gracefully
  164.         # fall back to the __repr__ if the object has no default method.
  165.         try:
  166.             return str(self.__call__())
  167.         except pythoncom.com_error, details:
  168.             if details[0] not in ERRORS_BAD_CONTEXT:
  169.                 raise
  170.             return self.__repr__()
  171.  
  172.     # Delegate comparison to the oleobjs, as they know how to do identity.
  173.     def __cmp__(self, other):
  174.         other = getattr(other, "_oleobj_", other)
  175.         return cmp(self._oleobj_, other)
  176.  
  177.     def __int__(self):
  178.         return int(self.__call__())
  179.  
  180.     def __len__(self):
  181.         invkind, dispid = self._find_dispatch_type_("Count")
  182.         if invkind:
  183.             return self._oleobj_.Invoke(dispid, LCID, invkind, 1)
  184.         raise TypeError, "This dispatch object does not define a Count method"
  185.  
  186.     def _NewEnum(self):
  187.         invkind, dispid = self._find_dispatch_type_("_NewEnum")
  188.         if invkind is None:
  189.             return None
  190.         
  191.         enum = self._oleobj_.InvokeTypes(pythoncom.DISPID_NEWENUM,LCID,invkind,(13, 10),())
  192.         import util
  193.         return util.WrapEnum(enum, None)
  194.  
  195.     def __getitem__(self, index): # syver modified
  196.         # Improved __getitem__ courtesy Syver Enstad
  197.         # Must check _NewEnum before Item, to ensure b/w compat.
  198.         if isinstance(index, IntType):
  199.             if self.__dict__['_enum_'] is None:
  200.                 self.__dict__['_enum_'] = self._NewEnum()
  201.             if self.__dict__['_enum_'] is not None:
  202.                 return self._get_good_object_(self._enum_.__getitem__(index))
  203.         # See if we have an "Item" method/property we can use (goes hand in hand with Count() above!)
  204.         invkind, dispid = self._find_dispatch_type_("Item")
  205.         if invkind is not None:
  206.             return self._get_good_object_(self._oleobj_.Invoke(dispid, LCID, invkind, 1, index))
  207.         raise TypeError, "This object does not support enumeration"
  208.  
  209.     def __setitem__(self, index, *args):
  210.         # XXX - todo - We should support calling Item() here too!
  211. #        print "__setitem__ with", index, args
  212.         if self._olerepr_.defaultDispatchName:
  213.             invkind, dispid = self._find_dispatch_type_(self._olerepr_.defaultDispatchName)
  214.         else:
  215.             invkind, dispid = pythoncom.DISPATCH_PROPERTYPUT | pythoncom.DISPATCH_PROPERTYPUTREF, pythoncom.DISPID_VALUE
  216.         if invkind is not None:
  217.             allArgs = (dispid,LCID,invkind,0,index) + args
  218.             return self._get_good_object_(apply(self._oleobj_.Invoke,allArgs),self._olerepr_.defaultDispatchName,None)
  219.         raise TypeError, "This dispatch object does not define a default method"
  220.  
  221.     def _find_dispatch_type_(self, methodName):
  222.         if self._olerepr_.mapFuncs.has_key(methodName):
  223.             item = self._olerepr_.mapFuncs[methodName]
  224.             return item.desc[4], item.dispid
  225.  
  226.         if self._olerepr_.propMapGet.has_key(methodName):
  227.             item = self._olerepr_.propMapGet[methodName]
  228.             return item.desc[4], item.dispid
  229.  
  230.         try:
  231.             dispid = self._oleobj_.GetIDsOfNames(0,methodName)
  232.         except:    ### what error?
  233.             return None, None
  234.         return pythoncom.DISPATCH_METHOD | pythoncom.DISPATCH_PROPERTYGET, dispid
  235.  
  236.     def _ApplyTypes_(self, dispid, wFlags, retType, argTypes, user, resultCLSID, *args):
  237.         result = apply(self._oleobj_.InvokeTypes, (dispid, LCID, wFlags, retType, argTypes) + args)
  238.         return self._get_good_object_(result, user, resultCLSID)
  239.  
  240.     def _wrap_dispatch_(self, ob, userName = None, returnCLSID = None, UnicodeToString = NeedUnicodeConversions):
  241.         # Given a dispatch object, wrap it in a class
  242.         return Dispatch(ob, userName, UnicodeToString=UnicodeToString)
  243.  
  244.     def _get_good_single_object_(self,ob,userName = None, ReturnCLSID=None):
  245.         if iunkType==type(ob):
  246.             try:
  247.                 ob = ob.QueryInterface(pythoncom.IID_IDispatch)
  248.                 # If this works, we then enter the "is dispatch" test below.
  249.             except pythoncom.com_error:
  250.                 # It is an IUnknown, but not an IDispatch, so just let it through.
  251.                 pass
  252.         if dispatchType==type(ob):
  253.             # make a new instance of (probably this) class.
  254.             return self._wrap_dispatch_(ob, userName, ReturnCLSID)
  255.         elif self._unicode_to_string_ and UnicodeType==type(ob):  
  256.             return str(ob)
  257.         else:
  258.             return ob
  259.         
  260.     def _get_good_object_(self,ob,userName = None, ReturnCLSID=None):
  261.         """Given an object (usually the retval from a method), make it a good object to return.
  262.            Basically checks if it is a COM object, and wraps it up.
  263.            Also handles the fact that a retval may be a tuple of retvals"""
  264.         if ob is None: # Quick exit!
  265.             return None
  266.         elif type(ob)==TupleType:
  267.             return tuple(map(lambda o, s=self, oun=userName, rc=ReturnCLSID: s._get_good_single_object_(o, oun, rc),  ob))
  268.         else:
  269.             return self._get_good_single_object_(ob)
  270.         
  271.     def _make_method_(self, name):
  272.         "Make a method object - Assumes in olerepr funcmap"
  273.         methodName = build.MakePublicAttributeName(name) # translate keywords etc.
  274.         methodCodeList = self._olerepr_.MakeFuncMethod(self._olerepr_.mapFuncs[name], methodName,0)
  275.         methodCode = string.join(methodCodeList,"\n")
  276.         try:
  277. #            print "Method code for %s is:\n" % self._username_, methodCode
  278. #            self._print_details_()
  279.             codeObject = compile(methodCode, "<COMObject %s>" % self._username_,"exec")
  280.             # Exec the code object
  281.             tempNameSpace = {}
  282.             exec codeObject in globals(), tempNameSpace # self.__dict__, self.__dict__
  283.             name = methodName
  284.             # Save the function in map.
  285.             fn = self._builtMethods_[name] = tempNameSpace[name]
  286.             newMeth = new.instancemethod(fn, self, self.__class__)
  287.             return newMeth
  288.         except:
  289.             debug_print("Error building OLE definition for code ", methodCode)
  290.             traceback.print_exc()
  291.         return None
  292.         
  293.     def _Release_(self):
  294.         """Cleanup object - like a close - to force cleanup when you dont 
  295.            want to rely on Python's reference counting."""
  296.         for childCont in self._mapCachedItems_.values():
  297.             childCont._Release_()
  298.         self._mapCachedItems_ = {}
  299.         if self._oleobj_:
  300.             self._oleobj_.Release()
  301.             self.__dict__['_oleobj_'] = None
  302.         if self._olerepr_:
  303.             self.__dict__['_olerepr_'] = None
  304.         self._enum_ = None
  305.  
  306.     def _proc_(self, name, *args):
  307.         """Call the named method as a procedure, rather than function.
  308.            Mainly used by Word.Basic, which whinges about such things."""
  309.         try:
  310.             item = self._olerepr_.mapFuncs[name]
  311.             dispId = item.dispid
  312.             return self._get_good_object_(apply( self._oleobj_.Invoke, (dispId, LCID, item.desc[4], 0 ) + (args) ))
  313.         except KeyError:
  314.             raise AttributeError, name
  315.         
  316.     def _print_details_(self):
  317.         "Debug routine - dumps what it knows about an object."
  318.         print "AxDispatch container",self._username_
  319.         try:
  320.             print "Methods:"
  321.             for method in self._olerepr_.mapFuncs.keys():
  322.                 print "\t", method
  323.             print "Props:"
  324.             for prop, entry in self._olerepr_.propMap.items():
  325.                 print "\t%s = 0x%x - %s" % (prop, entry.dispid, `entry`)
  326.             print "Get Props:"
  327.             for prop, entry in self._olerepr_.propMapGet.items():
  328.                 print "\t%s = 0x%x - %s" % (prop, entry.dispid, `entry`)
  329.             print "Put Props:"
  330.             for prop, entry in self._olerepr_.propMapPut.items():
  331.                 print "\t%s = 0x%x - %s" % (prop, entry.dispid, `entry`)
  332.         except:
  333.             traceback.print_exc()
  334.  
  335.     def __LazyMap__(self, attr):
  336.         try:
  337.             if self._LazyAddAttr_(attr):
  338.                 debug_attr_print("%s.__LazyMap__(%s) added something" % (self._username_,attr))
  339.                 return 1
  340.         except AttributeError:
  341.             return 0
  342.  
  343.     # Using the typecomp, lazily create a new attribute definition.
  344.     def _LazyAddAttr_(self,attr):
  345.         if self._lazydata_ is None: return 0
  346.         res = 0
  347.         i = 0
  348.         typeinfo, typecomp = self._lazydata_
  349.         olerepr = self._olerepr_
  350.         try:
  351.             x,t = typecomp.Bind(attr,i)
  352.             if x==1:    #it's a FUNCDESC
  353.                 r = olerepr._AddFunc_(typeinfo,t,0)
  354.             elif x==2:    #it's a VARDESC
  355.                 r = olerepr._AddVar_(typeinfo,t,0)
  356.             else:        #not found or TYPEDESC/IMPLICITAPP
  357.                 r=None
  358.  
  359.             if not r is None:
  360.                 key, map = r[0],r[1]
  361.                 item = map[key]
  362.                 if map==olerepr.propMapPut:
  363.                     olerepr._propMapPutCheck_(key,item)
  364.                 elif map==olerepr.propMapGet:
  365.                     olerepr._propMapGetCheck_(key,item)
  366.                 res = 1
  367.         except:
  368.             pass
  369.         return res
  370.  
  371.     def __AttrToID__(self,attr):
  372.             debug_attr_print("Calling GetIDsOfNames for property %s in Dispatch container %s" % (attr, self._username_))
  373.             return self._oleobj_.GetIDsOfNames(0,attr)
  374.  
  375.     def __getattr__(self, attr):
  376.         if attr[0]=='_' and attr[-1]=='_': # Fast-track.
  377.             raise AttributeError, attr
  378.         # If a known method, create new instance and return.
  379.         try:
  380.             return new.instancemethod(self._builtMethods_[attr], self, self.__class__)
  381.         except KeyError:
  382.             pass
  383.         # XXX - Note that we current are case sensitive in the method.
  384.         #debug_attr_print("GetAttr called for %s on DispatchContainer %s" % (attr,self._username_))
  385.         # First check if it is in the method map.  Note that an actual method
  386.         # must not yet exist, (otherwise we would not be here).  This
  387.         # means we create the actual method object - which also means
  388.         # this code will never be asked for that method name again.
  389.         if self._olerepr_.mapFuncs.has_key(attr):
  390.             return self._make_method_(attr)
  391.  
  392.         # Delegate to property maps/cached items
  393.         retEntry = None
  394.         if self._olerepr_ and self._oleobj_:
  395.             # first check general property map, then specific "put" map.
  396.             if self._olerepr_.propMap.has_key(attr):
  397.                 retEntry = self._olerepr_.propMap[attr]
  398.             if retEntry is None and self._olerepr_.propMapGet.has_key(attr):
  399.                 retEntry = self._olerepr_.propMapGet[attr]
  400.             # Not found so far - See what COM says.
  401.             if retEntry is None:
  402.                 try:
  403.                     if self.__LazyMap__(attr):
  404.                         if self._olerepr_.mapFuncs.has_key(attr): return self._make_method_(attr)
  405.                         if self._olerepr_.propMap.has_key(attr):
  406.                             retEntry = self._olerepr_.propMap[attr]
  407.                         if retEntry is None and self._olerepr_.propMapGet.has_key(attr):
  408.                             retEntry = self._olerepr_.propMapGet[attr]
  409.                     if retEntry is None:
  410.                         retEntry = build.MapEntry(self.__AttrToID__(attr), (attr,))
  411.                 except pythoncom.ole_error:
  412.                     pass # No prop by that name - retEntry remains None.
  413.  
  414.         if not retEntry is None: # see if in my cache
  415.             try:
  416.                 ret = self._mapCachedItems_[retEntry.dispid]
  417.                 debug_attr_print ("Cached items has attribute!", ret)
  418.                 return ret
  419.             except (KeyError, AttributeError):
  420.                 debug_attr_print("Attribute %s not in cache" % attr)
  421.  
  422.         # If we are still here, and have a retEntry, get the OLE item
  423.         if not retEntry is None:
  424.             debug_attr_print("Getting property Id 0x%x from OLE object" % retEntry.dispid)
  425.             try:
  426.                 ret = self._oleobj_.Invoke(retEntry.dispid,0,pythoncom.DISPATCH_PROPERTYGET,1)
  427.             except pythoncom.com_error, details:
  428.                 if details[0] in ERRORS_BAD_CONTEXT:
  429.                     # May be a method.
  430.                     self._olerepr_.mapFuncs[attr] = retEntry
  431.                     return self._make_method_(attr)
  432.                 raise pythoncom.com_error, details
  433.             self._olerepr_.propMap[attr] = retEntry
  434.             debug_attr_print("OLE returned ", ret)
  435.             return self._get_good_object_(ret)
  436.  
  437.         # no where else to look.
  438.         raise AttributeError, "%s.%s" % (self._username_, attr)
  439.  
  440.     def __setattr__(self, attr, value):
  441.         if self.__dict__.has_key(attr): # Fast-track - if already in our dict, just make the assignment.
  442.             # XXX - should maybe check method map - if someone assigns to a method,
  443.             # it could mean something special (not sure what, tho!)
  444.             self.__dict__[attr] = value
  445.             return
  446.         # Allow property assignment.
  447.         debug_attr_print("SetAttr called for %s.%s=%s on DispatchContainer" % (self._username_, attr, `value`))
  448.         if self._olerepr_:
  449.             # Check the "general" property map.
  450.             if self._olerepr_.propMap.has_key(attr):
  451.                 self._oleobj_.Invoke(self._olerepr_.propMap[attr].dispid, 0, pythoncom.DISPATCH_PROPERTYPUT, 0, value)
  452.                 return
  453.             # Check the specific "put" map.
  454.             if self._olerepr_.propMapPut.has_key(attr):
  455.                 self._oleobj_.Invoke(self._olerepr_.propMapPut[attr].dispid, 0, pythoncom.DISPATCH_PROPERTYPUT, 0, value)
  456.                 return
  457.  
  458.         # Try the OLE Object
  459.         if self._oleobj_:
  460.             if self.__LazyMap__(attr):
  461.                 # Check the "general" property map.
  462.                 if self._olerepr_.propMap.has_key(attr):
  463.                     self._oleobj_.Invoke(self._olerepr_.propMap[attr].dispid, 0, pythoncom.DISPATCH_PROPERTYPUT, 0, value)
  464.                     return
  465.                 # Check the specific "put" map.
  466.                 if self._olerepr_.propMapPut.has_key(attr):
  467.                     self._oleobj_.Invoke(self._olerepr_.propMapPut[attr].dispid, 0, pythoncom.DISPATCH_PROPERTYPUT, 0, value)
  468.                     return
  469.             try:
  470.                 entry = build.MapEntry(self.__AttrToID__(attr),(attr,))
  471.             except pythoncom.com_error:
  472.                 # No attribute of that name
  473.                 entry = None
  474.             if entry is not None:
  475.                 try:
  476.                     self._oleobj_.Invoke(entry.dispid, 0, pythoncom.DISPATCH_PROPERTYPUT, 0, value)
  477.                     self._olerepr_.propMap[attr] = entry
  478.                     debug_attr_print("__setattr__ property %s (id=0x%x) in Dispatch container %s" % (attr, entry.dispid, self._username_))
  479.                     return
  480.                 except pythoncom.com_error:
  481.                     pass
  482.         raise AttributeError, "Property '%s.%s' can not be set." % (self._username_, attr)
  483.