From 8947cd8347826ed20040d5ce85783dc9e2abe62e Mon Sep 17 00:00:00 2001 From: Bartosz Sosnowski Date: Thu, 26 Jan 2017 11:52:48 +0000 Subject: [PATCH 1/3] build,tools: add comtypes library to tools Add comtypes (http://starship.python.net/crew/theller/comtypes/) Python library to tools. Library provides COM support for Python, which we use to locate Visual Studio 2017 installation path. --- tools/comtypes/LICENSE.txt | 24 + tools/comtypes/MANIFEST.in | 1 + tools/comtypes/README | 31 + tools/comtypes/comtypes/GUID.py | 101 ++ tools/comtypes/comtypes/__init__.py | 1390 +++++++++++++++++ tools/comtypes/comtypes/_comobject.py | 774 +++++++++ tools/comtypes/comtypes/_meta.py | 61 + tools/comtypes/comtypes/_safearray.py | 128 ++ tools/comtypes/comtypes/automation.py | 881 +++++++++++ tools/comtypes/comtypes/client/__init__.py | 266 ++++ tools/comtypes/comtypes/client/_code_cache.py | 130 ++ tools/comtypes/comtypes/client/_events.py | 285 ++++ tools/comtypes/comtypes/client/_generate.py | 195 +++ tools/comtypes/comtypes/client/dynamic.py | 165 ++ tools/comtypes/comtypes/client/lazybind.py | 267 ++++ tools/comtypes/comtypes/connectionpoints.py | 94 ++ tools/comtypes/comtypes/errorinfo.py | 105 ++ tools/comtypes/comtypes/gen/__init__.py | 1 + tools/comtypes/comtypes/git.py | 65 + tools/comtypes/comtypes/hresult.py | 76 + tools/comtypes/comtypes/logutil.py | 51 + tools/comtypes/comtypes/messageloop.py | 50 + tools/comtypes/comtypes/npsupport.py | 104 ++ tools/comtypes/comtypes/patcher.py | 75 + tools/comtypes/comtypes/persist.py | 212 +++ tools/comtypes/comtypes/safearray.py | 397 +++++ tools/comtypes/comtypes/shelllink.py | 217 +++ tools/comtypes/comtypes/tools/__init__.py | 1 + .../comtypes/comtypes/tools/codegenerator.py | 1002 ++++++++++++ tools/comtypes/comtypes/tools/tlbparser.py | 752 +++++++++ tools/comtypes/comtypes/tools/typedesc.py | 138 ++ .../comtypes/comtypes/tools/typedesc_base.py | 205 +++ tools/comtypes/comtypes/typeinfo.py | 913 +++++++++++ tools/comtypes/comtypes/util.py | 96 ++ tools/comtypes/comtypes/viewobject.py | 160 ++ 35 files changed, 9413 insertions(+) create mode 100644 tools/comtypes/LICENSE.txt create mode 100644 tools/comtypes/MANIFEST.in create mode 100644 tools/comtypes/README create mode 100644 tools/comtypes/comtypes/GUID.py create mode 100644 tools/comtypes/comtypes/__init__.py create mode 100644 tools/comtypes/comtypes/_comobject.py create mode 100644 tools/comtypes/comtypes/_meta.py create mode 100644 tools/comtypes/comtypes/_safearray.py create mode 100644 tools/comtypes/comtypes/automation.py create mode 100644 tools/comtypes/comtypes/client/__init__.py create mode 100644 tools/comtypes/comtypes/client/_code_cache.py create mode 100644 tools/comtypes/comtypes/client/_events.py create mode 100644 tools/comtypes/comtypes/client/_generate.py create mode 100644 tools/comtypes/comtypes/client/dynamic.py create mode 100644 tools/comtypes/comtypes/client/lazybind.py create mode 100644 tools/comtypes/comtypes/connectionpoints.py create mode 100644 tools/comtypes/comtypes/errorinfo.py create mode 100644 tools/comtypes/comtypes/gen/__init__.py create mode 100644 tools/comtypes/comtypes/git.py create mode 100644 tools/comtypes/comtypes/hresult.py create mode 100644 tools/comtypes/comtypes/logutil.py create mode 100644 tools/comtypes/comtypes/messageloop.py create mode 100644 tools/comtypes/comtypes/npsupport.py create mode 100644 tools/comtypes/comtypes/patcher.py create mode 100644 tools/comtypes/comtypes/persist.py create mode 100644 tools/comtypes/comtypes/safearray.py create mode 100644 tools/comtypes/comtypes/shelllink.py create mode 100644 tools/comtypes/comtypes/tools/__init__.py create mode 100644 tools/comtypes/comtypes/tools/codegenerator.py create mode 100644 tools/comtypes/comtypes/tools/tlbparser.py create mode 100644 tools/comtypes/comtypes/tools/typedesc.py create mode 100644 tools/comtypes/comtypes/tools/typedesc_base.py create mode 100644 tools/comtypes/comtypes/typeinfo.py create mode 100644 tools/comtypes/comtypes/util.py create mode 100644 tools/comtypes/comtypes/viewobject.py 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 From b8d9f30242e4ea040e238b97fb6d66544fa0080c Mon Sep 17 00:00:00 2001 From: Bartosz Sosnowski Date: Thu, 26 Jan 2017 12:11:53 +0000 Subject: [PATCH 2/3] build,tools: add support for VS2017 to gyp Add support for creating VS2017 projects to gyp. --- tools/gyp/pylib/gyp/MSVSVersion.py | 33 ++++++++++++++++++++++++++- tools/gyp/pylib/gyp/generator/msvs.py | 14 ++++++++++-- 2 files changed, 44 insertions(+), 3 deletions(-) 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'): From c18a79f8b0485364d5071e649eb170f810185da6 Mon Sep 17 00:00:00 2001 From: Bartosz Sosnowski Date: Thu, 26 Jan 2017 11:57:19 +0000 Subject: [PATCH 3/3] build: add support for Visual Studio 2017 Add support for Visual Studio 2017. Python script is used to query COM server about location of VS installation and installed SDK version. Those are passed to GYP as environment variables. --- BUILDING.md | 3 + tools/find_vs2017.py | 173 +++++++++++++++++++++++++++++++++++++++++++ vcbuild.bat | 25 ++++++- 3 files changed, 200 insertions(+), 1 deletion(-) create mode 100644 tools/find_vs2017.py 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/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/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