11364750dSJames Henderson# DExTer : Debugging Experience Tester
21364750dSJames Henderson# ~~~~~~   ~         ~~         ~   ~~
31364750dSJames Henderson#
41364750dSJames Henderson# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
51364750dSJames Henderson# See https://llvm.org/LICENSE.txt for license information.
61364750dSJames Henderson# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
71364750dSJames Henderson"""Communication via the Windows COM interface."""
81364750dSJames Henderson
91364750dSJames Hendersonimport inspect
101364750dSJames Hendersonimport time
111364750dSJames Hendersonimport sys
121364750dSJames Henderson
131364750dSJames Henderson# pylint: disable=import-error
141364750dSJames Hendersonimport win32com.client as com
151364750dSJames Hendersonimport win32api
16*f98ee40fSTobias Hieta
171364750dSJames Henderson# pylint: enable=import-error
181364750dSJames Henderson
191364750dSJames Hendersonfrom dex.utils.Exceptions import LoadDebuggerException
201364750dSJames Henderson
211364750dSJames Henderson_com_error = com.pywintypes.com_error  # pylint: disable=no-member
221364750dSJames Henderson
231364750dSJames Henderson
241364750dSJames Hendersondef get_file_version(file_):
251364750dSJames Henderson    try:
26*f98ee40fSTobias Hieta        info = win32api.GetFileVersionInfo(file_, "\\")
27*f98ee40fSTobias Hieta        ms = info["FileVersionMS"]
28*f98ee40fSTobias Hieta        ls = info["FileVersionLS"]
29*f98ee40fSTobias Hieta        return ".".join(
30*f98ee40fSTobias Hieta            str(s)
31*f98ee40fSTobias Hieta            for s in [
321364750dSJames Henderson                win32api.HIWORD(ms),
331364750dSJames Henderson                win32api.LOWORD(ms),
341364750dSJames Henderson                win32api.HIWORD(ls),
35*f98ee40fSTobias Hieta                win32api.LOWORD(ls),
36*f98ee40fSTobias Hieta            ]
37*f98ee40fSTobias Hieta        )
381364750dSJames Henderson    except com.pywintypes.error:  # pylint: disable=no-member
39*f98ee40fSTobias Hieta        return "no versioninfo present"
401364750dSJames Henderson
411364750dSJames Henderson
421364750dSJames Hendersondef _handle_com_error(e):
431364750dSJames Henderson    exc = sys.exc_info()
441364750dSJames Henderson    msg = win32api.FormatMessage(e.hresult)
451364750dSJames Henderson    try:
46*f98ee40fSTobias Hieta        msg = msg.decode("CP1251")
471364750dSJames Henderson    except AttributeError:
481364750dSJames Henderson        pass
491364750dSJames Henderson    msg = msg.strip()
501364750dSJames Henderson    return msg, exc
511364750dSJames Henderson
521364750dSJames Henderson
531364750dSJames Hendersonclass ComObject(object):
541364750dSJames Henderson    """Wrap a raw Windows COM object in a class that implements auto-retry of
551364750dSJames Henderson    failed calls.
561364750dSJames Henderson    """
571364750dSJames Henderson
581364750dSJames Henderson    def __init__(self, raw):
591364750dSJames Henderson        assert not isinstance(raw, ComObject), raw
60*f98ee40fSTobias Hieta        self.__dict__["raw"] = raw
611364750dSJames Henderson
621364750dSJames Henderson    def __str__(self):
631364750dSJames Henderson        return self._call(self.raw.__str__)
641364750dSJames Henderson
651364750dSJames Henderson    def __getattr__(self, key):
661364750dSJames Henderson        if key in self.__dict__:
671364750dSJames Henderson            return self.__dict__[key]
681364750dSJames Henderson        return self._call(self.raw.__getattr__, key)
691364750dSJames Henderson
701364750dSJames Henderson    def __setattr__(self, key, val):
711364750dSJames Henderson        if key in self.__dict__:
721364750dSJames Henderson            self.__dict__[key] = val
731364750dSJames Henderson        self._call(self.raw.__setattr__, key, val)
741364750dSJames Henderson
751364750dSJames Henderson    def __getitem__(self, key):
761364750dSJames Henderson        return self._call(self.raw.__getitem__, key)
771364750dSJames Henderson
781364750dSJames Henderson    def __setitem__(self, key, val):
791364750dSJames Henderson        self._call(self.raw.__setitem__, key, val)
801364750dSJames Henderson
811364750dSJames Henderson    def __call__(self, *args):
821364750dSJames Henderson        return self._call(self.raw, *args)
831364750dSJames Henderson
841364750dSJames Henderson    @classmethod
851364750dSJames Henderson    def _call(cls, fn, *args):
861364750dSJames Henderson        """COM calls tend to randomly fail due to thread sync issues.
871364750dSJames Henderson        The Microsoft recommended solution is to set up a message filter object
881364750dSJames Henderson        to automatically retry failed calls, but this seems prohibitively hard
891364750dSJames Henderson        from python, so this is a custom solution to do the same thing.
901364750dSJames Henderson        All COM accesses should go through this function.
911364750dSJames Henderson        """
921364750dSJames Henderson        ex = AssertionError("this should never be raised!")
931364750dSJames Henderson
94*f98ee40fSTobias Hieta        assert (
95*f98ee40fSTobias Hieta            inspect.isfunction(fn) or inspect.ismethod(fn) or inspect.isbuiltin(fn)
96*f98ee40fSTobias Hieta        ), (fn, type(fn))
971364750dSJames Henderson        retries = ([0] * 50) + ([1] * 5)
981364750dSJames Henderson        for r in retries:
991364750dSJames Henderson            try:
1001364750dSJames Henderson                try:
1011364750dSJames Henderson                    result = fn(*args)
102*f98ee40fSTobias Hieta                    if inspect.ismethod(result) or "win32com" in str(result.__class__):
1031364750dSJames Henderson                        result = ComObject(result)
1041364750dSJames Henderson                    return result
1051364750dSJames Henderson                except _com_error as e:
1061364750dSJames Henderson                    msg, _ = _handle_com_error(e)
1071364750dSJames Henderson                    e = WindowsError(msg)  # pylint: disable=undefined-variable
1081364750dSJames Henderson                    raise e
1091364750dSJames Henderson            except (AttributeError, TypeError, OSError) as e:
1101364750dSJames Henderson                ex = e
1111364750dSJames Henderson                time.sleep(r)
1121364750dSJames Henderson        raise ex
1131364750dSJames Henderson
1141364750dSJames Henderson
1151364750dSJames Hendersonclass DTE(ComObject):
1161364750dSJames Henderson    def __init__(self, class_string):
1171364750dSJames Henderson        try:
1181364750dSJames Henderson            super(DTE, self).__init__(com.DispatchEx(class_string))
1191364750dSJames Henderson        except _com_error as e:
1201364750dSJames Henderson            msg, exc = _handle_com_error(e)
1211364750dSJames Henderson            raise LoadDebuggerException(
122*f98ee40fSTobias Hieta                "{} [{}]".format(msg, class_string), orig_exception=exc
123*f98ee40fSTobias Hieta            )
124