diff --git a/BUILDING.md b/BUILDING.md index a28759e1c7cc1e..84cd38a336b481 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -111,6 +111,9 @@ Prerequisites: * [Visual Studio 2015 Update 3](https://www.visualstudio.com/), all editions including the Community edition (remember to select "Common Tools for Visual C++ 2015" feature during installation). + * [Visual Studio 2017 RC](https://www.visualstudio.com/vs/visual-studio-2017-rc/), + all editions. Required are "VC++ 2017 v141 toolset" and one of the + "Windows 10 SDKS" components. * Basic Unix tools required for some tests, [Git for Windows](http://git-scm.com/download/win) includes Git Bash and tools which can be included in the global `PATH`. diff --git a/tools/comtypes/LICENSE.txt b/tools/comtypes/LICENSE.txt new file mode 100644 index 00000000000000..51bfc346ac073a --- /dev/null +++ b/tools/comtypes/LICENSE.txt @@ -0,0 +1,24 @@ +This software is OSI Certified Open Source Software. +OSI Certified is a certification mark of the Open Source Initiative. + +Copyright (c) 2006-2013, Thomas Heller. +Copyright (c) 2014, Comtypes Developers. +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/tools/comtypes/MANIFEST.in b/tools/comtypes/MANIFEST.in new file mode 100644 index 00000000000000..ab30e9aceec166 --- /dev/null +++ b/tools/comtypes/MANIFEST.in @@ -0,0 +1 @@ +include *.txt diff --git a/tools/comtypes/README b/tools/comtypes/README new file mode 100644 index 00000000000000..5dad588b6381ae --- /dev/null +++ b/tools/comtypes/README @@ -0,0 +1,31 @@ +comtypes +======== + +**comtypes** is a lightweight Python COM package, based on the ctypes_ +FFI library, in less than 10000 lines of code (not counting the +tests). + +**comtypes** allows to define, call, and implement custom and +dispatch-based COM interfaces in pure Python. It works on Windows, +64-bit Windows, and Windows CE. + +Documentation: + + https://pythonhosted.org/comtypes + + Contribute using the `source repository and issue tracker + `_ on GitHub. + +Mailing list: + + http://gmane.org/info.php?group=gmane.comp.python.comtypes.user + + https://lists.sourceforge.net/lists/listinfo/comtypes-users/ + +Download: + + Releases can be downloaded in the PyPI page: + + https://pypi.python.org/pypi/comtypes + +.. _ctypes: http://docs.python.org/lib/module-ctypes.html diff --git a/tools/comtypes/comtypes/GUID.py b/tools/comtypes/comtypes/GUID.py new file mode 100644 index 00000000000000..198526aa5c14e2 --- /dev/null +++ b/tools/comtypes/comtypes/GUID.py @@ -0,0 +1,101 @@ +from ctypes import * +import sys + +if sys.version_info >= (2, 6): + def binary(obj): + return bytes(obj) +else: + def binary(obj): + return buffer(obj) + +BYTE = c_byte +WORD = c_ushort +DWORD = c_ulong + +_ole32 = oledll.ole32 + +_StringFromCLSID = _ole32.StringFromCLSID +_CoTaskMemFree = windll.ole32.CoTaskMemFree +_ProgIDFromCLSID = _ole32.ProgIDFromCLSID +_CLSIDFromString = _ole32.CLSIDFromString +_CLSIDFromProgID = _ole32.CLSIDFromProgID +_CoCreateGuid = _ole32.CoCreateGuid + +# Note: Comparing GUID instances by comparing their buffers +# is slightly faster than using ole32.IsEqualGUID. + +class GUID(Structure): + _fields_ = [("Data1", DWORD), + ("Data2", WORD), + ("Data3", WORD), + ("Data4", BYTE * 8)] + + def __init__(self, name=None): + if name is not None: + _CLSIDFromString(unicode(name), byref(self)) + + def __repr__(self): + return u'GUID("%s")' % unicode(self) + + def __unicode__(self): + p = c_wchar_p() + _StringFromCLSID(byref(self), byref(p)) + result = p.value + _CoTaskMemFree(p) + return result + __str__ = __unicode__ + + def __cmp__(self, other): + if isinstance(other, GUID): + return cmp(binary(self), binary(other)) + return -1 + + def __nonzero__(self): + return self != GUID_null + + def __eq__(self, other): + return isinstance(other, GUID) and \ + binary(self) == binary(other) + + def __hash__(self): + # We make GUID instances hashable, although they are mutable. + return hash(binary(self)) + + def copy(self): + return GUID(unicode(self)) + + def from_progid(cls, progid): + """Get guid from progid, ... + """ + if hasattr(progid, "_reg_clsid_"): + progid = progid._reg_clsid_ + if isinstance(progid, cls): + return progid + elif isinstance(progid, basestring): + if progid.startswith("{"): + return cls(progid) + inst = cls() + _CLSIDFromProgID(unicode(progid), byref(inst)) + return inst + else: + raise TypeError("Cannot construct guid from %r" % progid) + from_progid = classmethod(from_progid) + + def as_progid(self): + "Convert a GUID into a progid" + progid = c_wchar_p() + _ProgIDFromCLSID(byref(self), byref(progid)) + result = progid.value + _CoTaskMemFree(progid) + return result + + def create_new(cls): + "Create a brand new guid" + guid = cls() + _CoCreateGuid(byref(guid)) + return guid + create_new = classmethod(create_new) + +GUID_null = GUID() + +__all__ = ["GUID"] diff --git a/tools/comtypes/comtypes/__init__.py b/tools/comtypes/comtypes/__init__.py new file mode 100644 index 00000000000000..774c8b647d917d --- /dev/null +++ b/tools/comtypes/comtypes/__init__.py @@ -0,0 +1,1390 @@ +import types +import sys +import os + +# comtypes version numbers follow semver (http://semver.org/) and PEP 440 +__version__ = "1.1.3" + +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, 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, 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 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, 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 len(outargs) == 1: # rescode is not iterable + return rescode.__ctypes_from_outparam__() + + rescode = list(rescode) + for outnum, o in 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 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): + "base class for COM interface pointer classes" + __metaclass__ = _compointer_meta + 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(unicode): + "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): + """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 + __metaclass__ = _cominterface_meta + _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(unicode(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 +################################################################ diff --git a/tools/comtypes/comtypes/_comobject.py b/tools/comtypes/comtypes/_comobject.py new file mode 100644 index 00000000000000..d3fca710447604 --- /dev/null +++ b/tools/comtypes/comtypes/_comobject.py @@ -0,0 +1,774 @@ +from ctypes import ( + FormatError, POINTER, Structure, WINFUNCTYPE, byref, c_long, c_void_p, + oledll, pointer, windll +) +from _ctypes import CopyComPointer +import logging +import os + +from comtypes import COMError, ReturnHRESULT, instancemethod, _encode_idl +from comtypes.errorinfo import ISupportErrorInfo, ReportException, ReportError +from comtypes import IPersist +from comtypes.hresult import ( + DISP_E_BADINDEX, DISP_E_MEMBERNOTFOUND, E_FAIL, E_NOINTERFACE, + E_INVALIDARG, E_NOTIMPL, RPC_E_CHANGED_MODE, S_FALSE, S_OK +) +from comtypes.typeinfo import IProvideClassInfo, IProvideClassInfo2 + + +logger = logging.getLogger(__name__) +_debug = logger.debug +_warning = logger.warning +_error = logger.error + +################################################################ +# COM object implementation + +# so we don't have to import comtypes.automation +DISPATCH_METHOD = 1 +DISPATCH_PROPERTYGET = 2 +DISPATCH_PROPERTYPUT = 4 +DISPATCH_PROPERTYPUTREF = 8 + + +class E_NotImplemented(Exception): + """COM method is not implemented""" + + +def HRESULT_FROM_WIN32(errcode): + "Convert a Windows error code into a HRESULT value." + if errcode is None: + return 0x80000000 + if errcode & 0x80000000: + return errcode + return (errcode & 0xFFFF) | 0x80070000 + + +def winerror(exc): + """Return the windows error code from a WindowsError or COMError + instance.""" + try: + code = exc[0] + if isinstance(code, (int, long)): + return code + except IndexError: + pass + # Sometimes, a WindowsError instance has no error code. An access + # violation raised by ctypes has only text, for example. In this + # cases we return a generic error code. + return E_FAIL + + +def _do_implement(interface_name, method_name): + def _not_implemented(*args): + """Return E_NOTIMPL because the method is not implemented.""" + _debug("unimplemented method %s_%s called", interface_name, + method_name) + return E_NOTIMPL + return _not_implemented + + +def catch_errors(obj, mth, paramflags, interface, mthname): + clsid = getattr(obj, "_reg_clsid_", None) + + def call_with_this(*args, **kw): + try: + result = mth(*args, **kw) + except ReturnHRESULT, err: + (hresult, text) = err.args + return ReportError(text, iid=interface._iid_, clsid=clsid, + hresult=hresult) + except (COMError, WindowsError), details: + _error("Exception in %s.%s implementation:", interface.__name__, + mthname, exc_info=True) + return HRESULT_FROM_WIN32(winerror(details)) + except E_NotImplemented: + _warning("Unimplemented method %s.%s called", interface.__name__, + mthname) + return E_NOTIMPL + except: + _error("Exception in %s.%s implementation:", interface.__name__, + mthname, exc_info=True) + return ReportException(E_FAIL, interface._iid_, clsid=clsid) + if result is None: + return S_OK + return result + if paramflags is None: + has_outargs = False + else: + has_outargs = bool([x[0] for x in paramflags + if x[0] & 2]) + call_with_this.has_outargs = has_outargs + return call_with_this + + +################################################################ + +def hack(inst, mth, paramflags, interface, mthname): + if paramflags is None: + return catch_errors(inst, mth, paramflags, interface, mthname) + code = mth.func_code + if code.co_varnames[1:2] == ("this",): + return catch_errors(inst, mth, paramflags, interface, mthname) + dirflags = [f[0] for f in paramflags] + # An argument is an input arg either if flags are NOT set in the + # idl file, or if the flags contain 'in'. In other words, the + # direction flag is either exactly '0' or has the '1' bit set: + # Output arguments have flag '2' + + args_out_idx = [] + args_in_idx = [] + for i, a in enumerate(dirflags): + if a&2: + args_out_idx.append(i) + if a&1 or a==0: + args_in_idx.append(i) + args_out = len(args_out_idx) + + ## XXX Remove this: +## if args_in != code.co_argcount - 1: +## return catch_errors(inst, mth, interface, mthname) + + clsid = getattr(inst, "_reg_clsid_", None) + + def call_without_this(this, *args): + # Method implementations could check for and return E_POINTER + # themselves. Or an error will be raised when + # 'outargs[i][0] = value' is executed. +## for a in outargs: +## if not a: +## return E_POINTER + + #make argument list for handler by index array built above + inargs = [] + for a in args_in_idx: + inargs.append(args[a]) + try: + result = mth(*inargs) + if args_out == 1: + args[args_out_idx[0]][0] = result + elif args_out != 0: + if len(result) != args_out: + msg = "Method should have returned a %s-tuple" % args_out + raise ValueError(msg) + for i, value in enumerate(result): + args[args_out_idx[i]][0] = value + except ReturnHRESULT, err: + (hresult, text) = err.args + return ReportError(text, iid=interface._iid_, clsid=clsid, + hresult=hresult) + except COMError, err: + (hr, text, details) = err.args + _error("Exception in %s.%s implementation:", interface.__name__, + mthname, exc_info=True) + try: + descr, source, helpfile, helpcontext, progid = details + except (ValueError, TypeError): + msg = str(details) + else: + msg = "%s: %s" % (source, descr) + hr = HRESULT_FROM_WIN32(hr) + return ReportError(msg, iid=interface._iid_, clsid=clsid, + hresult=hr) + except WindowsError, details: + _error("Exception in %s.%s implementation:", interface.__name__, + mthname, exc_info=True) + hr = HRESULT_FROM_WIN32(winerror(details)) + return ReportException(hr, interface._iid_, clsid=clsid) + except E_NotImplemented: + _warning("Unimplemented method %s.%s called", interface.__name__, + mthname) + return E_NOTIMPL + except: + _error("Exception in %s.%s implementation:", interface.__name__, + mthname, exc_info=True) + return ReportException(E_FAIL, interface._iid_, clsid=clsid) + return S_OK + if args_out: + call_without_this.has_outargs = True + return call_without_this + + +class _MethodFinder(object): + def __init__(self, inst): + self.inst = inst + # map lower case names to names with correct spelling. + self.names = dict([(n.lower(), n) for n in dir(inst)]) + + def get_impl(self, interface, mthname, paramflags, idlflags): + mth = self.find_impl(interface, mthname, paramflags, idlflags) + if mth is None: + return _do_implement(interface.__name__, mthname) + return hack(self.inst, mth, paramflags, interface, mthname) + + def find_method(self, fq_name, mthname): + # Try to find a method, first with the fully qualified name + # ('IUnknown_QueryInterface'), if that fails try the simple + # name ('QueryInterface') + try: + return getattr(self.inst, fq_name) + except AttributeError: + pass + return getattr(self.inst, mthname) + + def find_impl(self, interface, mthname, paramflags, idlflags): + fq_name = "%s_%s" % (interface.__name__, mthname) + if interface._case_insensitive_: + # simple name, like 'QueryInterface' + mthname = self.names.get(mthname.lower(), mthname) + # qualified name, like 'IUnknown_QueryInterface' + fq_name = self.names.get(fq_name.lower(), fq_name) + + try: + return self.find_method(fq_name, mthname) + except AttributeError: + pass + propname = mthname[5:] # strip the '_get_' or '_set' prefix + if interface._case_insensitive_: + propname = self.names.get(propname.lower(), propname) + # propput and propget is done with 'normal' attribute access, + # but only for COM properties that do not take additional + # arguments: + + if "propget" in idlflags and len(paramflags) == 1: + return self.getter(propname) + if "propput" in idlflags and len(paramflags) == 1: + return self.setter(propname) + _debug("%r: %s.%s not implemented", self.inst, interface.__name__, + mthname) + return None + + def setter(self, propname): + # + def set(self, value): + try: + # XXX this may not be correct is the object implements + # _get_PropName but not _set_PropName + setattr(self, propname, value) + except AttributeError: + raise E_NotImplemented() + return instancemethod(set, self.inst, type(self.inst)) + + def getter(self, propname): + def get(self): + try: + return getattr(self, propname) + except AttributeError: + raise E_NotImplemented() + return instancemethod(get, self.inst, type(self.inst)) + + +def _create_vtbl_type(fields, itf): + try: + return _vtbl_types[fields] + except KeyError: + class Vtbl(Structure): + _fields_ = fields + Vtbl.__name__ = "Vtbl_%s" % itf.__name__ + _vtbl_types[fields] = Vtbl + return Vtbl + +# Ugh. Another type cache to avoid leaking types. +_vtbl_types = {} + +################################################################ + +try: + if os.name == "ce": + _InterlockedIncrement = windll.coredll.InterlockedIncrement + _InterlockedDecrement = windll.coredll.InterlockedDecrement + else: + _InterlockedIncrement = windll.kernel32.InterlockedIncrement + _InterlockedDecrement = windll.kernel32.InterlockedDecrement +except AttributeError: + import threading + _lock = threading.Lock() + _acquire = _lock.acquire + _release = _lock.release + # win 64 doesn't have these functions + + def _InterlockedIncrement(ob): + _acquire() + refcnt = ob.value + 1 + ob.value = refcnt + _release() + return refcnt + + def _InterlockedDecrement(ob): + _acquire() + refcnt = ob.value - 1 + ob.value = refcnt + _release() + return refcnt +else: + _InterlockedIncrement.argtypes = [POINTER(c_long)] + _InterlockedDecrement.argtypes = [POINTER(c_long)] + _InterlockedIncrement.restype = c_long + _InterlockedDecrement.restype = c_long + + +class LocalServer(object): + + _queue = None + + def run(self, classobjects): + # Use windll instead of oledll so that we don't get an + # exception on a FAILED hresult: + result = windll.ole32.CoInitialize(None) + if RPC_E_CHANGED_MODE == result: + # we're running in MTA: no message pump needed + _debug("Server running in MTA") + self.run_mta() + else: + # we're running in STA: need a message pump + _debug("Server running in STA") + if result >= 0: + # we need a matching CoUninitialize() call for a successful + # CoInitialize(). + windll.ole32.CoUninitialize() + self.run_sta() + + for obj in classobjects: + obj._revoke_class() + + def run_sta(self): + from comtypes import messageloop + messageloop.run() + + def run_mta(self): + import Queue + self._queue = Queue.Queue() + self._queue.get() + + def Lock(self): + oledll.ole32.CoAddRefServerProcess() + + def Unlock(self): + rc = oledll.ole32.CoReleaseServerProcess() + if rc == 0: + if self._queue: + self._queue.put(42) + else: + windll.user32.PostQuitMessage(0) + + +class InprocServer(object): + + def __init__(self): + self.locks = c_long(0) + + def Lock(self): + _InterlockedIncrement(self.locks) + + def Unlock(self): + _InterlockedDecrement(self.locks) + + def DllCanUnloadNow(self): + if self.locks.value: + return S_FALSE + if COMObject._instances_: + return S_FALSE + return S_OK + + +class COMObject(object): + _instances_ = {} + + def __new__(cls, *args, **kw): + self = super(COMObject, cls).__new__(cls) + if isinstance(self, c_void_p): + # We build the VTables only for direct instances of + # CoClass, not for POINTERs to CoClass. + return self + if hasattr(self, "_com_interfaces_"): + self.__prepare_comobject() + return self + + def __prepare_comobject(self): + # When a CoClass instance is created, COM pointers to all + # interfaces are created. Also, the CoClass must be kept alive as + # until the COM reference count drops to zero, even if no Python + # code keeps a reference to the object. + # + # The _com_pointers_ instance variable maps string interface iids + # to C compatible COM pointers. + self._com_pointers_ = {} + # COM refcount starts at zero. + self._refcnt = c_long(0) + + # Some interfaces have a default implementation in COMObject: + # - ISupportErrorInfo + # - IPersist (if the subclass has a _reg_clsid_ attribute) + # - IProvideClassInfo (if the subclass has a _reg_clsid_ attribute) + # - IProvideClassInfo2 (if the subclass has a _outgoing_interfaces_ + # attribute) + # + # Add these if they are not listed in _com_interfaces_. + interfaces = tuple(self._com_interfaces_) + if ISupportErrorInfo not in interfaces: + interfaces += (ISupportErrorInfo,) + if hasattr(self, "_reg_typelib_"): + from comtypes.typeinfo import LoadRegTypeLib + self._COMObject__typelib = LoadRegTypeLib(*self._reg_typelib_) + if hasattr(self, "_reg_clsid_"): + if IProvideClassInfo not in interfaces: + interfaces += (IProvideClassInfo,) + if hasattr(self, "_outgoing_interfaces_") and \ + IProvideClassInfo2 not in interfaces: + interfaces += (IProvideClassInfo2,) + if hasattr(self, "_reg_clsid_"): + if IPersist not in interfaces: + interfaces += (IPersist,) + for itf in interfaces[::-1]: + self.__make_interface_pointer(itf) + + def __make_interface_pointer(self, itf): + methods = [] # method implementations + fields = [] # (name, prototype) for virtual function table + iids = [] # interface identifiers. + # iterate over interface inheritance in reverse order to build the + # virtual function table, and leave out the 'object' base class. + finder = self._get_method_finder_(itf) + for interface in itf.__mro__[-2::-1]: + iids.append(interface._iid_) + for m in interface._methods_: + restype, mthname, argtypes, paramflags, idlflags, helptext = m + proto = WINFUNCTYPE(restype, c_void_p, *argtypes) + fields.append((mthname, proto)) + mth = finder.get_impl(interface, mthname, paramflags, idlflags) + methods.append(proto(mth)) + Vtbl = _create_vtbl_type(tuple(fields), itf) + vtbl = Vtbl(*methods) + for iid in iids: + self._com_pointers_[iid] = pointer(pointer(vtbl)) + if hasattr(itf, "_disp_methods_"): + self._dispimpl_ = {} + for m in itf._disp_methods_: + what, mthname, idlflags, restype, argspec = m + ################# + # What we have: + # + # restypes is a ctypes type or None + # argspec is seq. of (['in'], paramtype, paramname) tuples (or + # lists?) + ################# + # What we need: + # + # idlflags must contain 'propget', 'propset' and so on: + # Must be constructed by converting disptype + # + # paramflags must be a sequence + # of (F_IN|F_OUT|F_RETVAL, paramname[, default-value]) tuples + # + # comtypes has this function which helps: + # def _encode_idl(names): + # # convert to F_xxx and sum up "in", "out", + # # "retval" values found in _PARAMFLAGS, ignoring + # # other stuff. + # return sum([_PARAMFLAGS.get(n, 0) for n in names]) + ################# + + if what == "DISPMETHOD": + if 'propget' in idlflags: + invkind = 2 # DISPATCH_PROPERTYGET + mthname = "_get_" + mthname + elif 'propput' in idlflags: + invkind = 4 # DISPATCH_PROPERTYPUT + mthname = "_set_" + mthname + elif 'propputref' in idlflags: + invkind = 8 # DISPATCH_PROPERTYPUTREF + mthname = "_setref_" + mthname + else: + invkind = 1 # DISPATCH_METHOD + if restype: + argspec = argspec + ((['out'], restype, ""),) + self.__make_dispentry(finder, interface, mthname, + idlflags, argspec, invkind) + elif what == "DISPPROPERTY": + # DISPPROPERTY have implicit "out" + if restype: + argspec += ((['out'], restype, ""),) + self.__make_dispentry(finder, interface, + "_get_" + mthname, + idlflags, argspec, + 2 # DISPATCH_PROPERTYGET + ) + if not 'readonly' in idlflags: + self.__make_dispentry(finder, interface, + "_set_" + mthname, + idlflags, argspec, + 4) # DISPATCH_PROPERTYPUT + # Add DISPATCH_PROPERTYPUTREF also? + + def __make_dispentry(self, + finder, interface, mthname, + idlflags, argspec, invkind): + # We build a _dispmap_ entry now that maps invkind and + # dispid to implementations that the finder finds; + # IDispatch_Invoke will later call it. + paramflags = [((_encode_idl(x[0]), x[1]) + tuple(x[3:])) + for x in argspec] + # XXX can the dispid be at a different index? Check codegenerator. + dispid = idlflags[0] + impl = finder.get_impl(interface, mthname, paramflags, idlflags) + self._dispimpl_[(dispid, invkind)] = impl + # invkind is really a set of flags; we allow both + # DISPATCH_METHOD and DISPATCH_PROPERTYGET (win32com uses + # this, maybe other languages too?) + if invkind in (1, 2): + self._dispimpl_[(dispid, 3)] = impl + + def _get_method_finder_(self, itf): + # This method can be overridden to customize how methods are + # found. + return _MethodFinder(self) + + ################################################################ + # LocalServer / InprocServer stuff + __server__ = None + + @staticmethod + def __run_inprocserver__(): + if COMObject.__server__ is None: + COMObject.__server__ = InprocServer() + elif isinstance(COMObject.__server__, InprocServer): + pass + else: + raise RuntimeError("Wrong server type") + + @staticmethod + def __run_localserver__(classobjects): + assert COMObject.__server__ is None + # XXX Decide whether we are in STA or MTA + server = COMObject.__server__ = LocalServer() + server.run(classobjects) + COMObject.__server__ = None + + @staticmethod + def __keep__(obj): + COMObject._instances_[obj] = None + _debug("%d active COM objects: Added %r", len(COMObject._instances_), + obj) + if COMObject.__server__: + COMObject.__server__.Lock() + + @staticmethod + def __unkeep__(obj): + try: + del COMObject._instances_[obj] + except AttributeError: + _debug("? active COM objects: Removed %r", obj) + else: + _debug("%d active COM objects: Removed %r", + len(COMObject._instances_), obj) + _debug("Remaining: %s", COMObject._instances_.keys()) + if COMObject.__server__: + COMObject.__server__.Unlock() + # + ################################################################ + + ######################################################### + # IUnknown methods implementations + def IUnknown_AddRef(self, this, + __InterlockedIncrement=_InterlockedIncrement, + _debug=_debug): + result = __InterlockedIncrement(self._refcnt) + if result == 1: + self.__keep__(self) + _debug("%r.AddRef() -> %s", self, result) + return result + + def _final_release_(self): + """This method may be overridden in subclasses + to free allocated resources or so.""" + pass + + def IUnknown_Release(self, this, + __InterlockedDecrement=_InterlockedDecrement, + _debug=_debug): + # If this is called at COM shutdown, _InterlockedDecrement() + # must still be available, although module level variables may + # have been deleted already - so we supply it as default + # argument. + result = __InterlockedDecrement(self._refcnt) + _debug("%r.Release() -> %s", self, result) + if result == 0: + self._final_release_() + self.__unkeep__(self) + # Hm, why isn't this cleaned up by the cycle gc? + self._com_pointers_ = {} + return result + + def IUnknown_QueryInterface(self, this, riid, ppvObj, _debug=_debug): + # XXX This is probably too slow. + # riid[0].hashcode() alone takes 33 us! + iid = riid[0] + ptr = self._com_pointers_.get(iid, None) + if ptr is not None: + # CopyComPointer(src, dst) calls AddRef! + _debug("%r.QueryInterface(%s) -> S_OK", self, iid) + return CopyComPointer(ptr, ppvObj) + _debug("%r.QueryInterface(%s) -> E_NOINTERFACE", self, iid) + return E_NOINTERFACE + + def QueryInterface(self, interface): + "Query the object for an interface pointer" + # This method is NOT the implementation of + # IUnknown::QueryInterface, instead it is supposed to be + # called on an COMObject by user code. It allows to get COM + # interface pointers from COMObject instances. + ptr = self._com_pointers_.get(interface._iid_, None) + if ptr is None: + raise COMError(E_NOINTERFACE, FormatError(E_NOINTERFACE), + (None, None, 0, None, None)) + # CopyComPointer(src, dst) calls AddRef! + result = POINTER(interface)() + CopyComPointer(ptr, byref(result)) + return result + + ################################################################ + # ISupportErrorInfo::InterfaceSupportsErrorInfo implementation + def ISupportErrorInfo_InterfaceSupportsErrorInfo(self, this, riid): + if riid[0] in self._com_pointers_: + return S_OK + return S_FALSE + + ################################################################ + # IProvideClassInfo::GetClassInfo implementation + def IProvideClassInfo_GetClassInfo(self): + try: + self.__typelib + except AttributeError: + raise WindowsError(E_NOTIMPL) + return self.__typelib.GetTypeInfoOfGuid(self._reg_clsid_) + + ################################################################ + # IProvideClassInfo2::GetGUID implementation + + def IProvideClassInfo2_GetGUID(self, dwGuidKind): + # GUIDKIND_DEFAULT_SOURCE_DISP_IID = 1 + if dwGuidKind != 1: + raise WindowsError(E_INVALIDARG) + return self._outgoing_interfaces_[0]._iid_ + + ################################################################ + # IDispatch methods + @property + def __typeinfo(self): + # XXX Looks like this better be a static property, set by the + # code that sets __typelib also... + iid = self._com_interfaces_[0]._iid_ + return self.__typelib.GetTypeInfoOfGuid(iid) + + def IDispatch_GetTypeInfoCount(self): + try: + self.__typelib + except AttributeError: + return 0 + else: + return 1 + + def IDispatch_GetTypeInfo(self, this, itinfo, lcid, ptinfo): + if itinfo != 0: + return DISP_E_BADINDEX + try: + ptinfo[0] = self.__typeinfo + return S_OK + except AttributeError: + return E_NOTIMPL + + def IDispatch_GetIDsOfNames(self, this, riid, rgszNames, cNames, lcid, + rgDispId): + # This call uses windll instead of oledll so that a failed + # call to DispGetIDsOfNames will return a HRESULT instead of + # raising an error. + try: + tinfo = self.__typeinfo + except AttributeError: + return E_NOTIMPL + return windll.oleaut32.DispGetIDsOfNames(tinfo, + rgszNames, cNames, rgDispId) + + def IDispatch_Invoke(self, this, dispIdMember, riid, lcid, wFlags, + pDispParams, pVarResult, pExcepInfo, puArgErr): + try: + self._dispimpl_ + except AttributeError: + try: + tinfo = self.__typeinfo + except AttributeError: + # Hm, we pretend to implement IDispatch, but have no + # typeinfo, and so cannot fulfill the contract. Should we + # better return E_NOTIMPL or DISP_E_MEMBERNOTFOUND? Some + # clients call IDispatch_Invoke with 'known' DISPID_...' + # values, without going through GetIDsOfNames first. + return DISP_E_MEMBERNOTFOUND + # This call uses windll instead of oledll so that a failed + # call to DispInvoke will return a HRESULT instead of raising + # an error. + interface = self._com_interfaces_[0] + ptr = self._com_pointers_[interface._iid_] + return windll.oleaut32.DispInvoke( + ptr, tinfo, dispIdMember, wFlags, pDispParams, pVarResult, + pExcepInfo, puArgErr + ) + + try: + # XXX Hm, wFlags should be considered a SET of flags... + mth = self._dispimpl_[(dispIdMember, wFlags)] + except KeyError: + return DISP_E_MEMBERNOTFOUND + + # Unpack the parameters: It would be great if we could use the + # DispGetParam function - but we cannot since it requires that + # we pass a VARTYPE for each argument and we do not know that. + # + # Seems that n arguments have dispids (0, 1, ..., n-1). + # Unnamed arguments are packed into the DISPPARAMS array in + # reverse order (starting with the highest dispid), named + # arguments are packed in the order specified by the + # rgdispidNamedArgs array. + # + params = pDispParams[0] + + if wFlags & (4 | 8): + # DISPATCH_PROPERTYPUT + # DISPATCH_PROPERTYPUTREF + # + # How are the parameters unpacked for propertyput + # operations with additional parameters? Can propput + # have additional args? + args = [params.rgvarg[i].value + for i in reversed(range(params.cNamedArgs))] + # MSDN: pVarResult is ignored if DISPATCH_PROPERTYPUT or + # DISPATCH_PROPERTYPUTREF is specified. + return mth(this, *args) + + else: + # DISPATCH_METHOD + # DISPATCH_PROPERTYGET + # the positions of named arguments + # + # 2to3 has problems to translate 'range(...)[::-1]' + # correctly, so use 'list(range)[::-1]' instead (will be + # fixed in Python 3.1, probably): + named_indexes = [params.rgdispidNamedArgs[i] + for i in range(params.cNamedArgs)] + # the positions of unnamed arguments + num_unnamed = params.cArgs - params.cNamedArgs + unnamed_indexes = list(reversed(range(num_unnamed))) + # It seems that this code calculates the indexes of the + # parameters in the params.rgvarg array correctly. + indexes = named_indexes + unnamed_indexes + args = [params.rgvarg[i].value for i in indexes] + + if pVarResult and getattr(mth, "has_outargs", False): + args.append(pVarResult) + return mth(this, *args) + + ################################################################ + # IPersist interface + def IPersist_GetClassID(self): + return self._reg_clsid_ + +__all__ = ["COMObject"] diff --git a/tools/comtypes/comtypes/_meta.py b/tools/comtypes/comtypes/_meta.py new file mode 100644 index 00000000000000..2d26b15d15648b --- /dev/null +++ b/tools/comtypes/comtypes/_meta.py @@ -0,0 +1,61 @@ +# comtypes._meta helper module +from ctypes import POINTER, c_void_p, cast +import comtypes + +################################################################ +# metaclass for CoClass (in comtypes/__init__.py) + +def _wrap_coclass(self): + # We are an IUnknown pointer, represented as a c_void_p instance, + # but we really want this interface: + itf = self._com_interfaces_[0] + punk = cast(self, POINTER(itf)) + result = punk.QueryInterface(itf) + result.__dict__["__clsid"] = str(self._reg_clsid_) + return result + +def _coclass_from_param(cls, obj): + if isinstance(obj, (cls._com_interfaces_[0], cls)): + return obj + raise TypeError(obj) + +# +# The mro() of a POINTER(App) type, where class App is a subclass of CoClass: +# +# POINTER(App) +# App +# CoClass +# c_void_p +# _SimpleCData +# _CData +# object + +class _coclass_meta(type): + # metaclass for CoClass + # + # When a CoClass subclass is created, create a POINTER(...) type + # for that class, with bases and c_void_p. Also, the + # POINTER(...) type gets a __ctypes_from_outparam__ method which + # will QueryInterface for the default interface: the first one on + # the coclass' _com_interfaces_ list. + def __new__(cls, name, bases, namespace): + klass = type.__new__(cls, name, bases, namespace) + if bases == (object,): + return klass + # XXX We should insist that a _reg_clsid_ is present. + if "_reg_clsid_" in namespace: + clsid = namespace["_reg_clsid_"] + comtypes.com_coclass_registry[str(clsid)] = klass + PTR = _coclass_pointer_meta("POINTER(%s)" % klass.__name__, + (klass, c_void_p), + {"__ctypes_from_outparam__": _wrap_coclass, + "from_param": classmethod(_coclass_from_param), + }) + from ctypes import _pointer_type_cache + _pointer_type_cache[klass] = PTR + + return klass + +# will not work if we change the order of the two base classes! +class _coclass_pointer_meta(type(c_void_p), _coclass_meta): + pass diff --git a/tools/comtypes/comtypes/_safearray.py b/tools/comtypes/comtypes/_safearray.py new file mode 100644 index 00000000000000..76e41b9d79afc1 --- /dev/null +++ b/tools/comtypes/comtypes/_safearray.py @@ -0,0 +1,128 @@ +"""SAFEARRAY api functions, data types, and constants.""" + +from ctypes import * +from ctypes.wintypes import * +from comtypes import HRESULT, GUID + +################################################################ +##if __debug__: +## from ctypeslib.dynamic_module import include +## include("""\ +## #define UNICODE +## #define NO_STRICT +## #include +## """, +## persist=True) + +################################################################ + +VARTYPE = c_ushort +PVOID = c_void_p +USHORT = c_ushort + +_oleaut32 = WinDLL("oleaut32") + +class tagSAFEARRAYBOUND(Structure): + _fields_ = [ + ('cElements', DWORD), + ('lLbound', LONG), +] +SAFEARRAYBOUND = tagSAFEARRAYBOUND + +class tagSAFEARRAY(Structure): + _fields_ = [ + ('cDims', USHORT), + ('fFeatures', USHORT), + ('cbElements', DWORD), + ('cLocks', DWORD), + ('pvData', PVOID), + ('rgsabound', SAFEARRAYBOUND * 1), + ] +SAFEARRAY = tagSAFEARRAY + +SafeArrayAccessData = _oleaut32.SafeArrayAccessData +SafeArrayAccessData.restype = HRESULT +# Last parameter manually changed from POINTER(c_void_p) to c_void_p: +SafeArrayAccessData.argtypes = [POINTER(SAFEARRAY), c_void_p] + +SafeArrayCreateVectorEx = _oleaut32.SafeArrayCreateVectorEx +SafeArrayCreateVectorEx.restype = POINTER(SAFEARRAY) +SafeArrayCreateVectorEx.argtypes = [VARTYPE, LONG, DWORD, PVOID] + +SafeArrayCreateEx = _oleaut32.SafeArrayCreateEx +SafeArrayCreateEx.restype = POINTER(SAFEARRAY) +SafeArrayCreateEx.argtypes = [VARTYPE, c_uint, POINTER(SAFEARRAYBOUND), PVOID] + +SafeArrayCreate = _oleaut32.SafeArrayCreate +SafeArrayCreate.restype = POINTER(SAFEARRAY) +SafeArrayCreate.argtypes = [VARTYPE, c_uint, POINTER(SAFEARRAYBOUND)] + +SafeArrayUnaccessData = _oleaut32.SafeArrayUnaccessData +SafeArrayUnaccessData.restype = HRESULT +SafeArrayUnaccessData.argtypes = [POINTER(SAFEARRAY)] + +_SafeArrayGetVartype = _oleaut32.SafeArrayGetVartype +_SafeArrayGetVartype.restype = HRESULT +_SafeArrayGetVartype.argtypes = [POINTER(SAFEARRAY), POINTER(VARTYPE)] +def SafeArrayGetVartype(pa): + result = VARTYPE() + _SafeArrayGetVartype(pa, result) + return result.value + +SafeArrayGetElement = _oleaut32.SafeArrayGetElement +SafeArrayGetElement.restype = HRESULT +SafeArrayGetElement.argtypes = [POINTER(SAFEARRAY), POINTER(LONG), c_void_p] + +SafeArrayDestroy = _oleaut32.SafeArrayDestroy +SafeArrayDestroy.restype = HRESULT +SafeArrayDestroy.argtypes = [POINTER(SAFEARRAY)] + +SafeArrayCreateVector = _oleaut32.SafeArrayCreateVector +SafeArrayCreateVector.restype = POINTER(SAFEARRAY) +SafeArrayCreateVector.argtypes = [VARTYPE, LONG, DWORD] + +SafeArrayDestroyData = _oleaut32.SafeArrayDestroyData +SafeArrayDestroyData.restype = HRESULT +SafeArrayDestroyData.argtypes = [POINTER(SAFEARRAY)] + +SafeArrayGetDim = _oleaut32.SafeArrayGetDim +SafeArrayGetDim.restype = UINT +SafeArrayGetDim.argtypes = [POINTER(SAFEARRAY)] + +_SafeArrayGetLBound = _oleaut32.SafeArrayGetLBound +_SafeArrayGetLBound.restype = HRESULT +_SafeArrayGetLBound.argtypes = [POINTER(SAFEARRAY), UINT, POINTER(LONG)] +def SafeArrayGetLBound(pa, dim): + result = LONG() + _SafeArrayGetLBound(pa, dim, result) + return result.value + +_SafeArrayGetUBound = _oleaut32.SafeArrayGetUBound +_SafeArrayGetUBound.restype = HRESULT +_SafeArrayGetUBound.argtypes = [POINTER(SAFEARRAY), UINT, POINTER(LONG)] +def SafeArrayGetUBound(pa, dim): + result = LONG() + _SafeArrayGetUBound(pa, dim, result) + return result.value + + +SafeArrayLock = _oleaut32.SafeArrayLock +SafeArrayLock.restype = HRESULT +SafeArrayLock.argtypes = [POINTER(SAFEARRAY)] +SafeArrayPtrOfIndex = _oleaut32.SafeArrayPtrOfIndex +SafeArrayPtrOfIndex.restype = HRESULT +# Last parameter manually changed from POINTER(c_void_p) to c_void_p: +SafeArrayPtrOfIndex.argtypes = [POINTER(SAFEARRAY), POINTER(LONG), c_void_p] +SafeArrayUnlock = _oleaut32.SafeArrayUnlock +SafeArrayUnlock.restype = HRESULT +SafeArrayUnlock.argtypes = [POINTER(SAFEARRAY)] +_SafeArrayGetIID = _oleaut32.SafeArrayGetIID +_SafeArrayGetIID.restype = HRESULT +_SafeArrayGetIID.argtypes = [POINTER(SAFEARRAY), POINTER(GUID)] +def SafeArrayGetIID(pa): + result = GUID() + _SafeArrayGetIID(pa, result) + return result +SafeArrayDestroyDescriptor = _oleaut32.SafeArrayDestroyDescriptor +SafeArrayDestroyDescriptor.restype = HRESULT +SafeArrayDestroyDescriptor.argtypes = [POINTER(SAFEARRAY)] diff --git a/tools/comtypes/comtypes/automation.py b/tools/comtypes/comtypes/automation.py new file mode 100644 index 00000000000000..7b709826d2bf83 --- /dev/null +++ b/tools/comtypes/comtypes/automation.py @@ -0,0 +1,881 @@ +# comtypes.automation module +import array +import datetime +import decimal + +from ctypes import * +from ctypes import _Pointer +from _ctypes import CopyComPointer +from comtypes import IUnknown, GUID, IID, STDMETHOD, BSTR, COMMETHOD, COMError +from comtypes.hresult import * +from comtypes.patcher import Patch +from comtypes import npsupport +try: + from comtypes import _safearray +except (ImportError, AttributeError): + class _safearray(object): + tagSAFEARRAY = None + +from ctypes.wintypes import DWORD, LONG, UINT, VARIANT_BOOL, WCHAR, WORD + + +LCID = DWORD +DISPID = LONG +SCODE = LONG + +VARTYPE = c_ushort + +DISPATCH_METHOD = 1 +DISPATCH_PROPERTYGET = 2 +DISPATCH_PROPERTYPUT = 4 +DISPATCH_PROPERTYPUTREF = 8 + +tagINVOKEKIND = c_int +INVOKE_FUNC = DISPATCH_METHOD +INVOKE_PROPERTYGET = DISPATCH_PROPERTYGET +INVOKE_PROPERTYPUT = DISPATCH_PROPERTYPUT +INVOKE_PROPERTYPUTREF = DISPATCH_PROPERTYPUTREF +INVOKEKIND = tagINVOKEKIND + + +################################ +# helpers +IID_NULL = GUID() +riid_null = byref(IID_NULL) +_byref_type = type(byref(c_int())) + +# 30. December 1899, midnight. For VT_DATE. +_com_null_date = datetime.datetime(1899, 12, 30, 0, 0, 0) + +################################################################ +# VARIANT, in all it's glory. +VARENUM = c_int # enum +VT_EMPTY = 0 +VT_NULL = 1 +VT_I2 = 2 +VT_I4 = 3 +VT_R4 = 4 +VT_R8 = 5 +VT_CY = 6 +VT_DATE = 7 +VT_BSTR = 8 +VT_DISPATCH = 9 +VT_ERROR = 10 +VT_BOOL = 11 +VT_VARIANT = 12 +VT_UNKNOWN = 13 +VT_DECIMAL = 14 +VT_I1 = 16 +VT_UI1 = 17 +VT_UI2 = 18 +VT_UI4 = 19 +VT_I8 = 20 +VT_UI8 = 21 +VT_INT = 22 +VT_UINT = 23 +VT_VOID = 24 +VT_HRESULT = 25 +VT_PTR = 26 +VT_SAFEARRAY = 27 +VT_CARRAY = 28 +VT_USERDEFINED = 29 +VT_LPSTR = 30 +VT_LPWSTR = 31 +VT_RECORD = 36 +VT_INT_PTR = 37 +VT_UINT_PTR = 38 +VT_FILETIME = 64 +VT_BLOB = 65 +VT_STREAM = 66 +VT_STORAGE = 67 +VT_STREAMED_OBJECT = 68 +VT_STORED_OBJECT = 69 +VT_BLOB_OBJECT = 70 +VT_CF = 71 +VT_CLSID = 72 +VT_VERSIONED_STREAM = 73 +VT_BSTR_BLOB = 4095 +VT_VECTOR = 4096 +VT_ARRAY = 8192 +VT_BYREF = 16384 +VT_RESERVED = 32768 +VT_ILLEGAL = 65535 +VT_ILLEGALMASKED = 4095 +VT_TYPEMASK = 4095 + + +class tagCY(Structure): + _fields_ = [("int64", c_longlong)] +CY = tagCY +CURRENCY = CY + + +class tagDEC(Structure): + _fields_ = [("wReserved", c_ushort), + ("scale", c_ubyte), + ("sign", c_ubyte), + ("Hi32", c_ulong), + ("Lo64", c_ulonglong)] + + def as_decimal(self): + """ Convert a tagDEC struct to Decimal. + + See http://msdn.microsoft.com/en-us/library/cc234586.aspx for the tagDEC + specification. + + """ + digits = (self.Hi32 << 64) + self.Lo64 + decimal_str = "{0}{1}e-{2}".format( + '-' if self.sign else '', + digits, + self.scale, + ) + return decimal.Decimal(decimal_str) + + +DECIMAL = tagDEC + + +# The VARIANT structure is a good candidate for implementation in a C +# helper extension. At least the get/set methods. +class tagVARIANT(Structure): + class U_VARIANT1(Union): + class __tagVARIANT(Structure): + # The C Header file defn of VARIANT is much more complicated, but + # this is the ctypes version - functional as well. + class U_VARIANT2(Union): + class _tagBRECORD(Structure): + _fields_ = [("pvRecord", c_void_p), + ("pRecInfo", POINTER(IUnknown))] + _fields_ = [ + ("VT_BOOL", VARIANT_BOOL), + ("VT_I1", c_byte), + ("VT_I2", c_short), + ("VT_I4", c_long), + ("VT_I8", c_longlong), + ("VT_INT", c_int), + ("VT_UI1", c_ubyte), + ("VT_UI2", c_ushort), + ("VT_UI4", c_ulong), + ("VT_UI8", c_ulonglong), + ("VT_UINT", c_uint), + ("VT_R4", c_float), + ("VT_R8", c_double), + ("VT_CY", c_longlong), + ("c_wchar_p", c_wchar_p), + ("c_void_p", c_void_p), + ("pparray", POINTER(POINTER(_safearray.tagSAFEARRAY))), + + ("bstrVal", BSTR), + ("_tagBRECORD", _tagBRECORD), + ] + _anonymous_ = ["_tagBRECORD"] + _fields_ = [("vt", VARTYPE), + ("wReserved1", c_ushort), + ("wReserved2", c_ushort), + ("wReserved3", c_ushort), + ("_", U_VARIANT2) + ] + _fields_ = [("__VARIANT_NAME_2", __tagVARIANT), + ("decVal", DECIMAL)] + _anonymous_ = ["__VARIANT_NAME_2"] + _fields_ = [("__VARIANT_NAME_1", U_VARIANT1)] + _anonymous_ = ["__VARIANT_NAME_1"] + + def __init__(self, *args): + if args: + self.value = args[0] + + def __del__(self): + if self._b_needsfree_: + # XXX This does not work. _b_needsfree_ is never + # set because the buffer is internal to the object. + _VariantClear(self) + + def __repr__(self): + if self.vt & VT_BYREF: + return "VARIANT(vt=0x%x, byref(%r))" % (self.vt, self[0]) + return "VARIANT(vt=0x%x, %r)" % (self.vt, self.value) + + def from_param(cls, value): + if isinstance(value, cls): + return value + return cls(value) + from_param = classmethod(from_param) + + def __setitem__(self, index, value): + # This method allows to change the value of a + # (VT_BYREF|VT_xxx) variant in place. + if index != 0: + raise IndexError(index) + if not self.vt & VT_BYREF: + raise TypeError("set_byref requires a VT_BYREF VARIANT instance") + typ = _vartype_to_ctype[self.vt & ~VT_BYREF] + cast(self._.c_void_p, POINTER(typ))[0] = value + + # see also c:/sf/pywin32/com/win32com/src/oleargs.cpp 54 + def _set_value(self, value): + _VariantClear(self) + if value is None: + self.vt = VT_NULL + elif (hasattr(value, '__len__') and len(value) == 0 + and not isinstance(value, basestring)): + self.vt = VT_NULL + # since bool is a subclass of int, this check must come before + # the check for int + elif isinstance(value, bool): + self.vt = VT_BOOL + self._.VT_BOOL = value + elif isinstance(value, (int, c_int)): + self.vt = VT_I4 + self._.VT_I4 = value + elif isinstance(value, long): + u = self._ + # try VT_I4 first. + u.VT_I4 = value + if u.VT_I4 == value: + # it did work. + self.vt = VT_I4 + return + # try VT_UI4 next. + if value >= 0: + u.VT_UI4 = value + if u.VT_UI4 == value: + # did work. + self.vt = VT_UI4 + return + # try VT_I8 next. + if value >= 0: + u.VT_I8 = value + if u.VT_I8 == value: + # did work. + self.vt = VT_I8 + return + # try VT_UI8 next. + if value >= 0: + u.VT_UI8 = value + if u.VT_UI8 == value: + # did work. + self.vt = VT_UI8 + return + # VT_R8 is last resort. + self.vt = VT_R8 + u.VT_R8 = float(value) + elif isinstance(value, (float, c_double)): + self.vt = VT_R8 + self._.VT_R8 = value + elif isinstance(value, (str, unicode)): + self.vt = VT_BSTR + # do the c_wchar_p auto unicode conversion + self._.c_void_p = _SysAllocStringLen(value, len(value)) + elif isinstance(value, datetime.datetime): + delta = value - _com_null_date + # a day has 24 * 60 * 60 = 86400 seconds + com_days = delta.days + (delta.seconds + delta.microseconds * 1e-6) / 86400. + self.vt = VT_DATE + self._.VT_R8 = com_days + elif npsupport.isdatetime64(value): + com_days = value - npsupport.com_null_date64 + com_days /= npsupport.numpy.timedelta64(1, 'D') + self.vt = VT_DATE + self._.VT_R8 = com_days + elif decimal is not None and isinstance(value, decimal.Decimal): + self._.VT_CY = int(round(value * 10000)) + self.vt = VT_CY + elif isinstance(value, POINTER(IDispatch)): + CopyComPointer(value, byref(self._)) + self.vt = VT_DISPATCH + elif isinstance(value, POINTER(IUnknown)): + CopyComPointer(value, byref(self._)) + self.vt = VT_UNKNOWN + elif isinstance(value, (list, tuple)): + obj = _midlSAFEARRAY(VARIANT).create(value) + memmove(byref(self._), byref(obj), sizeof(obj)) + self.vt = VT_ARRAY | obj._vartype_ + elif isinstance(value, array.array): + vartype = _arraycode_to_vartype[value.typecode] + typ = _vartype_to_ctype[vartype] + obj = _midlSAFEARRAY(typ).create(value) + memmove(byref(self._), byref(obj), sizeof(obj)) + self.vt = VT_ARRAY | obj._vartype_ + elif npsupport.isndarray(value): + # Try to convert a simple array of basic types. + descr = value.dtype.descr[0][1] + typ = npsupport.numpy.ctypeslib._typecodes.get(descr) + if typ is None: + # Try for variant + obj = _midlSAFEARRAY(VARIANT).create(value) + else: + obj = _midlSAFEARRAY(typ).create(value) + memmove(byref(self._), byref(obj), sizeof(obj)) + self.vt = VT_ARRAY | obj._vartype_ + elif isinstance(value, Structure) and hasattr(value, "_recordinfo_"): + guids = value._recordinfo_ + from comtypes.typeinfo import GetRecordInfoFromGuids + ri = GetRecordInfoFromGuids(*guids) + self.vt = VT_RECORD + # Assigning a COM pointer to a structure field does NOT + # call AddRef(), have to call it manually: + ri.AddRef() + self._.pRecInfo = ri + self._.pvRecord = ri.RecordCreateCopy(byref(value)) + elif isinstance(getattr(value, "_comobj", None), POINTER(IDispatch)): + CopyComPointer(value._comobj, byref(self._)) + self.vt = VT_DISPATCH + elif isinstance(value, VARIANT): + _VariantCopy(self, value) + elif isinstance(value, c_ubyte): + self._.VT_UI1 = value + self.vt = VT_UI1 + elif isinstance(value, c_char): + self._.VT_UI1 = ord(value.value) + self.vt = VT_UI1 + elif isinstance(value, c_byte): + self._.VT_I1 = value + self.vt = VT_I1 + elif isinstance(value, c_ushort): + self._.VT_UI2 = value + self.vt = VT_UI2 + elif isinstance(value, c_short): + self._.VT_I2 = value + self.vt = VT_I2 + elif isinstance(value, c_uint): + self.vt = VT_UI4 + self._.VT_UI4 = value + elif isinstance(value, c_float): + self.vt = VT_R4 + self._.VT_R4 = value + elif isinstance(value, c_int64): + self.vt = VT_I8 + self._.VT_I8 = value + elif isinstance(value, c_uint64): + self.vt = VT_UI8 + self._.VT_UI8 = value + elif isinstance(value, _byref_type): + ref = value._obj + self._.c_void_p = addressof(ref) + self.__keepref = value + self.vt = _ctype_to_vartype[type(ref)] | VT_BYREF + elif isinstance(value, _Pointer): + ref = value.contents + self._.c_void_p = addressof(ref) + self.__keepref = value + self.vt = _ctype_to_vartype[type(ref)] | VT_BYREF + else: + raise TypeError("Cannot put %r in VARIANT" % value) + # buffer -> SAFEARRAY of VT_UI1 ? + + # c:/sf/pywin32/com/win32com/src/oleargs.cpp 197 + def _get_value(self, dynamic=False): + vt = self.vt + if vt in (VT_EMPTY, VT_NULL): + return None + elif vt == VT_I1: + return self._.VT_I1 + elif vt == VT_I2: + return self._.VT_I2 + elif vt == VT_I4: + return self._.VT_I4 + elif vt == VT_I8: + return self._.VT_I8 + elif vt == VT_UI8: + return self._.VT_UI8 + elif vt == VT_INT: + return self._.VT_INT + elif vt == VT_UI1: + return self._.VT_UI1 + elif vt == VT_UI2: + return self._.VT_UI2 + elif vt == VT_UI4: + return self._.VT_UI4 + elif vt == VT_UINT: + return self._.VT_UINT + elif vt == VT_R4: + return self._.VT_R4 + elif vt == VT_R8: + return self._.VT_R8 + elif vt == VT_BOOL: + return self._.VT_BOOL + elif vt == VT_BSTR: + return self._.bstrVal + elif vt == VT_DATE: + days = self._.VT_R8 + return datetime.timedelta(days=days) + _com_null_date + elif vt == VT_CY: + return self._.VT_CY / decimal.Decimal("10000") + elif vt == VT_UNKNOWN: + val = self._.c_void_p + if not val: + # We should/could return a NULL COM pointer. + # But the code generation must be able to construct one + # from the __repr__ of it. + return None # XXX? + ptr = cast(val, POINTER(IUnknown)) + # cast doesn't call AddRef (it should, imo!) + ptr.AddRef() + return ptr.__ctypes_from_outparam__() + elif vt == VT_DECIMAL: + return self.decVal.as_decimal() + elif vt == VT_DISPATCH: + val = self._.c_void_p + if not val: + # See above. + return None # XXX? + ptr = cast(val, POINTER(IDispatch)) + # cast doesn't call AddRef (it should, imo!) + ptr.AddRef() + if not dynamic: + return ptr.__ctypes_from_outparam__() + else: + from comtypes.client.dynamic import Dispatch + return Dispatch(ptr) + # see also c:/sf/pywin32/com/win32com/src/oleargs.cpp + elif self.vt & VT_BYREF: + return self + elif vt == VT_RECORD: + from comtypes.client import GetModule + from comtypes.typeinfo import IRecordInfo + + # Retrieving a COM pointer from a structure field does NOT + # call AddRef(), have to call it manually: + punk = self._.pRecInfo + punk.AddRef() + ri = punk.QueryInterface(IRecordInfo) + + # find typelib + tlib = ri.GetTypeInfo().GetContainingTypeLib()[0] + + # load typelib wrapper module + mod = GetModule(tlib) + # retrive the type and create an instance + value = getattr(mod, ri.GetName())() + # copy data into the instance + ri.RecordCopy(self._.pvRecord, byref(value)) + + return value + elif self.vt & VT_ARRAY: + typ = _vartype_to_ctype[self.vt & ~VT_ARRAY] + return cast(self._.pparray, _midlSAFEARRAY(typ)).unpack() + else: + raise NotImplementedError("typecode %d = 0x%x)" % (vt, vt)) + + def __getitem__(self, index): + if index != 0: + raise IndexError(index) + if self.vt == VT_BYREF|VT_VARIANT: + v = VARIANT() + # apparently VariantCopyInd doesn't work always with + # VT_BYREF|VT_VARIANT, so do it manually. + v = cast(self._.c_void_p, POINTER(VARIANT))[0] + return v.value + else: + v = VARIANT() + _VariantCopyInd(v, self) + return v.value + + +# these are missing: +## getter[VT_ERROR] +## getter[VT_ARRAY] +## getter[VT_BYREF|VT_UI1] +## getter[VT_BYREF|VT_I2] +## getter[VT_BYREF|VT_I4] +## getter[VT_BYREF|VT_R4] +## getter[VT_BYREF|VT_R8] +## getter[VT_BYREF|VT_BOOL] +## getter[VT_BYREF|VT_ERROR] +## getter[VT_BYREF|VT_CY] +## getter[VT_BYREF|VT_DATE] +## getter[VT_BYREF|VT_BSTR] +## getter[VT_BYREF|VT_UNKNOWN] +## getter[VT_BYREF|VT_DISPATCH] +## getter[VT_BYREF|VT_ARRAY] +## getter[VT_BYREF|VT_VARIANT] +## getter[VT_BYREF] +## getter[VT_BYREF|VT_DECIMAL] +## getter[VT_BYREF|VT_I1] +## getter[VT_BYREF|VT_UI2] +## getter[VT_BYREF|VT_UI4] +## getter[VT_BYREF|VT_INT] +## getter[VT_BYREF|VT_UINT] + + value = property(_get_value, _set_value) + + def __ctypes_from_outparam__(self): + # XXX Manual resource management, because of the VARIANT bug: + result = self.value + self.value = None + return result + + def ChangeType(self, typecode): + _VariantChangeType(self, + self, + 0, + typecode) + +VARIANT = tagVARIANT +VARIANTARG = VARIANT + +_oleaut32 = OleDLL("oleaut32") + +_VariantChangeType = _oleaut32.VariantChangeType +_VariantChangeType.argtypes = (POINTER(VARIANT), POINTER(VARIANT), c_ushort, VARTYPE) + +_VariantClear = _oleaut32.VariantClear +_VariantClear.argtypes = (POINTER(VARIANT),) + +_SysAllocStringLen = windll.oleaut32.SysAllocStringLen +_SysAllocStringLen.argtypes = c_wchar_p, c_uint +_SysAllocStringLen.restype = c_void_p + +_VariantCopy = _oleaut32.VariantCopy +_VariantCopy.argtypes = POINTER(VARIANT), POINTER(VARIANT) + +_VariantCopyInd = _oleaut32.VariantCopyInd +_VariantCopyInd.argtypes = POINTER(VARIANT), POINTER(VARIANT) + +# some commonly used VARIANT instances +VARIANT.null = VARIANT(None) +VARIANT.empty = VARIANT() +VARIANT.missing = v = VARIANT() +v.vt = VT_ERROR +v._.VT_I4 = 0x80020004L +del v + +_carg_obj = type(byref(c_int())) +from _ctypes import Array as _CArrayType + +@Patch(POINTER(VARIANT)) +class _(object): + # Override the default .from_param classmethod of POINTER(VARIANT). + # This allows to pass values which can be stored in VARIANTs as + # function parameters declared as POINTER(VARIANT). See + # InternetExplorer's Navigate2() method, or Word's Close() method, for + # examples. + def from_param(cls, arg): + # accept POINTER(VARIANT) instance + if isinstance(arg, POINTER(VARIANT)): + return arg + # accept byref(VARIANT) instance + if isinstance(arg, _carg_obj) and isinstance(arg._obj, VARIANT): + return arg + # accept VARIANT instance + if isinstance(arg, VARIANT): + return byref(arg) + if isinstance(arg, _CArrayType) and arg._type_ is VARIANT: + # accept array of VARIANTs + return arg + # anything else which can be converted to a VARIANT. + return byref(VARIANT(arg)) + from_param = classmethod(from_param) + + def __setitem__(self, index, value): + # This is to support the same sematics as a pointer instance: + # variant[0] = value + self[index].value = value + +################################################################ +# interfaces, structures, ... +class IEnumVARIANT(IUnknown): + _iid_ = GUID('{00020404-0000-0000-C000-000000000046}') + _idlflags_ = ['hidden'] + _dynamic = False + def __iter__(self): + return self + + def next(self): + item, fetched = self.Next(1) + if fetched: + return item + raise StopIteration + + def __getitem__(self, index): + self.Reset() + # Does not yet work. +## if isinstance(index, slice): +## self.Skip(index.start or 0) +## return self.Next(index.stop or sys.maxint) + self.Skip(index) + item, fetched = self.Next(1) + if fetched: + return item + raise IndexError + + def Next(self, celt): + fetched = c_ulong() + if celt == 1: + v = VARIANT() + self.__com_Next(celt, v, fetched) + return v._get_value(dynamic=self._dynamic), fetched.value + array = (VARIANT * celt)() + self.__com_Next(celt, array, fetched) + result = [v._get_value(dynamic=self._dynamic) for v in array[:fetched.value]] + for v in array: + v.value = None + return result + +IEnumVARIANT._methods_ = [ + COMMETHOD([], HRESULT, 'Next', + ( ['in'], c_ulong, 'celt' ), + ( ['out'], POINTER(VARIANT), 'rgvar' ), + ( ['out'], POINTER(c_ulong), 'pceltFetched' )), + COMMETHOD([], HRESULT, 'Skip', + ( ['in'], c_ulong, 'celt' )), + COMMETHOD([], HRESULT, 'Reset'), + COMMETHOD([], HRESULT, 'Clone', + ( ['out'], POINTER(POINTER(IEnumVARIANT)), 'ppenum' )), +] + + +##from _ctypes import VARIANT_set +##import new +##VARIANT.value = property(VARIANT._get_value, new.instancemethod(VARIANT_set, None, VARIANT)) + + +class tagEXCEPINFO(Structure): + def __repr__(self): + return "" % \ + ((self.wCode, self.bstrSource, self.bstrDescription, self.bstrHelpFile, self.dwHelpContext, + self.pfnDeferredFillIn, self.scode),) +tagEXCEPINFO._fields_ = [ + ('wCode', WORD), + ('wReserved', WORD), + ('bstrSource', BSTR), + ('bstrDescription', BSTR), + ('bstrHelpFile', BSTR), + ('dwHelpContext', DWORD), + ('pvReserved', c_void_p), +## ('pfnDeferredFillIn', WINFUNCTYPE(HRESULT, POINTER(tagEXCEPINFO))), + ('pfnDeferredFillIn', c_void_p), + ('scode', SCODE), +] +EXCEPINFO = tagEXCEPINFO + +class tagDISPPARAMS(Structure): + _fields_ = [ + # C:/Programme/gccxml/bin/Vc71/PlatformSDK/oaidl.h 696 + ('rgvarg', POINTER(VARIANTARG)), + ('rgdispidNamedArgs', POINTER(DISPID)), + ('cArgs', UINT), + ('cNamedArgs', UINT), + ] + def __del__(self): + if self._b_needsfree_: + for i in range(self.cArgs): + self.rgvarg[i].value = None +DISPPARAMS = tagDISPPARAMS + +DISPID_VALUE = 0 +DISPID_UNKNOWN = -1 +DISPID_PROPERTYPUT = -3 +DISPID_NEWENUM = -4 +DISPID_EVALUATE = -5 +DISPID_CONSTRUCTOR = -6 +DISPID_DESTRUCTOR = -7 +DISPID_COLLECT = -8 + +class IDispatch(IUnknown): + _iid_ = GUID("{00020400-0000-0000-C000-000000000046}") + _methods_ = [ + COMMETHOD([], HRESULT, 'GetTypeInfoCount', + (['out'], POINTER(UINT) ) ), + COMMETHOD([], HRESULT, 'GetTypeInfo', + (['in'], UINT, 'index'), + (['in'], LCID, 'lcid', 0), +## Normally, we would declare this parameter in this way: +## (['out'], POINTER(POINTER(ITypeInfo)) ) ), +## but we cannot import comtypes.typeinfo at the top level (recursive imports!). + (['out'], POINTER(POINTER(IUnknown)) ) ), + STDMETHOD(HRESULT, 'GetIDsOfNames', [POINTER(IID), POINTER(c_wchar_p), + UINT, LCID, POINTER(DISPID)]), + STDMETHOD(HRESULT, 'Invoke', [DISPID, POINTER(IID), LCID, WORD, + POINTER(DISPPARAMS), POINTER(VARIANT), + POINTER(EXCEPINFO), POINTER(UINT)]), + ] + + def GetTypeInfo(self, index, lcid=0): + """Return type information. Index 0 specifies typeinfo for IDispatch""" + import comtypes.typeinfo + result = self._GetTypeInfo(index, lcid) + return result.QueryInterface(comtypes.typeinfo.ITypeInfo) + + def GetIDsOfNames(self, *names, **kw): + """Map string names to integer ids.""" + lcid = kw.pop("lcid", 0) + assert not kw + arr = (c_wchar_p * len(names))(*names) + ids = (DISPID * len(names))() + self.__com_GetIDsOfNames(riid_null, arr, len(names), lcid, ids) + return ids[:] + + def _invoke(self, memid, invkind, lcid, *args): + var = VARIANT() + argerr = c_uint() + dp = DISPPARAMS() + + if args: + array = (VARIANT * len(args))() + + for i, a in enumerate(args[::-1]): + array[i].value = a + + dp.cArgs = len(args) + if invkind in (DISPATCH_PROPERTYPUT, DISPATCH_PROPERTYPUTREF): + dp.cNamedArgs = 1 + dp.rgdispidNamedArgs = pointer(DISPID(DISPID_PROPERTYPUT)) + dp.rgvarg = array + + self.__com_Invoke(memid, riid_null, lcid, invkind, + dp, var, None, argerr) + return var._get_value(dynamic=True) + + def Invoke(self, dispid, *args, **kw): + """Invoke a method or property.""" + + # Memory management in Dispatch::Invoke calls: + # http://msdn.microsoft.com/library/en-us/automat/htm/chap5_4x2q.asp + # Quote: + # The *CALLING* code is responsible for releasing all strings and + # objects referred to by rgvarg[ ] or placed in *pVarResult. + # + # For comtypes this is handled in DISPPARAMS.__del__ and VARIANT.__del__. + _invkind = kw.pop("_invkind", 1) # DISPATCH_METHOD + _lcid = kw.pop("_lcid", 0) + if kw: + raise ValueError("named parameters not yet implemented") + + result = VARIANT() + excepinfo = EXCEPINFO() + argerr = c_uint() + + if _invkind in (DISPATCH_PROPERTYPUT, DISPATCH_PROPERTYPUTREF): # propput + array = (VARIANT * len(args))() + + for i, a in enumerate(args[::-1]): + array[i].value = a + + dp = DISPPARAMS() + dp.cArgs = len(args) + dp.cNamedArgs = 1 + dp.rgvarg = array + dp.rgdispidNamedArgs = pointer(DISPID(DISPID_PROPERTYPUT)) + else: + array = (VARIANT * len(args))() + + for i, a in enumerate(args[::-1]): + array[i].value = a + + dp = DISPPARAMS() + dp.cArgs = len(args) + dp.cNamedArgs = 0 + dp.rgvarg = array + + try: + self.__com_Invoke(dispid, riid_null, _lcid, _invkind, byref(dp), + byref(result), byref(excepinfo), byref(argerr)) + except COMError, err: + (hresult, text, details) = err.args + if hresult == DISP_E_EXCEPTION: + details = (excepinfo.bstrDescription, excepinfo.bstrSource, + excepinfo.bstrHelpFile, excepinfo.dwHelpContext, + excepinfo.scode) + raise COMError(hresult, text, details) + elif hresult == DISP_E_PARAMNOTFOUND: + # MSDN says: You get the error DISP_E_PARAMNOTFOUND + # when you try to set a property and you have not + # initialized the cNamedArgs and rgdispidNamedArgs + # elements of your DISPPARAMS structure. + # + # So, this looks like a bug. + raise COMError(hresult, text, argerr.value) + elif hresult == DISP_E_TYPEMISMATCH: + # MSDN: One or more of the arguments could not be + # coerced. + # + # Hm, should we raise TypeError, or COMError? + raise COMError(hresult, text, + ("TypeError: Parameter %s" % (argerr.value + 1), + args)) + raise + return result._get_value(dynamic=True) + + # XXX Would separate methods for _METHOD, _PROPERTYGET and _PROPERTYPUT be better? + + +################################################################ +# safearrays +# XXX Only one-dimensional arrays are currently implemented + +# map ctypes types to VARTYPE values + +_arraycode_to_vartype = { + "d": VT_R8, + "f": VT_R4, + "l": VT_I4, + "i": VT_INT, + "h": VT_I2, + "b": VT_I1, + "I": VT_UINT, + "L": VT_UI4, + "H": VT_UI2, + "B": VT_UI1, + } + +_ctype_to_vartype = { + c_byte: VT_I1, + c_ubyte: VT_UI1, + + c_short: VT_I2, + c_ushort: VT_UI2, + + c_long: VT_I4, + c_ulong: VT_UI4, + + c_float: VT_R4, + c_double: VT_R8, + + c_longlong: VT_I8, + c_ulonglong: VT_UI8, + + VARIANT_BOOL: VT_BOOL, + + BSTR: VT_BSTR, + VARIANT: VT_VARIANT, + + # SAFEARRAY(VARIANT *) + # + # It is unlear to me if this is allowed or not. Apparently there + # are typelibs that define such an argument type, but it may be + # that these are buggy. + # + # Point is that SafeArrayCreateEx(VT_VARIANT|VT_BYREF, ..) fails. + # The MSDN docs for SafeArrayCreate() have a notice that neither + # VT_ARRAY not VT_BYREF may be set, this notice is missing however + # for SafeArrayCreateEx(). + # + # We have this code here to make sure that comtypes can import + # such a typelib, although calling ths method will fail because + # such an array cannot be created. + POINTER(VARIANT): VT_BYREF|VT_VARIANT, + + # This is needed to import Esri ArcObjects (esriSystem.olb). + POINTER(BSTR): VT_BYREF|VT_BSTR, + + # These are not yet implemented: +## POINTER(IUnknown): VT_UNKNOWN, +## POINTER(IDispatch): VT_DISPATCH, + } + +_vartype_to_ctype = {} +for c, v in _ctype_to_vartype.iteritems(): + _vartype_to_ctype[v] = c +_vartype_to_ctype[VT_INT] = _vartype_to_ctype[VT_I4] +_vartype_to_ctype[VT_UINT] = _vartype_to_ctype[VT_UI4] +_ctype_to_vartype[c_char] = VT_UI1 + + + +try: + from comtypes.safearray import _midlSAFEARRAY +except (ImportError, AttributeError): + pass diff --git a/tools/comtypes/comtypes/client/__init__.py b/tools/comtypes/comtypes/client/__init__.py new file mode 100644 index 00000000000000..d9216ae77d75ce --- /dev/null +++ b/tools/comtypes/comtypes/client/__init__.py @@ -0,0 +1,266 @@ +'''comtypes.client - High level client level COM support package. +''' + +################################################################ +# +# TODO: +# +# - refactor some code into modules +# +################################################################ + +import sys, os +import ctypes + +import comtypes +from comtypes.hresult import * +import comtypes.automation +import comtypes.typeinfo +import comtypes.client.dynamic + +from comtypes.client._events import GetEvents, ShowEvents, PumpEvents +from comtypes.client._generate import GetModule + +import logging +logger = logging.getLogger(__name__) + +__all__ = ["CreateObject", "GetActiveObject", "CoGetObject", + "GetEvents", "ShowEvents", "PumpEvents", "GetModule", + "GetClassObject"] + +from comtypes.client._code_cache import _find_gen_dir + +gen_dir = _find_gen_dir() +import comtypes.gen + +### for testing +##gen_dir = None + +def wrap_outparam(punk): + logger.debug("wrap_outparam(%s)", punk) + if not punk: + return None + if punk.__com_interface__ == comtypes.automation.IDispatch: + return GetBestInterface(punk) + return punk + +def GetBestInterface(punk): + """Try to QueryInterface a COM pointer to the 'most useful' + interface. + + Get type information for the provided object, either via + IDispatch.GetTypeInfo(), or via IProvideClassInfo.GetClassInfo(). + Generate a wrapper module for the typelib, and QI for the + interface found. + """ + if not punk: # NULL COM pointer + return punk # or should we return None? + # find the typelib and the interface name + logger.debug("GetBestInterface(%s)", punk) + try: + try: + pci = punk.QueryInterface(comtypes.typeinfo.IProvideClassInfo) + logger.debug("Does implement IProvideClassInfo") + except comtypes.COMError: + # Some COM objects support IProvideClassInfo2, but not IProvideClassInfo. + # These objects are broken, but we support them anyway. + logger.debug("Does NOT implement IProvideClassInfo, trying IProvideClassInfo2") + pci = punk.QueryInterface(comtypes.typeinfo.IProvideClassInfo2) + logger.debug("Does implement IProvideClassInfo2") + tinfo = pci.GetClassInfo() # TypeInfo for the CoClass + # find the interface marked as default + ta = tinfo.GetTypeAttr() + for index in range(ta.cImplTypes): + if tinfo.GetImplTypeFlags(index) == 1: + break + else: + if ta.cImplTypes != 1: + # Hm, should we use dynamic now? + raise TypeError("No default interface found") + # Only one interface implemented, use that (even if + # not marked as default). + index = 0 + href = tinfo.GetRefTypeOfImplType(index) + tinfo = tinfo.GetRefTypeInfo(href) + except comtypes.COMError: + logger.debug("Does NOT implement IProvideClassInfo/IProvideClassInfo2") + try: + pdisp = punk.QueryInterface(comtypes.automation.IDispatch) + except comtypes.COMError: + logger.debug("No Dispatch interface: %s", punk) + return punk + try: + tinfo = pdisp.GetTypeInfo(0) + except comtypes.COMError: + pdisp = comtypes.client.dynamic.Dispatch(pdisp) + logger.debug("IDispatch.GetTypeInfo(0) failed: %s" % pdisp) + return pdisp + typeattr = tinfo.GetTypeAttr() + logger.debug("Default interface is %s", typeattr.guid) + try: + punk.QueryInterface(comtypes.IUnknown, typeattr.guid) + except comtypes.COMError: + logger.debug("Does not implement default interface, returning dynamic object") + return comtypes.client.dynamic.Dispatch(punk) + + itf_name = tinfo.GetDocumentation(-1)[0] # interface name + tlib = tinfo.GetContainingTypeLib()[0] # typelib + + # import the wrapper, generating it on demand + mod = GetModule(tlib) + # Python interface class + interface = getattr(mod, itf_name) + logger.debug("Implements default interface from typeinfo %s", interface) + # QI for this interface + # XXX + # What to do if this fails? + # In the following example the engine.Eval() call returns + # such an object. + # + # engine = CreateObject("MsScriptControl.ScriptControl") + # engine.Language = "JScript" + # engine.Eval("[1, 2, 3]") + # + # 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 +# backwards compatibility: +wrap = GetBestInterface + +# Should we do this for POINTER(IUnknown) also? +ctypes.POINTER(comtypes.automation.IDispatch).__ctypes_from_outparam__ = wrap_outparam + +################################################################ +# +# Typelib constants +# +class Constants(object): + """This class loads the type library from the supplied object, + then exposes constants in the type library as attributes.""" + def __init__(self, obj): + obj = obj.QueryInterface(comtypes.automation.IDispatch) + tlib, index = obj.GetTypeInfo(0).GetContainingTypeLib() + self.tcomp = tlib.GetTypeComp() + + def __getattr__(self, name): + try: + kind, desc = self.tcomp.Bind(name) + except (WindowsError, comtypes.COMError): + raise AttributeError(name) + if kind != "variable": + raise AttributeError(name) + return desc._.lpvarValue[0].value + + def _bind_type(self, name): + return self.tcomp.BindType(name) + +################################################################ +# +# Object creation +# +def GetActiveObject(progid, interface=None, dynamic=False): + """Return a pointer to a running COM object that has been + registered with COM. + + 'progid' may be a string like "Excel.Application", + a string specifying a clsid, a GUID instance, or an object with + a _clsid_ attribute which should be any of the above. + 'interface' allows to force a certain interface. + 'dynamic=True' will return a dynamic dispatch object. + """ + clsid = comtypes.GUID.from_progid(progid) + if dynamic: + if interface is not None: + raise ValueError("interface and dynamic are mutually exclusive") + interface = comtypes.automation.IDispatch + elif interface is None: + interface = getattr(progid, "_com_interfaces_", [None])[0] + obj = comtypes.GetActiveObject(clsid, interface=interface) + if dynamic: + return comtypes.client.dynamic.Dispatch(obj) + return _manage(obj, clsid, interface=interface) + +def _manage(obj, clsid, interface): + obj.__dict__['__clsid'] = str(clsid) + if interface is None: + obj = GetBestInterface(obj) + return obj + +def GetClassObject(progid, + clsctx=None, + pServerInfo=None, + interface=None): + """Create and return the class factory for a COM object. + + 'clsctx' specifies how to create the object, use the CLSCTX_... constants. + 'pServerInfo', if used, must be a pointer to a comtypes.COSERVERINFO instance + 'interface' may be used to request an interface other than IClassFactory + """ + clsid = comtypes.GUID.from_progid(progid) + return comtypes.CoGetClassObject(clsid, + clsctx, pServerInfo, interface) + +def CreateObject(progid, # which object to create + clsctx=None, # how to create the object + machine=None, # where to create the object + interface=None, # the interface we want + dynamic=False, # use dynamic dispatch + pServerInfo=None): # server info struct for remoting + """Create a COM object from 'progid', and try to QueryInterface() + it to the most useful interface, generating typelib support on + demand. A pointer to this interface is returned. + + 'progid' may be a string like "InternetExplorer.Application", + a string specifying a clsid, a GUID instance, or an object with + a _clsid_ attribute which should be any of the above. + 'clsctx' specifies how to create the object, use the CLSCTX_... constants. + 'machine' allows to specify a remote machine to create the object on. + 'interface' allows to force a certain interface + 'dynamic=True' will return a dynamic dispatch object + 'pServerInfo', if used, must be a pointer to a comtypes.COSERVERINFO instance + This supercedes 'machine'. + + You can also later request to receive events with GetEvents(). + """ + clsid = comtypes.GUID.from_progid(progid) + logger.debug("%s -> %s", progid, clsid) + if dynamic: + if interface: + raise ValueError("interface and dynamic are mutually exclusive") + interface = comtypes.automation.IDispatch + elif interface is None: + interface = getattr(progid, "_com_interfaces_", [None])[0] + if machine is None and pServerInfo is None: + logger.debug("CoCreateInstance(%s, clsctx=%s, interface=%s)", + clsid, clsctx, interface) + obj = comtypes.CoCreateInstance(clsid, clsctx=clsctx, interface=interface) + else: + logger.debug("CoCreateInstanceEx(%s, clsctx=%s, interface=%s, machine=%s,\ + pServerInfo=%s)", + clsid, clsctx, interface, machine, pServerInfo) + if machine is not None and pServerInfo is not None: + msg = "You can notset both the machine name and server info." + raise ValueError(msg) + obj = comtypes.CoCreateInstanceEx(clsid, clsctx=clsctx, + interface=interface, machine=machine, pServerInfo=pServerInfo) + if dynamic: + return comtypes.client.dynamic.Dispatch(obj) + return _manage(obj, clsid, interface=interface) + +def CoGetObject(displayname, interface=None, dynamic=False): + """Create an object by calling CoGetObject(displayname). + + Additional parameters have the same meaning as in CreateObject(). + """ + if dynamic: + if interface is not None: + raise ValueError("interface and dynamic are mutually exclusive") + interface = comtypes.automation.IDispatch + punk = comtypes.CoGetObject(displayname, interface) + if dynamic: + return comtypes.client.dynamic.Dispatch(punk) + return _manage(punk, + clsid=None, + interface=interface) diff --git a/tools/comtypes/comtypes/client/_code_cache.py b/tools/comtypes/comtypes/client/_code_cache.py new file mode 100644 index 00000000000000..0275381924ab2a --- /dev/null +++ b/tools/comtypes/comtypes/client/_code_cache.py @@ -0,0 +1,130 @@ +"""comtypes.client._code_cache helper module. + +The main function is _find_gen_dir(), which on-demand creates the +comtypes.gen package and returns a directory where generated code can +be written to. +""" +import ctypes, logging, os, sys, tempfile, types +logger = logging.getLogger(__name__) + +def _find_gen_dir(): + """Create, if needed, and return a directory where automatically + generated modules will be created. + + Usually, this is the directory 'Lib/site-packages/comtypes/gen'. + + If the above directory cannot be created, or if it is not a + directory in the file system (when comtypes is imported from a + zip-archive or a zipped egg), or if the current user cannot create + files in this directory, an additional directory is created and + appended to comtypes.gen.__path__ . + + For a Python script using comtypes, the additional directory is + '%APPDATA%\\Python\Python25\comtypes_cache'. + + For an executable frozen with py2exe, the additional directory is + '%TEMP%\comtypes_cache\-25'. + """ + _create_comtypes_gen_package() + from comtypes import gen + if not _is_writeable(gen.__path__): + # check type of executable image to determine a subdirectory + # where generated modules are placed. + ftype = getattr(sys, "frozen", None) + version_str = "%d%d" % sys.version_info[:2] + if ftype == None: + # Python script + subdir = r"Python\Python%s\comtypes_cache" % version_str + basedir = _get_appdata_dir() + + elif ftype == "dll": + # dll created with py2exe + path = _get_module_filename(sys.frozendllhandle) + base = os.path.splitext(os.path.basename(path))[0] + subdir = r"comtypes_cache\%s-%s" % (base, version_str) + basedir = tempfile.gettempdir() + + else: # ftype in ('windows_exe', 'console_exe') + # exe created by py2exe + base = os.path.splitext(os.path.basename(sys.executable))[0] + subdir = r"comtypes_cache\%s-%s" % (base, version_str) + basedir = tempfile.gettempdir() + + gen_dir = os.path.join(basedir, subdir) + if not os.path.exists(gen_dir): + logger.info("Creating writeable comtypes cache directory: '%s'", gen_dir) + os.makedirs(gen_dir) + gen.__path__.append(gen_dir) + result = os.path.abspath(gen.__path__[-1]) + logger.info("Using writeable comtypes cache directory: '%s'", result) + return result + +################################################################ + +if os.name == "ce": + SHGetSpecialFolderPath = ctypes.OleDLL("coredll").SHGetSpecialFolderPath + GetModuleFileName = ctypes.WinDLL("coredll").GetModuleFileNameW +else: + SHGetSpecialFolderPath = ctypes.OleDLL("shell32.dll").SHGetSpecialFolderPathW + GetModuleFileName = ctypes.WinDLL("kernel32.dll").GetModuleFileNameW +SHGetSpecialFolderPath.argtypes = [ctypes.c_ulong, ctypes.c_wchar_p, + ctypes.c_int, ctypes.c_int] +GetModuleFileName.restype = ctypes.c_ulong +GetModuleFileName.argtypes = [ctypes.c_ulong, ctypes.c_wchar_p, ctypes.c_ulong] + +CSIDL_APPDATA = 26 +MAX_PATH = 260 + +def _create_comtypes_gen_package(): + """Import (creating it if needed) the comtypes.gen package.""" + try: + import comtypes.gen + logger.info("Imported existing %s", comtypes.gen) + except ImportError: + import comtypes + logger.info("Could not import comtypes.gen, trying to create it.") + try: + comtypes_path = os.path.abspath(os.path.join(comtypes.__path__[0], "gen")) + if not os.path.isdir(comtypes_path): + os.mkdir(comtypes_path) + logger.info("Created comtypes.gen directory: '%s'", comtypes_path) + comtypes_init = os.path.join(comtypes_path, "__init__.py") + if not os.path.exists(comtypes_init): + logger.info("Writing __init__.py file: '%s'", comtypes_init) + ofi = open(comtypes_init, "w") + ofi.write("# comtypes.gen package, directory for generated files.\n") + ofi.close() + except (OSError, IOError), details: + logger.info("Creating comtypes.gen package failed: %s", details) + module = sys.modules["comtypes.gen"] = types.ModuleType("comtypes.gen") + comtypes.gen = module + comtypes.gen.__path__ = [] + logger.info("Created a memory-only package.") + +def _is_writeable(path): + """Check if the first part, if any, on path is a directory in + which we can create files.""" + if not path: + return False + try: + tempfile.TemporaryFile(dir=path[0]) + except (OSError, IOError), details: + logger.debug("Path is unwriteable: %s", details) + return False + return True + +def _get_module_filename(hmodule): + """Call the Windows GetModuleFileName function which determines + the path from a module handle.""" + path = ctypes.create_unicode_buffer(MAX_PATH) + if GetModuleFileName(hmodule, path, MAX_PATH): + return path.value + raise ctypes.WinError() + +def _get_appdata_dir(): + """Return the 'file system directory that serves as a common + repository for application-specific data' - CSIDL_APPDATA""" + path = ctypes.create_unicode_buffer(MAX_PATH) + # get u'C:\\Documents and Settings\\\\Application Data' + SHGetSpecialFolderPath(0, path, CSIDL_APPDATA, True) + return path.value diff --git a/tools/comtypes/comtypes/client/_events.py b/tools/comtypes/comtypes/client/_events.py new file mode 100644 index 00000000000000..12264dc9a4dddd --- /dev/null +++ b/tools/comtypes/comtypes/client/_events.py @@ -0,0 +1,285 @@ +import ctypes +import traceback +import comtypes +import comtypes.hresult +import comtypes.automation +import comtypes.typeinfo +import comtypes.connectionpoints +from comtypes.client._generate import GetModule +import logging +logger = logging.getLogger(__name__) + +class _AdviseConnection(object): + def __init__(self, source, interface, receiver): + self.cp = None + self.cookie = None + self.receiver = None + self._connect(source, interface, receiver) + + def _connect(self, source, interface, receiver): + cpc = source.QueryInterface(comtypes.connectionpoints.IConnectionPointContainer) + self.cp = cpc.FindConnectionPoint(ctypes.byref(interface._iid_)) + logger.debug("Start advise %s", interface) + self.cookie = self.cp.Advise(receiver) + self.receiver = receiver + + def disconnect(self): + if self.cookie: + self.cp.Unadvise(self.cookie) + logger.debug("Unadvised %s", self.cp) + self.cp = None + self.cookie = None + del self.receiver + + def __del__(self): + try: + if self.cookie is not None: + self.cp.Unadvise(self.cookie) + except (comtypes.COMError, WindowsError): + # Are we sure we want to ignore errors here? + pass + +def FindOutgoingInterface(source): + """XXX Describe the strategy that is used...""" + # If the COM object implements IProvideClassInfo2, it is easy to + # find the default outgoing interface. + try: + pci = source.QueryInterface(comtypes.typeinfo.IProvideClassInfo2) + guid = pci.GetGUID(1) + except comtypes.COMError: + pass + else: + # another try: block needed? + try: + interface = comtypes.com_interface_registry[str(guid)] + except KeyError: + tinfo = pci.GetClassInfo() + tlib, index = tinfo.GetContainingTypeLib() + GetModule(tlib) + interface = comtypes.com_interface_registry[str(guid)] + logger.debug("%s using sinkinterface %s", source, interface) + return interface + + # If we can find the CLSID of the COM object, we can look for a + # registered outgoing interface (__clsid has been set by + # comtypes.client): + clsid = source.__dict__.get('__clsid') + try: + interface = comtypes.com_coclass_registry[clsid]._outgoing_interfaces_[0] + except KeyError: + pass + else: + logger.debug("%s using sinkinterface from clsid %s", source, interface) + return interface + +## interface = find_single_connection_interface(source) +## if interface: +## return interface + + raise TypeError("cannot determine source interface") + +def find_single_connection_interface(source): + # Enumerate the connection interfaces. If we find a single one, + # return it, if there are more, we give up since we cannot + # determine which one to use. + cpc = source.QueryInterface(comtypes.connectionpoints.IConnectionPointContainer) + enum = cpc.EnumConnectionPoints() + iid = enum.next().GetConnectionInterface() + try: + enum.next() + except StopIteration: + try: + interface = comtypes.com_interface_registry[str(iid)] + except KeyError: + return None + else: + logger.debug("%s using sinkinterface from iid %s", source, interface) + return interface + else: + logger.debug("%s has more than one connection point", source) + + return None + +def report_errors(func): + # This decorator preserves parts of the decorated function + # signature, so that the comtypes special-casing for the 'this' + # parameter still works. + if func.func_code.co_varnames[:2] == ('self', 'this'): + def error_printer(self, this, *args, **kw): + try: + return func(self, this, *args, **kw) + except: + traceback.print_exc() + raise + else: + def error_printer(*args, **kw): + try: + return func(*args, **kw) + except: + traceback.print_exc() + raise + return error_printer + +from comtypes._comobject import _MethodFinder +class _SinkMethodFinder(_MethodFinder): + """Special MethodFinder, for finding and decorating event handler + methods. Looks for methods on two objects. Also decorates the + event handlers with 'report_errors' which will print exceptions in + event handlers. + """ + def __init__(self, inst, sink): + super(_SinkMethodFinder, self).__init__(inst) + self.sink = sink + + def find_method(self, fq_name, mthname): + impl = self._find_method(fq_name, mthname) + # Caller of this method catches AttributeError, + # so we need to be careful in the following code + # not to raise one... + try: + # impl is a bound method, dissect it... + im_self, im_func = impl.im_self, impl.im_func + # decorate it with an error printer... + method = report_errors(im_func) + # and make a new bound method from it again. + return comtypes.instancemethod(method, + im_self, + type(im_self)) + except AttributeError, details: + raise RuntimeError(details) + + def _find_method(self, fq_name, mthname): + try: + return super(_SinkMethodFinder, self).find_method(fq_name, mthname) + except AttributeError: + try: + return getattr(self.sink, fq_name) + except AttributeError: + return getattr(self.sink, mthname) + +def CreateEventReceiver(interface, handler): + + class Sink(comtypes.COMObject): + _com_interfaces_ = [interface] + + def _get_method_finder_(self, itf): + # Use a special MethodFinder that will first try 'self', + # then the sink. + return _SinkMethodFinder(self, handler) + + sink = Sink() + + # Since our Sink object doesn't have typeinfo, it needs a + # _dispimpl_ dictionary to dispatch events received via Invoke. + if issubclass(interface, comtypes.automation.IDispatch) \ + and not hasattr(sink, "_dispimpl_"): + finder = sink._get_method_finder_(interface) + dispimpl = sink._dispimpl_ = {} + for m in interface._methods_: + restype, mthname, argtypes, paramflags, idlflags, helptext = m + # Can dispid be at a different index? Should check code generator... + # ...but hand-written code should also work... + dispid = idlflags[0] + impl = finder.get_impl(interface, mthname, paramflags, idlflags) + # XXX Wouldn't work for 'propget', 'propput', 'propputref' + # methods - are they allowed on event interfaces? + dispimpl[(dispid, comtypes.automation.DISPATCH_METHOD)] = impl + + return sink + +def GetEvents(source, sink, interface=None): + """Receive COM events from 'source'. Events will call methods on + the 'sink' object. 'interface' is the source interface to use. + """ + # When called from CreateObject, the sourceinterface has already + # been determined by the coclass. Otherwise, the only thing that + # makes sense is to use IProvideClassInfo2 to get the default + # source interface. + if interface is None: + interface = FindOutgoingInterface(source) + + rcv = CreateEventReceiver(interface, sink) + return _AdviseConnection(source, interface, rcv) + +class EventDumper(object): + """Universal sink for COM events.""" + + def __getattr__(self, name): + "Create event handler methods on demand" + if name.startswith("__") and name.endswith("__"): + raise AttributeError(name) + print "# event found:", name + def handler(self, this, *args, **kw): + # XXX handler is called with 'this'. Should we really print "None" instead? + args = (None,) + args + print "Event %s(%s)" % (name, ", ".join([repr(a) for a in args])) + return comtypes.instancemethod(handler, self, EventDumper) + +def ShowEvents(source, interface=None): + """Receive COM events from 'source'. A special event sink will be + used that first prints the names of events that are found in the + outgoing interface, and will also print out the events when they + are fired. + """ + return comtypes.client.GetEvents(source, sink=EventDumper(), interface=interface) + +# This type is used inside 'PumpEvents', but if we create the type +# afresh each time 'PumpEvents' is called we end up creating cyclic +# garbage for each call. So we define it here instead. +_handles_type = ctypes.c_void_p * 1 + +def PumpEvents(timeout): + """This following code waits for 'timeout' seconds in the way + required for COM, internally doing the correct things depending + on the COM appartment of the current thread. It is possible to + terminate the message loop by pressing CTRL+C, which will raise + a KeyboardInterrupt. + """ + # XXX Should there be a way to pass additional event handles which + # can terminate this function? + + # XXX XXX XXX + # + # It may be that I misunderstood the CoWaitForMultipleHandles + # function. Is a message loop required in a STA? Seems so... + # + # MSDN says: + # + # If the caller resides in a single-thread apartment, + # CoWaitForMultipleHandles enters the COM modal loop, and the + # thread's message loop will continue to dispatch messages using + # the thread's message filter. If no message filter is registered + # for the thread, the default COM message processing is used. + # + # If the calling thread resides in a multithread apartment (MTA), + # CoWaitForMultipleHandles calls the Win32 function + # MsgWaitForMultipleObjects. + + hevt = ctypes.windll.kernel32.CreateEventA(None, True, False, None) + handles = _handles_type(hevt) + RPC_S_CALLPENDING = -2147417835 + +## @ctypes.WINFUNCTYPE(ctypes.c_int, ctypes.c_uint) + def HandlerRoutine(dwCtrlType): + if dwCtrlType == 0: # CTRL+C + ctypes.windll.kernel32.SetEvent(hevt) + return 1 + return 0 + HandlerRoutine = ctypes.WINFUNCTYPE(ctypes.c_int, ctypes.c_uint)(HandlerRoutine) + + ctypes.windll.kernel32.SetConsoleCtrlHandler(HandlerRoutine, 1) + + try: + try: + res = ctypes.oledll.ole32.CoWaitForMultipleHandles(0, + int(timeout * 1000), + len(handles), handles, + ctypes.byref(ctypes.c_ulong())) + except WindowsError, details: + if details.args[0] != RPC_S_CALLPENDING: # timeout expired + raise + else: + raise KeyboardInterrupt + finally: + ctypes.windll.kernel32.CloseHandle(hevt) + ctypes.windll.kernel32.SetConsoleCtrlHandler(HandlerRoutine, 0) diff --git a/tools/comtypes/comtypes/client/_generate.py b/tools/comtypes/comtypes/client/_generate.py new file mode 100644 index 00000000000000..f4f5fcfda25017 --- /dev/null +++ b/tools/comtypes/comtypes/client/_generate.py @@ -0,0 +1,195 @@ +import types +import os +import sys +import comtypes.client +import comtypes.tools.codegenerator + +import logging +logger = logging.getLogger(__name__) + +__verbose__ = __debug__ + +if os.name == "ce": + # Windows CE has a hard coded PATH + # XXX Additionally there's an OEM path, plus registry settings. + # We don't currently use the latter. + PATH = ["\\Windows", "\\"] +else: + PATH = os.environ["PATH"].split(os.pathsep) + +def _my_import(fullname): + # helper function to import dotted modules + import comtypes.gen + if comtypes.client.gen_dir \ + and comtypes.client.gen_dir not in comtypes.gen.__path__: + comtypes.gen.__path__.append(comtypes.client.gen_dir) + return __import__(fullname, globals(), locals(), ['DUMMY']) + +def _name_module(tlib): + # Determine the name of a typelib wrapper module. + libattr = tlib.GetLibAttr() + modname = "_%s_%s_%s_%s" % \ + (str(libattr.guid)[1:-1].replace("-", "_"), + libattr.lcid, + libattr.wMajorVerNum, + libattr.wMinorVerNum) + return "comtypes.gen." + modname + +def GetModule(tlib): + """Create a module wrapping a COM typelibrary on demand. + + 'tlib' must be an ITypeLib COM pointer instance, the pathname of a + type library, or a tuple/list specifying the arguments to a + comtypes.typeinfo.LoadRegTypeLib call: + + (libid, wMajorVerNum, wMinorVerNum, lcid=0) + + Or it can be an object with _reg_libid_ and _reg_version_ + attributes. + + A relative pathname is interpreted as relative to the callers + __file__, if this exists. + + This function determines the module name from the typelib + attributes, then tries to import it. If that fails because the + module doesn't exist, the module is generated into the + comtypes.gen package. + + It is possible to delete the whole comtypes\gen directory to + remove all generated modules, the directory and the __init__.py + file in it will be recreated when needed. + + If comtypes.gen __path__ is not a directory (in a frozen + executable it lives in a zip archive), generated modules are only + created in memory without writing them to the file system. + + Example: + + GetModule("shdocvw.dll") + + would create modules named + + comtypes.gen._EAB22AC0_30C1_11CF_A7EB_0000C05BAE0B_0_1_1 + comtypes.gen.SHDocVw + + containing the Python wrapper code for the type library used by + Internet Explorer. The former module contains all the code, the + latter is a short stub loading the former. + """ + pathname = None + if isinstance(tlib, basestring): + # pathname of type library + if not os.path.isabs(tlib): + # If a relative pathname is used, we try to interpret + # this pathname as relative to the callers __file__. + frame = sys._getframe(1) + _file_ = frame.f_globals.get("__file__", None) + if _file_ is not None: + directory = os.path.dirname(os.path.abspath(_file_)) + abspath = os.path.normpath(os.path.join(directory, tlib)) + # If the file does exist, we use it. Otherwise it may + # still be that the file is on Windows search path for + # typelibs, and we leave the pathname alone. + if os.path.isfile(abspath): + tlib = abspath + logger.debug("GetModule(%s)", tlib) + pathname = tlib + tlib = comtypes.typeinfo.LoadTypeLibEx(tlib) + elif isinstance(tlib, (tuple, list)): + # sequence containing libid and version numbers + logger.debug("GetModule(%s)", (tlib,)) + tlib = comtypes.typeinfo.LoadRegTypeLib(comtypes.GUID(tlib[0]), *tlib[1:]) + elif hasattr(tlib, "_reg_libid_"): + # a COMObject implementation + logger.debug("GetModule(%s)", tlib) + tlib = comtypes.typeinfo.LoadRegTypeLib(comtypes.GUID(tlib._reg_libid_), + *tlib._reg_version_) + else: + # an ITypeLib pointer + logger.debug("GetModule(%s)", tlib.GetLibAttr()) + + # create and import the module + mod = _CreateWrapper(tlib, pathname) + try: + modulename = tlib.GetDocumentation(-1)[0] + except comtypes.COMError: + return mod + if modulename is None: + return mod + if sys.version_info < (3, 0): + modulename = modulename.encode("mbcs") + + # create and import the friendly-named module + try: + mod = _my_import("comtypes.gen." + modulename) + except Exception, details: + logger.info("Could not import comtypes.gen.%s: %s", modulename, details) + else: + return mod + # the module is always regenerated if the import fails + if __verbose__: + print "# Generating comtypes.gen.%s" % modulename + # determine the Python module name + fullname = _name_module(tlib) + modname = fullname.split(".")[-1] + code = "from comtypes.gen import %s\nglobals().update(%s.__dict__)\n" % (modname, modname) + code += "__name__ = 'comtypes.gen.%s'" % modulename + if comtypes.client.gen_dir is None: + mod = types.ModuleType("comtypes.gen." + modulename) + mod.__file__ = os.path.join(os.path.abspath(comtypes.gen.__path__[0]), + "") + exec code in mod.__dict__ + sys.modules["comtypes.gen." + modulename] = mod + setattr(comtypes.gen, modulename, mod) + return mod + # create in file system, and import it + ofi = open(os.path.join(comtypes.client.gen_dir, modulename + ".py"), "w") + ofi.write(code) + ofi.close() + return _my_import("comtypes.gen." + modulename) + +def _CreateWrapper(tlib, pathname=None): + # helper which creates and imports the real typelib wrapper module. + fullname = _name_module(tlib) + try: + return sys.modules[fullname] + except KeyError: + pass + + modname = fullname.split(".")[-1] + + try: + return _my_import(fullname) + except Exception, details: + logger.info("Could not import %s: %s", fullname, details) + + # 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: + import cStringIO + ofi = cStringIO.StringIO() + else: + ofi = open(os.path.join(comtypes.client.gen_dir, modname + ".py"), "w") + # XXX use logging! + if __verbose__: + print "# Generating comtypes.gen.%s" % modname + generate_module(tlib, ofi, pathname) + + if comtypes.client.gen_dir is None: + code = ofi.getvalue() + mod = types.ModuleType(fullname) + mod.__file__ = os.path.join(os.path.abspath(comtypes.gen.__path__[0]), + "") + exec code in mod.__dict__ + sys.modules[fullname] = mod + setattr(comtypes.gen, modname, mod) + else: + ofi.close() + mod = _my_import(fullname) + return mod + +################################################################ + +if __name__ == "__main__": + # When started as script, generate typelib wrapper from .tlb file. + GetModule(sys.argv[1]) diff --git a/tools/comtypes/comtypes/client/dynamic.py b/tools/comtypes/comtypes/client/dynamic.py new file mode 100644 index 00000000000000..4ca216f49e2e63 --- /dev/null +++ b/tools/comtypes/comtypes/client/dynamic.py @@ -0,0 +1,165 @@ +import ctypes +import comtypes.automation +import comtypes.typeinfo +import comtypes.client +import comtypes.client.lazybind + +from comtypes import COMError, IUnknown, _is_object +import comtypes.hresult as hres + +# 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 = [ + hres.DISP_E_MEMBERNOTFOUND, + hres.DISP_E_BADPARAMCOUNT, + hres.DISP_E_PARAMNOTOPTIONAL, + hres.DISP_E_TYPEMISMATCH, + hres.E_INVALIDARG, +] + +def Dispatch(obj): + # Wrap an object in a Dispatch instance, exposing methods and properties + # via fully dynamic dispatch + if isinstance(obj, _Dispatch): + return obj + if isinstance(obj, ctypes.POINTER(comtypes.automation.IDispatch)): + try: + tinfo = obj.GetTypeInfo(0) + except (comtypes.COMError, WindowsError): + return _Dispatch(obj) + return comtypes.client.lazybind.Dispatch(obj, tinfo) + return obj + +class MethodCaller: + # Wrong name: does not only call methods but also handle + # property accesses. + def __init__(self, _id, _obj): + self._id = _id + self._obj = _obj + + def __call__(self, *args): + return self._obj._comobj.Invoke(self._id, *args) + + def __getitem__(self, *args): + return self._obj._comobj.Invoke(self._id, *args, + **dict(_invkind=comtypes.automation.DISPATCH_PROPERTYGET)) + + 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)) + +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 + self.__dict__["_methods"] = set() + + def __enum(self): + e = self._comobj.Invoke(-4) # DISPID_NEWENUM + return e.QueryInterface(comtypes.automation.IEnumVARIANT) + + def __cmp__(self, other): + if not isinstance(other, _Dispatch): + return 1 + return cmp(self._comobj, other._comobj) + + def __hash__(self): + return hash(self._comobj) + + def __getitem__(self, index): + enum = self.__enum() + if index > 0: + if 0 != enum.Skip(index): + raise IndexError("index out of range") + item, fetched = enum.Next(1) + if not fetched: + raise IndexError("index out of range") + return item + + def QueryInterface(self, *args): + "QueryInterface is forwarded to the real com object." + return self._comobj.QueryInterface(*args) + + def _FlagAsMethod(self, *names): + """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. + """ + self._methods.update(names) + + def __getattr__(self, name): + if name.startswith("__") and name.endswith("__"): + raise AttributeError(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 + + if name in self._methods: + result = MethodCaller(dispid, self) + self.__dict__[name] = result + return result + + flags = comtypes.automation.DISPATCH_PROPERTYGET + try: + result = self._comobj.Invoke(dispid, _invkind=flags) + except COMError, err: + (hresult, text, details) = err.args + if hresult in ERRORS_BAD_CONTEXT: + result = MethodCaller(dispid, self) + self.__dict__[name] = result + else: + # The line break is important for 2to3 to work correctly + raise + except: + # The line break is important for 2to3 to work correctly + raise + + return result + + def __setattr__(self, name, value): + dispid = self._ids.get(name) + if not dispid: + dispid = self._comobj.GetIDsOfNames(name)[0] + self._ids[name] = dispid + # Detect whether to use DISPATCH_PROPERTYPUT or + # DISPATCH_PROPERTYPUTREF + flags = 8 if _is_object(value) else 4 + return self._comobj.Invoke(dispid, value, _invkind=flags) + + def __iter__(self): + return _Collection(self.__enum()) + +## def __setitem__(self, index, value): +## self._comobj.Invoke(-3, index, value, +## _invkind=comtypes.automation.DISPATCH_PROPERTYPUT|comtypes.automation.DISPATCH_PROPERTYPUTREF) + +class _Collection(object): + def __init__(self, enum): + self.enum = enum + + def next(self): + item, fetched = self.enum.Next(1) + if fetched: + return item + raise StopIteration + + def __iter__(self): + return self + +__all__ = ["Dispatch"] diff --git a/tools/comtypes/comtypes/client/lazybind.py b/tools/comtypes/comtypes/client/lazybind.py new file mode 100644 index 00000000000000..4705e4a74a8275 --- /dev/null +++ b/tools/comtypes/comtypes/client/lazybind.py @@ -0,0 +1,267 @@ +import comtypes +import comtypes.automation + +from comtypes.automation import IEnumVARIANT +from comtypes.automation import DISPATCH_METHOD +from comtypes.automation import DISPATCH_PROPERTYGET +from comtypes.automation import DISPATCH_PROPERTYPUT +from comtypes.automation import DISPATCH_PROPERTYPUTREF + +from comtypes.automation import DISPID_VALUE +from comtypes.automation import DISPID_NEWENUM + +from comtypes.typeinfo import FUNC_PUREVIRTUAL, FUNC_DISPATCH + + +class FuncDesc(object): + """Stores important FUNCDESC properties by copying them from a + real FUNCDESC instance. + """ + def __init__(self, **kw): + self.__dict__.update(kw) + +# What is missing? +# +# Should NamedProperty support __call__()? + +_all_slice = slice(None, None, None) + + +class NamedProperty(object): + def __init__(self, disp, get, put, putref): + self.get = get + self.put = put + self.putref = putref + self.disp = disp + + def __getitem__(self, arg): + if self.get is None: + raise TypeError("unsubscriptable object") + if isinstance(arg, tuple): + return self.disp._comobj._invoke(self.get.memid, + self.get.invkind, + 0, + *arg) + elif arg == _all_slice: + return self.disp._comobj._invoke(self.get.memid, + self.get.invkind, + 0) + return self.disp._comobj._invoke(self.get.memid, + self.get.invkind, + 0, + *[arg]) + + def __call__(self, *args): + if self.get is None: + raise TypeError("object is not callable") + return self.disp._comobj._invoke(self.get.memid, + self.get.invkind, + 0, + *args) + + def __setitem__(self, name, value): + # See discussion in Dispatch.__setattr__ below. + if self.put is None and self.putref is None: + raise TypeError("object does not support item assignment") + if comtypes._is_object(value): + descr = self.putref or self.put + else: + descr = self.put or self.putref + if isinstance(name, tuple): + self.disp._comobj._invoke(descr.memid, + descr.invkind, + 0, + *(name + (value,))) + elif name == _all_slice: + self.disp._comobj._invoke(descr.memid, + descr.invkind, + 0, + value) + else: + self.disp._comobj._invoke(descr.memid, + descr.invkind, + 0, + name, + value) + + def __iter__(self): + """ Explicitly disallow iteration. """ + msg = "%r is not iterable" % self.disp + raise TypeError(msg) + + +# The following 'Dispatch' class, returned from +# CreateObject(progid, dynamic=True) +# differ in behaviour from objects created with +# CreateObject(progid, dynamic=False) +# (let us call the latter 'Custom' objects for this discussion): +# +# +# 1. Dispatch objects support __call__(), custom objects do not +# +# 2. Custom objects method support named arguments, Dispatch +# objects do not (could be added, would probably be expensive) + +class Dispatch(object): + """Dynamic dispatch for an object the exposes type information. + Binding at runtime is done via ITypeComp::Bind calls. + """ + def __init__(self, comobj, tinfo): + self.__dict__["_comobj"] = comobj + self.__dict__["_tinfo"] = tinfo + self.__dict__["_tcomp"] = tinfo.GetTypeComp() + self.__dict__["_tdesc"] = {} +## self.__dict__["_iid"] = tinfo.GetTypeAttr().guid + + def __bind(self, name, invkind): + """Bind (name, invkind) and return a FuncDesc instance or + None. Results (even unsuccessful ones) are cached.""" + # We could cache the info in the class instead of the + # instance, but we would need an additional key for that: + # self._iid + try: + return self._tdesc[(name, invkind)] + except KeyError: + try: + descr = self._tcomp.Bind(name, invkind)[1] + except comtypes.COMError: + info = None + else: + # Using a separate instance to store interesting + # attributes of descr avoids that the typecomp instance is + # kept alive... + info = FuncDesc(memid=descr.memid, + invkind=descr.invkind, + cParams=descr.cParams, + funckind=descr.funckind) + self._tdesc[(name, invkind)] = info + return info + + def QueryInterface(self, *args): + "QueryInterface is forwarded to the real com object." + return self._comobj.QueryInterface(*args) + + def __cmp__(self, other): + if not isinstance(other, Dispatch): + return 1 + return cmp(self._comobj, other._comobj) + + def __eq__(self, other): + return isinstance(other, Dispatch) and \ + self._comobj == other._comobj + + def __hash__(self): + return hash(self._comobj) + + def __getattr__(self, name): + """Get a COM attribute.""" + if name.startswith("__") and name.endswith("__"): + raise AttributeError(name) + # check for propget or method + descr = self.__bind(name, DISPATCH_METHOD | DISPATCH_PROPERTYGET) + if descr is None: + raise AttributeError(name) + if descr.invkind == DISPATCH_PROPERTYGET: + # DISPATCH_PROPERTYGET + if descr.funckind == FUNC_DISPATCH: + if descr.cParams == 0: + return self._comobj._invoke(descr.memid, descr.invkind, 0) + elif descr.funckind == FUNC_PUREVIRTUAL: + # FUNC_PUREVIRTUAL descriptions contain the property + # itself as a parameter. + if descr.cParams == 1: + return self._comobj._invoke(descr.memid, descr.invkind, 0) + else: + raise RuntimeError("funckind %d not yet implemented" % descr.funckind) + put = self.__bind(name, DISPATCH_PROPERTYPUT) + putref = self.__bind(name, DISPATCH_PROPERTYPUTREF) + return NamedProperty(self, descr, put, putref) + else: + # DISPATCH_METHOD + def caller(*args): + return self._comobj._invoke(descr.memid, descr.invkind, 0, *args) + try: + caller.__name__ = name + except TypeError: + # In Python 2.3, __name__ is readonly + pass + return caller + + def __setattr__(self, name, value): + # Hm, this can be a propput, a propputref, or 'both' property. + # (Or nothing at all.) + # + # Whether propput or propputref is called will depend on what + # is available, and on the type of 'value' as determined by + # comtypes._is_object(value). + # + # I think that the following table MAY be correct; although I + # have no idea whether the cases marked (?) are really valid. + # + # invkind available | _is_object(value) | invkind we should use + # --------------------------------------------------------------- + # put | True | put (?) + # put | False | put + # putref | True | putref + # putref | False | putref (?) + # put, putref | True | putref + # put, putref | False | put + put = self.__bind(name, DISPATCH_PROPERTYPUT) + putref = self.__bind(name, DISPATCH_PROPERTYPUTREF) + if not put and not putref: + raise AttributeError(name) + if comtypes._is_object(value): + descr = putref or put + else: + descr = put or putref + if descr.cParams == 1: + self._comobj._invoke(descr.memid, descr.invkind, 0, value) + return + raise AttributeError(name) + + def __call__(self, *args): + return self._comobj._invoke(DISPID_VALUE, + DISPATCH_METHOD | DISPATCH_PROPERTYGET, + 0, + *args) + + def __getitem__(self, arg): + if isinstance(arg, tuple): + args = arg + elif arg == _all_slice: + args = () + else: + args = (arg,) + + try: + return self._comobj._invoke(DISPID_VALUE, + DISPATCH_METHOD | DISPATCH_PROPERTYGET, + 0, + *args) + except comtypes.COMError: + return iter(self)[arg] + + def __setitem__(self, name, value): + if comtypes._is_object(value): + invkind = DISPATCH_PROPERTYPUTREF + else: + invkind = DISPATCH_PROPERTYPUT + + if isinstance(name, tuple): + args = name + (value,) + elif name == _all_slice: + args = (value,) + else: + args = (name, value) + return self._comobj._invoke(DISPID_VALUE, + invkind, + 0, + *args) + + def __iter__(self): + punk = self._comobj._invoke(DISPID_NEWENUM, + DISPATCH_METHOD | DISPATCH_PROPERTYGET, + 0) + enum = punk.QueryInterface(IEnumVARIANT) + enum._dynamic = True + return enum diff --git a/tools/comtypes/comtypes/connectionpoints.py b/tools/comtypes/comtypes/connectionpoints.py new file mode 100644 index 00000000000000..bed8d0daa342ed --- /dev/null +++ b/tools/comtypes/comtypes/connectionpoints.py @@ -0,0 +1,94 @@ +from ctypes import * +from comtypes import IUnknown, COMMETHOD, GUID, HRESULT, dispid +_GUID = GUID + +class tagCONNECTDATA(Structure): + _fields_ = [ + ('pUnk', POINTER(IUnknown)), + ('dwCookie', c_ulong), + ] +CONNECTDATA = tagCONNECTDATA + +################################################################ + +class IConnectionPointContainer(IUnknown): + _iid_ = GUID('{B196B284-BAB4-101A-B69C-00AA00341D07}') + _idlflags_ = [] + +class IConnectionPoint(IUnknown): + _iid_ = GUID('{B196B286-BAB4-101A-B69C-00AA00341D07}') + _idlflags_ = [] + +class IEnumConnections(IUnknown): + _iid_ = GUID('{B196B287-BAB4-101A-B69C-00AA00341D07}') + _idlflags_ = [] + + def __iter__(self): + return self + + def next(self): + cp, fetched = self.Next(1) + if fetched == 0: + raise StopIteration + return cp + +class IEnumConnectionPoints(IUnknown): + _iid_ = GUID('{B196B285-BAB4-101A-B69C-00AA00341D07}') + _idlflags_ = [] + + def __iter__(self): + return self + + def next(self): + cp, fetched = self.Next(1) + if fetched == 0: + raise StopIteration + return cp + +################################################################ + +IConnectionPointContainer._methods_ = [ + COMMETHOD([], HRESULT, 'EnumConnectionPoints', + ( ['out'], POINTER(POINTER(IEnumConnectionPoints)), 'ppEnum' )), + COMMETHOD([], HRESULT, 'FindConnectionPoint', + ( ['in'], POINTER(_GUID), 'riid' ), + ( ['out'], POINTER(POINTER(IConnectionPoint)), 'ppCP' )), +] + +IConnectionPoint._methods_ = [ + COMMETHOD([], HRESULT, 'GetConnectionInterface', + ( ['out'], POINTER(_GUID), 'pIID' )), + COMMETHOD([], HRESULT, 'GetConnectionPointContainer', + ( ['out'], POINTER(POINTER(IConnectionPointContainer)), 'ppCPC' )), + COMMETHOD([], HRESULT, 'Advise', + ( ['in'], POINTER(IUnknown), 'pUnkSink' ), + ( ['out'], POINTER(c_ulong), 'pdwCookie' )), + COMMETHOD([], HRESULT, 'Unadvise', + ( ['in'], c_ulong, 'dwCookie' )), + COMMETHOD([], HRESULT, 'EnumConnections', + ( ['out'], POINTER(POINTER(IEnumConnections)), 'ppEnum' )), +] + +IEnumConnections._methods_ = [ + COMMETHOD([], HRESULT, 'Next', + ( ['in'], c_ulong, 'cConnections' ), + ( ['out'], POINTER(tagCONNECTDATA), 'rgcd' ), + ( ['out'], POINTER(c_ulong), 'pcFetched' )), + COMMETHOD([], HRESULT, 'Skip', + ( ['in'], c_ulong, 'cConnections' )), + COMMETHOD([], HRESULT, 'Reset'), + COMMETHOD([], HRESULT, 'Clone', + ( ['out'], POINTER(POINTER(IEnumConnections)), 'ppEnum' )), +] + +IEnumConnectionPoints._methods_ = [ + COMMETHOD([], HRESULT, 'Next', + ( ['in'], c_ulong, 'cConnections' ), + ( ['out'], POINTER(POINTER(IConnectionPoint)), 'ppCP' ), + ( ['out'], POINTER(c_ulong), 'pcFetched' )), + COMMETHOD([], HRESULT, 'Skip', + ( ['in'], c_ulong, 'cConnections' )), + COMMETHOD([], HRESULT, 'Reset'), + COMMETHOD([], HRESULT, 'Clone', + ( ['out'], POINTER(POINTER(IEnumConnectionPoints)), 'ppEnum' )), +] diff --git a/tools/comtypes/comtypes/errorinfo.py b/tools/comtypes/comtypes/errorinfo.py new file mode 100644 index 00000000000000..992cb4de5995ff --- /dev/null +++ b/tools/comtypes/comtypes/errorinfo.py @@ -0,0 +1,105 @@ +import sys +from ctypes import * +from comtypes import IUnknown, HRESULT, COMMETHOD, GUID, BSTR +from comtypes.hresult import * + +LPCOLESTR = c_wchar_p +DWORD = c_ulong + +class ICreateErrorInfo(IUnknown): + _iid_ = GUID("{22F03340-547D-101B-8E65-08002B2BD119}") + _methods_ = [ + COMMETHOD([], HRESULT, 'SetGUID', + (['in'], POINTER(GUID), "rguid")), + COMMETHOD([], HRESULT, 'SetSource', + (['in'], LPCOLESTR, "szSource")), + COMMETHOD([], HRESULT, 'SetDescription', + (['in'], LPCOLESTR, "szDescription")), + COMMETHOD([], HRESULT, 'SetHelpFile', + (['in'], LPCOLESTR, "szHelpFile")), + COMMETHOD([], HRESULT, 'SetHelpContext', + (['in'], DWORD, "dwHelpContext")) + ] + +class IErrorInfo(IUnknown): + _iid_ = GUID("{1CF2B120-547D-101B-8E65-08002B2BD119}") + _methods_ = [ + COMMETHOD([], HRESULT, 'GetGUID', + (['out'], POINTER(GUID), "pGUID")), + COMMETHOD([], HRESULT, 'GetSource', + (['out'], POINTER(BSTR), "pBstrSource")), + COMMETHOD([], HRESULT, 'GetDescription', + (['out'], POINTER(BSTR), "pBstrDescription")), + COMMETHOD([], HRESULT, 'GetHelpFile', + (['out'], POINTER(BSTR), "pBstrHelpFile")), + COMMETHOD([], HRESULT, 'GetHelpContext', + (['out'], POINTER(DWORD), "pdwHelpContext")), + ] + +class ISupportErrorInfo(IUnknown): + _iid_ = GUID("{DF0B3D60-548F-101B-8E65-08002B2BD119}") + _methods_ = [ + COMMETHOD([], HRESULT, 'InterfaceSupportsErrorInfo', + (['in'], POINTER(GUID), 'riid')) + ] + +################################################################ +_oleaut32 = oledll.oleaut32 + +def CreateErrorInfo(): + cei = POINTER(ICreateErrorInfo)() + _oleaut32.CreateErrorInfo(byref(cei)) + return cei + +def GetErrorInfo(): + """Get the error information for the current thread.""" + errinfo = POINTER(IErrorInfo)() + if S_OK == _oleaut32.GetErrorInfo(0, byref(errinfo)): + return errinfo + return None + +def SetErrorInfo(errinfo): + """Set error information for the current thread.""" + return _oleaut32.SetErrorInfo(0, errinfo) + +def ReportError(text, iid, + clsid=None, helpfile=None, helpcontext=0, hresult=DISP_E_EXCEPTION): + """Report a COM error. Returns the passed in hresult value.""" + ei = CreateErrorInfo() + ei.SetDescription(text) + ei.SetGUID(iid) + if helpfile is not None: + ei.SetHelpFile(helpfile) + if helpcontext is not None: + ei.SetHelpContext(helpcontext) + if clsid is not None: + if isinstance(clsid, basestring): + clsid = GUID(clsid) + try: + progid = clsid.as_progid() + except WindowsError: + pass + else: + ei.SetSource(progid) # progid for the class or application that created the error + _oleaut32.SetErrorInfo(0, ei) + return hresult + +def ReportException(hresult, iid, clsid=None, helpfile=None, helpcontext=None, + stacklevel=None): + """Report a COM exception. Returns the passed in hresult value.""" + typ, value, tb = sys.exc_info() + if stacklevel is not None: + for _ in range(stacklevel): + tb = tb.tb_next + line = tb.tb_frame.f_lineno + name = tb.tb_frame.f_globals["__name__"] + text = "%s: %s (%s, line %d)" % (typ, value, name, line) + else: + text = "%s: %s" % (typ, value) + return ReportError(text, iid, + clsid=clsid, helpfile=helpfile, helpcontext=helpcontext, + hresult=hresult) + +__all__ = ["ICreateErrorInfo", "IErrorInfo", "ISupportErrorInfo", + "ReportError", "ReportException", + "SetErrorInfo", "GetErrorInfo", "CreateErrorInfo"] diff --git a/tools/comtypes/comtypes/gen/__init__.py b/tools/comtypes/comtypes/gen/__init__.py new file mode 100644 index 00000000000000..40bf6c8d50d2be --- /dev/null +++ b/tools/comtypes/comtypes/gen/__init__.py @@ -0,0 +1 @@ +# comtypes.gen package, directory for generated files. diff --git a/tools/comtypes/comtypes/git.py b/tools/comtypes/comtypes/git.py new file mode 100644 index 00000000000000..ef9f60ab2ec7d0 --- /dev/null +++ b/tools/comtypes/comtypes/git.py @@ -0,0 +1,65 @@ +"""comtypes.git - access the process wide global interface table + +The global interface table provides a way to marshal interface pointers +between different threading appartments. +""" +from ctypes import * +from comtypes import IUnknown, STDMETHOD, COMMETHOD, \ + GUID, HRESULT, CoCreateInstance, CLSCTX_INPROC_SERVER + +DWORD = c_ulong + +class IGlobalInterfaceTable(IUnknown): + _iid_ = GUID("{00000146-0000-0000-C000-000000000046}") + _methods_ = [ + STDMETHOD(HRESULT, "RegisterInterfaceInGlobal", + [POINTER(IUnknown), POINTER(GUID), POINTER(DWORD)]), + STDMETHOD(HRESULT, "RevokeInterfaceFromGlobal", [DWORD]), + STDMETHOD(HRESULT, "GetInterfaceFromGlobal", + [DWORD, POINTER(GUID), POINTER(POINTER(IUnknown))]), + ] + + def RegisterInterfaceInGlobal(self, obj, interface=IUnknown): + cookie = DWORD() + self.__com_RegisterInterfaceInGlobal(obj, interface._iid_, cookie) + return cookie.value + + def GetInterfaceFromGlobal(self, cookie, interface=IUnknown): + ptr = POINTER(interface)() + self.__com_GetInterfaceFromGlobal(cookie, interface._iid_, ptr) + return ptr + + def RevokeInterfaceFromGlobal(self, cookie): + self.__com_RevokeInterfaceFromGlobal(cookie) + + +# It was a pain to get this CLSID: it's neither in the registry, nor +# in any header files. I had to compile a C program, and find it out +# with the debugger. Apparently it is in uuid.lib. +CLSID_StdGlobalInterfaceTable = GUID("{00000323-0000-0000-C000-000000000046}") + +git = CoCreateInstance(CLSID_StdGlobalInterfaceTable, + interface=IGlobalInterfaceTable, + clsctx=CLSCTX_INPROC_SERVER) + +RevokeInterfaceFromGlobal = git.RevokeInterfaceFromGlobal +RegisterInterfaceInGlobal = git.RegisterInterfaceInGlobal +GetInterfaceFromGlobal = git.GetInterfaceFromGlobal + +__all__ = ["RegisterInterfaceInGlobal", "RevokeInterfaceFromGlobal", "GetInterfaceFromGlobal"] + +if __name__ == "__main__": + from comtypes.typeinfo import CreateTypeLib, ICreateTypeLib + + tlib = CreateTypeLib("foo.bar") # we don not save it later + assert (tlib.AddRef(), tlib.Release()) == (2, 1) + + cookie = RegisterInterfaceInGlobal(tlib) + assert (tlib.AddRef(), tlib.Release()) == (3, 2) + + GetInterfaceFromGlobal(cookie, ICreateTypeLib) + GetInterfaceFromGlobal(cookie, ICreateTypeLib) + GetInterfaceFromGlobal(cookie) + assert (tlib.AddRef(), tlib.Release()) == (3, 2) + RevokeInterfaceFromGlobal(cookie) + assert (tlib.AddRef(), tlib.Release()) == (2, 1) diff --git a/tools/comtypes/comtypes/hresult.py b/tools/comtypes/comtypes/hresult.py new file mode 100644 index 00000000000000..85f75ceb137651 --- /dev/null +++ b/tools/comtypes/comtypes/hresult.py @@ -0,0 +1,76 @@ +# comtypes.hresult +# COM success and error codes +# +# Note that the codes should be written in decimal notation! + +S_OK = 0 +S_FALSE = 1 + +E_UNEXPECTED = -2147418113 #0x8000FFFFL + +E_NOTIMPL = -2147467263 #0x80004001L +E_NOINTERFACE = -2147467262 #0x80004002L +E_POINTER = -2147467261 #0x80004003L +E_FAIL = -2147467259 #0x80004005L +E_INVALIDARG = -2147024809 #0x80070057L +E_OUTOFMEMORY = -2147024882 # 0x8007000EL + +CLASS_E_NOAGGREGATION = -2147221232 #0x80040110L +CLASS_E_CLASSNOTAVAILABLE = -2147221231 #0x80040111L + +CO_E_CLASSSTRING = -2147221005 #0x800401F3L + +# connection point error codes +CONNECT_E_CANNOTCONNECT = -2147220990 +CONNECT_E_ADVISELIMIT = -2147220991 +CONNECT_E_NOCONNECTION = -2147220992 + +TYPE_E_ELEMENTNOTFOUND = -2147352077 #0x8002802BL + +TYPE_E_REGISTRYACCESS = -2147319780 #0x8002801CL +TYPE_E_CANTLOADLIBRARY = -2147312566 #0x80029C4AL + +# all the DISP_E_ values from windows.h +DISP_E_BUFFERTOOSMALL = -2147352557 +DISP_E_DIVBYZERO = -2147352558 +DISP_E_NOTACOLLECTION = -2147352559 +DISP_E_BADCALLEE = -2147352560 +DISP_E_PARAMNOTOPTIONAL = -2147352561 #0x8002000F +DISP_E_BADPARAMCOUNT = -2147352562 #0x8002000E +DISP_E_ARRAYISLOCKED = -2147352563 #0x8002000D +DISP_E_UNKNOWNLCID = -2147352564 #0x8002000C +DISP_E_BADINDEX = -2147352565 #0x8002000B +DISP_E_OVERFLOW = -2147352566 #0x8002000A +DISP_E_EXCEPTION = -2147352567 #0x80020009 +DISP_E_BADVARTYPE = -2147352568 #0x80020008 +DISP_E_NONAMEDARGS = -2147352569 #0x80020007 +DISP_E_UNKNOWNNAME = -2147352570 #0x80020006 +DISP_E_TYPEMISMATCH = -2147352571 #0800020005 +DISP_E_PARAMNOTFOUND = -2147352572 #0x80020004 +DISP_E_MEMBERNOTFOUND = -2147352573 #0x80020003 +DISP_E_UNKNOWNINTERFACE = -2147352575 #0x80020001 + +RPC_E_CHANGED_MODE = -2147417850 # 0x80010106 +RPC_E_SERVERFAULT = -2147417851 # 0x80010105 + +# 'macros' and constants to create your own HRESULT values: + +def MAKE_HRESULT(sev, fac, code): + # A hresult is SIGNED in comtypes + from ctypes import c_long + return c_long((sev << 31 | fac << 16 | code)).value + +SEVERITY_ERROR = 1 +SEVERITY_SUCCESS = 0 + +FACILITY_ITF = 4 +FACILITY_WIN32 = 7 + +def HRESULT_FROM_WIN32(x): + # make signed + from ctypes import c_long + x = c_long(x).value + if x < 0: + return x + # 0x80000000 | FACILITY_WIN32 << 16 | x & 0xFFFF + return c_long(0x80070000 | (x & 0xFFFF)).value diff --git a/tools/comtypes/comtypes/logutil.py b/tools/comtypes/comtypes/logutil.py new file mode 100644 index 00000000000000..3cb799aafcd565 --- /dev/null +++ b/tools/comtypes/comtypes/logutil.py @@ -0,0 +1,51 @@ +# logutil.py +import logging, ctypes + +class NTDebugHandler(logging.Handler): + def emit(self, record, + writeA=ctypes.windll.kernel32.OutputDebugStringA, + writeW=ctypes.windll.kernel32.OutputDebugStringW): + text = self.format(record) + if isinstance(text, str): + writeA(text + "\n") + else: + writeW(text + u"\n") +logging.NTDebugHandler = NTDebugHandler + +def setup_logging(*pathnames): + import ConfigParser + + parser = ConfigParser.ConfigParser() + parser.optionxform = str # use case sensitive option names! + + parser.read(pathnames) + + DEFAULTS = {"handler": "StreamHandler()", + "format": "%(levelname)s:%(name)s:%(message)s", + "level": "WARNING"} + + def get(section, option): + try: + return parser.get(section, option, True) + except (ConfigParser.NoOptionError, ConfigParser.NoSectionError): + return DEFAULTS[option] + + levelname = get("logging", "level") + format = get("logging", "format") + handlerclass = get("logging", "handler") + + # convert level name to level value + level = getattr(logging, levelname) + # create the handler instance + handler = eval(handlerclass, vars(logging)) + formatter = logging.Formatter(format) + handler.setFormatter(formatter) + logging.root.addHandler(handler) + logging.root.setLevel(level) + + try: + for name, value in parser.items("logging.levels", True): + value = getattr(logging, value) + logging.getLogger(name).setLevel(value) + except ConfigParser.NoSectionError: + pass diff --git a/tools/comtypes/comtypes/messageloop.py b/tools/comtypes/comtypes/messageloop.py new file mode 100644 index 00000000000000..a0d58a28a76eba --- /dev/null +++ b/tools/comtypes/comtypes/messageloop.py @@ -0,0 +1,50 @@ +import ctypes +from ctypes import WinDLL, byref, WinError +from ctypes.wintypes import MSG +_user32 = WinDLL("user32") + +GetMessage = _user32.GetMessageA +GetMessage.argtypes = [ + ctypes.c_void_p, + ctypes.c_void_p, + ctypes.c_uint, + ctypes.c_uint, +] +TranslateMessage = _user32.TranslateMessage +DispatchMessage = _user32.DispatchMessageA + + +class _MessageLoop(object): + + def __init__(self): + self._filters = [] + + def insert_filter(self, obj, index=-1): + self._filters.insert(index, obj) + + def remove_filter(self, obj): + self._filters.remove(obj) + + def run(self): + msg = MSG() + lpmsg = byref(msg) + while 1: + ret = GetMessage(lpmsg, 0, 0, 0) + if ret == -1: + raise WinError() + elif ret == 0: + return # got WM_QUIT + if not self.filter_message(lpmsg): + TranslateMessage(lpmsg) + DispatchMessage(lpmsg) + + def filter_message(self, lpmsg): + return any(filter(lpmsg) for filter in self._filters) + +_messageloop = _MessageLoop() + +run = _messageloop.run +insert_filter = _messageloop.insert_filter +remove_filter = _messageloop.remove_filter + +__all__ = ["run", "insert_filter", "remove_filter"] diff --git a/tools/comtypes/comtypes/npsupport.py b/tools/comtypes/comtypes/npsupport.py new file mode 100644 index 00000000000000..3a362fb9e75208 --- /dev/null +++ b/tools/comtypes/comtypes/npsupport.py @@ -0,0 +1,104 @@ +""" Consolidation of numpy support utilities. """ +import sys + +try: + import numpy +except ImportError: + numpy = None + + +HAVE_NUMPY = numpy is not None + +is_64bits = sys.maxsize > 2**32 + + +def _make_variant_dtype(): + """ Create a dtype for VARIANT. This requires support for Unions, which is + available in numpy version 1.7 or greater. + + This does not support the decimal type. + + Returns None if the dtype cannot be created. + + """ + + # pointer typecode + ptr_typecode = '>> class MyClass: + ... def __init__(self, param): + ... self.param = param + ... def bar(self): + ... print("orig bar") + + To add attributes to MyClass, you can use Patch: + + >>> @Patch(MyClass) + ... class JustANamespace: + ... def print_param(self): + ... print(self.param) + >>> ob = MyClass('foo') + >>> ob.print_param() + foo + + The namespace is assigned None, so there's no mistaking the purpose + >>> JustANamespace + + The patcher will replace the existing methods: + + >>> @Patch(MyClass) + ... class SomeNamespace: + ... def bar(self): + ... print("replaced bar") + >>> ob = MyClass('foo') + >>> ob.bar() + replaced bar + + But it will not replace methods if no_replace is indicated. + + >>> @Patch(MyClass) + ... class AnotherNamespace: + ... @no_replace + ... def bar(self): + ... print("candy bar") + >>> ob = MyClass('foo') + >>> ob.bar() + replaced bar + + """ + + def __init__(self, target): + self.target = target + + def __call__(self, patches): + for name, value in vars(patches).items(): + if name in vars(ReferenceEmptyClass): + continue + no_replace = getattr(value, '__no_replace', False) + if no_replace and hasattr(self.target, name): + continue + setattr(self.target, name, value) + +def no_replace(f): + """ + Method decorator to indicate that a method definition shall + silently be ignored if it already exists in the target class. + """ + f.__no_replace = True + return f + +class ReferenceEmptyClass(object): + """ + This empty class will serve as a reference for attributes present on + any class. + """ diff --git a/tools/comtypes/comtypes/persist.py b/tools/comtypes/comtypes/persist.py new file mode 100644 index 00000000000000..77fae34728e13c --- /dev/null +++ b/tools/comtypes/comtypes/persist.py @@ -0,0 +1,212 @@ +"""This module defines the following interfaces: + + IErrorLog + IPropertyBag + IPersistPropertyBag + IPropertyBag2 + IPersistPropertyBag2 + +The 'DictPropertyBag' class is a class implementing the IPropertyBag +interface, useful in client code. +""" +from ctypes import * +from ctypes.wintypes import WORD, DWORD, BOOL +from comtypes import GUID, IUnknown, COMMETHOD, HRESULT, dispid +from comtypes import IPersist +from comtypes.automation import VARIANT, tagEXCEPINFO + +# XXX Replace by canonical solution!!! +WSTRING = c_wchar_p + +class IErrorLog(IUnknown): + _iid_ = GUID('{3127CA40-446E-11CE-8135-00AA004BB851}') + _idlflags_ = [] + _methods_ = [ + COMMETHOD([], HRESULT, 'AddError', + ( ['in'], WSTRING, 'pszPropName' ), + ( ['in'], POINTER(tagEXCEPINFO), 'pExcepInfo' )), + ] + +class IPropertyBag(IUnknown): + _iid_ = GUID('{55272A00-42CB-11CE-8135-00AA004BB851}') + _idlflags_ = [] + _methods_ = [ + # XXX Note: According to MSDN, pVar and pErrorLog are ['in', 'out'] parameters. + # + # XXX ctypes does NOT yet accept POINTER(IErrorLog) as 'out' parameter: + # TypeError: 'out' parameter 3 must be a pointer type, not POINTER(IErrorLog) + COMMETHOD([], HRESULT, 'Read', + ( ['in'], WSTRING, 'pszPropName' ), + ( ['in', 'out'], POINTER(VARIANT), 'pVar' ), + ( ['in'], POINTER(IErrorLog), 'pErrorLog' )), +## ( ['in', 'out'], POINTER(IErrorLog), 'pErrorLog' )), + COMMETHOD([], HRESULT, 'Write', + ( ['in'], WSTRING, 'pszPropName' ), + ( ['in'], POINTER(VARIANT), 'pVar' )), + ] + +class IPersistPropertyBag(IPersist): + _iid_ = GUID('{37D84F60-42CB-11CE-8135-00AA004BB851}') + _idlflags_ = [] + _methods_ = [ + COMMETHOD([], HRESULT, 'InitNew'), + COMMETHOD([], HRESULT, 'Load', + ( ['in'], POINTER(IPropertyBag), 'pPropBag' ), + ( ['in'], POINTER(IErrorLog), 'pErrorLog' )), + COMMETHOD([], HRESULT, 'Save', + ( ['in'], POINTER(IPropertyBag), 'pPropBag' ), + ( ['in'], c_int, 'fClearDirty' ), + ( ['in'], c_int, 'fSaveAllProperties' )), + ] + + +CLIPFORMAT = WORD + +PROPBAG2_TYPE_UNDEFINED = 0 +PROPBAG2_TYPE_DATA = 1 +PROPBAG2_TYPE_URL = 2 +PROPBAG2_TYPE_OBJECT = 3 +PROPBAG2_TYPE_STREAM = 4 +PROPBAG2_TYPE_STORAGE = 5 +PROPBAG2_TYPE_MONIKER = 6 + +class tagPROPBAG2(Structure): + _fields_ = [ + ('dwType', c_ulong), + ('vt', c_ushort), + ('cfType', CLIPFORMAT), + ('dwHint', c_ulong), + ('pstrName', WSTRING), + ('clsid', GUID), + ] + +class IPropertyBag2(IUnknown): + _iid_ = GUID('{22F55882-280B-11D0-A8A9-00A0C90C2004}') + _idlflags_ = [] + _methods_ = [ + COMMETHOD([], HRESULT, 'Read', + ( ['in'], c_ulong, 'cProperties' ), + ( ['in'], POINTER(tagPROPBAG2), 'pPropBag' ), + ( ['in'], POINTER(IErrorLog), 'pErrLog' ), + ( ['out'], POINTER(VARIANT), 'pvarValue' ), + ( ['out'], POINTER(HRESULT), 'phrError' )), + COMMETHOD([], HRESULT, 'Write', + ( ['in'], c_ulong, 'cProperties' ), + ( ['in'], POINTER(tagPROPBAG2), 'pPropBag' ), + ( ['in'], POINTER(VARIANT), 'pvarValue' )), + COMMETHOD([], HRESULT, 'CountProperties', + ( ['out'], POINTER(c_ulong), 'pcProperties' )), + COMMETHOD([], HRESULT, 'GetPropertyInfo', + ( ['in'], c_ulong, 'iProperty' ), + ( ['in'], c_ulong, 'cProperties' ), + ( ['out'], POINTER(tagPROPBAG2), 'pPropBag' ), + ( ['out'], POINTER(c_ulong), 'pcProperties' )), + COMMETHOD([], HRESULT, 'LoadObject', + ( ['in'], WSTRING, 'pstrName' ), + ( ['in'], c_ulong, 'dwHint' ), + ( ['in'], POINTER(IUnknown), 'punkObject' ), + ( ['in'], POINTER(IErrorLog), 'pErrLog' )), + ] + +class IPersistPropertyBag2(IPersist): + _iid_ = GUID('{22F55881-280B-11D0-A8A9-00A0C90C2004}') + _idlflags_ = [] + _methods_ = [ + COMMETHOD([], HRESULT, 'InitNew'), + COMMETHOD([], HRESULT, 'Load', + ( ['in'], POINTER(IPropertyBag2), 'pPropBag' ), + ( ['in'], POINTER(IErrorLog), 'pErrLog' )), + COMMETHOD([], HRESULT, 'Save', + ( ['in'], POINTER(IPropertyBag2), 'pPropBag' ), + ( ['in'], c_int, 'fClearDirty' ), + ( ['in'], c_int, 'fSaveAllProperties' )), + COMMETHOD([], HRESULT, 'IsDirty'), + ] + + +# STGM constants +# Access +STGM_READ = 0x00000000 +STGM_WRITE = 0x00000001 +STGM_READWRITE = 0x00000002 + +# Sharing +STGM_SHARE_EXCLUSIVE = 0x00000010 +STGM_SHARE_DENY_WRITE = 0x00000020 +STGM_SHARE_DENY_READ = 0x00000030 +STGM_SHARE_DENY_NONE = 0x00000040 +STGM_PRIORITY = 0x00040000 + +# Creation +STGM_FAILIFTHERE = 0x00000000 +STGM_CREATE = 0x00001000 +STGM_CONVERT = 0x00020000 + +# Transactioning +STGM_DIRECT = 0x00000000 +STGM_TRANSACTED = 0x00010000 + +# Transactioning Performance +STGM_NOSCRATCH = 0x00100000 +STGM_NOSNAPSHOT = 0x00200000 + +# Direct SWMR and Simple +STGM_SIMPLE = 0x08000000 +STGM_DIRECT_SWMR = 0x00400000 + +# Delete on release +STGM_DELETEONRELEASE = 0x04000000 + +LPOLESTR = LPCOLESTR = c_wchar_p + +class IPersistFile(IPersist): + _iid_ = GUID('{0000010B-0000-0000-C000-000000000046}') + _idlflags_ = [] + _methods_ = [ + COMMETHOD([], HRESULT, 'IsDirty'), + COMMETHOD([], HRESULT, 'Load', + ( ['in'], LPCOLESTR, 'pszFileName' ), + ( ['in'], DWORD, 'dwMode' )), + COMMETHOD([], HRESULT, 'Save', + ( ['in'], LPCOLESTR, 'pszFileName' ), + ( ['in'], BOOL, 'fRemember' )), + COMMETHOD([], HRESULT, 'SaveCompleted', + ( ['in'], LPCOLESTR, 'pszFileName' )), + COMMETHOD([], HRESULT, 'GetCurFile', + ( ['out'], POINTER(LPOLESTR), 'ppszFileName' )) + ] + + +from comtypes import COMObject +from comtypes.hresult import * +class DictPropertyBag(COMObject): + """An object implementing the IProperty interface on a dictionary. + + Pass named values in the constructor for the client to Read(), or + retrieve from the .values instance variable after the client has + called Load(). + """ + _com_interfaces_ = [IPropertyBag] + + def __init__(self, **kw): + super(DictPropertyBag, self).__init__() + self.values = kw + + def Read(self, this, name, pVar, errorlog): + try: + val = self.values[name] + except KeyError: + return E_INVALIDARG + # The caller did provide info about the type that is expected + # with the pVar[0].vt typecode, except when this is VT_EMPTY. + var = pVar[0] + typecode = var.vt + var.value = val + if typecode: + var.ChangeType(typecode) + return S_OK + + def Write(self, this, name, var): + val = var[0].value + self.values[name] = val + return S_OK diff --git a/tools/comtypes/comtypes/safearray.py b/tools/comtypes/comtypes/safearray.py new file mode 100644 index 00000000000000..bef101377a9e9d --- /dev/null +++ b/tools/comtypes/comtypes/safearray.py @@ -0,0 +1,397 @@ +import threading +import array +from ctypes import (POINTER, Structure, byref, cast, c_long, memmove, pointer, + sizeof) +from comtypes import _safearray, IUnknown, com_interface_registry, npsupport +from comtypes.patcher import Patch + +numpy = npsupport.numpy +_safearray_type_cache = {} + + +class _SafeArrayAsNdArrayContextManager(object): + '''Context manager allowing safe arrays to be extracted as ndarrays. + + This is thread-safe. + + Example + ------- + + This works in python >= 2.5 + >>> with safearray_as_ndarray: + >>> my_arr = com_object.AsSafeArray + >>> type(my_arr) + numpy.ndarray + + ''' + thread_local = threading.local() + + def __enter__(self): + try: + self.thread_local.count += 1 + except AttributeError: + self.thread_local.count = 1 + + def __exit__(self, exc_type, exc_value, traceback): + self.thread_local.count -= 1 + + def __nonzero__(self): + '''True if context manager is currently entered on given thread. + + ''' + return bool(getattr(self.thread_local, 'count', 0)) + + +# Global _SafeArrayAsNdArrayContextManager +safearray_as_ndarray = _SafeArrayAsNdArrayContextManager() + + +################################################################ +# This is THE PUBLIC function: the gateway to the SAFEARRAY functionality. +def _midlSAFEARRAY(itemtype): + """This function mimics the 'SAFEARRAY(aType)' IDL idiom. It + returns a subtype of SAFEARRAY, instances will be built with a + typecode VT_... corresponding to the aType, which must be one of + the supported ctypes. + """ + try: + return POINTER(_safearray_type_cache[itemtype]) + except KeyError: + sa_type = _make_safearray_type(itemtype) + _safearray_type_cache[itemtype] = sa_type + return POINTER(sa_type) + + +def _make_safearray_type(itemtype): + # Create and return a subclass of tagSAFEARRAY + from comtypes.automation import _ctype_to_vartype, VT_RECORD, \ + VT_UNKNOWN, IDispatch, VT_DISPATCH + + meta = type(_safearray.tagSAFEARRAY) + sa_type = meta.__new__(meta, + "SAFEARRAY_%s" % itemtype.__name__, + (_safearray.tagSAFEARRAY,), {}) + + try: + vartype = _ctype_to_vartype[itemtype] + extra = None + except KeyError: + if issubclass(itemtype, Structure): + try: + guids = itemtype._recordinfo_ + except AttributeError: + extra = None + else: + from comtypes.typeinfo import GetRecordInfoFromGuids + extra = GetRecordInfoFromGuids(*guids) + vartype = VT_RECORD + elif issubclass(itemtype, POINTER(IDispatch)): + vartype = VT_DISPATCH + extra = pointer(itemtype._iid_) + elif issubclass(itemtype, POINTER(IUnknown)): + vartype = VT_UNKNOWN + extra = pointer(itemtype._iid_) + else: + raise TypeError(itemtype) + + @Patch(POINTER(sa_type)) + class _(object): + # Should explain the ideas how SAFEARRAY is used in comtypes + _itemtype_ = itemtype # a ctypes type + _vartype_ = vartype # a VARTYPE value: VT_... + _needsfree = False + + @classmethod + def create(cls, value, extra=None): + """Create a POINTER(SAFEARRAY_...) instance of the correct + type; value is an object containing the items to store. + + Python lists, tuples, and array.array instances containing + compatible item types can be passed to create + one-dimensional arrays. To create multidimensional arrys, + numpy arrays must be passed. + """ + if npsupport.isndarray(value): + return cls.create_from_ndarray(value, extra) + + # For VT_UNKNOWN or VT_DISPATCH, extra must be a pointer to + # the GUID of the interface. + # + # For VT_RECORD, extra must be a pointer to an IRecordInfo + # describing the record. + + # XXX How to specify the lbound (3. parameter to CreateVectorEx)? + # XXX How to write tests for lbound != 0? + pa = _safearray.SafeArrayCreateVectorEx(cls._vartype_, + 0, + len(value), + extra) + if not pa: + if cls._vartype_ == VT_RECORD and extra is None: + raise TypeError("Cannot create SAFEARRAY type VT_RECORD without IRecordInfo.") + # Hm, there may be other reasons why the creation fails... + raise MemoryError() + # We now have a POINTER(tagSAFEARRAY) instance which we must cast + # to the correct type: + pa = cast(pa, cls) + # Now, fill the data in: + ptr = POINTER(cls._itemtype_)() # container for the values + _safearray.SafeArrayAccessData(pa, byref(ptr)) + try: + if isinstance(value, array.array): + addr, n = value.buffer_info() + nbytes = len(value) * sizeof(cls._itemtype_) + memmove(ptr, addr, nbytes) + else: + for index, item in enumerate(value): + ptr[index] = item + finally: + _safearray.SafeArrayUnaccessData(pa) + return pa + + @classmethod + def create_from_ndarray(cls, value, extra, lBound=0): + from comtypes.automation import VARIANT + # If processing VARIANT, makes sure the array type is correct. + if cls._itemtype_ is VARIANT: + if value.dtype != npsupport.VARIANT_dtype: + value = _ndarray_to_variant_array(value) + else: + ai = value.__array_interface__ + if ai["version"] != 3: + raise TypeError("only __array_interface__ version 3 supported") + if cls._itemtype_ != numpy.ctypeslib._typecodes[ai["typestr"]]: + raise TypeError("Wrong array item type") + + # SAFEARRAYs have Fortran order; convert the numpy array if needed + if not value.flags.f_contiguous: + value = numpy.array(value, order="F") + + # For VT_UNKNOWN or VT_DISPATCH, extra must be a pointer to + # the GUID of the interface. + # + # For VT_RECORD, extra must be a pointer to an IRecordInfo + # describing the record. + rgsa = (_safearray.SAFEARRAYBOUND * value.ndim)() + nitems = 1 + for i, d in enumerate(value.shape): + nitems *= d + rgsa[i].cElements = d + rgsa[i].lBound = lBound + pa = _safearray.SafeArrayCreateEx(cls._vartype_, + value.ndim, # cDims + rgsa, # rgsaBound + extra) # pvExtra + if not pa: + if cls._vartype_ == VT_RECORD and extra is None: + raise TypeError("Cannot create SAFEARRAY type VT_RECORD without IRecordInfo.") + # Hm, there may be other reasons why the creation fails... + raise MemoryError() + # We now have a POINTER(tagSAFEARRAY) instance which we must cast + # to the correct type: + pa = cast(pa, cls) + # Now, fill the data in: + ptr = POINTER(cls._itemtype_)() # pointer to the item values + _safearray.SafeArrayAccessData(pa, byref(ptr)) + try: + nbytes = nitems * sizeof(cls._itemtype_) + memmove(ptr, value.ctypes.data, nbytes) + finally: + _safearray.SafeArrayUnaccessData(pa) + return pa + + @classmethod + def from_param(cls, value): + if not isinstance(value, cls): + value = cls.create(value, extra) + value._needsfree = True + return value + + def __getitem__(self, index): + # pparray[0] returns the whole array contents. + if index != 0: + raise IndexError("Only index 0 allowed") + return self.unpack() + + def __setitem__(self, index, value): + # XXX Need this to implement [in, out] safearrays in COM servers! +## print "__setitem__", index, value + raise TypeError("Setting items not allowed") + + def __ctypes_from_outparam__(self): + self._needsfree = True + return self[0] + + def __del__(self, _SafeArrayDestroy=_safearray.SafeArrayDestroy): + if self._needsfree: + _SafeArrayDestroy(self) + + def _get_size(self, dim): + "Return the number of elements for dimension 'dim'" + ub = _safearray.SafeArrayGetUBound(self, dim) + 1 + lb = _safearray.SafeArrayGetLBound(self, dim) + return ub - lb + + def unpack(self): + """Unpack a POINTER(SAFEARRAY_...) into a Python tuple or ndarray.""" + dim = _safearray.SafeArrayGetDim(self) + + if dim == 1: + num_elements = self._get_size(1) + result = self._get_elements_raw(num_elements) + if safearray_as_ndarray: + import numpy + return numpy.asarray(result) + return tuple(result) + elif dim == 2: + # get the number of elements in each dimension + rows, cols = self._get_size(1), self._get_size(2) + # get all elements + result = self._get_elements_raw(rows * cols) + # this must be reshaped and transposed because it is + # flat, and in VB order + if safearray_as_ndarray: + import numpy + return numpy.asarray(result).reshape((cols, rows)).T + result = [tuple(result[r::rows]) for r in range(rows)] + return tuple(result) + else: + lowerbounds = [_safearray.SafeArrayGetLBound(self, d) + for d in range(1, dim+1)] + indexes = (c_long * dim)(*lowerbounds) + upperbounds = [_safearray.SafeArrayGetUBound(self, d) + for d in range(1, dim+1)] + row = self._get_row(0, indexes, lowerbounds, upperbounds) + if safearray_as_ndarray: + import numpy + return numpy.asarray(row) + return row + + def _get_elements_raw(self, num_elements): + """Returns a flat list or ndarray containing ALL elements in + the safearray.""" + from comtypes.automation import VARIANT + # XXX Not sure this is true: + # For VT_UNKNOWN and VT_DISPATCH, we should retrieve the + # interface iid by SafeArrayGetIID(). + ptr = POINTER(self._itemtype_)() # container for the values + _safearray.SafeArrayAccessData(self, byref(ptr)) + try: + if self._itemtype_ == VARIANT: + # We have to loop over each item, so we get no + # speedup by creating an ndarray here. + return [i.value for i in ptr[:num_elements]] + elif issubclass(self._itemtype_, POINTER(IUnknown)): + iid = _safearray.SafeArrayGetIID(self) + itf = com_interface_registry[str(iid)] + # COM interface pointers retrieved from array + # must be AddRef()'d if non-NULL. + elems = ptr[:num_elements] + result = [] + # We have to loop over each item, so we get no + # speedup by creating an ndarray here. + for p in elems: + if bool(p): + p.AddRef() + result.append(p.QueryInterface(itf)) + else: + # return a NULL-interface pointer. + result.append(POINTER(itf)()) + return result + else: + # If the safearray element are NOT native python + # objects, the containing safearray must be kept + # alive until all the elements are destroyed. + if not issubclass(self._itemtype_, Structure): + # Create an ndarray if requested. This is where + # we can get the most speed-up. + # XXX Only try to convert types known to + # numpy.ctypeslib. + if (safearray_as_ndarray and self._itemtype_ in + numpy.ctypeslib._typecodes.values()): + arr = numpy.ctypeslib.as_array(ptr, + (num_elements,)) + return arr.copy() + return ptr[:num_elements] + + def keep_safearray(v): + v.__keepref = self + return v + return [keep_safearray(x) for x in ptr[:num_elements]] + finally: + _safearray.SafeArrayUnaccessData(self) + + def _get_row(self, dim, indices, lowerbounds, upperbounds): + # loop over the index of dimension 'dim' + # we have to restore the index of the dimension we're looping over + restore = indices[dim] + + result = [] + obj = self._itemtype_() + pobj = byref(obj) + if dim+1 == len(indices): + # It should be faster to lock the array and get a whole row at once? + # How to calculate the pointer offset? + for i in range(indices[dim], upperbounds[dim]+1): + indices[dim] = i + _safearray.SafeArrayGetElement(self, indices, pobj) + result.append(obj.value) + else: + for i in range(indices[dim], upperbounds[dim]+1): + indices[dim] = i + result.append(self._get_row(dim+1, indices, lowerbounds, upperbounds)) + indices[dim] = restore + return tuple(result) # for compatibility with pywin32. + + @Patch(POINTER(POINTER(sa_type))) + class __(object): + + @classmethod + def from_param(cls, value): + if isinstance(value, cls._type_): + return byref(value) + return byref(cls._type_.create(value, extra)) + + def __setitem__(self, index, value): + # create an LP_SAFEARRAY_... instance + pa = self._type_.create(value, extra) + # XXX Must we destroy the currently contained data? + # fill it into self + super(POINTER(POINTER(sa_type)), self).__setitem__(index, pa) + + return sa_type + + +def _ndarray_to_variant_array(value): + """ Convert an ndarray to VARIANT_dtype array """ + # Check that variant arrays are supported + if npsupport.VARIANT_dtype is None: + msg = "VARIANT ndarrays require NumPy 1.7 or newer." + raise RuntimeError(msg) + + # special cases + if numpy.issubdtype(value.dtype, npsupport.datetime64): + return _datetime64_ndarray_to_variant_array(value) + + from comtypes.automation import VARIANT + # Empty array + varr = numpy.zeros(value.shape, npsupport.VARIANT_dtype, order='F') + # Convert each value to a variant and put it in the array. + varr.flat = [VARIANT(v) for v in value.flat] + return varr + + +def _datetime64_ndarray_to_variant_array(value): + """ Convert an ndarray of datetime64 to VARIANT_dtype array """ + # The OLE automation date format is a floating point value, counting days + # since midnight 30 December 1899. Hours and minutes are represented as + # fractional days. + from comtypes.automation import VT_DATE + value = numpy.array(value, "datetime64[ns]") + value = value - npsupport.com_null_date64 + # Convert to days + value = value / numpy.timedelta64(1, 'D') + varr = numpy.zeros(value.shape, npsupport.VARIANT_dtype, order='F') + varr['vt'] = VT_DATE + varr['_']['VT_R8'].flat = value.flat + return varr diff --git a/tools/comtypes/comtypes/shelllink.py b/tools/comtypes/comtypes/shelllink.py new file mode 100644 index 00000000000000..b512359a8ca2eb --- /dev/null +++ b/tools/comtypes/comtypes/shelllink.py @@ -0,0 +1,217 @@ +from ctypes import * +from ctypes.wintypes import DWORD, WIN32_FIND_DATAA, WIN32_FIND_DATAW, MAX_PATH +from comtypes import IUnknown, GUID, COMMETHOD, HRESULT, CoClass + +# for GetPath +SLGP_SHORTPATH = 0x1 +SLGP_UNCPRIORITY = 0x2 +SLGP_RAWPATH = 0x4 + +# for SetShowCmd, GetShowCmd +##SW_SHOWNORMAL +##SW_SHOWMAXIMIZED +##SW_SHOWMINNOACTIVE + + +# for Resolve +##SLR_INVOKE_MSI +##SLR_NOLINKINFO +##SLR_NO_UI +##SLR_NOUPDATE +##SLR_NOSEARCH +##SLR_NOTRACK +##SLR_UPDATE + +# fake these... +ITEMIDLIST = c_int +LPITEMIDLIST = LPCITEMIDLIST = POINTER(ITEMIDLIST) + +class IShellLinkA(IUnknown): + _iid_ = GUID('{000214EE-0000-0000-C000-000000000046}') + _methods_ = [ + COMMETHOD([], HRESULT, 'GetPath', + ( ['in', 'out'], c_char_p, 'pszFile' ), + ( ['in'], c_int, 'cchMaxPath' ), + ( ['in', 'out'], POINTER(WIN32_FIND_DATAA), 'pfd' ), + ( ['in'], DWORD, 'fFlags' )), + COMMETHOD([], HRESULT, 'GetIDList', + ( ['retval', 'out'], POINTER(LPITEMIDLIST), 'ppidl' )), + COMMETHOD([], HRESULT, 'SetIDList', + ( ['in'], LPCITEMIDLIST, 'pidl' )), + COMMETHOD([], HRESULT, 'GetDescription', + ( ['in', 'out'], c_char_p, 'pszName' ), + ( ['in'], c_int, 'cchMaxName' )), + COMMETHOD([], HRESULT, 'SetDescription', + ( ['in'], c_char_p, 'pszName' )), + COMMETHOD([], HRESULT, 'GetWorkingDirectory', + ( ['in', 'out'], c_char_p, 'pszDir' ), + ( ['in'], c_int, 'cchMaxPath' )), + COMMETHOD([], HRESULT, 'SetWorkingDirectory', + ( ['in'], c_char_p, 'pszDir' )), + COMMETHOD([], HRESULT, 'GetArguments', + ( ['in', 'out'], c_char_p, 'pszArgs' ), + ( ['in'], c_int, 'cchMaxPath' )), + COMMETHOD([], HRESULT, 'SetArguments', + ( ['in'], c_char_p, 'pszArgs' )), + COMMETHOD(['propget'], HRESULT, 'Hotkey', + ( ['retval', 'out'], POINTER(c_short), 'pwHotkey' )), + COMMETHOD(['propput'], HRESULT, 'Hotkey', + ( ['in'], c_short, 'pwHotkey' )), + COMMETHOD(['propget'], HRESULT, 'ShowCmd', + ( ['retval', 'out'], POINTER(c_int), 'piShowCmd' )), + COMMETHOD(['propput'], HRESULT, 'ShowCmd', + ( ['in'], c_int, 'piShowCmd' )), + COMMETHOD([], HRESULT, 'GetIconLocation', + ( ['in', 'out'], c_char_p, 'pszIconPath' ), + ( ['in'], c_int, 'cchIconPath' ), + ( ['in', 'out'], POINTER(c_int), 'piIcon' )), + COMMETHOD([], HRESULT, 'SetIconLocation', + ( ['in'], c_char_p, 'pszIconPath' ), + ( ['in'], c_int, 'iIcon' )), + COMMETHOD([], HRESULT, 'SetRelativePath', + ( ['in'], c_char_p, 'pszPathRel' ), + ( ['in'], DWORD, 'dwReserved' )), + COMMETHOD([], HRESULT, 'Resolve', + ( ['in'], c_int, 'hwnd' ), + ( ['in'], DWORD, 'fFlags' )), + COMMETHOD([], HRESULT, 'SetPath', + ( ['in'], c_char_p, 'pszFile' )), + ] + + def GetPath(self, flags=SLGP_SHORTPATH): + buf = create_string_buffer(MAX_PATH) + # We're not interested in WIN32_FIND_DATA + self.__com_GetPath(buf, MAX_PATH, None, flags) + return buf.value + + def GetDescription(self): + buf = create_string_buffer(1024) + self.__com_GetDescription(buf, 1024) + return buf.value + + def GetWorkingDirectory(self): + buf = create_string_buffer(MAX_PATH) + self.__com_GetWorkingDirectory(buf, MAX_PATH) + return buf.value + + def GetArguments(self): + buf = create_string_buffer(1024) + self.__com_GetArguments(buf, 1024) + return buf.value + + def GetIconLocation(self): + iIcon = c_int() + buf = create_string_buffer(MAX_PATH) + self.__com_GetIconLocation(buf, MAX_PATH, byref(iIcon)) + return buf.value, iIcon.value + +class IShellLinkW(IUnknown): + _iid_ = GUID('{000214F9-0000-0000-C000-000000000046}') + _methods_ = [ + COMMETHOD([], HRESULT, 'GetPath', + ( ['in', 'out'], c_wchar_p, 'pszFile' ), + ( ['in'], c_int, 'cchMaxPath' ), + ( ['in', 'out'], POINTER(WIN32_FIND_DATAW), 'pfd' ), + ( ['in'], DWORD, 'fFlags' )), + COMMETHOD([], HRESULT, 'GetIDList', + ( ['retval', 'out'], POINTER(LPITEMIDLIST), 'ppidl' )), + COMMETHOD([], HRESULT, 'SetIDList', + ( ['in'], LPCITEMIDLIST, 'pidl' )), + COMMETHOD([], HRESULT, 'GetDescription', + ( ['in', 'out'], c_wchar_p, 'pszName' ), + ( ['in'], c_int, 'cchMaxName' )), + COMMETHOD([], HRESULT, 'SetDescription', + ( ['in'], c_wchar_p, 'pszName' )), + COMMETHOD([], HRESULT, 'GetWorkingDirectory', + ( ['in', 'out'], c_wchar_p, 'pszDir' ), + ( ['in'], c_int, 'cchMaxPath' )), + COMMETHOD([], HRESULT, 'SetWorkingDirectory', + ( ['in'], c_wchar_p, 'pszDir' )), + COMMETHOD([], HRESULT, 'GetArguments', + ( ['in', 'out'], c_wchar_p, 'pszArgs' ), + ( ['in'], c_int, 'cchMaxPath' )), + COMMETHOD([], HRESULT, 'SetArguments', + ( ['in'], c_wchar_p, 'pszArgs' )), + COMMETHOD(['propget'], HRESULT, 'Hotkey', + ( ['retval', 'out'], POINTER(c_short), 'pwHotkey' )), + COMMETHOD(['propput'], HRESULT, 'Hotkey', + ( ['in'], c_short, 'pwHotkey' )), + COMMETHOD(['propget'], HRESULT, 'ShowCmd', + ( ['retval', 'out'], POINTER(c_int), 'piShowCmd' )), + COMMETHOD(['propput'], HRESULT, 'ShowCmd', + ( ['in'], c_int, 'piShowCmd' )), + COMMETHOD([], HRESULT, 'GetIconLocation', + ( ['in', 'out'], c_wchar_p, 'pszIconPath' ), + ( ['in'], c_int, 'cchIconPath' ), + ( ['in', 'out'], POINTER(c_int), 'piIcon' )), + COMMETHOD([], HRESULT, 'SetIconLocation', + ( ['in'], c_wchar_p, 'pszIconPath' ), + ( ['in'], c_int, 'iIcon' )), + COMMETHOD([], HRESULT, 'SetRelativePath', + ( ['in'], c_wchar_p, 'pszPathRel' ), + ( ['in'], DWORD, 'dwReserved' )), + COMMETHOD([], HRESULT, 'Resolve', + ( ['in'], c_int, 'hwnd' ), + ( ['in'], DWORD, 'fFlags' )), + COMMETHOD([], HRESULT, 'SetPath', + ( ['in'], c_wchar_p, 'pszFile' )), + ] + + def GetPath(self, flags=SLGP_SHORTPATH): + buf = create_unicode_buffer(MAX_PATH) + # We're not interested in WIN32_FIND_DATA + self.__com_GetPath(buf, MAX_PATH, None, flags) + return buf.value + + def GetDescription(self): + buf = create_unicode_buffer(1024) + self.__com_GetDescription(buf, 1024) + return buf.value + + def GetWorkingDirectory(self): + buf = create_unicode_buffer(MAX_PATH) + self.__com_GetWorkingDirectory(buf, MAX_PATH) + return buf.value + + def GetArguments(self): + buf = create_unicode_buffer(1024) + self.__com_GetArguments(buf, 1024) + return buf.value + + def GetIconLocation(self): + iIcon = c_int() + buf = create_unicode_buffer(MAX_PATH) + self.__com_GetIconLocation(buf, MAX_PATH, byref(iIcon)) + return buf.value, iIcon.value + +class ShellLink(CoClass): + u'ShellLink class' + _reg_clsid_ = GUID('{00021401-0000-0000-C000-000000000046}') + _idlflags_ = [] + _com_interfaces_ = [IShellLinkW, IShellLinkA] + + +if __name__ == "__main__": + + import sys + import comtypes + from comtypes.client import CreateObject + from comtypes.persist import IPersistFile + + + + shortcut = CreateObject(ShellLink) + print shortcut + ##help(shortcut) + + shortcut.SetPath(sys.executable) + + shortcut.SetDescription("Python %s" % sys.version) + shortcut.SetIconLocation(sys.executable, 1) + + print shortcut.GetPath(2) + print shortcut.GetIconLocation() + + pf = shortcut.QueryInterface(IPersistFile) + pf.Save("foo.lnk", True) + print pf.GetCurFile() diff --git a/tools/comtypes/comtypes/tools/__init__.py b/tools/comtypes/comtypes/tools/__init__.py new file mode 100644 index 00000000000000..a35f8aa391976f --- /dev/null +++ b/tools/comtypes/comtypes/tools/__init__.py @@ -0,0 +1 @@ +# the comtypes.tools package diff --git a/tools/comtypes/comtypes/tools/codegenerator.py b/tools/comtypes/comtypes/tools/codegenerator.py new file mode 100644 index 00000000000000..be0f0566db60f2 --- /dev/null +++ b/tools/comtypes/comtypes/tools/codegenerator.py @@ -0,0 +1,1002 @@ +# Code generator to generate code for everything contained in COM type +# libraries. +import os +import cStringIO +import keyword +from comtypes.tools import typedesc +import comtypes.client +import comtypes.client._generate + +version = "$Rev$"[6:-2] + +__warn_on_munge__ = __debug__ + + +class lcid(object): + def __repr__(self): + return "_lcid" +lcid = lcid() + +class dispid(object): + def __init__(self, memid): + self.memid = memid + + def __repr__(self): + return "dispid(%s)" % self.memid + +class helpstring(object): + def __init__(self, text): + self.text = text + + def __repr__(self): + return "helpstring(%r)" % self.text + + +# XXX Should this be in ctypes itself? +ctypes_names = { + "unsigned char": "c_ubyte", + "signed char": "c_byte", + "char": "c_char", + + "wchar_t": "c_wchar", + + "short unsigned int": "c_ushort", + "short int": "c_short", + + "long unsigned int": "c_ulong", + "long int": "c_long", + "long signed int": "c_long", + + "unsigned int": "c_uint", + "int": "c_int", + + "long long unsigned int": "c_ulonglong", + "long long int": "c_longlong", + + "double": "c_double", + "float": "c_float", + + # Hm... + "void": "None", +} + +def get_real_type(tp): + if type(tp) is typedesc.Typedef: + return get_real_type(tp.typ) + elif isinstance(tp, typedesc.CvQualifiedType): + return get_real_type(tp.typ) + return tp + +ASSUME_STRINGS = True + +def _calc_packing(struct, fields, pack, isStruct): + # Try a certain packing, raise PackingError if field offsets, + # total size ot total alignment is wrong. + if struct.size is None: # incomplete struct + return -1 + if struct.name in dont_assert_size: + return None + if struct.bases: + size = struct.bases[0].size + total_align = struct.bases[0].align + else: + size = 0 + total_align = 8 # in bits + for i, f in enumerate(fields): + if f.bits: # this code cannot handle bit field sizes. +## print "##XXX FIXME" + return -2 # XXX FIXME + s, a = storage(f.typ) + if pack is not None: + a = min(pack, a) + if size % a: + size += a - size % a + if isStruct: + if size != f.offset: + raise PackingError("field %s offset (%s/%s)" % (f.name, size, f.offset)) + size += s + else: + size = max(size, s) + total_align = max(total_align, a) + if total_align != struct.align: + raise PackingError("total alignment (%s/%s)" % (total_align, struct.align)) + a = total_align + if pack is not None: + a = min(pack, a) + if size % a: + size += a - size % a + if size != struct.size: + raise PackingError("total size (%s/%s)" % (size, struct.size)) + +def calc_packing(struct, fields): + # try several packings, starting with unspecified packing + isStruct = isinstance(struct, typedesc.Structure) + for pack in [None, 16*8, 8*8, 4*8, 2*8, 1*8]: + try: + _calc_packing(struct, fields, pack, isStruct) + except PackingError, details: + continue + else: + if pack is None: + return None + return pack/8 + raise PackingError("PACKING FAILED: %s" % details) + +class PackingError(Exception): + pass + +try: + set +except NameError: + # Python 2.3 + from sets import Set as set + +# XXX These should be filtered out in gccxmlparser. +dont_assert_size = set( + [ + "__si_class_type_info_pseudo", + "__class_type_info_pseudo", + ] + ) + +def storage(t): + # return the size and alignment of a type + if isinstance(t, typedesc.Typedef): + return storage(t.typ) + elif isinstance(t, typedesc.ArrayType): + s, a = storage(t.typ) + return s * (int(t.max) - int(t.min) + 1), a + return int(t.size), int(t.align) + +################################################################ + +class Generator(object): + + def __init__(self, ofi, known_symbols=None): + self._externals = {} + self.output = ofi + self.stream = cStringIO.StringIO() + self.imports = cStringIO.StringIO() +## self.stream = self.imports = self.output + self.known_symbols = known_symbols or {} + + self.done = set() # type descriptions that have been generated + self.names = set() # names that have been generated + + def generate(self, item): + if item in self.done: + return + if isinstance(item, typedesc.StructureHead): + name = getattr(item.struct, "name", None) + else: + name = getattr(item, "name", None) + if name in self.known_symbols: + mod = self.known_symbols[name] + print >> self.imports, "from %s import %s" % (mod, name) + self.done.add(item) + if isinstance(item, typedesc.Structure): + self.done.add(item.get_head()) + self.done.add(item.get_body()) + return + mth = getattr(self, type(item).__name__) + # to avoid infinite recursion, we have to mark it as done + # before actually generating the code. + self.done.add(item) + mth(item) + + def generate_all(self, items): + for item in items: + self.generate(item) + + def _make_relative_path(self, path1, path2): + """path1 and path2 are pathnames. + Return path1 as a relative path to path2, if possible. + """ + path1 = os.path.abspath(path1) + path2 = os.path.abspath(path2) + common = os.path.commonprefix([os.path.normcase(path1), + os.path.normcase(path2)]) + if not os.path.isdir(common): + return path1 + if not common.endswith("\\"): + return path1 + if not os.path.isdir(path2): + path2 = os.path.dirname(path2) + # strip the common prefix + path1 = path1[len(common):] + path2 = path2[len(common):] + + parts2 = path2.split("\\") + return "..\\" * len(parts2) + path1 + + def generate_code(self, items, filename=None): + self.filename = filename + if filename is not None: + # Hm, what is the CORRECT encoding? + print >> self.output, "# -*- coding: mbcs -*-" + if os.path.isabs(filename): + # absolute path + print >> self.output, "typelib_path = %r" % filename + elif not os.path.dirname(filename) and not os.path.isfile(filename): + # no directory given, and not in current directory. + print >> self.output, "typelib_path = %r" % filename + else: + # relative path; make relative to comtypes.gen. + path = self._make_relative_path(filename, comtypes.gen.__path__[0]) + print >> self.output, "import os" + print >> self.output, "typelib_path = os.path.normpath(" + print >> self.output, " os.path.abspath(os.path.join(os.path.dirname(__file__)," + print >> self.output, " %r)))" % path + + p = os.path.normpath(os.path.abspath(os.path.join(comtypes.gen.__path__[0], + path))) + assert os.path.isfile(p) + print >> self.imports, "_lcid = 0 # change this if required" + print >> self.imports, "from ctypes import *" + items = set(items) + loops = 0 + while items: + loops += 1 + self.more = set() + self.generate_all(items) + + items |= self.more + items -= self.done + + self.output.write(self.imports.getvalue()) + self.output.write("\n\n") + self.output.write(self.stream.getvalue()) + + import textwrap + wrapper = textwrap.TextWrapper(subsequent_indent=" ", + break_long_words=False) + # XXX The space before '%s' is needed to make sure that the entire list + # does not get pushed to the next line when the first name is + # excessively long. + text = "__all__ = [ %s]" % ", ".join([repr(str(n)) for n in self.names]) + + for line in wrapper.wrap(text): + print >> self.output, line + print >> self.output, "from comtypes import _check_version; _check_version(%r)" % version + return loops + + def type_name(self, t, generate=True): + # Return a string, containing an expression which can be used + # to refer to the type. Assumes the 'from ctypes import *' + # namespace is available. + if isinstance(t, typedesc.SAFEARRAYType): + return "_midlSAFEARRAY(%s)" % self.type_name(t.typ) +## if isinstance(t, typedesc.CoClass): +## return "%s._com_interfaces_[0]" % t.name + if isinstance(t, typedesc.Typedef): + return t.name + if isinstance(t, typedesc.PointerType): + if ASSUME_STRINGS: + x = get_real_type(t.typ) + if isinstance(x, typedesc.FundamentalType): + if x.name == "char": + self.need_STRING() + return "STRING" + elif x.name == "wchar_t": + self.need_WSTRING() + return "WSTRING" + + result = "POINTER(%s)" % self.type_name(t.typ, generate) + # XXX Better to inspect t.typ! + if result.startswith("POINTER(WINFUNCTYPE"): + return result[len("POINTER("):-1] + if result.startswith("POINTER(CFUNCTYPE"): + return result[len("POINTER("):-1] + elif result == "POINTER(None)": + return "c_void_p" + return result + elif isinstance(t, typedesc.ArrayType): + return "%s * %s" % (self.type_name(t.typ, generate), int(t.max)+1) + elif isinstance(t, typedesc.FunctionType): + args = [self.type_name(x, generate) for x in [t.returns] + list(t.iterArgTypes())] + if "__stdcall__" in t.attributes: + return "WINFUNCTYPE(%s)" % ", ".join(args) + else: + return "CFUNCTYPE(%s)" % ", ".join(args) + elif isinstance(t, typedesc.CvQualifiedType): + # const and volatile are ignored + return "%s" % self.type_name(t.typ, generate) + elif isinstance(t, typedesc.FundamentalType): + return ctypes_names[t.name] + elif isinstance(t, typedesc.Structure): + return t.name + elif isinstance(t, typedesc.Enumeration): + if t.name: + return t.name + return "c_int" # enums are integers + return t.name + + def need_VARIANT_imports(self, value): + text = repr(value) + if "Decimal(" in text: + print >> self.imports, "from decimal import Decimal" + if "datetime.datetime(" in text: + print >> self.imports, "import datetime" + + _STRING_defined = False + def need_STRING(self): + if self._STRING_defined: + return + print >> self.imports, "STRING = c_char_p" + self._STRING_defined = True + + _WSTRING_defined = False + def need_WSTRING(self): + if self._WSTRING_defined: + return + print >> self.imports, "WSTRING = c_wchar_p" + self._WSTRING_defined = True + + _OPENARRAYS_defined = False + def need_OPENARRAYS(self): + if self._OPENARRAYS_defined: + return + print >> self.imports, "OPENARRAY = POINTER(c_ubyte) # hack, see comtypes/tools/codegenerator.py" + self._OPENARRAYS_defined = True + + _arraytypes = 0 + def ArrayType(self, tp): + self._arraytypes += 1 + self.generate(get_real_type(tp.typ)) + self.generate(tp.typ) + + _enumvalues = 0 + def EnumValue(self, tp): + value = int(tp.value) + if keyword.iskeyword(tp.name): + # XXX use logging! + if __warn_on_munge__: + print "# Fixing keyword as EnumValue for %s" % tp.name + tp.name += "_" + print >> self.stream, \ + "%s = %d" % (tp.name, value) + self.names.add(tp.name) + self._enumvalues += 1 + + _enumtypes = 0 + def Enumeration(self, tp): + self._enumtypes += 1 + print >> self.stream + if tp.name: + print >> self.stream, "# values for enumeration '%s'" % tp.name + else: + print >> self.stream, "# values for unnamed enumeration" + # Some enumerations have the same name for the enum type + # and an enum value. Excel's XlDisplayShapes is such an example. + # Since we don't have separate namespaces for the type and the values, + # we generate the TYPE last, overwriting the value. XXX + for item in tp.values: + self.generate(item) + if tp.name: + print >> self.stream, "%s = c_int # enum" % tp.name + self.names.add(tp.name) + + _GUID_defined = False + def need_GUID(self): + if self._GUID_defined: + return + self._GUID_defined = True + modname = self.known_symbols.get("GUID") + if modname: + print >> self.imports, "from %s import GUID" % modname + + _typedefs = 0 + def Typedef(self, tp): + self._typedefs += 1 + if type(tp.typ) in (typedesc.Structure, typedesc.Union): + self.generate(tp.typ.get_head()) + self.more.add(tp.typ) + else: + self.generate(tp.typ) + if self.type_name(tp.typ) in self.known_symbols: + stream = self.imports + else: + stream = self.stream + if tp.name != self.type_name(tp.typ): + print >> stream, "%s = %s" % \ + (tp.name, self.type_name(tp.typ)) + self.names.add(tp.name) + + def FundamentalType(self, item): + pass # we should check if this is known somewhere + + def StructureHead(self, head): + for struct in head.struct.bases: + self.generate(struct.get_head()) + self.more.add(struct) + if head.struct.location: + print >> self.stream, "# %s %s" % head.struct.location + basenames = [self.type_name(b) for b in head.struct.bases] + if basenames: + self.need_GUID() + method_names = [m.name for m in head.struct.members if type(m) is typedesc.Method] + print >> self.stream, "class %s(%s):" % (head.struct.name, ", ".join(basenames)) + print >> self.stream, " _iid_ = GUID('{}') # please look up iid and fill in!" + if "Enum" in method_names: + print >> self.stream, " def __iter__(self):" + print >> self.stream, " return self.Enum()" + elif method_names == "Next Skip Reset Clone".split(): + print >> self.stream, " def __iter__(self):" + print >> self.stream, " return self" + print >> self.stream + print >> self.stream, " def next(self):" + print >> self.stream, " arr, fetched = self.Next(1)" + print >> self.stream, " if fetched == 0:" + print >> self.stream, " raise StopIteration" + print >> self.stream, " return arr[0]" + else: + methods = [m for m in head.struct.members if type(m) is typedesc.Method] + if methods: + # Hm. We cannot generate code for IUnknown... + print >> self.stream, "assert 0, 'cannot generate code for IUnknown'" + print >> self.stream, "class %s(_com_interface):" % head.struct.name + print >> self.stream, " pass" + elif type(head.struct) == typedesc.Structure: + print >> self.stream, "class %s(Structure):" % head.struct.name + if hasattr(head.struct, "_recordinfo_"): + print >> self.stream, " _recordinfo_ = %r" % (head.struct._recordinfo_,) + else: + print >> self.stream, " pass" + elif type(head.struct) == typedesc.Union: + print >> self.stream, "class %s(Union):" % head.struct.name + print >> self.stream, " pass" + self.names.add(head.struct.name) + + _structures = 0 + def Structure(self, struct): + self._structures += 1 + self.generate(struct.get_head()) + self.generate(struct.get_body()) + + Union = Structure + + def StructureBody(self, body): + fields = [] + methods = [] + for m in body.struct.members: + if type(m) is typedesc.Field: + fields.append(m) + if type(m.typ) is typedesc.Typedef: + self.generate(get_real_type(m.typ)) + self.generate(m.typ) + elif type(m) is typedesc.Method: + methods.append(m) + self.generate(m.returns) + self.generate_all(m.iterArgTypes()) + elif type(m) is typedesc.Constructor: + pass + + # we don't need _pack_ on Unions (I hope, at least), and not + # on COM interfaces: + if not methods: + try: + pack = calc_packing(body.struct, fields) + if pack is not None: + print >> self.stream, "%s._pack_ = %s" % (body.struct.name, pack) + except PackingError, details: + # if packing fails, write a warning comment to the output. + import warnings + message = "Structure %s: %s" % (body.struct.name, details) + warnings.warn(message, UserWarning) + print >> self.stream, "# WARNING: %s" % details + + if fields: + if body.struct.bases: + assert len(body.struct.bases) == 1 + self.generate(body.struct.bases[0].get_body()) + # field definition normally span several lines. + # Before we generate them, we need to 'import' everything they need. + # So, call type_name for each field once, + for f in fields: + self.type_name(f.typ) + print >> self.stream, "%s._fields_ = [" % body.struct.name + if body.struct.location: + print >> self.stream, " # %s %s" % body.struct.location + # unnamed fields will get autogenerated names "_", "_1". "_2", "_3", ... + unnamed_index = 0 + for f in fields: + if not f.name: + if unnamed_index: + fieldname = "_%d" % unnamed_index + else: + fieldname = "_" + unnamed_index += 1 + print >> self.stream, " # Unnamed field renamed to '%s'" % fieldname + else: + fieldname = f.name + if f.bits is None: + print >> self.stream, " ('%s', %s)," % (fieldname, self.type_name(f.typ)) + else: + print >> self.stream, " ('%s', %s, %s)," % (fieldname, self.type_name(f.typ), f.bits) + print >> self.stream, "]" + + if body.struct.size is None: + msg = ("# The size provided by the typelib is incorrect.\n" + "# The size and alignment check for %s is skipped.") + print >> self.stream, msg % body.struct.name + elif body.struct.name not in dont_assert_size: + size = body.struct.size // 8 + print >> self.stream, "assert sizeof(%s) == %s, sizeof(%s)" % \ + (body.struct.name, size, body.struct.name) + align = body.struct.align // 8 + print >> self.stream, "assert alignment(%s) == %s, alignment(%s)" % \ + (body.struct.name, align, body.struct.name) + + if methods: + self.need_COMMETHOD() + # method definitions normally span several lines. + # Before we generate them, we need to 'import' everything they need. + # So, call type_name for each field once, + for m in methods: + self.type_name(m.returns) + for a in m.iterArgTypes(): + self.type_name(a) + print >> self.stream, "%s._methods_ = [" % body.struct.name + if body.struct.location: + print >> self.stream, "# %s %s" % body.struct.location + + for m in methods: + if m.location: + print >> self.stream, " # %s %s" % m.location + print >> self.stream, " COMMETHOD([], %s, '%s'," % ( + self.type_name(m.returns), + m.name) + for a in m.iterArgTypes(): + print >> self.stream, \ + " ( [], %s, )," % self.type_name(a) + print >> self.stream, " )," + print >> self.stream, "]" + + _midlSAFEARRAY_defined = False + def need_midlSAFEARRAY(self): + if self._midlSAFEARRAY_defined: + return + print >> self.imports, "from comtypes.automation import _midlSAFEARRAY" + self._midlSAFEARRAY_defined = True + + _CoClass_defined = False + def need_CoClass(self): + if self._CoClass_defined: + return + print >> self.imports, "from comtypes import CoClass" + self._CoClass_defined = True + + _dispid_defined = False + def need_dispid(self): + if self._dispid_defined: + return + print >> self.imports, "from comtypes import dispid" + self._dispid_defined = True + + _COMMETHOD_defined = False + def need_COMMETHOD(self): + if self._COMMETHOD_defined: + return + print >> self.imports, "from comtypes import helpstring" + print >> self.imports, "from comtypes import COMMETHOD" + self._COMMETHOD_defined = True + + _DISPMETHOD_defined = False + def need_DISPMETHOD(self): + if self._DISPMETHOD_defined: + return + print >> self.imports, "from comtypes import DISPMETHOD, DISPPROPERTY, helpstring" + self._DISPMETHOD_defined = True + + ################################################################ + # top-level typedesc generators + # + def TypeLib(self, lib): + # lib.name, lib.gui, lib.major, lib.minor, lib.doc + + # Hm, in user code we have to write: + # class MyServer(COMObject, ...): + # _com_interfaces_ = [MyTypeLib.IInterface] + # _reg_typelib_ = MyTypeLib.Library._reg_typelib_ + # ^^^^^^^ + # Should the '_reg_typelib_' attribute be at top-level in the + # generated code, instead as being an attribute of the + # 'Library' symbol? + print >> self.stream, "class Library(object):" + if lib.doc: + print >> self.stream, " %r" % lib.doc + if lib.name: + print >> self.stream, " name = %r" % lib.name + print >> self.stream, " _reg_typelib_ = (%r, %r, %r)" % (lib.guid, lib.major, lib.minor) + print >> self.stream + + def External(self, ext): + # ext.docs - docstring of typelib + # ext.symbol_name - symbol to generate + # ext.tlib - the ITypeLib pointer to the typelibrary containing the symbols definition + # + # ext.name filled in here + + libdesc = str(ext.tlib.GetLibAttr()) # str(TLIBATTR) is unique for a given typelib + if libdesc in self._externals: # typelib wrapper already created + modname = self._externals[libdesc] + # we must fill in ext.name, it is used by self.type_name() + ext.name = "%s.%s" % (modname, ext.symbol_name) + return + + modname = comtypes.client._generate._name_module(ext.tlib) + ext.name = "%s.%s" % (modname, ext.symbol_name) + self._externals[libdesc] = modname + print >> self.imports, "import", modname + comtypes.client.GetModule(ext.tlib) + + def Constant(self, tp): + print >> self.stream, \ + "%s = %r # Constant %s" % (tp.name, + tp.value, + self.type_name(tp.typ, False)) + self.names.add(tp.name) + + def SAFEARRAYType(self, sa): + self.generate(sa.typ) + self.need_midlSAFEARRAY() + + _pointertypes = 0 + def PointerType(self, tp): + self._pointertypes += 1 + if type(tp.typ) is typedesc.ComInterface: + # this defines the class + self.generate(tp.typ.get_head()) + # this defines the _methods_ + self.more.add(tp.typ) + elif type(tp.typ) is typedesc.PointerType: + self.generate(tp.typ) + elif type(tp.typ) in (typedesc.Union, typedesc.Structure): + self.generate(tp.typ.get_head()) + self.more.add(tp.typ) + elif type(tp.typ) is typedesc.Typedef: + self.generate(tp.typ) + else: + self.generate(tp.typ) + + def CoClass(self, coclass): + self.need_GUID() + self.need_CoClass() + print >> self.stream, "class %s(CoClass):" % coclass.name + doc = getattr(coclass, "doc", None) + if doc: + print >> self.stream, " %r" % doc + print >> self.stream, " _reg_clsid_ = GUID(%r)" % coclass.clsid + print >> self.stream, " _idlflags_ = %s" % coclass.idlflags + if self.filename is not None: + print >> self.stream, " _typelib_path_ = typelib_path" +##X print >> self.stream, "POINTER(%s).__ctypes_from_outparam__ = wrap" % coclass.name + + libid = coclass.tlibattr.guid + wMajor, wMinor = coclass.tlibattr.wMajorVerNum, coclass.tlibattr.wMinorVerNum + print >> self.stream, " _reg_typelib_ = (%r, %s, %s)" % (str(libid), wMajor, wMinor) + + for itf, idlflags in coclass.interfaces: + self.generate(itf.get_head()) + implemented = [] + sources = [] + for item in coclass.interfaces: + # item is (interface class, impltypeflags) + if item[1] & 2: # IMPLTYPEFLAG_FSOURCE + # source interface + where = sources + else: + # sink interface + where = implemented + if item[1] & 1: # IMPLTYPEFLAG_FDEAULT + # The default interface should be the first item on the list + where.insert(0, item[0].name) + else: + where.append(item[0].name) + if implemented: + print >> self.stream, "%s._com_interfaces_ = [%s]" % (coclass.name, ", ".join(implemented)) + if sources: + print >> self.stream, "%s._outgoing_interfaces_ = [%s]" % (coclass.name, ", ".join(sources)) + print >> self.stream + self.names.add(coclass.name) + + def ComInterface(self, itf): + self.generate(itf.get_head()) + self.generate(itf.get_body()) + self.names.add(itf.name) + + def _is_enuminterface(self, itf): + # Check if this is an IEnumXXX interface + if not itf.name.startswith("IEnum"): + return False + member_names = [mth.name for mth in itf.members] + for name in ("Next", "Skip", "Reset", "Clone"): + if name not in member_names: + return False + return True + + def ComInterfaceHead(self, head): + if head.itf.name in self.known_symbols: + return + base = head.itf.base + if head.itf.base is None: + # we don't beed to generate IUnknown + return + self.generate(base.get_head()) + self.more.add(base) + basename = self.type_name(head.itf.base) + + self.need_GUID() + print >> self.stream, "class %s(%s):" % (head.itf.name, basename) + print >> self.stream, " _case_insensitive_ = True" + doc = getattr(head.itf, "doc", None) + if doc: + print >> self.stream, " %r" % doc + print >> self.stream, " _iid_ = GUID(%r)" % head.itf.iid + print >> self.stream, " _idlflags_ = %s" % head.itf.idlflags + + if self._is_enuminterface(head.itf): + print >> self.stream, " def __iter__(self):" + print >> self.stream, " return self" + print >> self.stream + + print >> self.stream, " def next(self):" + print >> self.stream, " item, fetched = self.Next(1)" + print >> self.stream, " if fetched:" + print >> self.stream, " return item" + print >> self.stream, " raise StopIteration" + print >> self.stream + + print >> self.stream, " def __getitem__(self, index):" + print >> self.stream, " self.Reset()" + print >> self.stream, " self.Skip(index)" + print >> self.stream, " item, fetched = self.Next(1)" + print >> self.stream, " if fetched:" + print >> self.stream, " return item" + print >> self.stream, " raise IndexError(index)" + print >> self.stream + + def ComInterfaceBody(self, body): + # The base class must be fully generated, including the + # _methods_ list. + self.generate(body.itf.base) + + # make sure we can generate the body + for m in body.itf.members: + for a in m.arguments: + self.generate(a[0]) + self.generate(m.returns) + + self.need_COMMETHOD() + self.need_dispid() + print >> self.stream, "%s._methods_ = [" % body.itf.name + for m in body.itf.members: + if isinstance(m, typedesc.ComMethod): + self.make_ComMethod(m, "dual" in body.itf.idlflags) + else: + raise TypeError("what's this?") + + print >> self.stream, "]" + print >> self.stream, "################################################################" + print >> self.stream, "## code template for %s implementation" % body.itf.name + print >> self.stream, "##class %s_Impl(object):" % body.itf.name + + methods = {} + for m in body.itf.members: + if isinstance(m, typedesc.ComMethod): + # m.arguments is a sequence of tuples: + # (argtype, argname, idlflags, docstring) + # Some typelibs have unnamed method parameters! + inargs = [a[1] or '' for a in m.arguments + if not 'out' in a[2]] + outargs = [a[1] or '' for a in m.arguments + if 'out' in a[2]] + if 'propget' in m.idlflags: + methods.setdefault(m.name, [0, inargs, outargs, m.doc])[0] |= 1 + elif 'propput' in m.idlflags: + methods.setdefault(m.name, [0, inargs[:-1], inargs[-1:], m.doc])[0] |= 2 + else: + methods[m.name] = [0, inargs, outargs, m.doc] + + for name, (typ, inargs, outargs, doc) in methods.iteritems(): + if typ == 0: # method + print >> self.stream, "## def %s(%s):" % (name, ", ".join(["self"] + inargs)) + print >> self.stream, "## %r" % (doc or "-no docstring-") + print >> self.stream, "## #return %s" % (", ".join(outargs)) + elif typ == 1: # propget + print >> self.stream, "## @property" + print >> self.stream, "## def %s(%s):" % (name, ", ".join(["self"] + inargs)) + print >> self.stream, "## %r" % (doc or "-no docstring-") + print >> self.stream, "## #return %s" % (", ".join(outargs)) + elif typ == 2: # propput + print >> self.stream, "## def _set(%s):" % ", ".join(["self"] + inargs + outargs) + print >> self.stream, "## %r" % (doc or "-no docstring-") + print >> self.stream, "## %s = property(fset = _set, doc = _set.__doc__)" % name + elif typ == 3: # propget + propput + print >> self.stream, "## def _get(%s):" % ", ".join(["self"] + inargs) + print >> self.stream, "## %r" % (doc or "-no docstring-") + print >> self.stream, "## #return %s" % (", ".join(outargs)) + print >> self.stream, "## def _set(%s):" % ", ".join(["self"] + inargs + outargs) + print >> self.stream, "## %r" % (doc or "-no docstring-") + print >> self.stream, "## %s = property(_get, _set, doc = _set.__doc__)" % name + else: + raise RuntimeError("BUG") + print >> self.stream, "##" + print >> self.stream + + def DispInterface(self, itf): + self.generate(itf.get_head()) + self.generate(itf.get_body()) + self.names.add(itf.name) + + def DispInterfaceHead(self, head): + self.generate(head.itf.base) + basename = self.type_name(head.itf.base) + + self.need_GUID() + print >> self.stream, "class %s(%s):" % (head.itf.name, basename) + print >> self.stream, " _case_insensitive_ = True" + doc = getattr(head.itf, "doc", None) + if doc: + print >> self.stream, " %r" % doc + print >> self.stream, " _iid_ = GUID(%r)" % head.itf.iid + print >> self.stream, " _idlflags_ = %s" % head.itf.idlflags + print >> self.stream, " _methods_ = []" + + def DispInterfaceBody(self, body): + # make sure we can generate the body + for m in body.itf.members: + if isinstance(m, typedesc.DispMethod): + for a in m.arguments: + self.generate(a[0]) + self.generate(m.returns) + elif isinstance(m, typedesc.DispProperty): + self.generate(m.typ) + else: + raise TypeError(m) + + self.need_dispid() + self.need_DISPMETHOD() + print >> self.stream, "%s._disp_methods_ = [" % body.itf.name + for m in body.itf.members: + if isinstance(m, typedesc.DispMethod): + self.make_DispMethod(m) + elif isinstance(m, typedesc.DispProperty): + self.make_DispProperty(m) + else: + raise TypeError(m) + print >> self.stream, "]" + + ################################################################ + # non-toplevel method generators + # + def make_ComMethod(self, m, isdual): + # typ, name, idlflags, default + if isdual: + idlflags = [dispid(m.memid)] + m.idlflags + else: + # We don't include the dispid for non-dispatch COM interfaces + idlflags = m.idlflags + if __debug__ and m.doc: + idlflags.insert(1, helpstring(m.doc)) + code = " COMMETHOD(%r, %s, '%s'" % ( + idlflags, + self.type_name(m.returns), + m.name) + + if not m.arguments: + print >> self.stream, "%s)," % code + else: + print >> self.stream, "%s," % code + self.stream.write(" ") + arglist = [] + for typ, name, idlflags, default in m.arguments: + type_name = self.type_name(typ) + ########################################################### + # IDL files that contain 'open arrays' or 'conformant + # varying arrays' method parameters are strange. + # These arrays have both a 'size_is()' and + # 'length_is()' attribute, like this example from + # dia2.idl (in the DIA SDK): + # + # interface IDiaSymbol: IUnknown { + # ... + # HRESULT get_dataBytes( + # [in] DWORD cbData, + # [out] DWORD *pcbData, + # [out, size_is(cbData), + # length_is(*pcbData)] BYTE data[] + # ); + # + # The really strange thing is that the decompiled type + # library then contains this declaration, which declares + # the interface itself as [out] method parameter: + # + # interface IDiaSymbol: IUnknown { + # ... + # HRESULT _stdcall get_dataBytes( + # [in] unsigned long cbData, + # [out] unsigned long* pcbData, + # [out] IDiaSymbol data); + # + # Of course, comtypes does not accept a COM interface + # as method parameter; so replace the parameter type + # with the comtypes spelling of 'unsigned char *', and + # mark the parameter as [in, out], so the IDL + # equivalent would be like this: + # + # interface IDiaSymbol: IUnknown { + # ... + # HRESULT _stdcall get_dataBytes( + # [in] unsigned long cbData, + # [out] unsigned long* pcbData, + # [in, out] BYTE data[]); + ########################################################### + if isinstance(typ, typedesc.ComInterface): + self.need_OPENARRAYS() + type_name = "OPENARRAY" + if 'in' not in idlflags: + idlflags.append('in') + if 'lcid' in idlflags:# and 'in' in idlflags: + default = lcid + if default is not None: + self.need_VARIANT_imports(default) + arglist.append("( %r, %s, '%s', %r )" % ( + idlflags, + type_name, + name, + default)) + else: + arglist.append("( %r, %s, '%s' )" % ( + idlflags, + type_name, + name)) + self.stream.write(",\n ".join(arglist)) + print >> self.stream, ")," + + def make_DispMethod(self, m): + idlflags = [dispid(m.dispid)] + m.idlflags + if __debug__ and m.doc: + idlflags.insert(1, helpstring(m.doc)) + # typ, name, idlflags, default + code = " DISPMETHOD(%r, %s, '%s'" % ( + idlflags, + self.type_name(m.returns), + m.name) + + if not m.arguments: + print >> self.stream, "%s)," % code + else: + print >> self.stream, "%s," % code + self.stream.write(" ") + arglist = [] + for typ, name, idlflags, default in m.arguments: + self.need_VARIANT_imports(default) + if default is not None: + arglist.append("( %r, %s, '%s', %r )" % ( + idlflags, + self.type_name(typ), + name, + default)) + else: + arglist.append("( %r, %s, '%s' )" % ( + idlflags, + self.type_name(typ), + name, + )) + self.stream.write(",\n ".join(arglist)) + print >> self.stream, ")," + + def make_DispProperty(self, prop): + idlflags = [dispid(prop.dispid)] + prop.idlflags + if __debug__ and prop.doc: + idlflags.insert(1, helpstring(prop.doc)) + print >> self.stream, " DISPPROPERTY(%r, %s, '%s')," % ( + idlflags, + self.type_name(prop.typ), + prop.name) + +# shortcut for development +if __name__ == "__main__": + import tlbparser + tlbparser.main() diff --git a/tools/comtypes/comtypes/tools/tlbparser.py b/tools/comtypes/comtypes/tools/tlbparser.py new file mode 100644 index 00000000000000..d9d118f93e2a40 --- /dev/null +++ b/tools/comtypes/comtypes/tools/tlbparser.py @@ -0,0 +1,752 @@ +import sys + +from comtypes import automation, typeinfo, COMError +from comtypes.tools import typedesc +from ctypes import c_void_p, sizeof, alignment + +try: + set +except NameError: + from sets import Set as set + +# Is the process 64-bit? +is_64bits = sys.maxsize > 2**32 + + +################################ + +def PTR(typ): + return typedesc.PointerType(typ, + sizeof(c_void_p)*8, + alignment(c_void_p)*8) + +# basic C data types, with size and alignment in bits +char_type = typedesc.FundamentalType("char", 8, 8) +uchar_type = typedesc.FundamentalType("unsigned char", 8, 8) +wchar_t_type = typedesc.FundamentalType("wchar_t", 16, 16) +short_type = typedesc.FundamentalType("short int", 16, 16) +ushort_type = typedesc.FundamentalType("short unsigned int", 16, 16) +int_type = typedesc.FundamentalType("int", 32, 32) +uint_type = typedesc.FundamentalType("unsigned int", 32, 32) +long_type = typedesc.FundamentalType("long int", 32, 32) +ulong_type = typedesc.FundamentalType("long unsigned int", 32, 32) +longlong_type = typedesc.FundamentalType("long long int", 64, 64) +ulonglong_type = typedesc.FundamentalType("long long unsigned int", 64, 64) +float_type = typedesc.FundamentalType("float", 32, 32) +double_type = typedesc.FundamentalType("double", 64, 64) + +# basic COM data types +BSTR_type = typedesc.Typedef("BSTR", PTR(wchar_t_type)) +SCODE_type = typedesc.Typedef("SCODE", int_type) +VARIANT_BOOL_type = typedesc.Typedef("VARIANT_BOOL", short_type) +HRESULT_type = typedesc.Typedef("HRESULT", ulong_type) + +VARIANT_type = typedesc.Structure("VARIANT", + align=alignment(automation.VARIANT)*8, + members=[], bases=[], + size=sizeof(automation.VARIANT)*8) +IDISPATCH_type = typedesc.Typedef("IDispatch", None) +IUNKNOWN_type = typedesc.Typedef("IUnknown", None) +DECIMAL_type = typedesc.Structure("DECIMAL", + align=alignment(automation.DECIMAL)*8, + members=[], bases=[], + size=sizeof(automation.DECIMAL)*8) + +def midlSAFEARRAY(typ): + return typedesc.SAFEARRAYType(typ) + +# faked COM data types +CURRENCY_type = longlong_type # slightly wrong; should be scaled by 10000 - use subclass of longlong? +DATE_type = double_type # not *that* wrong... + +COMTYPES = { + automation.VT_I2: short_type, # 2 + automation.VT_I4: int_type, # 3 + automation.VT_R4: float_type, # 4 + automation.VT_R8: double_type, # 5 + automation.VT_CY: CURRENCY_type, # 6 + automation.VT_DATE: DATE_type, # 7 + automation.VT_BSTR: BSTR_type, # 8 + automation.VT_DISPATCH: PTR(IDISPATCH_type), # 9 + automation.VT_ERROR: SCODE_type, # 10 + automation.VT_BOOL: VARIANT_BOOL_type, # 11 + automation.VT_VARIANT: VARIANT_type, # 12 + automation.VT_UNKNOWN: PTR(IUNKNOWN_type), # 13 + automation.VT_DECIMAL: DECIMAL_type, # 14 + + automation.VT_I1: char_type, # 16 + automation.VT_UI1: uchar_type, # 17 + automation.VT_UI2: ushort_type, # 18 + automation.VT_UI4: ulong_type, # 19 + automation.VT_I8: longlong_type, # 20 + automation.VT_UI8: ulonglong_type, # 21 + automation.VT_INT: int_type, # 22 + automation.VT_UINT: uint_type, # 23 + automation.VT_VOID: typedesc.FundamentalType("void", 0, 0), # 24 + automation.VT_HRESULT: HRESULT_type, # 25 + automation.VT_LPSTR: PTR(char_type), # 30 + automation.VT_LPWSTR: PTR(wchar_t_type), # 31 +} + +#automation.VT_PTR = 26 # below +#automation.VT_SAFEARRAY = 27 +#automation.VT_CARRAY = 28 # below +#automation.VT_USERDEFINED = 29 # below + +#automation.VT_RECORD = 36 + +#automation.VT_ARRAY = 8192 +#automation.VT_BYREF = 16384 + +################################################################ + +class Parser(object): + + def make_type(self, tdesc, tinfo): + try: + return COMTYPES[tdesc.vt] + except KeyError: + pass + + if tdesc.vt == automation.VT_CARRAY: + typ = self.make_type(tdesc._.lpadesc[0].tdescElem, tinfo) + for i in range(tdesc._.lpadesc[0].cDims): + typ = typedesc.ArrayType(typ, + tdesc._.lpadesc[0].rgbounds[i].lLbound, + tdesc._.lpadesc[0].rgbounds[i].cElements-1) + return typ + + elif tdesc.vt == automation.VT_PTR: + typ = self.make_type(tdesc._.lptdesc[0], tinfo) + return PTR(typ) + + elif tdesc.vt == automation.VT_USERDEFINED: + try: + ti = tinfo.GetRefTypeInfo(tdesc._.hreftype) + except COMError, details: + type_name = "__error_hreftype_%d__" % tdesc._.hreftype + tlib_name = get_tlib_filename(self.tlib) + if tlib_name is None: + tlib_name = "unknown typelib" + message = "\n\tGetRefTypeInfo failed in %s: %s\n\tgenerating type '%s' instead" % \ + (tlib_name, details, type_name) + import warnings + warnings.warn(message, UserWarning); + result = typedesc.Structure(type_name, + align=8, + members=[], bases=[], + size=0) + return result + result = self.parse_typeinfo(ti) + assert result is not None, ti.GetDocumentation(-1)[0] + return result + + elif tdesc.vt == automation.VT_SAFEARRAY: + # SAFEARRAY(), see Don Box pp.331f + itemtype = self.make_type(tdesc._.lptdesc[0], tinfo) + return midlSAFEARRAY(itemtype) + + raise NotImplementedError(tdesc.vt) + + ################################################################ + + # TKIND_ENUM = 0 + def ParseEnum(self, tinfo, ta): + ta = tinfo.GetTypeAttr() + enum_name = tinfo.GetDocumentation(-1)[0] + enum = typedesc.Enumeration(enum_name, 32, 32) + self._register(enum_name, enum) + + for i in range(ta.cVars): + vd = tinfo.GetVarDesc(i) + name = tinfo.GetDocumentation(vd.memid)[0] + assert vd.varkind == typeinfo.VAR_CONST + num_val = vd._.lpvarValue[0].value + v = typedesc.EnumValue(name, num_val, enum) + enum.add_value(v) + return enum + + # TKIND_RECORD = 1 + def ParseRecord(self, tinfo, ta): + members = [] # will be filled later + struct_name, doc, helpcntext, helpfile = tinfo.GetDocumentation(-1) + struct = typedesc.Structure(struct_name, + align=ta.cbAlignment*8, + members=members, + bases=[], + size=ta.cbSizeInstance*8) + self._register(struct_name, struct) + + tlib, _ = tinfo.GetContainingTypeLib() + tlib_ta = tlib.GetLibAttr() + # If this is a 32-bit typlib being loaded in a 64-bit process, then the + # size and alignment are incorrect. Set the size to None to disable + # size checks and correct the alignment. + if is_64bits and tlib_ta.syskind == typeinfo.SYS_WIN32: + struct.size = None + struct.align = 64 + + if ta.guid: + struct._recordinfo_ = (str(tlib_ta.guid), + tlib_ta.wMajorVerNum, tlib_ta.wMinorVerNum, + tlib_ta.lcid, + str(ta.guid)) + + for i in range(ta.cVars): + vd = tinfo.GetVarDesc(i) + name = tinfo.GetDocumentation(vd.memid)[0] + offset = vd._.oInst * 8 + assert vd.varkind == typeinfo.VAR_PERINSTANCE + typ = self.make_type(vd.elemdescVar.tdesc, tinfo) + field = typedesc.Field(name, + typ, + None, # bits + offset) + members.append(field) + return struct + + # TKIND_MODULE = 2 + def ParseModule(self, tinfo, ta): + assert 0 == ta.cImplTypes + # functions + for i in range(ta.cFuncs): + # We skip all function definitions. There are several + # problems with these, and we can, for comtypes, ignore them. + continue + fd = tinfo.GetFuncDesc(i) + dllname, func_name, ordinal = tinfo.GetDllEntry(fd.memid, fd.invkind) + func_doc = tinfo.GetDocumentation(fd.memid)[1] + assert 0 == fd.cParamsOpt # XXX + returns = self.make_type(fd.elemdescFunc.tdesc, tinfo) + + if fd.callconv == typeinfo.CC_CDECL: + attributes = "__cdecl__" + elif fd.callconv == typeinfo.CC_STDCALL: + attributes = "__stdcall__" + else: + raise ValueError("calling convention %d" % fd.callconv) + + func = typedesc.Function(func_name, returns, attributes, extern=1) + if func_doc is not None: + func.doc = func_doc.encode("mbcs") + func.dllname = dllname + self._register(func_name, func) + for i in range(fd.cParams): + argtype = self.make_type(fd.lprgelemdescParam[i].tdesc, tinfo) + func.add_argument(argtype) + + # constants + for i in range(ta.cVars): + vd = tinfo.GetVarDesc(i) + name, var_doc = tinfo.GetDocumentation(vd.memid)[0:2] + assert vd.varkind == typeinfo.VAR_CONST + typ = self.make_type(vd.elemdescVar.tdesc, tinfo) + var_value = vd._.lpvarValue[0].value + v = typedesc.Constant(name, typ, var_value) + self._register(name, v) + if var_doc is not None: + v.doc = var_doc + + # TKIND_INTERFACE = 3 + def ParseInterface(self, tinfo, ta): + itf_name, itf_doc = tinfo.GetDocumentation(-1)[0:2] + assert ta.cImplTypes <= 1 + if ta.cImplTypes == 0 and itf_name != "IUnknown": + # Windows defines an interface IOleControlTypes in ocidl.idl. + # Don't known what artefact that is - we ignore it. + # It's an interface without methods anyway. + if itf_name != "IOleControlTypes": + message = "Ignoring interface %s which has no base interface" % itf_name + import warnings + warnings.warn(message, UserWarning); + return None + + itf = typedesc.ComInterface(itf_name, + members=[], + base=None, + iid=str(ta.guid), + idlflags=self.interface_type_flags(ta.wTypeFlags)) + if itf_doc: + itf.doc = itf_doc + self._register(itf_name, itf) + + if ta.cImplTypes: + hr = tinfo.GetRefTypeOfImplType(0) + tibase = tinfo.GetRefTypeInfo(hr) + itf.base = self.parse_typeinfo(tibase) + + assert ta.cVars == 0, "vars on an Interface?" + + members = [] + for i in range(ta.cFuncs): + fd = tinfo.GetFuncDesc(i) +## func_name = tinfo.GetDocumentation(fd.memid)[0] + func_name, func_doc = tinfo.GetDocumentation(fd.memid)[:2] + assert fd.funckind == typeinfo.FUNC_PUREVIRTUAL + returns = self.make_type(fd.elemdescFunc.tdesc, tinfo) + names = tinfo.GetNames(fd.memid, fd.cParams+1) + names.append("rhs") + names = names[:fd.cParams + 1] + assert len(names) == fd.cParams + 1 + flags = self.func_flags(fd.wFuncFlags) + flags += self.inv_kind(fd.invkind) + mth = typedesc.ComMethod(fd.invkind, fd.memid, func_name, returns, flags, func_doc) + mth.oVft = fd.oVft + for p in range(fd.cParams): + typ = self.make_type(fd.lprgelemdescParam[p].tdesc, tinfo) + name = names[p+1] + flags = fd.lprgelemdescParam[p]._.paramdesc.wParamFlags + if flags & typeinfo.PARAMFLAG_FHASDEFAULT: + # XXX should be handled by VARIANT itself + var = fd.lprgelemdescParam[p]._.paramdesc.pparamdescex[0].varDefaultValue + default = var.value + else: + default = None + mth.add_argument(typ, name, self.param_flags(flags), default) + members.append((fd.oVft, mth)) + # Sort the methods by oVft (VTable offset): Some typeinfo + # don't list methods in VTable order. + members.sort() + itf.members.extend([m[1] for m in members]) + + return itf + + # TKIND_DISPATCH = 4 + def ParseDispatch(self, tinfo, ta): + itf_name, doc = tinfo.GetDocumentation(-1)[0:2] + assert ta.cImplTypes == 1 + + hr = tinfo.GetRefTypeOfImplType(0) + tibase = tinfo.GetRefTypeInfo(hr) + base = self.parse_typeinfo(tibase) + members = [] + itf = typedesc.DispInterface(itf_name, + members=members, + base=base, + iid=str(ta.guid), + idlflags=self.interface_type_flags(ta.wTypeFlags)) + if doc is not None: + itf.doc = str(doc.split("\0")[0]) + self._register(itf_name, itf) + + # This code can only handle pure dispinterfaces. Dual + # interfaces are parsed in ParseInterface(). + assert ta.wTypeFlags & typeinfo.TYPEFLAG_FDUAL == 0 + + for i in range(ta.cVars): + vd = tinfo.GetVarDesc(i) + assert vd.varkind == typeinfo.VAR_DISPATCH + var_name, var_doc = tinfo.GetDocumentation(vd.memid)[0:2] + typ = self.make_type(vd.elemdescVar.tdesc, tinfo) + mth = typedesc.DispProperty(vd.memid, var_name, typ, self.var_flags(vd.wVarFlags), var_doc) + itf.members.append(mth) + + # At least the EXCEL typelib lists the IUnknown and IDispatch + # methods even for this kind of interface. I didn't find any + # indication about these methods in the various flags, so we + # have to exclude them by name. + # CLF: 12/14/2012 Do this in a way that does not exclude other methods. + # I have encountered typlibs where only "QueryInterface", "AddRef" + # and "Release" are to be skipped. + ignored_names = set(["QueryInterface", "AddRef", "Release", + "GetTypeInfoCount", "GetTypeInfo", + "GetIDsOfNames", "Invoke"]) + + for i in range(ta.cFuncs): + fd = tinfo.GetFuncDesc(i) + func_name, func_doc = tinfo.GetDocumentation(fd.memid)[:2] + if func_name in ignored_names: + continue + assert fd.funckind == typeinfo.FUNC_DISPATCH + + returns = self.make_type(fd.elemdescFunc.tdesc, tinfo) + names = tinfo.GetNames(fd.memid, fd.cParams+1) + names.append("rhs") + names = names[:fd.cParams + 1] + assert len(names) == fd.cParams + 1 # function name first, then parameter names + flags = self.func_flags(fd.wFuncFlags) + flags += self.inv_kind(fd.invkind) + mth = typedesc.DispMethod(fd.memid, fd.invkind, func_name, returns, flags, func_doc) + for p in range(fd.cParams): + typ = self.make_type(fd.lprgelemdescParam[p].tdesc, tinfo) + name = names[p+1] + flags = fd.lprgelemdescParam[p]._.paramdesc.wParamFlags + if flags & typeinfo.PARAMFLAG_FHASDEFAULT: + var = fd.lprgelemdescParam[p]._.paramdesc.pparamdescex[0].varDefaultValue + default = var.value + else: + default = None + mth.add_argument(typ, name, self.param_flags(flags), default) + itf.members.append(mth) + + return itf + + def inv_kind(self, invkind): + NAMES = {automation.DISPATCH_METHOD: [], + automation.DISPATCH_PROPERTYPUT: ["propput"], + automation.DISPATCH_PROPERTYPUTREF: ["propputref"], + automation.DISPATCH_PROPERTYGET: ["propget"]} + return NAMES[invkind] + + def func_flags(self, flags): + # map FUNCFLAGS values to idl attributes + NAMES = {typeinfo.FUNCFLAG_FRESTRICTED: "restricted", + typeinfo.FUNCFLAG_FSOURCE: "source", + typeinfo.FUNCFLAG_FBINDABLE: "bindable", + typeinfo.FUNCFLAG_FREQUESTEDIT: "requestedit", + typeinfo.FUNCFLAG_FDISPLAYBIND: "displaybind", + typeinfo.FUNCFLAG_FDEFAULTBIND: "defaultbind", + typeinfo.FUNCFLAG_FHIDDEN: "hidden", + typeinfo.FUNCFLAG_FUSESGETLASTERROR: "usesgetlasterror", + typeinfo.FUNCFLAG_FDEFAULTCOLLELEM: "defaultcollelem", + typeinfo.FUNCFLAG_FUIDEFAULT: "uidefault", + typeinfo.FUNCFLAG_FNONBROWSABLE: "nonbrowsable", + # typeinfo.FUNCFLAG_FREPLACEABLE: "???", + typeinfo.FUNCFLAG_FIMMEDIATEBIND: "immediatebind"} + return [NAMES[bit] for bit in NAMES if bit & flags] + + def param_flags(self, flags): + # map PARAMFLAGS values to idl attributes + NAMES = {typeinfo.PARAMFLAG_FIN: "in", + typeinfo.PARAMFLAG_FOUT: "out", + typeinfo.PARAMFLAG_FLCID: "lcid", + typeinfo.PARAMFLAG_FRETVAL: "retval", + typeinfo.PARAMFLAG_FOPT: "optional", + # typeinfo.PARAMFLAG_FHASDEFAULT: "", + # typeinfo.PARAMFLAG_FHASCUSTDATA: "", + } + return [NAMES[bit] for bit in NAMES if bit & flags] + + def coclass_type_flags(self, flags): + # map TYPEFLAGS values to idl attributes + NAMES = {typeinfo.TYPEFLAG_FAPPOBJECT: "appobject", + # typeinfo.TYPEFLAG_FCANCREATE: + typeinfo.TYPEFLAG_FLICENSED: "licensed", + # typeinfo.TYPEFLAG_FPREDECLID: + typeinfo.TYPEFLAG_FHIDDEN: "hidden", + typeinfo.TYPEFLAG_FCONTROL: "control", + typeinfo.TYPEFLAG_FDUAL: "dual", + typeinfo.TYPEFLAG_FNONEXTENSIBLE: "nonextensible", + typeinfo.TYPEFLAG_FOLEAUTOMATION: "oleautomation", + typeinfo.TYPEFLAG_FRESTRICTED: "restricted", + typeinfo.TYPEFLAG_FAGGREGATABLE: "aggregatable", + # typeinfo.TYPEFLAG_FREPLACEABLE: + # typeinfo.TYPEFLAG_FDISPATCHABLE # computed, no flag for this + typeinfo.TYPEFLAG_FREVERSEBIND: "reversebind", + typeinfo.TYPEFLAG_FPROXY: "proxy", + } + NEGATIVE_NAMES = {typeinfo.TYPEFLAG_FCANCREATE: "noncreatable"} + return [NAMES[bit] for bit in NAMES if bit & flags] + \ + [NEGATIVE_NAMES[bit] for bit in NEGATIVE_NAMES if not (bit & flags)] + + def interface_type_flags(self, flags): + # map TYPEFLAGS values to idl attributes + NAMES = {typeinfo.TYPEFLAG_FAPPOBJECT: "appobject", + # typeinfo.TYPEFLAG_FCANCREATE: + typeinfo.TYPEFLAG_FLICENSED: "licensed", + # typeinfo.TYPEFLAG_FPREDECLID: + typeinfo.TYPEFLAG_FHIDDEN: "hidden", + typeinfo.TYPEFLAG_FCONTROL: "control", + typeinfo.TYPEFLAG_FDUAL: "dual", + typeinfo.TYPEFLAG_FNONEXTENSIBLE: "nonextensible", + typeinfo.TYPEFLAG_FOLEAUTOMATION: "oleautomation", + typeinfo.TYPEFLAG_FRESTRICTED: "restricted", + typeinfo.TYPEFLAG_FAGGREGATABLE: "aggregatable", + # typeinfo.TYPEFLAG_FREPLACEABLE: + # typeinfo.TYPEFLAG_FDISPATCHABLE # computed, no flag for this + typeinfo.TYPEFLAG_FREVERSEBIND: "reversebind", + typeinfo.TYPEFLAG_FPROXY: "proxy", + } + NEGATIVE_NAMES = {} + return [NAMES[bit] for bit in NAMES if bit & flags] + \ + [NEGATIVE_NAMES[bit] for bit in NEGATIVE_NAMES if not (bit & flags)] + + def var_flags(self, flags): + NAMES = {typeinfo.VARFLAG_FREADONLY: "readonly", + typeinfo.VARFLAG_FSOURCE: "source", + typeinfo.VARFLAG_FBINDABLE: "bindable", + typeinfo.VARFLAG_FREQUESTEDIT: "requestedit", + typeinfo.VARFLAG_FDISPLAYBIND: "displaybind", + typeinfo.VARFLAG_FDEFAULTBIND: "defaultbind", + typeinfo.VARFLAG_FHIDDEN: "hidden", + typeinfo.VARFLAG_FRESTRICTED: "restricted", + typeinfo.VARFLAG_FDEFAULTCOLLELEM: "defaultcollelem", + typeinfo.VARFLAG_FUIDEFAULT: "uidefault", + typeinfo.VARFLAG_FNONBROWSABLE: "nonbrowsable", + typeinfo.VARFLAG_FREPLACEABLE: "replaceable", + typeinfo.VARFLAG_FIMMEDIATEBIND: "immediatebind" + } + return [NAMES[bit] for bit in NAMES if bit & flags] + + + # TKIND_COCLASS = 5 + def ParseCoClass(self, tinfo, ta): + # possible ta.wTypeFlags: helpstring, helpcontext, licensed, + # version, control, hidden, and appobject + coclass_name, doc = tinfo.GetDocumentation(-1)[0:2] + tlibattr = tinfo.GetContainingTypeLib()[0].GetLibAttr() + coclass = typedesc.CoClass(coclass_name, + str(ta.guid), + self.coclass_type_flags(ta.wTypeFlags), + tlibattr) + if doc is not None: + coclass.doc = doc + self._register(coclass_name, coclass) + + for i in range(ta.cImplTypes): + hr = tinfo.GetRefTypeOfImplType(i) + ti = tinfo.GetRefTypeInfo(hr) + itf = self.parse_typeinfo(ti) + flags = tinfo.GetImplTypeFlags(i) + coclass.add_interface(itf, flags) + return coclass + + # TKIND_ALIAS = 6 + def ParseAlias(self, tinfo, ta): + name = tinfo.GetDocumentation(-1)[0] + typ = self.make_type(ta.tdescAlias, tinfo) + alias = typedesc.Typedef(name, typ) + self._register(name, alias) + return alias + + # TKIND_UNION = 7 + def ParseUnion(self, tinfo, ta): + union_name, doc, helpcntext, helpfile = tinfo.GetDocumentation(-1) + members = [] + union = typedesc.Union(union_name, + align=ta.cbAlignment*8, + members=members, + bases=[], + size=ta.cbSizeInstance*8) + self._register(union_name, union) + + tlib, _ = tinfo.GetContainingTypeLib() + tlib_ta = tlib.GetLibAttr() + # If this is a 32-bit typlib being loaded in a 64-bit process, then the + # size and alignment are incorrect. Set the size to None to disable + # size checks and correct the alignment. + if is_64bits and tlib_ta.syskind == typeinfo.SYS_WIN32: + union.size = None + union.align = 64 + + for i in range(ta.cVars): + vd = tinfo.GetVarDesc(i) + name = tinfo.GetDocumentation(vd.memid)[0] + offset = vd._.oInst * 8 + assert vd.varkind == typeinfo.VAR_PERINSTANCE + typ = self.make_type(vd.elemdescVar.tdesc, tinfo) + field = typedesc.Field(name, + typ, + None, # bits + offset) + members.append(field) + return union + + ################################################################ + + def _typelib_module(self, tlib=None): + if tlib is None: + tlib = self.tlib + # return a string that uniquely identifies a typelib. + # The string doesn't have any meaning outside this instance. + return str(tlib.GetLibAttr()) + + def _register(self, name, value, tlib=None): + modname = self._typelib_module(tlib) + fullname = "%s.%s" % (modname, name) + if fullname in self.items: + # XXX Can we really allow this? It happens, at least. + if isinstance(value, typedesc.External): + return + # BUG: We try to register an item that's already registered. + raise ValueError("Bug: Multiple registered name '%s': %r" % (name, value)) + self.items[fullname] = value + + def parse_typeinfo(self, tinfo): + name = tinfo.GetDocumentation(-1)[0] + modname = self._typelib_module() + try: + return self.items["%s.%s" % (modname, name)] + except KeyError: + pass + + tlib = tinfo.GetContainingTypeLib()[0] + if tlib != self.tlib: + ta = tinfo.GetTypeAttr() + size = ta.cbSizeInstance * 8 + align = ta.cbAlignment * 8 + typ = typedesc.External(tlib, + name, + size, + align, + tlib.GetDocumentation(-1)[:2]) + self._register(name, typ, tlib) + return typ + + ta = tinfo.GetTypeAttr() + tkind = ta.typekind + + if tkind == typeinfo.TKIND_ENUM: # 0 + return self.ParseEnum(tinfo, ta) + elif tkind == typeinfo.TKIND_RECORD: # 1 + return self.ParseRecord(tinfo, ta) + elif tkind == typeinfo.TKIND_MODULE: # 2 + return self.ParseModule(tinfo, ta) + elif tkind == typeinfo.TKIND_INTERFACE: # 3 + return self.ParseInterface(tinfo, ta) + elif tkind == typeinfo.TKIND_DISPATCH: # 4 + try: + # GetRefTypeOfImplType(-1) returns the custom portion + # of a dispinterface, if it is dual + href = tinfo.GetRefTypeOfImplType(-1) + except COMError: + # no dual interface + return self.ParseDispatch(tinfo, ta) + tinfo = tinfo.GetRefTypeInfo(href) + ta = tinfo.GetTypeAttr() + assert ta.typekind == typeinfo.TKIND_INTERFACE + return self.ParseInterface(tinfo, ta) + elif tkind == typeinfo.TKIND_COCLASS: # 5 + return self.ParseCoClass(tinfo, ta) + elif tkind == typeinfo.TKIND_ALIAS: # 6 + return self.ParseAlias(tinfo, ta) + elif tkind == typeinfo.TKIND_UNION: # 7 + return self.ParseUnion(tinfo, ta) + else: + print "NYI", tkind +## raise "NYI", tkind + + def parse_LibraryDescription(self): + la = self.tlib.GetLibAttr() + name, doc = self.tlib.GetDocumentation(-1)[:2] + desc = typedesc.TypeLib(name, + str(la.guid), la.wMajorVerNum, la.wMinorVerNum, + doc) + self._register(None, desc) + + ################################################################ + + def parse(self): + self.parse_LibraryDescription() + + for i in range(self.tlib.GetTypeInfoCount()): + tinfo = self.tlib.GetTypeInfo(i) + self.parse_typeinfo(tinfo) + return self.items + +class TlbFileParser(Parser): + "Parses a type library from a file" + def __init__(self, path): + # XXX DOESN'T LOOK CORRECT: We should NOT register the typelib. + self.tlib = typeinfo.LoadTypeLibEx(path)#, regkind=typeinfo.REGKIND_REGISTER) + self.items = {} + +class TypeLibParser(Parser): + def __init__(self, tlib): + self.tlib = tlib + self.items = {} + +################################################################ +# some interesting typelibs + +## these do NOT work: + # XXX infinite loop? +## path = r"mshtml.tlb" # has propputref + + # has SAFEARRAY + # HRESULT Run(BSTR, SAFEARRAY(VARIANT)*, VARIANT*) +## path = "msscript.ocx" + + # has SAFEARRAY + # HRESULT AddAddress(SAFEARRAY(BSTR)*, SAFEARRAY(BSTR)*) +## path = r"c:\Programme\Microsoft Office\Office\MSWORD8.OLB" # has propputref + + # has SAFEARRAY: + # SAFEARRAY(unsigned char) FileSignatureInfo(BSTR, long, MsiSignatureInfo) +## path = r"msi.dll" # DispProperty + + # fails packing IDLDESC +## path = r"C:\Dokumente und Einstellungen\thomas\Desktop\tlb\win.tlb" + # fails packing WIN32_FIND_DATA +## path = r"C:\Dokumente und Einstellungen\thomas\Desktop\tlb\win32.tlb" + # has a POINTER(IUnknown) as default parameter value +## path = r"c:\Programme\Gemeinsame Dateien\Microsoft Shared\Speech\sapi.dll" + + +## path = r"hnetcfg.dll" +## path = r"simpdata.tlb" +## path = r"nscompat.tlb" +## path = r"stdole32.tlb" + +## path = r"shdocvw.dll" + +## path = r"c:\Programme\Microsoft Office\Office\MSO97.DLL" +## path = r"PICCLP32.OCX" # DispProperty +## path = r"MSHFLXGD.OCX" # DispProperty, propputref +## path = r"scrrun.dll" # propput AND propputref on IDictionary::Item +## path = r"C:\Dokumente und Einstellungen\thomas\Desktop\tlb\threadapi.tlb" + +## path = r"..\samples\BITS\bits2_0.tlb" + +## path = r"c:\vc98\include\activscp.tlb" + +def get_tlib_filename(tlib): + # seems if the typelib is not registered, there's no way to + # determine the filename. + from ctypes import windll, byref + from comtypes import BSTR + la = tlib.GetLibAttr() + name = BSTR() + try: + windll.oleaut32.QueryPathOfRegTypeLib + except AttributeError: + # Windows CE doesn't have this function + return None + if 0 == windll.oleaut32.QueryPathOfRegTypeLib(byref(la.guid), + la.wMajorVerNum, + la.wMinorVerNum, + 0, # lcid + byref(name) + ): + return name.value.split("\0")[0] + return None + +def _py2exe_hint(): + # If the tlbparser is frozen, we need to include these + import comtypes.persist + import comtypes.typeinfo + import comtypes.automation + +def generate_module(tlib, ofi, pathname): + known_symbols = {} + for name in ("comtypes.persist", + "comtypes.typeinfo", + "comtypes.automation", + "comtypes._others", + "comtypes", + "ctypes.wintypes", + "ctypes"): + try: + mod = __import__(name) + except ImportError: + if name == "comtypes._others": + continue + raise + for submodule in name.split(".")[1:]: + mod = getattr(mod, submodule) + for name in mod.__dict__: + known_symbols[name] = mod.__name__ + p = TypeLibParser(tlib) + if pathname is None: + pathname = get_tlib_filename(tlib) + items = p.parse() + + from codegenerator import Generator + + gen = Generator(ofi, + known_symbols=known_symbols, + ) + + gen.generate_code(items.values(), filename=pathname) + +# -eof- diff --git a/tools/comtypes/comtypes/tools/typedesc.py b/tools/comtypes/comtypes/tools/typedesc.py new file mode 100644 index 00000000000000..9665b62f33472a --- /dev/null +++ b/tools/comtypes/comtypes/tools/typedesc.py @@ -0,0 +1,138 @@ +# More type descriptions from parsed COM typelibaries, extending those +# in typedesc_base + +import ctypes +from comtypes.tools.typedesc_base import * + +class TypeLib(object): + def __init__(self, name, guid, major, minor, doc=None): + self.name = name + self.guid = guid + self.major = major + self.minor = minor + self.doc = doc + + def __repr__(self): + return "" % (self.name, self.guid, self.major, self.minor) + +class Constant(object): + def __init__(self, name, typ, value): + self.name = name + self.typ = typ + self.value = value + +class External(object): + def __init__(self, tlib, name, size, align, docs=None): + # the type library containing the symbol + self.tlib = tlib + # name of symbol + self.symbol_name = name + self.size = size + self.align = align + # type lib description + self.docs = docs + + def get_head(self): + # codegen might call this + return self + +class SAFEARRAYType(object): + def __init__(self, typ): + self.typ = typ + self.align = self.size = ctypes.sizeof(ctypes.c_void_p) * 8 + +class ComMethod(object): + # custom COM method, parsed from typelib + def __init__(self, invkind, memid, name, returns, idlflags, doc): + self.invkind = invkind + self.name = name + self.returns = returns + self.idlflags = idlflags + self.memid = memid + self.doc = doc + self.arguments = [] + + def add_argument(self, typ, name, idlflags, default): + self.arguments.append((typ, name, idlflags, default)) + +class DispMethod(object): + # dispatchable COM method, parsed from typelib + def __init__(self, dispid, invkind, name, returns, idlflags, doc): + self.dispid = dispid + self.invkind = invkind + self.name = name + self.returns = returns + self.idlflags = idlflags + self.doc = doc + self.arguments = [] + + def add_argument(self, typ, name, idlflags, default): + self.arguments.append((typ, name, idlflags, default)) + +class DispProperty(object): + # dispatchable COM property, parsed from typelib + def __init__(self, dispid, name, typ, idlflags, doc): + self.dispid = dispid + self.name = name + self.typ = typ + self.idlflags = idlflags + self.doc = doc + +class DispInterfaceHead(object): + def __init__(self, itf): + self.itf = itf + +class DispInterfaceBody(object): + def __init__(self, itf): + self.itf = itf + +class DispInterface(object): + def __init__(self, name, members, base, iid, idlflags): + self.name = name + self.members = members + self.base = base + self.iid = iid + self.idlflags = idlflags + self.itf_head = DispInterfaceHead(self) + self.itf_body = DispInterfaceBody(self) + + def get_body(self): + return self.itf_body + + def get_head(self): + return self.itf_head + +class ComInterfaceHead(object): + def __init__(self, itf): + self.itf = itf + +class ComInterfaceBody(object): + def __init__(self, itf): + self.itf = itf + +class ComInterface(object): + def __init__(self, name, members, base, iid, idlflags): + self.name = name + self.members = members + self.base = base + self.iid = iid + self.idlflags = idlflags + self.itf_head = ComInterfaceHead(self) + self.itf_body = ComInterfaceBody(self) + + def get_body(self): + return self.itf_body + + def get_head(self): + return self.itf_head + +class CoClass(object): + def __init__(self, name, clsid, idlflags, tlibattr): + self.name = name + self.clsid = clsid + self.idlflags = idlflags + self.tlibattr = tlibattr + self.interfaces = [] + + def add_interface(self, itf, idlflags): + self.interfaces.append((itf, idlflags)) diff --git a/tools/comtypes/comtypes/tools/typedesc_base.py b/tools/comtypes/comtypes/tools/typedesc_base.py new file mode 100644 index 00000000000000..534de91e12d18a --- /dev/null +++ b/tools/comtypes/comtypes/tools/typedesc_base.py @@ -0,0 +1,205 @@ +# typedesc.py - classes representing C type descriptions +try: + set +except NameError: + from sets import Set as set + +class Argument(object): + "a Parameter in the argument list of a callable (Function, Method, ...)" + def __init__(self, atype, name): + self.atype = atype + self.name = name + +class _HasArgs(object): + + def __init__(self): + self.arguments = [] + + def add_argument(self, arg): + assert isinstance(arg, Argument) + self.arguments.append(arg) + + def iterArgTypes(self): + for a in self.arguments: + yield a.atype + + def iterArgNames(self): + for a in self.arguments: + yield a.name + + def fixup_argtypes(self, typemap): + for a in self.arguments: + a.atype = typemap[a.atype] + + +################ + +class Alias(object): + # a C preprocessor alias, like #define A B + def __init__(self, name, alias, typ=None): + self.name = name + self.alias = alias + self.typ = typ + +class Macro(object): + # a C preprocessor definition with arguments + def __init__(self, name, args, body): + # all arguments are strings, args is the literal argument list + # *with* the parens around it: + # Example: Macro("CD_INDRIVE", "(status)", "((int)status > 0)") + self.name = name + self.args = args + self.body = body + +class File(object): + def __init__(self, name): + self.name = name + +class Function(_HasArgs): + location = None + def __init__(self, name, returns, attributes, extern): + _HasArgs.__init__(self) + self.name = name + self.returns = returns + self.attributes = attributes # dllimport, __stdcall__, __cdecl__ + self.extern = extern + +class Constructor(_HasArgs): + location = None + def __init__(self, name): + _HasArgs.__init__(self) + self.name = name + +class OperatorFunction(_HasArgs): + location = None + def __init__(self, name, returns): + _HasArgs.__init__(self) + self.name = name + self.returns = returns + +class FunctionType(_HasArgs): + location = None + def __init__(self, returns, attributes): + _HasArgs.__init__(self) + self.returns = returns + self.attributes = attributes + +class Method(_HasArgs): + location = None + def __init__(self, name, returns): + _HasArgs.__init__(self) + self.name = name + self.returns = returns + +class FundamentalType(object): + location = None + def __init__(self, name, size, align): + self.name = name + if name != "void": + self.size = int(size) + self.align = int(align) + +class PointerType(object): + location = None + def __init__(self, typ, size, align): + self.typ = typ + self.size = int(size) + self.align = int(align) + +class Typedef(object): + location = None + def __init__(self, name, typ): + self.name = name + self.typ = typ + +class ArrayType(object): + location = None + def __init__(self, typ, min, max): + self.typ = typ + self.min = min + self.max = max + +class StructureHead(object): + location = None + def __init__(self, struct): + self.struct = struct + +class StructureBody(object): + location = None + def __init__(self, struct): + self.struct = struct + +class _Struct_Union_Base(object): + location = None + def get_body(self): + return self.struct_body + + def get_head(self): + return self.struct_head + +class Structure(_Struct_Union_Base): + def __init__(self, name, align, members, bases, size, artificial=None): + self.name = name + self.align = int(align) + self.members = members + self.bases = bases + self.artificial = artificial + if size is not None: + self.size = int(size) + else: + self.size = None + self.struct_body = StructureBody(self) + self.struct_head = StructureHead(self) + +class Union(_Struct_Union_Base): + def __init__(self, name, align, members, bases, size, artificial=None): + self.name = name + self.align = int(align) + self.members = members + self.bases = bases + self.artificial = artificial + if size is not None: + self.size = int(size) + else: + self.size = None + self.struct_body = StructureBody(self) + self.struct_head = StructureHead(self) + +class Field(object): + def __init__(self, name, typ, bits, offset): + self.name = name + self.typ = typ + self.bits = bits + self.offset = int(offset) + +class CvQualifiedType(object): + def __init__(self, typ, const, volatile): + self.typ = typ + self.const = const + self.volatile = volatile + +class Enumeration(object): + location = None + def __init__(self, name, size, align): + self.name = name + self.size = int(size) + self.align = int(align) + self.values = [] + + def add_value(self, v): + self.values.append(v) + +class EnumValue(object): + def __init__(self, name, value, enumeration): + self.name = name + self.value = value + self.enumeration = enumeration + +class Variable(object): + location = None + def __init__(self, name, typ, init=None): + self.name = name + self.typ = typ + self.init = init + +################################################################ diff --git a/tools/comtypes/comtypes/typeinfo.py b/tools/comtypes/comtypes/typeinfo.py new file mode 100644 index 00000000000000..f72698df298256 --- /dev/null +++ b/tools/comtypes/comtypes/typeinfo.py @@ -0,0 +1,913 @@ +# XXX Should convert from STDMETHOD to COMMETHOD. + +# generated by 'xml2py' +# flags '..\tools\windows.xml -m comtypes -m comtypes.automation -w -r .*TypeLibEx -r .*TypeLib -o typeinfo.py' +# then hacked manually +import os +import sys +import weakref + +from ctypes import * +from ctypes.wintypes import ULONG +from comtypes import STDMETHOD +from comtypes import COMMETHOD +from comtypes import _GUID, GUID +# XXX should import more stuff from ctypes.wintypes... +from comtypes.automation import BSTR +from comtypes.automation import DISPID +from comtypes.automation import DISPPARAMS +from comtypes.automation import DWORD +from comtypes.automation import EXCEPINFO +from comtypes.automation import HRESULT +from comtypes.automation import IID +from comtypes.automation import IUnknown +from comtypes.automation import LCID +from comtypes.automation import LONG +from comtypes.automation import SCODE +from comtypes.automation import UINT +from comtypes.automation import VARIANT +from comtypes.automation import VARIANTARG +from comtypes.automation import VARTYPE +from comtypes.automation import WCHAR +from comtypes.automation import WORD +from comtypes.automation import tagVARIANT + +is_64_bit = sys.maxsize > 2**32 + +BOOL = c_int +HREFTYPE = DWORD +INT = c_int +MEMBERID = DISPID +OLECHAR = WCHAR +PVOID = c_void_p +SHORT = c_short +# See https://msdn.microsoft.com/en-us/library/windows/desktop/aa383751(v=vs.85).aspx#ULONG_PTR # noqa +ULONG_PTR = c_uint64 if is_64_bit else c_ulong + +USHORT = c_ushort +LPOLESTR = POINTER(OLECHAR) + +################################################################ +# enums +tagSYSKIND = c_int # enum +SYS_WIN16 = 0 +SYS_WIN32 = 1 +SYS_MAC = 2 +SYS_WIN64 = 3 +SYSKIND = tagSYSKIND + +tagREGKIND = c_int # enum +REGKIND_DEFAULT = 0 +REGKIND_REGISTER = 1 +REGKIND_NONE = 2 +REGKIND = tagREGKIND + +tagTYPEKIND = c_int # enum +TKIND_ENUM = 0 +TKIND_RECORD = 1 +TKIND_MODULE = 2 +TKIND_INTERFACE = 3 +TKIND_DISPATCH = 4 +TKIND_COCLASS = 5 +TKIND_ALIAS = 6 +TKIND_UNION = 7 +TKIND_MAX = 8 +TYPEKIND = tagTYPEKIND + +tagINVOKEKIND = c_int # enum +INVOKE_FUNC = 1 +INVOKE_PROPERTYGET = 2 +INVOKE_PROPERTYPUT = 4 +INVOKE_PROPERTYPUTREF = 8 +INVOKEKIND = tagINVOKEKIND + +tagDESCKIND = c_int # enum +DESCKIND_NONE = 0 +DESCKIND_FUNCDESC = 1 +DESCKIND_VARDESC = 2 +DESCKIND_TYPECOMP = 3 +DESCKIND_IMPLICITAPPOBJ = 4 +DESCKIND_MAX = 5 +DESCKIND = tagDESCKIND + +tagVARKIND = c_int # enum +VAR_PERINSTANCE = 0 +VAR_STATIC = 1 +VAR_CONST = 2 +VAR_DISPATCH = 3 +VARKIND = tagVARKIND + +tagFUNCKIND = c_int # enum +FUNC_VIRTUAL = 0 +FUNC_PUREVIRTUAL = 1 +FUNC_NONVIRTUAL = 2 +FUNC_STATIC = 3 +FUNC_DISPATCH = 4 +FUNCKIND = tagFUNCKIND + +tagCALLCONV = c_int # enum +CC_FASTCALL = 0 +CC_CDECL = 1 +CC_MSCPASCAL = 2 +CC_PASCAL = 2 +CC_MACPASCAL = 3 +CC_STDCALL = 4 +CC_FPFASTCALL = 5 +CC_SYSCALL = 6 +CC_MPWCDECL = 7 +CC_MPWPASCAL = 8 +CC_MAX = 9 +CALLCONV = tagCALLCONV + +IMPLTYPEFLAG_FDEFAULT = 1 +IMPLTYPEFLAG_FSOURCE = 2 +IMPLTYPEFLAG_FRESTRICTED = 4 +IMPLTYPEFLAG_FDEFAULTVTABLE = 8 + +tagTYPEFLAGS = c_int # enum +TYPEFLAG_FAPPOBJECT = 1 +TYPEFLAG_FCANCREATE = 2 +TYPEFLAG_FLICENSED = 4 +TYPEFLAG_FPREDECLID = 8 +TYPEFLAG_FHIDDEN = 16 +TYPEFLAG_FCONTROL = 32 +TYPEFLAG_FDUAL = 64 +TYPEFLAG_FNONEXTENSIBLE = 128 +TYPEFLAG_FOLEAUTOMATION = 256 +TYPEFLAG_FRESTRICTED = 512 +TYPEFLAG_FAGGREGATABLE = 1024 +TYPEFLAG_FREPLACEABLE = 2048 +TYPEFLAG_FDISPATCHABLE = 4096 +TYPEFLAG_FREVERSEBIND = 8192 +TYPEFLAG_FPROXY = 16384 +TYPEFLAGS = tagTYPEFLAGS + +tagFUNCFLAGS = c_int # enum +FUNCFLAG_FRESTRICTED = 1 +FUNCFLAG_FSOURCE = 2 +FUNCFLAG_FBINDABLE = 4 +FUNCFLAG_FREQUESTEDIT = 8 +FUNCFLAG_FDISPLAYBIND = 16 +FUNCFLAG_FDEFAULTBIND = 32 +FUNCFLAG_FHIDDEN = 64 +FUNCFLAG_FUSESGETLASTERROR = 128 +FUNCFLAG_FDEFAULTCOLLELEM = 256 +FUNCFLAG_FUIDEFAULT = 512 +FUNCFLAG_FNONBROWSABLE = 1024 +FUNCFLAG_FREPLACEABLE = 2048 +FUNCFLAG_FIMMEDIATEBIND = 4096 +FUNCFLAGS = tagFUNCFLAGS + +tagVARFLAGS = c_int # enum +VARFLAG_FREADONLY = 1 +VARFLAG_FSOURCE = 2 +VARFLAG_FBINDABLE = 4 +VARFLAG_FREQUESTEDIT = 8 +VARFLAG_FDISPLAYBIND = 16 +VARFLAG_FDEFAULTBIND = 32 +VARFLAG_FHIDDEN = 64 +VARFLAG_FRESTRICTED = 128 +VARFLAG_FDEFAULTCOLLELEM = 256 +VARFLAG_FUIDEFAULT = 512 +VARFLAG_FNONBROWSABLE = 1024 +VARFLAG_FREPLACEABLE = 2048 +VARFLAG_FIMMEDIATEBIND = 4096 +VARFLAGS = tagVARFLAGS + +PARAMFLAG_NONE = 0 +PARAMFLAG_FIN = 1 +PARAMFLAG_FOUT = 2 +PARAMFLAG_FLCID = 4 +PARAMFLAG_FRETVAL = 8 +PARAMFLAG_FOPT = 16 +PARAMFLAG_FHASDEFAULT = 32 +PARAMFLAG_FHASCUSTDATA = 64 + +################################################################ +# a helper +def _deref_with_release(ptr, release): + # Given a POINTER instance, return the pointed to value. + # Call the 'release' function with 'ptr' to release resources + # when the value is no longer needed. + result = ptr[0] + result.__ref__ = weakref.ref(result, lambda dead: release(ptr)) + return result + +# interfaces + +class ITypeLib(IUnknown): + _iid_ = GUID("{00020402-0000-0000-C000-000000000046}") + + # Commented out methods use the default implementation that comtypes + # automatically creates for COM methods. + +## def GetTypeInfoCount(self): +## "Return the number of type informations" + +## def GetTypeInfo(self, index): +## "Load type info by index" + +## def GetTypeInfoType(self, index): +## "Return the TYPEKIND of type information" + +## def GetTypeInfoOfGuid(self, guid): +## "Return type information for a guid" + + def GetLibAttr(self): + "Return type library attributes" + return _deref_with_release(self._GetLibAttr(), self.ReleaseTLibAttr) + +## def GetTypeComp(self): +## "Return an ITypeComp pointer." + +## def GetDocumentation(self, index): +## "Return documentation for a type description." + + def IsName(self, name, lHashVal=0): + """Check if there is type information for this name. + + Returns the name with capitalization found in the type + library, or None. + """ + from ctypes import create_unicode_buffer + namebuf = create_unicode_buffer(name) + found = BOOL() + self.__com_IsName(namebuf, lHashVal, byref(found)) + if found.value: + return namebuf[:].split("\0", 1)[0] + return None + + def FindName(self, name, lHashVal=0): + # Hm... + # Could search for more than one name - should we support this? + found = c_ushort(1) + tinfo = POINTER(ITypeInfo)() + memid = MEMBERID() + self.__com_FindName(name, lHashVal, byref(tinfo), byref(memid), byref(found)) + if found.value: + return memid.value, tinfo + return None + +## def ReleaseTLibAttr(self, ptla): +## "Release TLIBATTR" + +################ + +def fix_name(name): + # Some typelibs contain BSTR with embedded NUL characters, + # probably the len of the BSTR is wrong. + if name is None: + return name + return name.split("\0")[0] + +class ITypeInfo(IUnknown): + _iid_ = GUID("{00020401-0000-0000-C000-000000000046}") + + def GetTypeAttr(self): + "Return the TYPEATTR for this type" + return _deref_with_release(self._GetTypeAttr(), self.ReleaseTypeAttr) + +## def GetTypeComp(self): +## "Return ITypeComp pointer for this type" + + def GetDocumentation(self, memid): + """Return name, docstring, helpcontext, and helpfile for 'memid'.""" + name, doc, helpcontext, helpfile = self._GetDocumentation(memid) + return fix_name(name), fix_name(doc), helpcontext, fix_name(helpfile) + + def GetFuncDesc(self, index): + "Return FUNCDESC for index" + return _deref_with_release(self._GetFuncDesc(index), self.ReleaseFuncDesc) + + def GetVarDesc(self, index): + "Return VARDESC for index" + return _deref_with_release(self._GetVarDesc(index), self.ReleaseVarDesc) + + def GetNames(self, memid, count=1): + "Return names for memid" + names = (BSTR * count)() + cnames = c_uint() + self.__com_GetNames(memid, names, count, byref(cnames)) + return names[:cnames.value] + +## def GetRefTypeOfImplType(self, index): +## "Get the reftype of an implemented type" + +## def GetImplTypeFlags(self, index): +## "Get IMPLTYPEFLAGS" + + def GetIDsOfNames(self, *names): + "Maps function and argument names to identifiers" + rgsznames = (c_wchar_p * len(names))(*names) + ids = (MEMBERID * len(names))() + self.__com_GetIDsOfNames(rgsznames, len(names), ids) + return ids[:] + + + # not yet wrapped +## STDMETHOD(HRESULT, 'Invoke', [PVOID, MEMBERID, WORD, POINTER(DISPPARAMS), POINTER(VARIANT), POINTER(EXCEPINFO), POINTER(UINT)]), + +## def GetDllEntry(self, memid, invkind): +## "Return the dll name, function name, and ordinal for a function and invkind." + +## def GetRefTypeInfo(self, href): +## "Get type info for reftype" + + def AddressOfMember(self, memid, invkind): + "Get the address of a function in a dll" + raise "Check Me" + p = c_void_p() + self.__com_AddressOfMember(memid, invkind, byref(p)) + # XXX Would the default impl return the value of p? + return p.value + + def CreateInstance(self, punkouter=None, interface=IUnknown, iid=None): + if iid is None: + iid = interface._iid_ + return self._CreateInstance(punkouter, byref(interface._iid_)) + +## def GetMops(self, index): +## "Get marshalling opcodes (whatever that is...)" + +## def GetContainingTypeLib(self): +## "Return index into and the containing type lib itself" + +## def ReleaseTypeAttr(self, pta): + +## def ReleaseFuncDesc(self, pfd): + +## def ReleaseVarDesc(self, pvd): + +################ + +class ITypeComp(IUnknown): + _iid_ = GUID("{00020403-0000-0000-C000-000000000046}") + + def Bind(self, name, flags=0, lHashVal=0): + "Bind to a name" + bindptr = BINDPTR() + desckind = DESCKIND() + ti = POINTER(ITypeInfo)() + self.__com_Bind(name, lHashVal, flags, byref(ti), byref(desckind), byref(bindptr)) + kind = desckind.value + if kind == DESCKIND_FUNCDESC: + fd = bindptr.lpfuncdesc[0] + fd.__ref__ = weakref.ref(fd, lambda dead: ti.ReleaseFuncDesc(bindptr.lpfuncdesc)) + return "function", fd + elif kind == DESCKIND_VARDESC: + vd = bindptr.lpvardesc[0] + vd.__ref__ = weakref.ref(vd, lambda dead: ti.ReleaseVarDesc(bindptr.lpvardesc)) + return "variable", vd + elif kind == DESCKIND_TYPECOMP: + return "type", bindptr.lptcomp + elif kind == DESCKIND_IMPLICITAPPOBJ: + raise NotImplementedError + elif kind == DESCKIND_NONE: + raise NameError("Name %s not found" % name) + + def BindType(self, name, lHashVal=0): + "Bind a type, and return both the typeinfo and typecomp for it." + ti = POINTER(ITypeInfo)() + tc = POINTER(ITypeComp)() + self.__com_BindType(name, lHashVal, byref(ti), byref(tc)) + return ti, tc + + +################ + +class ICreateTypeLib(IUnknown): + _iid_ = GUID("{00020406-0000-0000-C000-000000000046}") + # C:/Programme/gccxml/bin/Vc71/PlatformSDK/oaidl.h 2149 + +class ICreateTypeLib2(ICreateTypeLib): + _iid_ = GUID("{0002040F-0000-0000-C000-000000000046}") + +class ICreateTypeInfo(IUnknown): + _iid_ = GUID("{00020405-0000-0000-C000-000000000046}") + # C:/Programme/gccxml/bin/Vc71/PlatformSDK/oaidl.h 915 + + def SetFuncAndParamNames(self, index, *names): + rgszNames = (c_wchar_p * len(names))() + for i, n in enumerate(names): + rgszNames[i] = n + return self._SetFuncAndParamNames(index, rgszNames, len(names)) + +class IRecordInfo(IUnknown): + # C:/vc98/include/OAIDL.H 5974 + _iid_ = GUID("{0000002F-0000-0000-C000-000000000046}") + + def GetFieldNames(self, *args): + count = c_ulong() + self.__com_GetFieldNames(count, None) + array = (BSTR * count.value)() + self.__com_GetFieldNames(count, array) + result = array[:] + # XXX Should SysFreeString the array contents. How to? + return result + +IRecordInfo. _methods_ = [ + COMMETHOD([], HRESULT, 'RecordInit', + (['in'], c_void_p, 'pvNew')), + COMMETHOD([], HRESULT, 'RecordClear', + (['in'], c_void_p, 'pvExisting')), + COMMETHOD([], HRESULT, 'RecordCopy', + (['in'], c_void_p, 'pvExisting'), + (['in'], c_void_p, 'pvNew')), + COMMETHOD([], HRESULT, 'GetGuid', + (['out'], POINTER(GUID), 'pguid')), + COMMETHOD([], HRESULT, 'GetName', + (['out'], POINTER(BSTR), 'pbstrName')), + COMMETHOD([], HRESULT, 'GetSize', + (['out'], POINTER(c_ulong), 'pcbSize')), + COMMETHOD([], HRESULT, 'GetTypeInfo', + (['out'], POINTER(POINTER(ITypeInfo)), 'ppTypeInfo')), + COMMETHOD([], HRESULT, 'GetField', + (['in'], c_void_p, 'pvData'), + (['in'], c_wchar_p, 'szFieldName'), + (['out'], POINTER(VARIANT), 'pvarField')), + COMMETHOD([], HRESULT, 'GetFieldNoCopy', + (['in'], c_void_p, 'pvData'), + (['in'], c_wchar_p, 'szFieldName'), + (['out'], POINTER(VARIANT), 'pvarField'), + (['out'], POINTER(c_void_p), 'ppvDataCArray')), + COMMETHOD([], HRESULT, 'PutField', + (['in'], c_ulong, 'wFlags'), + (['in'], c_void_p, 'pvData'), + (['in'], c_wchar_p, 'szFieldName'), + (['in'], POINTER(VARIANT), 'pvarField')), + COMMETHOD([], HRESULT, 'PutFieldNoCopy', + (['in'], c_ulong, 'wFlags'), + (['in'], c_void_p, 'pvData'), + (['in'], c_wchar_p, 'szFieldName'), + (['in'], POINTER(VARIANT), 'pvarField')), + COMMETHOD([], HRESULT, 'GetFieldNames', + (['in', 'out'], POINTER(c_ulong), 'pcNames'), + (['in'], POINTER(BSTR), 'rgBstrNames')), + COMMETHOD([], BOOL, 'IsMatchingType', + (['in'], POINTER(IRecordInfo))), + COMMETHOD([], HRESULT, 'RecordCreate'), + COMMETHOD([], HRESULT, 'RecordCreateCopy', + (['in'], c_void_p, 'pvSource'), + (['out'], POINTER(c_void_p), 'ppvDest')), + COMMETHOD([], HRESULT, 'RecordDestroy', + (['in'], c_void_p, 'pvRecord'))] + + + +################################################################ +# functions +_oleaut32 = oledll.oleaut32 + +def GetRecordInfoFromTypeInfo(tinfo): + "Return an IRecordInfo pointer to the UDT described in tinfo" + ri = POINTER(IRecordInfo)() + _oleaut32.GetRecordInfoFromTypeInfo(tinfo, byref(ri)) + return ri + +def GetRecordInfoFromGuids(rGuidTypeLib, verMajor, verMinor, lcid, rGuidTypeInfo): + ri = POINTER(IRecordInfo)() + _oleaut32.GetRecordInfoFromGuids(byref(GUID(rGuidTypeLib)), + verMajor, verMinor, lcid, + byref(GUID(rGuidTypeInfo)), + byref(ri)) + return ri + +def LoadRegTypeLib(guid, wMajorVerNum, wMinorVerNum, lcid=0): + "Load a registered type library" + tlib = POINTER(ITypeLib)() + _oleaut32.LoadRegTypeLib(byref(GUID(guid)), wMajorVerNum, wMinorVerNum, lcid, byref(tlib)) + return tlib + +if hasattr(_oleaut32, "LoadTypeLibEx"): + def LoadTypeLibEx(szFile, regkind=REGKIND_NONE): + "Load, and optionally register a type library file" + ptl = POINTER(ITypeLib)() + _oleaut32.LoadTypeLibEx(c_wchar_p(szFile), regkind, byref(ptl)) + return ptl +else: + def LoadTypeLibEx(szFile, regkind=REGKIND_NONE): + "Load, and optionally register a type library file" + ptl = POINTER(ITypeLib)() + _oleaut32.LoadTypeLib(c_wchar_p(szFile), byref(ptl)) + return ptl + +def LoadTypeLib(szFile): + "Load and register a type library file" + tlib = POINTER(ITypeLib)() + _oleaut32.LoadTypeLib(c_wchar_p(szFile), byref(tlib)) + return tlib + +def UnRegisterTypeLib(libID, wVerMajor, wVerMinor, lcid=0, syskind=SYS_WIN32): + "Unregister a registered type library" + return _oleaut32.UnRegisterTypeLib(byref(GUID(libID)), wVerMajor, wVerMinor, lcid, syskind) + +def RegisterTypeLib(tlib, fullpath, helpdir=None): + "Register a type library in the registry" + return _oleaut32.RegisterTypeLib(tlib, c_wchar_p(fullpath), c_wchar_p(helpdir)) + +def CreateTypeLib(filename, syskind=SYS_WIN32): + "Return a ICreateTypeLib2 pointer" + ctlib = POINTER(ICreateTypeLib2)() + _oleaut32.CreateTypeLib2(syskind, c_wchar_p(filename), byref(ctlib)) + return ctlib + +if os.name == "ce": + # See also: + # http://blogs.msdn.com/larryosterman/archive/2006/01/09/510856.aspx + # + # windows CE does not have QueryPathOfRegTypeLib. Emulate by reading the registry: + def QueryPathOfRegTypeLib(libid, wVerMajor, wVerMinor, lcid=0): + "Return the path of a registered type library" + import _winreg + try: + hkey = _winreg.OpenKey(_winreg.HKEY_CLASSES_ROOT, r"Typelib\%s\%s.%s\%x\win32" % (libid, wVerMajor, wVerMinor, lcid)) + except WindowsError: + # On CE, some typelib names are not in the ..\win32 subkey: + hkey = _winreg.OpenKey(_winreg.HKEY_CLASSES_ROOT, r"Typelib\%s\%s.%s\%x" % (libid, wVerMajor, wVerMinor, lcid)) + return _winreg.QueryValueEx(hkey, "")[0] +else: + def QueryPathOfRegTypeLib(libid, wVerMajor, wVerMinor, lcid=0): + "Return the path of a registered type library" + pathname = BSTR() + _oleaut32.QueryPathOfRegTypeLib(byref(GUID(libid)), wVerMajor, wVerMinor, lcid, byref(pathname)) + return pathname.value.split("\0")[0] + +################################################################ +# Structures + +class tagTLIBATTR(Structure): + # C:/Programme/gccxml/bin/Vc71/PlatformSDK/oaidl.h 4437 + def __repr__(self): + return "TLIBATTR(GUID=%s, Version=%s.%s, LCID=%s, FLags=0x%x)" % \ + (self.guid, self.wMajorVerNum, self.wMinorVerNum, self.lcid, self.wLibFlags) +TLIBATTR = tagTLIBATTR + +class tagTYPEATTR(Structure): + # C:/Programme/gccxml/bin/Vc71/PlatformSDK/oaidl.h 672 + def __repr__(self): + return "TYPEATTR(GUID=%s, typekind=%s, funcs=%s, vars=%s, impltypes=%s)" % \ + (self.guid, self.typekind, self.cFuncs, self.cVars, self.cImplTypes) +TYPEATTR = tagTYPEATTR + +class tagFUNCDESC(Structure): + # C:/Programme/gccxml/bin/Vc71/PlatformSDK/oaidl.h 769 + def __repr__(self): + return "FUNCDESC(memid=%s, cParams=%s, cParamsOpt=%s, callconv=%s, invkind=%s, funckind=%s)" % \ + (self.memid, self.cParams, self.cParamsOpt, self.callconv, self.invkind, self.funckind) + + +FUNCDESC = tagFUNCDESC +class tagVARDESC(Structure): + # C:/Programme/gccxml/bin/Vc71/PlatformSDK/oaidl.h 803 + pass +VARDESC = tagVARDESC + +class tagBINDPTR(Union): + # C:/Programme/gccxml/bin/Vc71/PlatformSDK/oaidl.h 3075 + pass +BINDPTR = tagBINDPTR +class tagTYPEDESC(Structure): + # C:/Programme/gccxml/bin/Vc71/PlatformSDK/oaidl.h 582 + pass +TYPEDESC = tagTYPEDESC +class tagIDLDESC(Structure): + # C:/Programme/gccxml/bin/Vc71/PlatformSDK/oaidl.h 633 + pass +IDLDESC = tagIDLDESC + +class tagARRAYDESC(Structure): + # C:/Programme/gccxml/bin/Vc71/PlatformSDK/oaidl.h 594 + pass + +################################################################ +# interface vtbl definitions + +ICreateTypeLib._methods_ = [ +# C:/Programme/gccxml/bin/Vc71/PlatformSDK/oaidl.h 2149 + COMMETHOD([], HRESULT, 'CreateTypeInfo', + (['in'], LPOLESTR, 'szName'), + (['in'], TYPEKIND, 'tkind'), + (['out'], POINTER(POINTER(ICreateTypeInfo)), 'ppCTInfo')), + STDMETHOD(HRESULT, 'SetName', [LPOLESTR]), + STDMETHOD(HRESULT, 'SetVersion', [WORD, WORD]), + STDMETHOD(HRESULT, 'SetGuid', [POINTER(GUID)]), + STDMETHOD(HRESULT, 'SetDocString', [LPOLESTR]), + STDMETHOD(HRESULT, 'SetHelpFileName', [LPOLESTR]), + STDMETHOD(HRESULT, 'SetHelpContext', [DWORD]), + STDMETHOD(HRESULT, 'SetLcid', [LCID]), + STDMETHOD(HRESULT, 'SetLibFlags', [UINT]), + STDMETHOD(HRESULT, 'SaveAllChanges', []), +] + +ICreateTypeLib2._methods_ = [ +# C:/Programme/gccxml/bin/Vc71/PlatformSDK/oaidl.h 2444 + STDMETHOD(HRESULT, 'DeleteTypeInfo', [POINTER(ITypeInfo)]), + STDMETHOD(HRESULT, 'SetCustData', [POINTER(GUID), POINTER(VARIANT)]), + STDMETHOD(HRESULT, 'SetHelpStringContext', [ULONG]), + STDMETHOD(HRESULT, 'SetHelpStringDll', [LPOLESTR]), + ] + +ITypeLib._methods_ = [ +# C:/Programme/gccxml/bin/Vc71/PlatformSDK/oaidl.h 4455 + COMMETHOD([], UINT, 'GetTypeInfoCount'), + COMMETHOD([], HRESULT, 'GetTypeInfo', + (['in'], UINT, 'index'), + (['out'], POINTER(POINTER(ITypeInfo)))), + COMMETHOD([], HRESULT, 'GetTypeInfoType', + (['in'], UINT, 'index'), + (['out'], POINTER(TYPEKIND))), + COMMETHOD([], HRESULT, 'GetTypeInfoOfGuid', + (['in'], POINTER(GUID)), + (['out'], POINTER(POINTER(ITypeInfo)))), + COMMETHOD([], HRESULT, 'GetLibAttr', + (['out'], POINTER(POINTER(TLIBATTR)))), + COMMETHOD([], HRESULT, 'GetTypeComp', + (['out'], POINTER(POINTER(ITypeComp)))), + COMMETHOD([], HRESULT, 'GetDocumentation', + (['in'], INT, 'index'), + (['out'], POINTER(BSTR)), + (['out'], POINTER(BSTR)), + (['out'], POINTER(DWORD)), + (['out'], POINTER(BSTR))), + COMMETHOD([], HRESULT, 'IsName', + # IsName changes the casing of the passed in name to + # match that in the type library. In the automatically + # wrapped version of this method, ctypes would pass a + # Python unicode string which would then be changed - + # very bad. So we have (see above) to implement the + # IsName method manually. + (['in', 'out'], LPOLESTR, 'name'), + (['in', 'optional'], DWORD, 'lHashVal', 0), + (['out'], POINTER(BOOL))), + STDMETHOD(HRESULT, 'FindName', [LPOLESTR, DWORD, POINTER(POINTER(ITypeInfo)), + POINTER(MEMBERID), POINTER(USHORT)]), + COMMETHOD([], None, 'ReleaseTLibAttr', + (['in'], POINTER(TLIBATTR))) +] + +ITypeInfo._methods_ = [ +# C:/Programme/gccxml/bin/Vc71/PlatformSDK/oaidl.h 3230 + COMMETHOD([], HRESULT, 'GetTypeAttr', + (['out'], POINTER(POINTER(TYPEATTR)), 'ppTypeAttr')), + COMMETHOD([], HRESULT, 'GetTypeComp', + (['out'], POINTER(POINTER(ITypeComp)))), + COMMETHOD([], HRESULT, 'GetFuncDesc', + (['in'], UINT, 'index'), + (['out'], POINTER(POINTER(FUNCDESC)))), + COMMETHOD([], HRESULT, 'GetVarDesc', + (['in'], UINT, 'index'), + (['out'], POINTER(POINTER(VARDESC)))), + STDMETHOD(HRESULT, 'GetNames', [MEMBERID, POINTER(BSTR), UINT, POINTER(UINT)]), + COMMETHOD([], HRESULT, 'GetRefTypeOfImplType', + (['in'], UINT, 'index'), + (['out'], POINTER(HREFTYPE))), + COMMETHOD([], HRESULT, 'GetImplTypeFlags', + (['in'], UINT, 'index'), + (['out'], POINTER(INT))), +## STDMETHOD(HRESULT, 'GetIDsOfNames', [POINTER(LPOLESTR), UINT, POINTER(MEMBERID)]), + # this one changed, to accept c_wchar_p array + STDMETHOD(HRESULT, 'GetIDsOfNames', [POINTER(c_wchar_p), UINT, POINTER(MEMBERID)]), + STDMETHOD(HRESULT, 'Invoke', [PVOID, MEMBERID, WORD, POINTER(DISPPARAMS), POINTER(VARIANT), POINTER(EXCEPINFO), POINTER(UINT)]), + + COMMETHOD([], HRESULT, 'GetDocumentation', + (['in'], MEMBERID, 'memid'), + (['out'], POINTER(BSTR), 'pBstrName'), + (['out'], POINTER(BSTR), 'pBstrDocString'), + (['out'], POINTER(DWORD), 'pdwHelpContext'), + (['out'], POINTER(BSTR), 'pBstrHelpFile')), + COMMETHOD([], HRESULT, 'GetDllEntry', + (['in'], MEMBERID, 'index'), + (['in'], INVOKEKIND, 'invkind'), + (['out'], POINTER(BSTR), 'pBstrDllName'), + (['out'], POINTER(BSTR), 'pBstrName'), + (['out'], POINTER(WORD), 'pwOrdinal')), + COMMETHOD([], HRESULT, 'GetRefTypeInfo', + (['in'], HREFTYPE, 'hRefType'), + (['out'], POINTER(POINTER(ITypeInfo)))), + STDMETHOD(HRESULT, 'AddressOfMember', [MEMBERID, INVOKEKIND, POINTER(PVOID)]), + COMMETHOD([], HRESULT, 'CreateInstance', + (['in'], POINTER(IUnknown), 'pUnkOuter'), + (['in'], POINTER(IID), 'refiid'), + (['out'], POINTER(POINTER(IUnknown)))), + COMMETHOD([], HRESULT, 'GetMops', + (['in'], MEMBERID, 'memid'), + (['out'], POINTER(BSTR))), + COMMETHOD([], HRESULT, 'GetContainingTypeLib', + (['out'], POINTER(POINTER(ITypeLib))), + (['out'], POINTER(UINT))), + COMMETHOD([], None, 'ReleaseTypeAttr', + (['in'], POINTER(TYPEATTR))), + COMMETHOD([], None, 'ReleaseFuncDesc', + (['in'], POINTER(FUNCDESC))), + COMMETHOD([], None, 'ReleaseVarDesc', + (['in'], POINTER(VARDESC))), +] + +ITypeComp._methods_ = [ +# C:/Programme/gccxml/bin/Vc71/PlatformSDK/oaidl.h 3090 + STDMETHOD(HRESULT, 'Bind', + [LPOLESTR, DWORD, WORD, POINTER(POINTER(ITypeInfo)), + POINTER(DESCKIND), POINTER(BINDPTR)]), + STDMETHOD(HRESULT, 'BindType', + [LPOLESTR, DWORD, POINTER(POINTER(ITypeInfo)), POINTER(POINTER(ITypeComp))]), +] + +ICreateTypeInfo._methods_ = [ +# C:/Programme/gccxml/bin/Vc71/PlatformSDK/oaidl.h 915 + STDMETHOD(HRESULT, 'SetGuid', [POINTER(GUID)]), + STDMETHOD(HRESULT, 'SetTypeFlags', [UINT]), + STDMETHOD(HRESULT, 'SetDocString', [LPOLESTR]), + STDMETHOD(HRESULT, 'SetHelpContext', [DWORD]), + STDMETHOD(HRESULT, 'SetVersion', [WORD, WORD]), +# STDMETHOD(HRESULT, 'AddRefTypeInfo', [POINTER(ITypeInfo), POINTER(HREFTYPE)]), + COMMETHOD([], HRESULT, 'AddRefTypeInfo', + (['in'], POINTER(ITypeInfo)), + (['out'], POINTER(HREFTYPE))), + STDMETHOD(HRESULT, 'AddFuncDesc', [UINT, POINTER(FUNCDESC)]), + STDMETHOD(HRESULT, 'AddImplType', [UINT, HREFTYPE]), + STDMETHOD(HRESULT, 'SetImplTypeFlags', [UINT, INT]), + STDMETHOD(HRESULT, 'SetAlignment', [WORD]), + STDMETHOD(HRESULT, 'SetSchema', [LPOLESTR]), + STDMETHOD(HRESULT, 'AddVarDesc', [UINT, POINTER(VARDESC)]), + STDMETHOD(HRESULT, 'SetFuncAndParamNames', [UINT, POINTER(c_wchar_p), UINT]), + STDMETHOD(HRESULT, 'SetVarName', [UINT, LPOLESTR]), + STDMETHOD(HRESULT, 'SetTypeDescAlias', [POINTER(TYPEDESC)]), + STDMETHOD(HRESULT, 'DefineFuncAsDllEntry', [UINT, LPOLESTR, LPOLESTR]), + STDMETHOD(HRESULT, 'SetFuncDocString', [UINT, LPOLESTR]), + STDMETHOD(HRESULT, 'SetVarDocString', [UINT, LPOLESTR]), + STDMETHOD(HRESULT, 'SetFuncHelpContext', [UINT, DWORD]), + STDMETHOD(HRESULT, 'SetVarHelpContext', [UINT, DWORD]), + STDMETHOD(HRESULT, 'SetMops', [UINT, BSTR]), + STDMETHOD(HRESULT, 'SetTypeIdldesc', [POINTER(IDLDESC)]), + STDMETHOD(HRESULT, 'LayOut', []), +] + +class IProvideClassInfo(IUnknown): + _iid_ = GUID("{B196B283-BAB4-101A-B69C-00AA00341D07}") + _methods_ = [ + # Returns the ITypeInfo interface for the object's coclass type information. + COMMETHOD([], HRESULT, "GetClassInfo", + ( ['out'], POINTER(POINTER(ITypeInfo)), "ppTI" ) ) + ] + +class IProvideClassInfo2(IProvideClassInfo): + _iid_ = GUID("{A6BC3AC0-DBAA-11CE-9DE3-00AA004BB851}") + _methods_ = [ + # Returns the GUID for the object's outgoing IID for its default event set. + COMMETHOD([], HRESULT, "GetGUID", + ( ['in'], DWORD, "dwGuidKind" ), + ( ['out', 'retval'], POINTER(GUID), "pGUID" )) + ] + + +################################################################ +# Structure fields + +tagTLIBATTR._fields_ = [ + # C:/Programme/gccxml/bin/Vc71/PlatformSDK/oaidl.h 4437 + ('guid', GUID), + ('lcid', LCID), + ('syskind', SYSKIND), + ('wMajorVerNum', WORD), + ('wMinorVerNum', WORD), + ('wLibFlags', WORD), +] +class N11tagTYPEDESC5DOLLAR_203E(Union): + # C:/Programme/gccxml/bin/Vc71/PlatformSDK/oaidl.h 584 + pass +N11tagTYPEDESC5DOLLAR_203E._fields_ = [ + # C:/Programme/gccxml/bin/Vc71/PlatformSDK/oaidl.h 584 + ('lptdesc', POINTER(tagTYPEDESC)), + ('lpadesc', POINTER(tagARRAYDESC)), + ('hreftype', HREFTYPE), +] +tagTYPEDESC._anonymous_ = ('_',) +tagTYPEDESC._fields_ = [ + # C:/Programme/gccxml/bin/Vc71/PlatformSDK/oaidl.h 582 + # Unnamed field renamed to '_' + ('_', N11tagTYPEDESC5DOLLAR_203E), + ('vt', VARTYPE), +] +tagIDLDESC._fields_ = [ + # C:/Programme/gccxml/bin/Vc71/PlatformSDK/oaidl.h 633 + ('dwReserved', ULONG_PTR), + ('wIDLFlags', USHORT), +] +tagTYPEATTR._fields_ = [ + # C:/Programme/gccxml/bin/Vc71/PlatformSDK/oaidl.h 672 + ('guid', GUID), + ('lcid', LCID), + ('dwReserved', DWORD), + ('memidConstructor', MEMBERID), + ('memidDestructor', MEMBERID), + ('lpstrSchema', LPOLESTR), + ('cbSizeInstance', DWORD), + ('typekind', TYPEKIND), + ('cFuncs', WORD), + ('cVars', WORD), + ('cImplTypes', WORD), + ('cbSizeVft', WORD), + ('cbAlignment', WORD), + ('wTypeFlags', WORD), + ('wMajorVerNum', WORD), + ('wMinorVerNum', WORD), + ('tdescAlias', TYPEDESC), + ('idldescType', IDLDESC), +] +class N10tagVARDESC5DOLLAR_205E(Union): + # C:/Programme/gccxml/bin/Vc71/PlatformSDK/oaidl.h 807 + pass +N10tagVARDESC5DOLLAR_205E._fields_ = [ + # C:/Programme/gccxml/bin/Vc71/PlatformSDK/oaidl.h 807 + ('oInst', DWORD), + ('lpvarValue', POINTER(VARIANT)), +] +class tagELEMDESC(Structure): + # C:/Programme/gccxml/bin/Vc71/PlatformSDK/oaidl.h 661 + pass +class N11tagELEMDESC5DOLLAR_204E(Union): + # C:/Programme/gccxml/bin/Vc71/PlatformSDK/oaidl.h 663 + pass + +class tagPARAMDESC(Structure): + # C:/Programme/gccxml/bin/Vc71/PlatformSDK/oaidl.h 609 + pass + +class tagPARAMDESCEX(Structure): + # C:/Programme/gccxml/bin/Vc71/PlatformSDK/oaidl.h 601 + pass +LPPARAMDESCEX = POINTER(tagPARAMDESCEX) + +tagPARAMDESC._fields_ = [ + # C:/Programme/gccxml/bin/Vc71/PlatformSDK/oaidl.h 609 + ('pparamdescex', LPPARAMDESCEX), + ('wParamFlags', USHORT), +] +PARAMDESC = tagPARAMDESC + +N11tagELEMDESC5DOLLAR_204E._fields_ = [ + # C:/Programme/gccxml/bin/Vc71/PlatformSDK/oaidl.h 663 + ('idldesc', IDLDESC), + ('paramdesc', PARAMDESC), +] +tagELEMDESC._fields_ = [ + # C:/Programme/gccxml/bin/Vc71/PlatformSDK/oaidl.h 661 + ('tdesc', TYPEDESC), + # Unnamed field renamed to '_' + ('_', N11tagELEMDESC5DOLLAR_204E), +] +ELEMDESC = tagELEMDESC + +tagVARDESC._fields_ = [ + # C:/Programme/gccxml/bin/Vc71/PlatformSDK/oaidl.h 803 + ('memid', MEMBERID), + ('lpstrSchema', LPOLESTR), + # Unnamed field renamed to '_' + ('_', N10tagVARDESC5DOLLAR_205E), + ('elemdescVar', ELEMDESC), + ('wVarFlags', WORD), + ('varkind', VARKIND), +] +tagBINDPTR._fields_ = [ + # C:/Programme/gccxml/bin/Vc71/PlatformSDK/oaidl.h 3075 + ('lpfuncdesc', POINTER(FUNCDESC)), + ('lpvardesc', POINTER(VARDESC)), + ('lptcomp', POINTER(ITypeComp)), +] + +tagFUNCDESC._fields_ = [ + # C:/Programme/gccxml/bin/Vc71/PlatformSDK/oaidl.h 769 + ('memid', MEMBERID), + ('lprgscode', POINTER(SCODE)), + ('lprgelemdescParam', POINTER(ELEMDESC)), + ('funckind', FUNCKIND), + ('invkind', INVOKEKIND), + ('callconv', CALLCONV), + ('cParams', SHORT), + ('cParamsOpt', SHORT), + ('oVft', SHORT), + ('cScodes', SHORT), + ('elemdescFunc', ELEMDESC), + ('wFuncFlags', WORD), +] + +tagPARAMDESCEX._fields_ = [ + # C:/Programme/gccxml/bin/Vc71/PlatformSDK/oaidl.h 601 + ('cBytes', DWORD), + ('varDefaultValue', VARIANTARG), +] + +class tagSAFEARRAYBOUND(Structure): + # C:/Programme/gccxml/bin/Vc71/PlatformSDK/oaidl.h 226 + _fields_ = [ + ('cElements', DWORD), + ('lLbound', LONG), + ] +SAFEARRAYBOUND = tagSAFEARRAYBOUND + +tagARRAYDESC._fields_ = [ + # C:/Programme/gccxml/bin/Vc71/PlatformSDK/oaidl.h 594 + ('tdescElem', TYPEDESC), + ('cDims', USHORT), + ('rgbounds', SAFEARRAYBOUND * 1), +] diff --git a/tools/comtypes/comtypes/util.py b/tools/comtypes/comtypes/util.py new file mode 100644 index 00000000000000..0eaf864db0947e --- /dev/null +++ b/tools/comtypes/comtypes/util.py @@ -0,0 +1,96 @@ +"""This module defines the funtions byref_at(cobj, offset) +and cast_field(struct, fieldname, fieldtype). +""" +from ctypes import * + +def _calc_offset(): + # Internal helper function that calculates where the object + # returned by a byref() call stores the pointer. + + # The definition of PyCArgObject in C code (that is the type of + # object that a byref() call returns): + class PyCArgObject(Structure): + class value(Union): + _fields_ = [("c", c_char), + ("h", c_short), + ("i", c_int), + ("l", c_long), + ("q", c_longlong), + ("d", c_double), + ("f", c_float), + ("p", c_void_p)] + # + # Thanks to Lenard Lindstrom for this tip: + # sizeof(PyObject_HEAD) is the same as object.__basicsize__. + # + _fields_ = [("PyObject_HEAD", c_byte * object.__basicsize__), + ("pffi_type", c_void_p), + ("tag", c_char), + ("value", value), + ("obj", c_void_p), + ("size", c_int)] + + _anonymous_ = ["value"] + + # additional checks to make sure that everything works as expected + + if sizeof(PyCArgObject) != type(byref(c_int())).__basicsize__: + raise RuntimeError("sizeof(PyCArgObject) invalid") + + obj = c_int() + ref = byref(obj) + + argobj = PyCArgObject.from_address(id(ref)) + + if argobj.obj != id(obj) or \ + argobj.p != addressof(obj) or \ + argobj.tag != 'P': + raise RuntimeError("PyCArgObject field definitions incorrect") + + return PyCArgObject.p.offset # offset of the pointer field + +################################################################ +# +# byref_at +# +def byref_at(obj, offset, + _byref=byref, + _c_void_p_from_address = c_void_p.from_address, + _byref_pointer_offset = _calc_offset() + ): + """byref_at(cobj, offset) behaves similar this C code: + + (((char *)&obj) + offset) + + In other words, the returned 'pointer' points to the address of + 'cobj' + 'offset'. 'offset' is in units of bytes. + """ + ref = _byref(obj) + # Change the pointer field in the created byref object by adding + # 'offset' to it: + _c_void_p_from_address(id(ref) + + _byref_pointer_offset).value += offset + return ref + + +################################################################ +# +# cast_field +# +def cast_field(struct, fieldname, fieldtype, offset=0, + _POINTER=POINTER, + _byref_at=byref_at, + _byref=byref, + _divmod=divmod, + _sizeof=sizeof, + ): + """cast_field(struct, fieldname, fieldtype) + + Return the contents of a struct field as it it were of type + 'fieldtype'. + """ + fieldoffset = getattr(type(struct), fieldname).offset + return cast(_byref_at(struct, fieldoffset), + _POINTER(fieldtype))[0] + +__all__ = ["byref_at", "cast_field"] diff --git a/tools/comtypes/comtypes/viewobject.py b/tools/comtypes/comtypes/viewobject.py new file mode 100644 index 00000000000000..110c6a6f00230a --- /dev/null +++ b/tools/comtypes/comtypes/viewobject.py @@ -0,0 +1,160 @@ +# XXX need to find out what the share from comtypes.dataobject. +from ctypes import * +from ctypes.wintypes import _RECTL, SIZEL, HDC, tagRECT, tagPOINT + +from comtypes import COMMETHOD +from comtypes import GUID +from comtypes import IUnknown + +class tagPALETTEENTRY(Structure): + _fields_ = [ + ('peRed', c_ubyte), + ('peGreen', c_ubyte), + ('peBlue', c_ubyte), + ('peFlags', c_ubyte), + ] +assert sizeof(tagPALETTEENTRY) == 4, sizeof(tagPALETTEENTRY) +assert alignment(tagPALETTEENTRY) == 1, alignment(tagPALETTEENTRY) + +class tagLOGPALETTE(Structure): + _pack_ = 2 + _fields_ = [ + ('palVersion', c_ushort), + ('palNumEntries', c_ushort), + ('palPalEntry', POINTER(tagPALETTEENTRY)), + ] +assert sizeof(tagLOGPALETTE) == 8, sizeof(tagLOGPALETTE) +assert alignment(tagLOGPALETTE) == 2, alignment(tagLOGPALETTE) + +class tagDVTARGETDEVICE(Structure): + _fields_ = [ + ('tdSize', c_ulong), + ('tdDriverNameOffset', c_ushort), + ('tdDeviceNameOffset', c_ushort), + ('tdPortNameOffset', c_ushort), + ('tdExtDevmodeOffset', c_ushort), + ('tdData', POINTER(c_ubyte)), + ] +assert sizeof(tagDVTARGETDEVICE) == 16, sizeof(tagDVTARGETDEVICE) +assert alignment(tagDVTARGETDEVICE) == 4, alignment(tagDVTARGETDEVICE) + +class tagExtentInfo(Structure): + _fields_ = [ + ('cb', c_ulong), + ('dwExtentMode', c_ulong), + ('sizelProposed', SIZEL), + ] + def __init__(self, *args, **kw): + self.cb = sizeof(self) + super(tagExtentInfo, self).__init__(*args, **kw) + def __repr__(self): + size = (self.sizelProposed.cx, self.sizelProposed.cy) + return "" % (self.dwExtentMode, + size, + id(self)) +assert sizeof(tagExtentInfo) == 16, sizeof(tagExtentInfo) +assert alignment(tagExtentInfo) == 4, alignment(tagExtentInfo) +DVEXTENTINFO = tagExtentInfo + +IAdviseSink = IUnknown # fake the interface + +class IViewObject(IUnknown): + _case_insensitive_ = False + _iid_ = GUID('{0000010D-0000-0000-C000-000000000046}') + _idlflags_ = [] + + _methods_ = [ + COMMETHOD([], HRESULT, 'Draw', + ( ['in'], c_ulong, 'dwDrawAspect' ), + ( ['in'], c_int, 'lindex' ), + ( ['in'], c_void_p, 'pvAspect' ), + ( ['in'], POINTER(tagDVTARGETDEVICE), 'ptd' ), + ( ['in'], HDC, 'hdcTargetDev' ), + ( ['in'], HDC, 'hdcDraw' ), + ( ['in'], POINTER(_RECTL), 'lprcBounds' ), + ( ['in'], POINTER(_RECTL), 'lprcWBounds' ), + ( ['in'], c_void_p, 'pfnContinue' ), # a pointer to a callback function + ( ['in'], c_ulong, 'dwContinue')), + COMMETHOD([], HRESULT, 'GetColorSet', + ( ['in'], c_ulong, 'dwDrawAspect' ), + ( ['in'], c_int, 'lindex' ), + ( ['in'], c_void_p, 'pvAspect' ), + ( ['in'], POINTER(tagDVTARGETDEVICE), 'ptd' ), + ( ['in'], HDC, 'hicTargetDev' ), + ( ['out'], POINTER(POINTER(tagLOGPALETTE)), 'ppColorSet' )), + COMMETHOD([], HRESULT, 'Freeze', + ( ['in'], c_ulong, 'dwDrawAspect' ), + ( ['in'], c_int, 'lindex' ), + ( ['in'], c_void_p, 'pvAspect' ), + ( ['out'], POINTER(c_ulong), 'pdwFreeze' )), + COMMETHOD([], HRESULT, 'Unfreeze', + ( ['in'], c_ulong, 'dwFreeze' )), + COMMETHOD([], HRESULT, 'SetAdvise', + ( ['in'], c_ulong, 'dwAspect' ), + ( ['in'], c_ulong, 'advf' ), + ( ['in'], POINTER(IAdviseSink), 'pAdvSink' )), + COMMETHOD([], HRESULT, 'GetAdvise', + ( ['out'], POINTER(c_ulong), 'pdwAspect' ), + ( ['out'], POINTER(c_ulong), 'pAdvf' ), + ( ['out'], POINTER(POINTER(IAdviseSink)), 'ppAdvSink' )), + ] + +class IViewObject2(IViewObject): + _case_insensitive_ = False + _iid_ = GUID('{00000127-0000-0000-C000-000000000046}') + _idlflags_ = [] + _methods_ = [ + COMMETHOD([], HRESULT, 'GetExtent', + ( ['in'], c_ulong, 'dwDrawAspect' ), + ( ['in'], c_int, 'lindex' ), + ( ['in'], POINTER(tagDVTARGETDEVICE), 'ptd' ), + ( ['out'], POINTER(SIZEL), 'lpsizel' )), + ] + +class IViewObjectEx(IViewObject2): + _case_insensitive_ = False + _iid_ = GUID('{3AF24292-0C96-11CE-A0CF-00AA00600AB8}') + _idlflags_ = [] + _methods_ = [ + COMMETHOD([], HRESULT, 'GetRect', + ( ['in'], c_ulong, 'dwAspect' ), + ( ['out'], POINTER(_RECTL), 'pRect' )), + COMMETHOD([], HRESULT, 'GetViewStatus', + ( ['out'], POINTER(c_ulong), 'pdwStatus' )), + COMMETHOD([], HRESULT, 'QueryHitPoint', + ( ['in'], c_ulong, 'dwAspect' ), + ( ['in'], POINTER(tagRECT), 'pRectBounds' ), + ( ['in'], tagPOINT, 'ptlLoc' ), + ( ['in'], c_int, 'lCloseHint' ), + ( ['out'], POINTER(c_ulong), 'pHitResult' )), + COMMETHOD([], HRESULT, 'QueryHitRect', + ( ['in'], c_ulong, 'dwAspect' ), + ( ['in'], POINTER(tagRECT), 'pRectBounds' ), + ( ['in'], POINTER(tagRECT), 'pRectLoc' ), + ( ['in'], c_int, 'lCloseHint' ), + ( ['out'], POINTER(c_ulong), 'pHitResult' )), + COMMETHOD([], HRESULT, 'GetNaturalExtent', + ( ['in'], c_ulong, 'dwAspect' ), + ( ['in'], c_int, 'lindex' ), + ( ['in'], POINTER(tagDVTARGETDEVICE), 'ptd' ), + ( ['in'], HDC, 'hicTargetDev' ), + ( ['in'], POINTER(tagExtentInfo), 'pExtentInfo' ), + ( ['out'], POINTER(SIZEL), 'pSizel' )), + ] + + +DVASPECT = c_int # enum +DVASPECT_CONTENT = 1 +DVASPECT_THUMBNAIL = 2 +DVASPECT_ICON = 4 +DVASPECT_DOCPRINT = 8 + +DVASPECT2 = c_int # enum +DVASPECT_OPAQUE = 16 +DVASPECT_TRANSPARENT = 32 + +DVEXTENTMODE = c_int # enum +# Container asks the object how big it wants to be to exactly fit its content: +DVEXTENT_CONTENT = 0 +# The container proposes a size to the object for its use in resizing: +DVEXTENT_INTEGRAL = 1 diff --git a/tools/find_vs2017.py b/tools/find_vs2017.py new file mode 100644 index 00000000000000..81b39348362104 --- /dev/null +++ b/tools/find_vs2017.py @@ -0,0 +1,173 @@ +import re +import sys +import os +from ctypes import * + +root_dir = os.path.dirname(__file__) +sys.path.insert(0, os.path.join(root_dir, 'comtypes')) + +from comtypes import IUnknown +from comtypes import GUID +from comtypes import COMMETHOD +from comtypes import BSTR +from comtypes import DWORD +from comtypes.safearray import _midlSAFEARRAY +from comtypes.client import CreateObject + + +""" Find Visual Studio 2017 C/C++ compiler install location """ + +class ISetupInstance(IUnknown): + _iid_ = GUID('{B41463C3-8866-43B5-BC33-2B0676F7F42E}') + _methods_ = [ + COMMETHOD([], HRESULT, 'GetInstanceId', + ( ['out'], POINTER(BSTR), 'pbstrInstanceId' ) ), + COMMETHOD([], HRESULT, 'GetInstallDate', + ( ['out'], POINTER(c_ulonglong), 'pInstallDate') ), + COMMETHOD([], HRESULT, 'GetInstallationName', + ( ['out'], POINTER(BSTR), 'pInstallationName') ), + COMMETHOD([], HRESULT, 'GetInstallationPath', + ( ['out'], POINTER(BSTR), 'pInstallationPath') ), + COMMETHOD([], HRESULT, 'GetInstallationVersion', + ( ['out'], POINTER(BSTR), 'pInstallationVersion') ), + COMMETHOD([], HRESULT, 'GetDisplayName', + ( ['in'], DWORD, 'lcid' ), + ( ['out'], POINTER(BSTR), 'pDisplayName') ), + COMMETHOD([], HRESULT, 'GetDescription', + ( ['in'], DWORD, 'lcid' ), + ( ['out'], POINTER(BSTR), 'pDescription') ), + COMMETHOD([], HRESULT, 'ResolvePath', + ( ['in'], c_wchar_p, 'pRelativePath' ), + ( ['out'], POINTER(BSTR), 'pAbsolutePath') ), + ] + +class ISetupPackageReference(IUnknown): + _iid_ = GUID('{da8d8a16-b2b6-4487-a2f1-594ccccd6bf5}') + _methods_ = [ + COMMETHOD([], HRESULT, 'GetId', + ( ['out'], POINTER(BSTR), 'pOut' ) ), + COMMETHOD([], HRESULT, 'GetVersion', + ( ['out'], POINTER(BSTR), 'pOut' ) ), + COMMETHOD([], HRESULT, 'GetChip', + ( ['out'], POINTER(BSTR), 'pOut' ) ), + COMMETHOD([], HRESULT, 'GetLanguage', + ( ['out'], POINTER(BSTR), 'pOut' ) ), + COMMETHOD([], HRESULT, 'GetBranch', + ( ['out'], POINTER(BSTR), 'pOut' ) ), + COMMETHOD([], HRESULT, 'GetType', + ( ['out'], POINTER(BSTR), 'pOut' ) ), + COMMETHOD([], HRESULT, 'GetUniqueId', + ( ['out'], POINTER(BSTR), 'pOut' ) ) + ] + +class ISetupInstance2(ISetupInstance): + _iid_ = GUID('{89143C9A-05AF-49B0-B717-72E218A2185C}') + _methods_ = [ + COMMETHOD([], HRESULT, 'GetState', + ( ['out'], POINTER(DWORD), 'pState' ) ), + COMMETHOD([], HRESULT, 'GetPackages', + ( ['out'], POINTER(_midlSAFEARRAY(POINTER(ISetupPackageReference))), 'ppPackage' ) ) + ] + +class IEnumSetupInstances(IUnknown): + _iid_ = GUID('{6380BCFF-41D3-4B2E-8B2E-BF8A6810C848}') + _methods_ = [ + COMMETHOD([], HRESULT, 'Next', + ( ['in'], c_ulong, 'celt'), + ( ['out'], POINTER(POINTER(ISetupInstance)), 'rgelt' ), + ( ['out'], POINTER(c_ulong), 'pceltFetched' ) ), + COMMETHOD([], HRESULT, 'Skip', + ( ['in'], c_ulong, 'celt' ) ), + COMMETHOD([], HRESULT, 'Reset'), + ] + +class ISetupConfiguration(IUnknown): + _iid_ = GUID('{42843719-DB4C-46C2-8E7C-64F1816EFD5B}') + _methods_ = [ + COMMETHOD([], HRESULT, 'EnumInstances', + ( ['out'], POINTER(POINTER(IEnumSetupInstances)), 'ppIESI' ) ), + COMMETHOD([], HRESULT, 'GetInstanceForCurrentProcess', + ( ['out'], POINTER(POINTER(ISetupInstance)), 'ppISI' ) ), + COMMETHOD([], HRESULT, 'GetInstanceForPath', + ( ['in'], c_wchar_p, 'wzPath'), + ( ['out'], POINTER(POINTER(ISetupInstance)), 'ppISI' ) ) + ] + +class ISetupConfiguration2(ISetupConfiguration) : + _iid_ = GUID('{26AAB78C-4A60-49D6-AF3B-3C35BC93365D}') + _methods_ = [ + COMMETHOD([], HRESULT, 'EnumAllInstances', + ( ['out'], POINTER(POINTER(IEnumSetupInstances)), 'ppIEnumSetupInstances' ) ) + ] + + +def GetVS2017CPPBasePath(): + installs = [] + iface = CreateObject(GUID('{177F0C4A-1CD3-4DE7-A32C-71DBBB9FA36D}')) + setupConfiguration = iface.QueryInterface(ISetupConfiguration2) + allInstances = setupConfiguration.EnumAllInstances() + while True: + result = allInstances.Next(1) + instance = result[0] + if not instance: + break + path = instance.GetInstallationPath() + version = instance.GetInstallationVersion() + instance2 = instance.QueryInterface(ISetupInstance2) + packages = instance2.GetPackages() + for package in packages: + packageId = package.GetId() + if packageId == 'Microsoft.VisualStudio.Component.VC.Tools.x86.x64': + installs.append(path) + return installs + +def GetInstalledVS2017WinSDKs(vs_path): + sdks = [] + has81sdk = False + win8preg = re.compile(r"Microsoft.VisualStudio.Component.Windows81SDK") + win10preg = re.compile(r"Microsoft.VisualStudio.Component.Windows10SDK.(\d+)") + iface = CreateObject(GUID('{177F0C4A-1CD3-4DE7-A32C-71DBBB9FA36D}')) + setupConfiguration = iface.QueryInterface(ISetupConfiguration2) + allInstances = setupConfiguration.EnumAllInstances() + while True: + result = allInstances.Next(1) + instance = result[0] + if not instance: + break + path = instance.GetInstallationPath() + if path != vs_path: + continue + instance2 = instance.QueryInterface(ISetupInstance2) + packages = instance2.GetPackages() + for package in packages: + packageId = package.GetId() + if win8preg.match(packageId): + has81sdk = True + else: + win10match = win10preg.match(packageId) + if win10match: + sdks.append('10.0.' + str(win10match.group(1)) + '.0') + + sdks.sort(reverse = True) + if has81sdk: + sdks.append('8.1') + return sdks + +def main(): + if len(sys.argv) == 1: + installs = GetVS2017CPPBasePath() + if len(installs) == 0: + return + for install in installs: + sdks = GetInstalledVS2017WinSDKs(install) + if len(sdks) > 0: + print install + return + print installs[0] + else: + sdks = GetInstalledVS2017WinSDKs(sys.argv[1]) + if len(sdks) > 0: + print sdks[0] + +if __name__ == '__main__': + main() diff --git a/tools/gyp/pylib/gyp/MSVSVersion.py b/tools/gyp/pylib/gyp/MSVSVersion.py index d9bfa684fa30c2..f6cff664166bd2 100644 --- a/tools/gyp/pylib/gyp/MSVSVersion.py +++ b/tools/gyp/pylib/gyp/MSVSVersion.py @@ -84,6 +84,10 @@ def SetupScript(self, target_arch): # vcvars32, which it can only find if VS??COMNTOOLS is set, which it # isn't always. if target_arch == 'x86': + if self.short_name == '2017': + return [os.path.normpath( + ps.path.join(self.path, 'Common7/Tools/VsDevCmd.bat')), '/no_logo', + '/arch=x86'] if self.short_name >= '2013' and self.short_name[-1] != 'e' and ( os.environ.get('PROCESSOR_ARCHITECTURE') == 'AMD64' or os.environ.get('PROCESSOR_ARCHITEW6432') == 'AMD64'): @@ -96,6 +100,10 @@ def SetupScript(self, target_arch): os.path.join(self.path, 'Common7/Tools/vsvars32.bat'))] else: assert target_arch == 'x64' + if self.short_name == '2017': + return [os.path.normpath( + ps.path.join(self.path, 'Common7/Tools/VsDevCmd.bat')), '/no_logo', + '/arch=x64'] arg = 'x86_amd64' # Use the 64-on-64 compiler if we're not using an express # edition and we're running on a 64bit OS. @@ -226,6 +234,15 @@ def _CreateVersion(name, path, sdk_based=False): if path: path = os.path.normpath(path) versions = { + '2017': VisualStudioVersion('2017', + 'Visual Studio 2017', + solution_version='12.00', + project_version='14.0', + flat_sln=False, + uses_vcxproj=True, + path=path, + sdk_based=sdk_based, + default_toolset='v141'), '2015': VisualStudioVersion('2015', 'Visual Studio 2015', solution_version='12.00', @@ -346,6 +363,7 @@ def _DetectVisualStudioVersions(versions_to_check, force_express): 2012(e) - Visual Studio 2012 (11) 2013(e) - Visual Studio 2013 (12) 2015 - Visual Studio 2015 (14) + 2017 - Visual Studio 2017 (15) Where (e) is e for express editions of MSVS and blank otherwise. """ version_to_year = { @@ -355,6 +373,7 @@ def _DetectVisualStudioVersions(versions_to_check, force_express): '11.0': '2012', '12.0': '2013', '14.0': '2015', + '15.0': '2017' } versions = [] for version in versions_to_check: @@ -395,6 +414,17 @@ def _DetectVisualStudioVersions(versions_to_check, force_express): versions.append(_CreateVersion(version_to_year[version] + 'e', os.path.join(path, '..'), sdk_based=True)) + if version == '15.0': + # The VC++ 2017 install location needs to be located using COM instead of + # the registry. For details see: + # https://blogs.msdn.microsoft.com/heaths/2016/09/15/changes-to-visual-studio-15-setup/ + # For now we use a hardcoded default with an environment variable + # override. + path = r'C:\Program Files (x86)\Microsoft Visual Studio\2017\Professional' + path = os.environ.get('vs2017_install', path) + if os.path.exists(path): + versions.append(_CreateVersion('2017', path)) + return versions @@ -410,7 +440,7 @@ def SelectVisualStudioVersion(version='auto', allow_fallback=True): if version == 'auto': version = os.environ.get('GYP_MSVS_VERSION', 'auto') version_map = { - 'auto': ('14.0', '12.0', '10.0', '9.0', '8.0', '11.0'), + 'auto': ('15.0', '14.0', '12.0', '10.0', '9.0', '8.0', '11.0'), '2005': ('8.0',), '2005e': ('8.0',), '2008': ('9.0',), @@ -422,6 +452,7 @@ def SelectVisualStudioVersion(version='auto', allow_fallback=True): '2013': ('12.0',), '2013e': ('12.0',), '2015': ('14.0',), + '2017': ('15.0',), } override_path = os.environ.get('GYP_MSVS_OVERRIDE_PATH') if override_path: diff --git a/tools/gyp/pylib/gyp/generator/msvs.py b/tools/gyp/pylib/gyp/generator/msvs.py index 44cc1304a2e8ed..7dd24f68403652 100644 --- a/tools/gyp/pylib/gyp/generator/msvs.py +++ b/tools/gyp/pylib/gyp/generator/msvs.py @@ -2622,7 +2622,7 @@ def _GetMSBuildProjectConfigurations(configurations): return [group] -def _GetMSBuildGlobalProperties(spec, guid, gyp_file_name): +def _GetMSBuildGlobalProperties(spec, version, guid, gyp_file_name): namespace = os.path.splitext(gyp_file_name)[0] properties = [ ['PropertyGroup', {'Label': 'Globals'}, @@ -2662,6 +2662,15 @@ def _GetMSBuildGlobalProperties(spec, guid, gyp_file_name): else: properties[0].append(['ApplicationType', 'Windows Store']) + msvs_windows_sdk_version = None + if msvs_windows_sdk_version == None and version.ShortName() == '2017': + vs2017_sdk = '10.0.14393.0' + vs2017_sdk = os.environ.get('vs2017_sdk', vs2017_sdk) + if vs2017_sdk: + msvs_windows_sdk_version = vs2017_sdk + if msvs_windows_sdk_version: + properties[0].append(['WindowsTargetPlatformVersion', + str(msvs_windows_sdk_version)]) return properties def _GetMSBuildConfigurationDetails(spec, build_file): @@ -3295,7 +3304,8 @@ def _GenerateMSBuildProject(project, options, version, generator_flags): }] content += _GetMSBuildProjectConfigurations(configurations) - content += _GetMSBuildGlobalProperties(spec, project.guid, project_file_name) + content += _GetMSBuildGlobalProperties(spec, version, project.guid, + project_file_name) content += import_default_section content += _GetMSBuildConfigurationDetails(spec, project.build_file) if spec.get('msvs_enable_winphone'): diff --git a/vcbuild.bat b/vcbuild.bat index bc578c8f1d2269..69fb745ff41322 100644 --- a/vcbuild.bat +++ b/vcbuild.bat @@ -49,6 +49,7 @@ if /i "%1"=="ia32" set target_arch=x86&goto arg-ok if /i "%1"=="x86" set target_arch=x86&goto arg-ok if /i "%1"=="x64" set target_arch=x64&goto arg-ok if /i "%1"=="vc2015" set target_env=vc2015&goto arg-ok +if /i "%1"=="vc2017" set target_env=vc2017&goto arg-ok if /i "%1"=="noprojgen" set noprojgen=1&goto arg-ok if /i "%1"=="nobuild" set nobuild=1&goto arg-ok if /i "%1"=="nosign" set "sign="&goto arg-ok @@ -135,7 +136,29 @@ if defined noprojgen if defined nobuild if not defined sign if not defined msi g @rem Set environment for msbuild +@rem Look for Visual Studio 2017 +:vc-set-2017 +if defined target_env if "%target_env%" NEQ "vc2017" goto vc-set-2015 +echo Looking for Visual Studio 2017 +for /f "delims=" %%i in ('python "%~dp0tools\find_vs2017.py"') do set VS2017_INSTALL=%%i +if not exist "%VS2017_INSTALL%\Common7\Tools\VsDevCmd.bat" goto vc-set-2015 +for /f "delims=" %%i in ('python "%~dp0tools\find_vs2017.py" "%VS2017_INSTALL%"') do set VS2017_SDK=%%i +echo Found Visual Studio 2017 +if defined msi ( + echo Cannot build the MSI with Visual Studio 2017 - it is not yet supported by WiX + goto vc-set-2015 +) +if "%VCVARS_VER%" NEQ "150" ( + call "%VS2017_INSTALL%\Common7\Tools\VsDevCmd.bat" /no_logo + set VCVARS_VER=150 +) +set GYP_MSVS_VERSION=2017 +set PLATFORM_TOOLSET=v141 +goto msbuild-found + @rem Look for Visual Studio 2015 +:vc-set-2015 +if defined target_env if "%target_env%" NEQ "vc2015" goto msbuild-not-found echo Looking for Visual Studio 2015 if not defined VS140COMNTOOLS goto msbuild-not-found if not exist "%VS140COMNTOOLS%\..\..\vc\vcvarsall.bat" goto msbuild-not-found @@ -365,7 +388,7 @@ echo Failed to create vc project files. goto exit :help -echo vcbuild.bat [debug/release] [msi] [test-all/test-uv/test-inspector/test-internet/test-pummel/test-simple/test-message] [clean] [noprojgen] [small-icu/full-icu/without-intl] [nobuild] [sign] [x86/x64] [vc2015] [download-all] [enable-vtune] +echo vcbuild.bat [debug/release] [msi] [test-all/test-uv/test-inspector/test-internet/test-pummel/test-simple/test-message] [clean] [noprojgen] [small-icu/full-icu/without-intl] [nobuild] [sign] [x86/x64] [vc2015/vc2017] [download-all] [enable-vtune] echo Examples: echo vcbuild.bat : builds release build echo vcbuild.bat debug : builds debug build