581 lines
22 KiB
Python
581 lines
22 KiB
Python
"""Support for dynamic COM client support.
|
|
|
|
Introduction
|
|
Dynamic COM client support is the ability to use a COM server without
|
|
prior knowledge of the server. This can be used to talk to almost all
|
|
COM servers, including much of MS Office.
|
|
|
|
In general, you should not use this module directly - see below.
|
|
|
|
Example
|
|
>>> import win32com.client
|
|
>>> xl = win32com.client.Dispatch("Excel.Application")
|
|
# The line above invokes the functionality of this class.
|
|
# xl is now an object we can use to talk to Excel.
|
|
>>> xl.Visible = 1 # The Excel window becomes visible.
|
|
|
|
"""
|
|
import sys
|
|
import traceback
|
|
import types
|
|
|
|
import pythoncom
|
|
import winerror
|
|
from . import build
|
|
|
|
from pywintypes import IIDType
|
|
|
|
import win32com.client # Needed as code we eval() references it.
|
|
|
|
debugging=0 # General debugging
|
|
debugging_attr=0 # Debugging dynamic attribute lookups.
|
|
|
|
LCID = 0x0
|
|
|
|
# These errors generally mean the property or method exists,
|
|
# but can't be used in this context - eg, property instead of a method, etc.
|
|
# Used to determine if we have a real error or not.
|
|
ERRORS_BAD_CONTEXT = [
|
|
winerror.DISP_E_MEMBERNOTFOUND,
|
|
winerror.DISP_E_BADPARAMCOUNT,
|
|
winerror.DISP_E_PARAMNOTOPTIONAL,
|
|
winerror.DISP_E_TYPEMISMATCH,
|
|
winerror.E_INVALIDARG,
|
|
]
|
|
|
|
ALL_INVOKE_TYPES = [
|
|
pythoncom.INVOKE_PROPERTYGET,
|
|
pythoncom.INVOKE_PROPERTYPUT,
|
|
pythoncom.INVOKE_PROPERTYPUTREF,
|
|
pythoncom.INVOKE_FUNC
|
|
]
|
|
|
|
def debug_print(*args):
|
|
if debugging:
|
|
for arg in args:
|
|
print(arg, end=' ')
|
|
print()
|
|
|
|
def debug_attr_print(*args):
|
|
if debugging_attr:
|
|
for arg in args:
|
|
print(arg, end=' ')
|
|
print()
|
|
|
|
# A helper to create method objects on the fly
|
|
py3k = sys.version_info > (3,0)
|
|
if py3k:
|
|
def MakeMethod(func, inst, cls):
|
|
return types.MethodType(func, inst) # class not needed in py3k
|
|
else:
|
|
MakeMethod = types.MethodType # all args used in py2k.
|
|
|
|
# get the type objects for IDispatch and IUnknown
|
|
PyIDispatchType = pythoncom.TypeIIDs[pythoncom.IID_IDispatch]
|
|
PyIUnknownType = pythoncom.TypeIIDs[pythoncom.IID_IUnknown]
|
|
|
|
if py3k:
|
|
_GoodDispatchTypes=(str, IIDType)
|
|
else:
|
|
_GoodDispatchTypes=(str, IIDType, str)
|
|
_defaultDispatchItem=build.DispatchItem
|
|
|
|
def _GetGoodDispatch(IDispatch, clsctx = pythoncom.CLSCTX_SERVER):
|
|
# quick return for most common case
|
|
if isinstance(IDispatch, PyIDispatchType):
|
|
return IDispatch
|
|
if isinstance(IDispatch, _GoodDispatchTypes):
|
|
try:
|
|
IDispatch = pythoncom.connect(IDispatch)
|
|
except pythoncom.ole_error:
|
|
IDispatch = pythoncom.CoCreateInstance(IDispatch, None, clsctx, pythoncom.IID_IDispatch)
|
|
else:
|
|
# may already be a wrapped class.
|
|
IDispatch = getattr(IDispatch, "_oleobj_", IDispatch)
|
|
return IDispatch
|
|
|
|
def _GetGoodDispatchAndUserName(IDispatch, userName, clsctx):
|
|
# Get a dispatch object, and a 'user name' (ie, the name as
|
|
# displayed to the user in repr() etc.
|
|
if userName is None:
|
|
# Displayed name should be a plain string in py2k, and unicode in py3k
|
|
if isinstance(IDispatch, str):
|
|
userName = IDispatch
|
|
elif not py3k and isinstance(IDispatch, str):
|
|
# 2to3 converts the above 'unicode' to 'str', but this will never be executed in py3k
|
|
userName = IDispatch.encode("ascii", "replace")
|
|
## ??? else userName remains None ???
|
|
elif not py3k and isinstance(userName, str):
|
|
# 2to3 converts the above 'unicode' to 'str', but this will never be executed in py3k
|
|
# As above - always a plain string in py2k
|
|
userName = userName.encode("ascii", "replace")
|
|
else:
|
|
userName = str(userName)
|
|
return (_GetGoodDispatch(IDispatch, clsctx), userName)
|
|
|
|
def _GetDescInvokeType(entry, invoke_type):
|
|
# determine the wFlags argument passed as input to IDispatch::Invoke
|
|
if not entry or not entry.desc: return invoke_type
|
|
varkind = entry.desc[4] # from VARDESC struct returned by ITypeComp::Bind
|
|
if varkind == pythoncom.VAR_DISPATCH and invoke_type == pythoncom.INVOKE_PROPERTYGET:
|
|
return pythoncom.INVOKE_FUNC | invoke_type # DISPATCH_METHOD & DISPATCH_PROPERTYGET can be combined in IDispatch::Invoke
|
|
else:
|
|
return invoke_type
|
|
|
|
def Dispatch(IDispatch, userName = None, createClass = None, typeinfo = None, UnicodeToString=None, clsctx = pythoncom.CLSCTX_SERVER):
|
|
assert UnicodeToString is None, "this is deprecated and will go away"
|
|
IDispatch, userName = _GetGoodDispatchAndUserName(IDispatch,userName,clsctx)
|
|
if createClass is None:
|
|
createClass = CDispatch
|
|
lazydata = None
|
|
try:
|
|
if typeinfo is None:
|
|
typeinfo = IDispatch.GetTypeInfo()
|
|
if typeinfo is not None:
|
|
try:
|
|
#try for a typecomp
|
|
typecomp = typeinfo.GetTypeComp()
|
|
lazydata = typeinfo, typecomp
|
|
except pythoncom.com_error:
|
|
pass
|
|
except pythoncom.com_error:
|
|
typeinfo = None
|
|
olerepr = MakeOleRepr(IDispatch, typeinfo, lazydata)
|
|
return createClass(IDispatch, olerepr, userName, lazydata=lazydata)
|
|
|
|
def MakeOleRepr(IDispatch, typeinfo, typecomp):
|
|
olerepr = None
|
|
if typeinfo is not None:
|
|
try:
|
|
attr = typeinfo.GetTypeAttr()
|
|
# If the type info is a special DUAL interface, magically turn it into
|
|
# a DISPATCH typeinfo.
|
|
if attr[5] == pythoncom.TKIND_INTERFACE and attr[11] & pythoncom.TYPEFLAG_FDUAL:
|
|
# Get corresponding Disp interface;
|
|
# -1 is a special value which does this for us.
|
|
href = typeinfo.GetRefTypeOfImplType(-1);
|
|
typeinfo = typeinfo.GetRefTypeInfo(href)
|
|
attr = typeinfo.GetTypeAttr()
|
|
if typecomp is None:
|
|
olerepr = build.DispatchItem(typeinfo, attr, None, 0)
|
|
else:
|
|
olerepr = build.LazyDispatchItem(attr, None)
|
|
except pythoncom.ole_error:
|
|
pass
|
|
if olerepr is None: olerepr = build.DispatchItem()
|
|
return olerepr
|
|
|
|
def DumbDispatch(IDispatch, userName = None, createClass = None,UnicodeToString=None, clsctx=pythoncom.CLSCTX_SERVER):
|
|
"Dispatch with no type info"
|
|
assert UnicodeToString is None, "this is deprecated and will go away"
|
|
IDispatch, userName = _GetGoodDispatchAndUserName(IDispatch,userName,clsctx)
|
|
if createClass is None:
|
|
createClass = CDispatch
|
|
return createClass(IDispatch, build.DispatchItem(), userName)
|
|
|
|
class CDispatch:
|
|
def __init__(self, IDispatch, olerepr, userName=None, UnicodeToString=None, lazydata=None):
|
|
assert UnicodeToString is None, "this is deprecated and will go away"
|
|
if userName is None: userName = "<unknown>"
|
|
self.__dict__['_oleobj_'] = IDispatch
|
|
self.__dict__['_username_'] = userName
|
|
self.__dict__['_olerepr_'] = olerepr
|
|
self.__dict__['_mapCachedItems_'] = {}
|
|
self.__dict__['_builtMethods_'] = {}
|
|
self.__dict__['_enum_'] = None
|
|
self.__dict__['_unicode_to_string_'] = None
|
|
self.__dict__['_lazydata_'] = lazydata
|
|
|
|
def __call__(self, *args):
|
|
"Provide 'default dispatch' COM functionality - allow instance to be called"
|
|
if self._olerepr_.defaultDispatchName:
|
|
invkind, dispid = self._find_dispatch_type_(self._olerepr_.defaultDispatchName)
|
|
else:
|
|
invkind, dispid = pythoncom.DISPATCH_METHOD | pythoncom.DISPATCH_PROPERTYGET, pythoncom.DISPID_VALUE
|
|
if invkind is not None:
|
|
allArgs = (dispid,LCID,invkind,1) + args
|
|
return self._get_good_object_(self._oleobj_.Invoke(*allArgs),self._olerepr_.defaultDispatchName,None)
|
|
raise TypeError("This dispatch object does not define a default method")
|
|
|
|
def __bool__(self):
|
|
return True # ie "if object:" should always be "true" - without this, __len__ is tried.
|
|
# _Possibly_ want to defer to __len__ if available, but Im not sure this is
|
|
# desirable???
|
|
|
|
def __repr__(self):
|
|
return "<COMObject %s>" % (self._username_)
|
|
|
|
def __str__(self):
|
|
# __str__ is used when the user does "print object", so we gracefully
|
|
# fall back to the __repr__ if the object has no default method.
|
|
try:
|
|
return str(self.__call__())
|
|
except pythoncom.com_error as details:
|
|
if details.hresult not in ERRORS_BAD_CONTEXT:
|
|
raise
|
|
return self.__repr__()
|
|
|
|
# Delegate comparison to the oleobjs, as they know how to do identity.
|
|
def __eq__(self, other):
|
|
other = getattr(other, "_oleobj_", other)
|
|
return self._oleobj_ == other
|
|
|
|
def __ne__(self, other):
|
|
other = getattr(other, "_oleobj_", other)
|
|
return self._oleobj_ != other
|
|
|
|
def __int__(self):
|
|
return int(self.__call__())
|
|
|
|
def __len__(self):
|
|
invkind, dispid = self._find_dispatch_type_("Count")
|
|
if invkind:
|
|
return self._oleobj_.Invoke(dispid, LCID, invkind, 1)
|
|
raise TypeError("This dispatch object does not define a Count method")
|
|
|
|
def _NewEnum(self):
|
|
try:
|
|
invkind = pythoncom.DISPATCH_METHOD | pythoncom.DISPATCH_PROPERTYGET
|
|
enum = self._oleobj_.InvokeTypes(pythoncom.DISPID_NEWENUM,LCID,invkind,(13, 10),())
|
|
except pythoncom.com_error:
|
|
return None # no enumerator for this object.
|
|
from . import util
|
|
return util.WrapEnum(enum, None)
|
|
|
|
def __getitem__(self, index): # syver modified
|
|
# Improved __getitem__ courtesy Syver Enstad
|
|
# Must check _NewEnum before Item, to ensure b/w compat.
|
|
if isinstance(index, int):
|
|
if self.__dict__['_enum_'] is None:
|
|
self.__dict__['_enum_'] = self._NewEnum()
|
|
if self.__dict__['_enum_'] is not None:
|
|
return self._get_good_object_(self._enum_.__getitem__(index))
|
|
# See if we have an "Item" method/property we can use (goes hand in hand with Count() above!)
|
|
invkind, dispid = self._find_dispatch_type_("Item")
|
|
if invkind is not None:
|
|
return self._get_good_object_(self._oleobj_.Invoke(dispid, LCID, invkind, 1, index))
|
|
raise TypeError("This object does not support enumeration")
|
|
|
|
def __setitem__(self, index, *args):
|
|
# XXX - todo - We should support calling Item() here too!
|
|
# print "__setitem__ with", index, args
|
|
if self._olerepr_.defaultDispatchName:
|
|
invkind, dispid = self._find_dispatch_type_(self._olerepr_.defaultDispatchName)
|
|
else:
|
|
invkind, dispid = pythoncom.DISPATCH_PROPERTYPUT | pythoncom.DISPATCH_PROPERTYPUTREF, pythoncom.DISPID_VALUE
|
|
if invkind is not None:
|
|
allArgs = (dispid,LCID,invkind,0,index) + args
|
|
return self._get_good_object_(self._oleobj_.Invoke(*allArgs),self._olerepr_.defaultDispatchName,None)
|
|
raise TypeError("This dispatch object does not define a default method")
|
|
|
|
def _find_dispatch_type_(self, methodName):
|
|
if methodName in self._olerepr_.mapFuncs:
|
|
item = self._olerepr_.mapFuncs[methodName]
|
|
return item.desc[4], item.dispid
|
|
|
|
if methodName in self._olerepr_.propMapGet:
|
|
item = self._olerepr_.propMapGet[methodName]
|
|
return item.desc[4], item.dispid
|
|
|
|
try:
|
|
dispid = self._oleobj_.GetIDsOfNames(0,methodName)
|
|
except: ### what error?
|
|
return None, None
|
|
return pythoncom.DISPATCH_METHOD | pythoncom.DISPATCH_PROPERTYGET, dispid
|
|
|
|
def _ApplyTypes_(self, dispid, wFlags, retType, argTypes, user, resultCLSID, *args):
|
|
result = self._oleobj_.InvokeTypes(*(dispid, LCID, wFlags, retType, argTypes) + args)
|
|
return self._get_good_object_(result, user, resultCLSID)
|
|
|
|
def _wrap_dispatch_(self, ob, userName = None, returnCLSID = None, UnicodeToString=None):
|
|
# Given a dispatch object, wrap it in a class
|
|
assert UnicodeToString is None, "this is deprecated and will go away"
|
|
return Dispatch(ob, userName)
|
|
|
|
def _get_good_single_object_(self,ob,userName = None, ReturnCLSID=None):
|
|
if isinstance(ob, PyIDispatchType):
|
|
# make a new instance of (probably this) class.
|
|
return self._wrap_dispatch_(ob, userName, ReturnCLSID)
|
|
if isinstance(ob, PyIUnknownType):
|
|
try:
|
|
ob = ob.QueryInterface(pythoncom.IID_IDispatch)
|
|
except pythoncom.com_error:
|
|
# It is an IUnknown, but not an IDispatch, so just let it through.
|
|
return ob
|
|
return self._wrap_dispatch_(ob, userName, ReturnCLSID)
|
|
return ob
|
|
|
|
def _get_good_object_(self,ob,userName = None, ReturnCLSID=None):
|
|
"""Given an object (usually the retval from a method), make it a good object to return.
|
|
Basically checks if it is a COM object, and wraps it up.
|
|
Also handles the fact that a retval may be a tuple of retvals"""
|
|
if ob is None: # Quick exit!
|
|
return None
|
|
elif isinstance(ob, tuple):
|
|
return tuple(map(lambda o, s=self, oun=userName, rc=ReturnCLSID: s._get_good_single_object_(o, oun, rc), ob))
|
|
else:
|
|
return self._get_good_single_object_(ob)
|
|
|
|
def _make_method_(self, name):
|
|
"Make a method object - Assumes in olerepr funcmap"
|
|
methodName = build.MakePublicAttributeName(name) # translate keywords etc.
|
|
methodCodeList = self._olerepr_.MakeFuncMethod(self._olerepr_.mapFuncs[name], methodName,0)
|
|
methodCode = "\n".join(methodCodeList)
|
|
try:
|
|
# print "Method code for %s is:\n" % self._username_, methodCode
|
|
# self._print_details_()
|
|
codeObject = compile(methodCode, "<COMObject %s>" % self._username_,"exec")
|
|
# Exec the code object
|
|
tempNameSpace = {}
|
|
# "Dispatch" in the exec'd code is win32com.client.Dispatch, not ours.
|
|
globNameSpace = globals().copy()
|
|
globNameSpace["Dispatch"] = win32com.client.Dispatch
|
|
exec(codeObject, globNameSpace, tempNameSpace) # self.__dict__, self.__dict__
|
|
name = methodName
|
|
# Save the function in map.
|
|
fn = self._builtMethods_[name] = tempNameSpace[name]
|
|
newMeth = MakeMethod(fn, self, self.__class__)
|
|
return newMeth
|
|
except:
|
|
debug_print("Error building OLE definition for code ", methodCode)
|
|
traceback.print_exc()
|
|
return None
|
|
|
|
def _Release_(self):
|
|
"""Cleanup object - like a close - to force cleanup when you dont
|
|
want to rely on Python's reference counting."""
|
|
for childCont in self._mapCachedItems_.values():
|
|
childCont._Release_()
|
|
self._mapCachedItems_ = {}
|
|
if self._oleobj_:
|
|
self._oleobj_.Release()
|
|
self.__dict__['_oleobj_'] = None
|
|
if self._olerepr_:
|
|
self.__dict__['_olerepr_'] = None
|
|
self._enum_ = None
|
|
|
|
def _proc_(self, name, *args):
|
|
"""Call the named method as a procedure, rather than function.
|
|
Mainly used by Word.Basic, which whinges about such things."""
|
|
try:
|
|
item = self._olerepr_.mapFuncs[name]
|
|
dispId = item.dispid
|
|
return self._get_good_object_(self._oleobj_.Invoke(*(dispId, LCID, item.desc[4], 0) + (args) ))
|
|
except KeyError:
|
|
raise AttributeError(name)
|
|
|
|
def _print_details_(self):
|
|
"Debug routine - dumps what it knows about an object."
|
|
print("AxDispatch container",self._username_)
|
|
try:
|
|
print("Methods:")
|
|
for method in self._olerepr_.mapFuncs.keys():
|
|
print("\t", method)
|
|
print("Props:")
|
|
for prop, entry in self._olerepr_.propMap.items():
|
|
print("\t%s = 0x%x - %s" % (prop, entry.dispid, repr(entry)))
|
|
print("Get Props:")
|
|
for prop, entry in self._olerepr_.propMapGet.items():
|
|
print("\t%s = 0x%x - %s" % (prop, entry.dispid, repr(entry)))
|
|
print("Put Props:")
|
|
for prop, entry in self._olerepr_.propMapPut.items():
|
|
print("\t%s = 0x%x - %s" % (prop, entry.dispid, repr(entry)))
|
|
except:
|
|
traceback.print_exc()
|
|
|
|
def __LazyMap__(self, attr):
|
|
try:
|
|
if self._LazyAddAttr_(attr):
|
|
debug_attr_print("%s.__LazyMap__(%s) added something" % (self._username_,attr))
|
|
return 1
|
|
except AttributeError:
|
|
return 0
|
|
|
|
# Using the typecomp, lazily create a new attribute definition.
|
|
def _LazyAddAttr_(self,attr):
|
|
if self._lazydata_ is None: return 0
|
|
res = 0
|
|
typeinfo, typecomp = self._lazydata_
|
|
olerepr = self._olerepr_
|
|
# We need to explicitly check each invoke type individually - simply
|
|
# specifying '0' will bind to "any member", which may not be the one
|
|
# we are actually after (ie, we may be after prop_get, but returned
|
|
# the info for the prop_put.)
|
|
for i in ALL_INVOKE_TYPES:
|
|
try:
|
|
x,t = typecomp.Bind(attr,i)
|
|
# Support 'Get' and 'Set' properties - see
|
|
# bug 1587023
|
|
if x==0 and attr[:3] in ('Set', 'Get'):
|
|
x,t = typecomp.Bind(attr[3:], i)
|
|
if x==1: #it's a FUNCDESC
|
|
r = olerepr._AddFunc_(typeinfo,t,0)
|
|
elif x==2: #it's a VARDESC
|
|
r = olerepr._AddVar_(typeinfo,t,0)
|
|
else: #not found or TYPEDESC/IMPLICITAPP
|
|
r=None
|
|
if not r is None:
|
|
key, map = r[0],r[1]
|
|
item = map[key]
|
|
if map==olerepr.propMapPut:
|
|
olerepr._propMapPutCheck_(key,item)
|
|
elif map==olerepr.propMapGet:
|
|
olerepr._propMapGetCheck_(key,item)
|
|
res = 1
|
|
except:
|
|
pass
|
|
return res
|
|
|
|
def _FlagAsMethod(self, *methodNames):
|
|
"""Flag these attribute names as being methods.
|
|
Some objects do not correctly differentiate methods and
|
|
properties, leading to problems when calling these methods.
|
|
|
|
Specifically, trying to say: ob.SomeFunc()
|
|
may yield an exception "None object is not callable"
|
|
In this case, an attempt to fetch the *property*has worked
|
|
and returned None, rather than indicating it is really a method.
|
|
Calling: ob._FlagAsMethod("SomeFunc")
|
|
should then allow this to work.
|
|
"""
|
|
for name in methodNames:
|
|
details = build.MapEntry(self.__AttrToID__(name), (name,))
|
|
self._olerepr_.mapFuncs[name] = details
|
|
|
|
def __AttrToID__(self,attr):
|
|
debug_attr_print("Calling GetIDsOfNames for property %s in Dispatch container %s" % (attr, self._username_))
|
|
return self._oleobj_.GetIDsOfNames(0,attr)
|
|
|
|
def __getattr__(self, attr):
|
|
if attr=='__iter__':
|
|
# We can't handle this as a normal method, as if the attribute
|
|
# exists, then it must return an iterable object.
|
|
try:
|
|
invkind = pythoncom.DISPATCH_METHOD | pythoncom.DISPATCH_PROPERTYGET
|
|
enum = self._oleobj_.InvokeTypes(pythoncom.DISPID_NEWENUM,LCID,invkind,(13, 10),())
|
|
except pythoncom.com_error:
|
|
raise AttributeError("This object can not function as an iterator")
|
|
# We must return a callable object.
|
|
class Factory:
|
|
def __init__(self, ob):
|
|
self.ob = ob
|
|
def __call__(self):
|
|
import win32com.client.util
|
|
return win32com.client.util.Iterator(self.ob)
|
|
return Factory(enum)
|
|
|
|
if attr.startswith('_') and attr.endswith('_'): # Fast-track.
|
|
raise AttributeError(attr)
|
|
# If a known method, create new instance and return.
|
|
try:
|
|
return MakeMethod(self._builtMethods_[attr], self, self.__class__)
|
|
except KeyError:
|
|
pass
|
|
# XXX - Note that we current are case sensitive in the method.
|
|
#debug_attr_print("GetAttr called for %s on DispatchContainer %s" % (attr,self._username_))
|
|
# First check if it is in the method map. Note that an actual method
|
|
# must not yet exist, (otherwise we would not be here). This
|
|
# means we create the actual method object - which also means
|
|
# this code will never be asked for that method name again.
|
|
if attr in self._olerepr_.mapFuncs:
|
|
return self._make_method_(attr)
|
|
|
|
# Delegate to property maps/cached items
|
|
retEntry = None
|
|
if self._olerepr_ and self._oleobj_:
|
|
# first check general property map, then specific "put" map.
|
|
retEntry = self._olerepr_.propMap.get(attr)
|
|
if retEntry is None:
|
|
retEntry = self._olerepr_.propMapGet.get(attr)
|
|
# Not found so far - See what COM says.
|
|
if retEntry is None:
|
|
try:
|
|
if self.__LazyMap__(attr):
|
|
if attr in self._olerepr_.mapFuncs: return self._make_method_(attr)
|
|
retEntry = self._olerepr_.propMap.get(attr)
|
|
if retEntry is None:
|
|
retEntry = self._olerepr_.propMapGet.get(attr)
|
|
if retEntry is None:
|
|
retEntry = build.MapEntry(self.__AttrToID__(attr), (attr,))
|
|
except pythoncom.ole_error:
|
|
pass # No prop by that name - retEntry remains None.
|
|
|
|
if not retEntry is None: # see if in my cache
|
|
try:
|
|
ret = self._mapCachedItems_[retEntry.dispid]
|
|
debug_attr_print ("Cached items has attribute!", ret)
|
|
return ret
|
|
except (KeyError, AttributeError):
|
|
debug_attr_print("Attribute %s not in cache" % attr)
|
|
|
|
# If we are still here, and have a retEntry, get the OLE item
|
|
if not retEntry is None:
|
|
invoke_type = _GetDescInvokeType(retEntry, pythoncom.INVOKE_PROPERTYGET)
|
|
debug_attr_print("Getting property Id 0x%x from OLE object" % retEntry.dispid)
|
|
try:
|
|
ret = self._oleobj_.Invoke(retEntry.dispid,0,invoke_type,1)
|
|
except pythoncom.com_error as details:
|
|
if details.hresult in ERRORS_BAD_CONTEXT:
|
|
# May be a method.
|
|
self._olerepr_.mapFuncs[attr] = retEntry
|
|
return self._make_method_(attr)
|
|
raise
|
|
debug_attr_print("OLE returned ", ret)
|
|
return self._get_good_object_(ret)
|
|
|
|
# no where else to look.
|
|
raise AttributeError("%s.%s" % (self._username_, attr))
|
|
|
|
def __setattr__(self, attr, value):
|
|
if attr in self.__dict__: # Fast-track - if already in our dict, just make the assignment.
|
|
# XXX - should maybe check method map - if someone assigns to a method,
|
|
# it could mean something special (not sure what, tho!)
|
|
self.__dict__[attr] = value
|
|
return
|
|
# Allow property assignment.
|
|
debug_attr_print("SetAttr called for %s.%s=%s on DispatchContainer" % (self._username_, attr, repr(value)))
|
|
|
|
if self._olerepr_:
|
|
# Check the "general" property map.
|
|
if attr in self._olerepr_.propMap:
|
|
entry = self._olerepr_.propMap[attr]
|
|
invoke_type = _GetDescInvokeType(entry, pythoncom.INVOKE_PROPERTYPUT)
|
|
self._oleobj_.Invoke(entry.dispid, 0, invoke_type, 0, value)
|
|
return
|
|
# Check the specific "put" map.
|
|
if attr in self._olerepr_.propMapPut:
|
|
entry = self._olerepr_.propMapPut[attr]
|
|
invoke_type = _GetDescInvokeType(entry, pythoncom.INVOKE_PROPERTYPUT)
|
|
self._oleobj_.Invoke(entry.dispid, 0, invoke_type, 0, value)
|
|
return
|
|
|
|
# Try the OLE Object
|
|
if self._oleobj_:
|
|
if self.__LazyMap__(attr):
|
|
# Check the "general" property map.
|
|
if attr in self._olerepr_.propMap:
|
|
entry = self._olerepr_.propMap[attr]
|
|
invoke_type = _GetDescInvokeType(entry, pythoncom.INVOKE_PROPERTYPUT)
|
|
self._oleobj_.Invoke(entry.dispid, 0, invoke_type, 0, value)
|
|
return
|
|
# Check the specific "put" map.
|
|
if attr in self._olerepr_.propMapPut:
|
|
entry = self._olerepr_.propMapPut[attr]
|
|
invoke_type = _GetDescInvokeType(entry, pythoncom.INVOKE_PROPERTYPUT)
|
|
self._oleobj_.Invoke(entry.dispid, 0, invoke_type, 0, value)
|
|
return
|
|
try:
|
|
entry = build.MapEntry(self.__AttrToID__(attr),(attr,))
|
|
except pythoncom.com_error:
|
|
# No attribute of that name
|
|
entry = None
|
|
if entry is not None:
|
|
try:
|
|
invoke_type = _GetDescInvokeType(entry, pythoncom.INVOKE_PROPERTYPUT)
|
|
self._oleobj_.Invoke(entry.dispid, 0, invoke_type, 0, value)
|
|
self._olerepr_.propMap[attr] = entry
|
|
debug_attr_print("__setattr__ property %s (id=0x%x) in Dispatch container %s" % (attr, entry.dispid, self._username_))
|
|
return
|
|
except pythoncom.com_error:
|
|
pass
|
|
raise AttributeError("Property '%s.%s' can not be set." % (self._username_, attr))
|