import types import sys import os # comtypes version numbers follow semver (http://semver.org/) and PEP 440 __version__ = "1.1.7" import logging class NullHandler(logging.Handler): """A Handler that does nothing.""" def emit(self, record): pass logger = logging.getLogger(__name__) # Add a NULL handler to the comtypes logger. This prevents getting a # message like this: # No handlers could be found for logger "comtypes" # when logging is not configured and logger.error() is called. logger.addHandler(NullHandler()) from ctypes import * from _ctypes import COMError from comtypes import patcher def _check_version(actual): from comtypes.tools.codegenerator import version as required if actual != required: raise ImportError("Wrong version") if not hasattr(sys, "frozen"): g = sys._getframe(1).f_globals mod_path = g.get("__file__") tlb_path = g.get("typelib_path") try: mod_mtime = os.stat(mod_path).st_mtime tlib_mtime = os.stat(tlb_path).st_mtime except (OSError, TypeError): return if mod_mtime < tlib_mtime: raise ImportError("Typelib newer than module") try: COMError() except TypeError: pass else: # Python 2.5 and 2.5.1 have a bug in the COMError implementation: # The type has no __init__ method, and no hresult, text, and # details instance vars. Work around this bug by monkeypatching # COMError. def monkeypatch_COMError(): def __init__(self, hresult, text, details): self.hresult = hresult self.text = text self.details = details super(COMError, self).__init__(hresult, text, details) COMError.__init__ = __init__ monkeypatch_COMError() del monkeypatch_COMError if sys.version_info >= (3, 0): pythonapi.PyInstanceMethod_New.argtypes = [py_object] pythonapi.PyInstanceMethod_New.restype = py_object PyInstanceMethod_Type = type(pythonapi.PyInstanceMethod_New(id)) def instancemethod(func, inst, cls): mth = PyInstanceMethod_Type(func) if inst is None: return mth return mth.__get__(inst) else: def instancemethod(func, inst, cls): return types.MethodType(func, inst, cls) class ReturnHRESULT(Exception): """ReturnHRESULT(hresult, text) Return a hresult code from a COM method implementation without logging an error. """ ##class IDLWarning(UserWarning): ## "Warn about questionable type information" from comtypes.GUID import GUID _GUID = GUID IID = GUID DWORD = c_ulong wireHWND = c_ulong ################################################################ # About COM apartments: # http://blogs.msdn.com/larryosterman/archive/2004/04/28/122240.aspx ################################################################ ################################################################ # constants for object creation CLSCTX_INPROC_SERVER = 1 CLSCTX_INPROC_HANDLER = 2 CLSCTX_LOCAL_SERVER = 4 CLSCTX_INPROC = 3 CLSCTX_SERVER = 5 CLSCTX_ALL = 7 CLSCTX_INPROC_SERVER16 = 8 CLSCTX_REMOTE_SERVER = 16 CLSCTX_INPROC_HANDLER16 = 32 CLSCTX_RESERVED1 = 64 CLSCTX_RESERVED2 = 128 CLSCTX_RESERVED3 = 256 CLSCTX_RESERVED4 = 512 CLSCTX_NO_CODE_DOWNLOAD = 1024 CLSCTX_RESERVED5 = 2048 CLSCTX_NO_CUSTOM_MARSHAL = 4096 CLSCTX_ENABLE_CODE_DOWNLOAD = 8192 CLSCTX_NO_FAILURE_LOG = 16384 CLSCTX_DISABLE_AAA = 32768 CLSCTX_ENABLE_AAA = 65536 CLSCTX_FROM_DEFAULT_CONTEXT = 131072 tagCLSCTX = c_int # enum CLSCTX = tagCLSCTX # Constants for security setups SEC_WINNT_AUTH_IDENTITY_UNICODE = 0x2 RPC_C_AUTHN_WINNT = 10 RPC_C_AUTHZ_NONE = 0 RPC_C_AUTHN_LEVEL_CONNECT = 2 RPC_C_IMP_LEVEL_IMPERSONATE = 3 EOAC_NONE = 0 ################################################################ # Initialization and shutdown _ole32 = oledll.ole32 _ole32_nohresult = windll.ole32 # use this for functions that don't return a HRESULT COINIT_MULTITHREADED = 0x0 COINIT_APARTMENTTHREADED = 0x2 COINIT_DISABLE_OLE1DDE = 0x4 COINIT_SPEED_OVER_MEMORY = 0x8 def CoInitialize(): return CoInitializeEx(COINIT_APARTMENTTHREADED) def CoInitializeEx(flags=None): if flags is None: if os.name == "ce": flags = getattr(sys, "coinit_flags", COINIT_MULTITHREADED) else: flags = getattr(sys, "coinit_flags", COINIT_APARTMENTTHREADED) logger.debug("CoInitializeEx(None, %s)", flags) _ole32.CoInitializeEx(None, flags) # COM is initialized automatically for the thread that imports this # module for the first time. sys.coinit_flags is passed as parameter # to CoInitializeEx, if defined, otherwise COINIT_APARTMENTTHREADED # (COINIT_MULTITHREADED on Windows CE) is used. # # A shutdown function is registered with atexit, so that # CoUninitialize is called when Python is shut down. CoInitializeEx() # We need to have CoUninitialize for multithreaded model where we have # to initialize and uninitialize COM for every new thread (except main) # in which we are using COM def CoUninitialize(): logger.debug("CoUninitialize()") _ole32_nohresult.CoUninitialize() def shutdown(func=_ole32_nohresult.CoUninitialize, _debug=logger.debug, _exc_clear=getattr(sys, "exc_clear", lambda: None)): # Make sure no COM pointers stay in exception frames. _exc_clear() # Sometimes, CoUnititialize, running at Python shutdown, # raises an exception. We suppress this when __debug__ is # False. _debug("Calling CoUnititialize()") if __debug__: func() else: try: func() except WindowsError: pass # Set the flag which means that calling obj.Release() is no longer # needed. if _cominterface_meta is not None: _cominterface_meta._com_shutting_down = True _debug("CoUnititialize() done.") import atexit atexit.register(shutdown) del shutdown ################################################################ # global registries. # allows to find interface classes by guid strings (iid) com_interface_registry = {} # allows to find coclasses by guid strings (clsid) com_coclass_registry = {} def _is_object(obj): """This function determines if the argument is a COM object. It is used in several places to determine whether propputref or propput setters have to be used.""" from comtypes.automation import VARIANT # A COM pointer is an 'Object' if isinstance(obj, POINTER(IUnknown)): return True # A COM pointer in a VARIANT is an 'Object', too elif isinstance(obj, VARIANT) and isinstance(obj.value, POINTER(IUnknown)): return True # It may be a dynamic dispatch object. return hasattr(obj, "_comobj") ################################################################ # The metaclasses... class _cominterface_meta(type): """Metaclass for COM interfaces. Automatically creates high level methods from COMMETHOD lists. """ # This flag is set to True by the atexit handler which calls # CoUnititialize. _com_shutting_down = False # Creates also a POINTER type for the newly created class. def __new__(self, name, bases, namespace): methods = namespace.pop("_methods_", None) dispmethods = namespace.pop("_disp_methods_", None) cls = type.__new__(self, name, bases, namespace) if methods is not None: cls._methods_ = methods if dispmethods is not None: cls._disp_methods_ = dispmethods # If we sublass a COM interface, for example: # # class IDispatch(IUnknown): # .... # # then we need to make sure that POINTER(IDispatch) is a # subclass of POINTER(IUnknown) because of the way ctypes # typechecks work. if bases == (object,): _ptr_bases = (cls, _compointer_base) else: _ptr_bases = (cls, POINTER(bases[0])) # The interface 'cls' is used as a mixin. p = type(_compointer_base)("POINTER(%s)" % cls.__name__, _ptr_bases, {"__com_interface__": cls, "_needs_com_addref_": None}) from ctypes import _pointer_type_cache _pointer_type_cache[cls] = p if cls._case_insensitive_: @patcher.Patch(p) class CaseInsensitive(object): # case insensitive attributes for COM methods and properties def __getattr__(self, name): """Implement case insensitive access to methods and properties""" try: fixed_name = self.__map_case__[name.lower()] except KeyError: raise AttributeError(name) if fixed_name != name: # prevent unbounded recursion return getattr(self, fixed_name) raise AttributeError(name) # __setattr__ is pretty heavy-weight, because it is called for # EVERY attribute assignment. Settings a non-com attribute # through this function takes 8.6 usec, while without this # function it takes 0.7 sec - 12 times slower. # # How much faster would this be if implemented in C? def __setattr__(self, name, value): """Implement case insensitive access to methods and properties""" object.__setattr__(self, self.__map_case__.get(name.lower(), name), value) @patcher.Patch(POINTER(p)) class ReferenceFix(object): def __setitem__(self, index, value): # We override the __setitem__ method of the # POINTER(POINTER(interface)) type, so that the COM # reference count is managed correctly. # # This is so that we can implement COM methods that have to # return COM pointers more easily and consistent. Instead of # using CopyComPointer in the method implementation, we can # simply do: # # def GetTypeInfo(self, this, ..., pptinfo): # if not pptinfo: return E_POINTER # pptinfo[0] = a_com_interface_pointer # return S_OK if index != 0: # CopyComPointer, which is in _ctypes, does only # handle an index of 0. This code does what # CopyComPointer should do if index != 0. if bool(value): value.AddRef() super(POINTER(p), self).__setitem__(index, value) return from _ctypes import CopyComPointer CopyComPointer(value, self) return cls def __setattr__(self, name, value): if name == "_methods_": # XXX I'm no longer sure why the code generator generates # "_methods_ = []" in the interface definition, and later # overrides this by "Interface._methods_ = [...] ## assert self.__dict__.get("_methods_", None) is None self._make_methods(value) self._make_specials() elif name == "_disp_methods_": assert self.__dict__.get("_disp_methods_", None) is None self._make_dispmethods(value) self._make_specials() type.__setattr__(self, name, value) def _make_specials(self): # This call installs methods that forward the Python protocols # to COM protocols. def has_name(name): # Determine whether a property or method named 'name' # exists if self._case_insensitive_: return name.lower() in self.__map_case__ return hasattr(self, name) # XXX These special methods should be generated by the code generator. if has_name("Count"): @patcher.Patch(self) class _(object): def __len__(self): "Return the the 'self.Count' property." return self.Count if has_name("Item"): @patcher.Patch(self) class _(object): # 'Item' is the 'default' value. Make it available by # calling the instance (Not sure this makes sense, but # win32com does this also). def __call__(self, *args, **kw): "Return 'self.Item(*args, **kw)'" return self.Item(*args, **kw) # does this make sense? It seems that all standard typelibs I've # seen so far that support .Item also support ._NewEnum @patcher.no_replace def __getitem__(self, index): "Return 'self.Item(index)'" # Handle tuples and all-slice if isinstance(index, tuple): args = index elif index == _all_slice: args = () else: args = (index,) try: result = self.Item(*args) except COMError as err: (hresult, text, details) = err.args if hresult == -2147352565: # DISP_E_BADINDEX raise IndexError("invalid index") else: raise # Note that result may be NULL COM pointer. There is no way # to interpret this properly, so it is returned as-is. # Hm, should we call __ctypes_from_outparam__ on the # result? return result @patcher.no_replace def __setitem__(self, index, value): "Attempt 'self.Item[index] = value'" try: self.Item[index] = value except COMError as err: (hresult, text, details) = err.args if hresult == -2147352565: # DISP_E_BADINDEX raise IndexError("invalid index") else: raise except TypeError: msg = "%r object does not support item assignment" raise TypeError(msg % type(self)) if has_name("_NewEnum"): @patcher.Patch(self) class _(object): def __iter__(self): "Return an iterator over the _NewEnum collection." # This method returns a pointer to _some_ _NewEnum interface. # It relies on the fact that the code generator creates next() # methods for them automatically. # # Better would maybe to return an object that # implements the Python iterator protocol, and # forwards the calls to the COM interface. enum = self._NewEnum if isinstance(enum, types.MethodType): # _NewEnum should be a propget property, with dispid -4. # # Sometimes, however, it is a method. enum = enum() if hasattr(enum, "Next"): return enum # _NewEnum returns an IUnknown pointer, QueryInterface() it to # IEnumVARIANT from comtypes.automation import IEnumVARIANT return enum.QueryInterface(IEnumVARIANT) def _make_case_insensitive(self): # The __map_case__ dictionary maps lower case names to the # names in the original spelling to enable case insensitive # method and attribute access. try: self.__dict__["__map_case__"] except KeyError: d = {} d.update(getattr(self, "__map_case__", {})) self.__map_case__ = d def _make_dispmethods(self, methods): if self._case_insensitive_: self._make_case_insensitive() # create dispinterface methods and properties on the interface 'self' properties = {} for m in methods: what, name, idlflags, restype, argspec = m # is it a property set or property get? is_prop = False # argspec is a sequence of tuples, each tuple is: # ([paramflags], type, name) try: memid = [x for x in idlflags if isinstance(x, int)][0] except IndexError: raise TypeError("no dispid found in idlflags") if what == "DISPPROPERTY": # DISPPROPERTY assert not argspec # XXX does not yet work for properties with parameters accessor = self._disp_property(memid, idlflags) is_prop = True setattr(self, name, accessor) elif what == "DISPMETHOD": # DISPMETHOD # argspec is a tuple of (idlflags, type, name[, # defval]) items. method = self._disp_method(memid, name, idlflags, restype, argspec) ## not in 2.3 method.__name__ = name if 'propget' in idlflags: nargs = len(argspec) properties.setdefault((name, nargs), [None, None, None])[0] = method is_prop = True elif 'propput' in idlflags: nargs = len(argspec)-1 properties.setdefault((name, nargs), [None, None, None])[1] = method is_prop = True elif 'propputref' in idlflags: nargs = len(argspec)-1 properties.setdefault((name, nargs), [None, None, None])[2] = method is_prop = True else: setattr(self, name, method) # COM is case insensitive. # # For a method, this is the real name. For a property, # this is the name WITHOUT the _set_ or _get_ prefix. if self._case_insensitive_: self.__map_case__[name.lower()] = name if is_prop: self.__map_case__[name[5:].lower()] = name[5:] for (name, nargs), methods in list(properties.items()): # methods contains [propget or None, propput or None, propputref or None] if methods[1] is not None and methods[2] is not None: # both propput and propputref. # # Create a setter method that examines the argument type # and calls 'propputref' if it is an Object (in the VB # sense), or call 'propput' otherwise. propput = methods[1] propputref = methods[2] def put_or_putref(self, *args): if _is_object(args[-1]): return propputref(self, *args) else: return propput(self, *args) methods[1] = put_or_putref del methods[2] elif methods[2] is not None: # use propputref del methods[1] else: # use propput (if any) del methods[2] if nargs: setattr(self, name, named_property("%s.%s" % (self.__name__, name), *methods)) else: assert len(methods) <= 2 setattr(self, name, property(*methods)) # COM is case insensitive if self._case_insensitive_: self.__map_case__[name.lower()] = name # Some ideas, (not only) related to disp_methods: # # Should the functions/methods we create have restype and/or # argtypes attributes? def _disp_method(self, memid, name, idlflags, restype, argspec): if 'propget' in idlflags: def getfunc(obj, *args, **kw): return self.Invoke(obj, memid, _invkind=2, *args, **kw) # DISPATCH_PROPERTYGET return getfunc elif 'propput' in idlflags: def putfunc(obj, *args, **kw): return self.Invoke(obj, memid, _invkind=4, *args, **kw) # DISPATCH_PROPERTYPUT return putfunc elif 'propputref' in idlflags: def putfunc(obj, *args, **kw): return self.Invoke(obj, memid, _invkind=8, *args, **kw) # DISPATCH_PROPERTYPUTREF return putfunc # a first attempt to make use of the restype. Still, support # for named arguments and default argument values should be # added. if hasattr(restype, "__com_interface__"): interface = restype.__com_interface__ def func(s, *args, **kw): result = self.Invoke(s, memid, _invkind=1, *args, **kw) if result is None: return return result.QueryInterface(interface) else: def func(obj, *args, **kw): return self.Invoke(obj, memid, _invkind=1, *args, **kw) # DISPATCH_METHOD return func def _disp_property(self, memid, idlflags): # XXX doc string missing in property def _get(obj): return obj.Invoke(memid, _invkind=2) # DISPATCH_PROPERTYGET if "readonly" in idlflags: return property(_get) def _set(obj, value): # Detect whether to use DISPATCH_PROPERTYPUT or # DISPATCH_PROPERTYPUTREF invkind = 8 if _is_object(value) else 4 return obj.Invoke(memid, value, _invkind=invkind) return property(_get, _set) def __get_baseinterface_methodcount(self): "Return the number of com methods in the base interfaces" try: result = 0 for itf in self.mro()[1:-1]: result += len(itf.__dict__["_methods_"]) return result except KeyError as err: (name,) = err.args if name == "_methods_": raise TypeError("baseinterface '%s' has no _methods_" % itf.__name__) raise def _fix_inout_args(self, func, argtypes, paramflags): # This function provides a workaround for a bug in ctypes. # [in, out] parameters must be converted with the argtype's # .from_param() method BEFORE they are passed to the _ctypes # build_callargs() function in Modules/_ctypes/_ctypes.c. # # For details see below. # # TODO: The workaround should be disabled when a ctypes # version is used where the bug is fixed. SIMPLETYPE = type(c_int) BYREFTYPE = type(byref(c_int())) def call_with_inout(self_, *args, **kw): args = list(args) # Indexed by order in the output outargs = {} outnum = 0 for i, info in enumerate(paramflags): direction = info[0] if direction & 3 == 3: # This is an [in, out] parameter. # # Determine name and required type of the parameter. name = info[1] # [in, out] parameters are passed as pointers, # this is the pointed-to type: atyp = argtypes[i]._type_ # Get the actual parameter, either as positional or # keyword arg. try: try: v = args[i] except IndexError: v = kw[name] except KeyError: # no parameter was passed, make an empty one # of the required type v = atyp() else: # parameter was passed, call .from_param() to # convert it to a ctypes type. if getattr(v, "_type_", None) is atyp: # Array of or pointer to type 'atyp' was # passed, pointer to 'atyp' expected. pass elif type(atyp) is SIMPLETYPE: # The from_param method of simple types # (c_int, c_double, ...) returns a byref() # object which we cannot use since later # it will be wrapped in a pointer. Simply # call the constructor with the argument # in that case. v = atyp(v) else: v = atyp.from_param(v) assert not isinstance(v, BYREFTYPE) outargs[outnum] = v outnum += 1 if len(args) > i: args[i] = v else: kw[name] = v elif direction & 2 == 2: outnum += 1 rescode = func(self_, *args, **kw) # If there is only a single output value, then do not expect it to # be iterable. if outnum == 1: # rescode is not iterable if len(outargs) == 1: rescode = rescode.__ctypes_from_outparam__() return rescode rescode = list(rescode) for outnum, o in list(outargs.items()): rescode[outnum] = o.__ctypes_from_outparam__() return rescode return call_with_inout def _make_methods(self, methods): if self._case_insensitive_: self._make_case_insensitive() # we insist on an _iid_ in THIS class! try: iid = self.__dict__["_iid_"] except KeyError: raise AttributeError("this class must define an _iid_") else: iid = str(iid) ## if iid in com_interface_registry: ## # Warn when multiple interfaces are defined with identical iids. ## # This would also trigger if we reload() a module that contains ## # interface types, so suppress the warning in this case. ## other = com_interface_registry[iid] ## if self.__name__ != other.__name__ or self.__module__ != other.__module__: ## text = "Multiple interface defn: %s, %s" % (self, other) ## warnings.warn(text, UserWarning) com_interface_registry[iid] = self del iid vtbl_offset = self.__get_baseinterface_methodcount() properties = {} # create private low level, and public high level methods for i, item in enumerate(methods): restype, name, argtypes, paramflags, idlflags, doc = item # the function prototype prototype = WINFUNCTYPE(restype, *argtypes) # a low level unbound method calling the com method. # attach it with a private name (__com_AddRef, for example), # so that custom method implementations can call it. # If the method returns a HRESULT, we pass the interface iid, # so that we can request error info for the interface. if restype == HRESULT: ## print "%s.%s" % (self.__name__, name) raw_func = prototype(i + vtbl_offset, name, None, self._iid_) func = prototype(i + vtbl_offset, name, paramflags, self._iid_) else: raw_func = prototype(i + vtbl_offset, name, None, None) func = prototype(i + vtbl_offset, name, paramflags, None) setattr(self, "_%s__com_%s" % (self.__name__, name), instancemethod(raw_func, None, self)) if paramflags: # see comment in the _fix_inout_args method dirflags = [(p[0]&3) for p in paramflags] if 3 in dirflags: ## fullname = "%s::%s" % (self.__name__, name) ## print "FIX %s" % fullname func = self._fix_inout_args(func, argtypes, paramflags) # 'func' is a high level function calling the COM method func.__doc__ = doc try: func.__name__ = name # for pyhelp except TypeError: # In Python 2.3, __name__ is a readonly attribute pass # make it an unbound method. Remember, 'self' is a type here. mth = instancemethod(func, None, self) # is it a property set or property get? is_prop = False # XXX Hm. What, when paramflags is None? # Or does have '0' values? # Seems we loose then, at least for properties... # The following code assumes that the docstrings for # propget and propput are identical. if "propget" in idlflags: assert name.startswith("_get_") nargs = len([flags for flags in paramflags if flags[0] & 7 in (0, 1)]) # XXX or should we do this? # nargs = len([flags for flags in paramflags # if (flags[0] & 1) or (flags[0] == 0)]) propname = name[len("_get_"):] properties.setdefault((propname, doc, nargs), [None, None, None])[0] = func is_prop = True elif "propput" in idlflags: assert name.startswith("_set_") nargs = len([flags for flags in paramflags if flags[0] & 7 in (0, 1)]) - 1 propname = name[len("_set_"):] properties.setdefault((propname, doc, nargs), [None, None, None])[1] = func is_prop = True elif "propputref" in idlflags: assert name.startswith("_setref_") nargs = len([flags for flags in paramflags if flags[0] & 7 in (0, 1)]) - 1 propname = name[len("_setref_"):] properties.setdefault((propname, doc, nargs), [None, None, None])[2] = func is_prop = True # We install the method in the class, except when it's a # property accessor. And we make sure we don't overwrite # a property that's already present in the class. if not is_prop: if hasattr(self, name): setattr(self, "_" + name, mth) else: setattr(self, name, mth) # COM is case insensitive. # # For a method, this is the real name. For a property, # this is the name WITHOUT the _set_ or _get_ prefix. if self._case_insensitive_: self.__map_case__[name.lower()] = name if is_prop: self.__map_case__[name[5:].lower()] = name[5:] # create public properties / attribute accessors for (name, doc, nargs), methods in list(properties.items()): # methods contains [propget or None, propput or None, propputref or None] if methods[1] is not None and methods[2] is not None: # both propput and propputref. # # Create a setter method that examines the argument type # and calls 'propputref' if it is an Object (in the VB # sense), or call 'propput' otherwise. propput = methods[1] propputref = methods[2] def put_or_putref(self, *args): if _is_object(args[-1]): return propputref(self, *args) else: return propput(self, *args) methods[1] = put_or_putref del methods[2] elif methods[2] is not None: # use propputref del methods[1] else: # use propput (if any) del methods[2] if nargs == 0: prop = property(*methods + [None, doc]) else: # Hm, must be a descriptor where the __get__ method # returns a bound object having __getitem__ and # __setitem__ methods. prop = named_property("%s.%s" % (self.__name__, name), *methods + [doc]) # Again, we should not overwrite class attributes that are # already present. if hasattr(self, name): setattr(self, "_" + name, prop) else: setattr(self, name, prop) # COM is case insensitive if self._case_insensitive_: self.__map_case__[name.lower()] = name ################################################################ # helper classes for COM propget / propput # Should they be implemented in C for speed? _all_slice = slice(None, None, None) class bound_named_property(object): def __init__(self, name, getter, setter, im_inst): self.name = name self.im_inst = im_inst self.getter = getter self.setter = setter def __getitem__(self, index): if self.getter is None: raise TypeError("unsubscriptable object") if isinstance(index, tuple): return self.getter(self.im_inst, *index) elif index == _all_slice: return self.getter(self.im_inst) else: return self.getter(self.im_inst, index) def __call__(self, *args): if self.getter is None: raise TypeError("object is not callable") return self.getter(self.im_inst, *args) def __setitem__(self, index, value): if self.setter is None: raise TypeError("object does not support item assignment") if isinstance(index, tuple): self.setter(self.im_inst, *(index + (value,))) elif index == _all_slice: self.setter(self.im_inst, value) else: self.setter(self.im_inst, index, value) def __repr__(self): return "" % (self.name, id(self)) def __iter__(self): """ Explicitly disallow iteration. """ msg = "%r is not iterable" % self.name raise TypeError(msg) class named_property(object): def __init__(self, name, fget=None, fset=None, doc=None): self.name = name self.getter = fget self.setter = fset self.__doc__ = doc def __get__(self, im_inst, im_class=None): if im_inst is None: return self return bound_named_property(self.name, self.getter, self.setter, im_inst) # Make this a data descriptor def __set__(self, obj): raise AttributeError("Unsettable attribute") def __repr__(self): return "" % (self.name, id(self)) ################################################################ class _compointer_meta(type(c_void_p), _cominterface_meta): "metaclass for COM interface pointer classes" # no functionality, but needed to avoid a metaclass conflict class _compointer_base(c_void_p, metaclass=_compointer_meta): "base class for COM interface pointer classes" def __del__(self, _debug=logger.debug): "Release the COM refcount we own." if self: # comtypes calls CoUnititialize() when the atexit handlers # runs. CoUninitialize() cleans up the COM objects that # are still alive. Python COM pointers may still be # present but we can no longer call Release() on them - # this may give a protection fault. So we need the # _com_shutting_down flag. # if not type(self)._com_shutting_down: _debug("Release %s", self) self.Release() def __cmp__(self, other): """Compare pointers to COM interfaces.""" # COM identity rule # # XXX To compare COM interface pointers, should we # automatically QueryInterface for IUnknown on both items, and # compare the pointer values? if not isinstance(other, _compointer_base): return 1 # get the value property of the c_void_p baseclass, this is the pointer value return cmp(super(_compointer_base, self).value, super(_compointer_base, other).value) def __eq__(self, other): if not isinstance(other, _compointer_base): return False # get the value property of the c_void_p baseclass, this is the pointer value return super(_compointer_base, self).value == super(_compointer_base, other).value def __hash__(self): """Return the hash value of the pointer.""" # hash the pointer values return hash(super(_compointer_base, self).value) # redefine the .value property; return the object itself. def __get_value(self): return self value = property(__get_value, doc="""Return self.""") def __repr__(self): ptr = super(_compointer_base, self).value return "<%s ptr=0x%x at %x>" % (self.__class__.__name__, ptr or 0, id(self)) # This fixes the problem when there are multiple python interface types # wrapping the same COM interface. This could happen because some interfaces # are contained in multiple typelibs. # # It also allows to pass a CoClass instance to an api # expecting a COM interface. def from_param(klass, value): """Convert 'value' into a COM pointer to the interface. This method accepts a COM pointer, or a CoClass instance which is QueryInterface()d.""" if value is None: return None # CLF: 2013-01-18 # A default value of 0, meaning null, can pass through to here. if value == 0: return None if isinstance(value, klass): return value # multiple python interface types for the same COM interface. # Do we need more checks here? if klass._iid_ == getattr(value, "_iid_", None): return value # Accept an CoClass instance which exposes the interface required. try: table = value._com_pointers_ except AttributeError: pass else: try: # a kind of QueryInterface return table[klass._iid_] except KeyError: raise TypeError("Interface %s not supported" % klass._iid_) return value.QueryInterface(klass.__com_interface__) from_param = classmethod(from_param) ################################################################ from ctypes import _SimpleCData class BSTR(_SimpleCData): "The windows BSTR data type" _type_ = "X" _needsfree = False def __repr__(self): return "%s(%r)" % (self.__class__.__name__, self.value) def __ctypes_from_outparam__(self): self._needsfree = True return self.value def __del__(self, _free=windll.oleaut32.SysFreeString): # Free the string if self owns the memory # or if instructed by __ctypes_from_outparam__. if self._b_base_ is None \ or self._needsfree: _free(self) def from_param(cls, value): """Convert into a foreign function call parameter.""" if isinstance(value, cls): return value # Although the builtin SimpleCData.from_param call does the # right thing, it doesn't ensure that SysFreeString is called # on destruction. return cls(value) from_param = classmethod(from_param) ################################################################ # IDL stuff class helpstring(str): "Specifies the helpstring for a COM method or property." class defaultvalue(object): "Specifies the default value for parameters marked optional." def __init__(self, value): self.value = value class dispid(int): "Specifies the DISPID of a method or property." # XXX STDMETHOD, COMMETHOD, DISPMETHOD, and DISPPROPERTY should return # instances with methods, or at least accessors instead of tuple. def STDMETHOD(restype, name, argtypes=()): "Specifies a COM method slot without idlflags" # restype, name, argtypes, paramflags, idlflags, docstring return restype, name, argtypes, None, (), None def DISPMETHOD(idlflags, restype, name, *argspec): "Specifies a method of a dispinterface" return "DISPMETHOD", name, idlflags, restype, argspec def DISPPROPERTY(idlflags, proptype, name): "Specifies a property of a dispinterface" return "DISPPROPERTY", name, idlflags, proptype, ()#, argspec # COMMETHOD returns: # restype, methodname, tuple(argtypes), tuple(paramflags), tuple(idlflags), helptext # # paramflags is a sequence of (flags (integer), paramname (string) # tuple(idlflags) is for the method itself: (dispid, 'readonly') # # Example: (HRESULT, 'Width', (c_long,), (2, 'rhs'), (4, 'readonly'), None) ## sample generated code: ## DISPPROPERTY([5, 'readonly'], OLE_YSIZE_HIMETRIC, 'Height'), ## DISPMETHOD([6], None, 'Render', ## ( [], c_int, 'hdc' ), ## ( [], c_int, 'x' ), ## ( [], c_int, 'y' )) ################################################################ _PARAMFLAGS = { "in": 1, "out": 2, "lcid": 4, "retval": 8, "optional": 16, } def _encode_idl(names): # sum up all values found in _PARAMFLAGS, ignoring all others. return sum([_PARAMFLAGS.get(n, 0) for n in names]) _NOTHING = object() def _unpack_argspec(idl, typ, name=None, defval=_NOTHING): return idl, typ, name, defval def COMMETHOD(idlflags, restype, methodname, *argspec): """Specifies a COM method slot with idlflags. XXX should explain the sematics of the arguments. """ paramflags = [] argtypes = [] # collect all helpstring instances # We should suppress docstrings when Python is started with -OO helptext = [t for t in idlflags if isinstance(t, helpstring)] # join them together(does this make sense?) and replace by None if empty. helptext = "".join(helptext) or None from comtypes.automation import VARIANT for item in argspec: idl, typ, argname, defval = _unpack_argspec(*item) pflags = _encode_idl(idl) if "optional" in idl: if defval is _NOTHING: if typ is VARIANT: defval = VARIANT.missing elif typ is POINTER(VARIANT): defval = pointer(VARIANT.missing) else: ## msg = ("'optional' only allowed for VARIANT and VARIANT*, not for %s" ## % typ.__name__) ## warnings.warn(msg, IDLWarning, stacklevel=2) defval = typ() if defval is _NOTHING: paramflags.append((pflags, argname)) else: paramflags.append((pflags, argname, defval)) argtypes.append(typ) if "propget" in idlflags: methodname = "_get_%s" % methodname elif "propput" in idlflags: methodname = "_set_%s" % methodname elif "propputref" in idlflags: methodname = "_setref_%s" % methodname return restype, methodname, tuple(argtypes), tuple(paramflags), tuple(idlflags), helptext ################################################################ # IUnknown, the root of all evil... class IUnknown(object, metaclass=_cominterface_meta): """The most basic COM interface. Each subclasses of IUnknown must define these class attributes: _iid_ - a GUID instance defining the identifier of this interface _methods_ - a list of methods for this interface. The _methods_ list must in VTable order. Methods are specified with STDMETHOD or COMMETHOD calls. """ _case_insensitive_ = False _iid_ = GUID("{00000000-0000-0000-C000-000000000046}") _methods_ = [ STDMETHOD(HRESULT, "QueryInterface", [POINTER(GUID), POINTER(c_void_p)]), STDMETHOD(c_ulong, "AddRef"), STDMETHOD(c_ulong, "Release") ] def QueryInterface(self, interface, iid=None): "QueryInterface(interface) -> instance" p = POINTER(interface)() if iid is None: iid = interface._iid_ self.__com_QueryInterface(byref(iid), byref(p)) clsid = self.__dict__.get('__clsid') if clsid is not None: p.__dict__['__clsid'] = clsid return p # these are only so that they get a docstring. # XXX There should be other ways to install a docstring. def AddRef(self): "Increase the internal refcount by one and return it." return self.__com_AddRef() def Release(self): "Decrease the internal refcount by one and return it." return self.__com_Release() # IPersist is a trivial interface, which allows to ask an object about # its clsid. class IPersist(IUnknown): _iid_ = GUID('{0000010C-0000-0000-C000-000000000046}') _idlflags_ = [] _methods_ = [ COMMETHOD([], HRESULT, 'GetClassID', ( ['out'], POINTER(GUID), 'pClassID' )), ] class IServiceProvider(IUnknown): _iid_ = GUID('{6D5140C1-7436-11CE-8034-00AA006009FA}') # Overridden QueryService to make it nicer to use (passing it an # interface and it returns a pointer to that interface) def QueryService(self, serviceIID, interface): p = POINTER(interface)() self._QueryService(byref(serviceIID), byref(interface._iid_), byref(p)) return p _methods_ = [ COMMETHOD([], HRESULT, 'QueryService', ( ['in'], POINTER(GUID), 'guidService' ), ( ['in'], POINTER(GUID), 'riid' ), ( ['in'], POINTER(c_void_p), 'ppvObject' )) ] ################################################################ def CoGetObject(displayname, interface): """Convert a displayname to a moniker, then bind and return the object identified by the moniker.""" if interface is None: interface = IUnknown punk = POINTER(interface)() # Do we need a way to specify the BIND_OPTS parameter? _ole32.CoGetObject(str(displayname), None, byref(interface._iid_), byref(punk)) return punk def CoCreateInstance(clsid, interface=None, clsctx=None, punkouter=None): """The basic windows api to create a COM class object and return a pointer to an interface. """ if clsctx is None: clsctx = CLSCTX_SERVER if interface is None: interface = IUnknown p = POINTER(interface)() iid = interface._iid_ _ole32.CoCreateInstance(byref(clsid), punkouter, clsctx, byref(iid), byref(p)) return p def CoGetClassObject(clsid, clsctx=None, pServerInfo=None, interface=None): if clsctx is None: clsctx = CLSCTX_SERVER if interface is None: import comtypes.server interface = comtypes.server.IClassFactory p = POINTER(interface)() _CoGetClassObject(clsid, clsctx, pServerInfo, interface._iid_, byref(p)) return p def GetActiveObject(clsid, interface=None): """Retrieves a pointer to a running object""" p = POINTER(IUnknown)() oledll.oleaut32.GetActiveObject(byref(clsid), None, byref(p)) if interface is not None: p = p.QueryInterface(interface) return p class MULTI_QI(Structure): _fields_ = [("pIID", POINTER(GUID)), ("pItf", POINTER(c_void_p)), ("hr", HRESULT)] class _COAUTHIDENTITY(Structure): _fields_ = [ ('User', POINTER(c_ushort)), ('UserLength', c_ulong), ('Domain', POINTER(c_ushort)), ('DomainLength', c_ulong), ('Password', POINTER(c_ushort)), ('PasswordLength', c_ulong), ('Flags', c_ulong), ] COAUTHIDENTITY = _COAUTHIDENTITY class _COAUTHINFO(Structure): _fields_ = [ ('dwAuthnSvc', c_ulong), ('dwAuthzSvc', c_ulong), ('pwszServerPrincName', c_wchar_p), ('dwAuthnLevel', c_ulong), ('dwImpersonationLevel', c_ulong), ('pAuthIdentityData', POINTER(_COAUTHIDENTITY)), ('dwCapabilities', c_ulong), ] COAUTHINFO = _COAUTHINFO class _COSERVERINFO(Structure): _fields_ = [ ('dwReserved1', c_ulong), ('pwszName', c_wchar_p), ('pAuthInfo', POINTER(_COAUTHINFO)), ('dwReserved2', c_ulong), ] COSERVERINFO = _COSERVERINFO _CoGetClassObject = _ole32.CoGetClassObject _CoGetClassObject.argtypes = [POINTER(GUID), DWORD, POINTER(COSERVERINFO), POINTER(GUID), POINTER(c_void_p)] class tagBIND_OPTS(Structure): _fields_ = [ ('cbStruct', c_ulong), ('grfFlags', c_ulong), ('grfMode', c_ulong), ('dwTickCountDeadline', c_ulong) ] # XXX Add __init__ which sets cbStruct? BIND_OPTS = tagBIND_OPTS class tagBIND_OPTS2(Structure): _fields_ = [ ('cbStruct', c_ulong), ('grfFlags', c_ulong), ('grfMode', c_ulong), ('dwTickCountDeadline', c_ulong), ('dwTrackFlags', c_ulong), ('dwClassContext', c_ulong), ('locale', c_ulong), ('pServerInfo', POINTER(_COSERVERINFO)), ] # XXX Add __init__ which sets cbStruct? BINDOPTS2 = tagBIND_OPTS2 #Structures for security setups ######################################### class _SEC_WINNT_AUTH_IDENTITY(Structure): _fields_ = [ ('User', POINTER(c_ushort)), ('UserLength', c_ulong), ('Domain', POINTER(c_ushort)), ('DomainLength', c_ulong), ('Password', POINTER(c_ushort)), ('PasswordLength', c_ulong), ('Flags', c_ulong), ] SEC_WINNT_AUTH_IDENTITY = _SEC_WINNT_AUTH_IDENTITY class _SOLE_AUTHENTICATION_INFO(Structure): _fields_ = [ ('dwAuthnSvc', c_ulong), ('dwAuthzSvc', c_ulong), ('pAuthInfo', POINTER(_SEC_WINNT_AUTH_IDENTITY)), ] SOLE_AUTHENTICATION_INFO = _SOLE_AUTHENTICATION_INFO class _SOLE_AUTHENTICATION_LIST(Structure): _fields_ = [ ('cAuthInfo', c_ulong), ('pAuthInfo', POINTER(_SOLE_AUTHENTICATION_INFO)), ] SOLE_AUTHENTICATION_LIST = _SOLE_AUTHENTICATION_LIST def CoCreateInstanceEx(clsid, interface=None, clsctx=None, machine=None, pServerInfo=None): """The basic windows api to create a COM class object and return a pointer to an interface, possibly on another machine. Passing both "machine" and "pServerInfo" results in a ValueError. """ if clsctx is None: clsctx=CLSCTX_LOCAL_SERVER|CLSCTX_REMOTE_SERVER if pServerInfo is not None: if machine is not None: msg = "Can not specify both machine name and server info" raise ValueError(msg) elif machine is not None: serverinfo = COSERVERINFO() serverinfo.pwszName = machine pServerInfo = byref(serverinfo) if interface is None: interface = IUnknown multiqi = MULTI_QI() multiqi.pIID = pointer(interface._iid_) _ole32.CoCreateInstanceEx(byref(clsid), None, clsctx, pServerInfo, 1, byref(multiqi)) return cast(multiqi.pItf, POINTER(interface)) ################################################################ from comtypes._comobject import COMObject # What's a coclass? # a POINTER to a coclass is allowed as parameter in a function declaration: # http://msdn.microsoft.com/library/en-us/midl/midl/oleautomation.asp from comtypes._meta import _coclass_meta class CoClass(COMObject, metaclass=_coclass_meta): pass ################################################################