# DExTer : Debugging Experience Tester # ~~~~~~ ~ ~~ ~ ~~ # # Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. # See https://llvm.org/LICENSE.txt for license information. # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception """Communication via the Windows COM interface.""" import inspect import time import sys # pylint: disable=import-error import win32com.client as com import win32api # pylint: enable=import-error from dex.utils.Exceptions import LoadDebuggerException _com_error = com.pywintypes.com_error # pylint: disable=no-member def get_file_version(file_): try: info = win32api.GetFileVersionInfo(file_, '\\') ms = info['FileVersionMS'] ls = info['FileVersionLS'] return '.'.join( str(s) for s in [ win32api.HIWORD(ms), win32api.LOWORD(ms), win32api.HIWORD(ls), win32api.LOWORD(ls) ]) except com.pywintypes.error: # pylint: disable=no-member return 'no versioninfo present' def _handle_com_error(e): exc = sys.exc_info() msg = win32api.FormatMessage(e.hresult) try: msg = msg.decode('CP1251') except AttributeError: pass msg = msg.strip() return msg, exc class ComObject(object): """Wrap a raw Windows COM object in a class that implements auto-retry of failed calls. """ def __init__(self, raw): assert not isinstance(raw, ComObject), raw self.__dict__['raw'] = raw def __str__(self): return self._call(self.raw.__str__) def __getattr__(self, key): if key in self.__dict__: return self.__dict__[key] return self._call(self.raw.__getattr__, key) def __setattr__(self, key, val): if key in self.__dict__: self.__dict__[key] = val self._call(self.raw.__setattr__, key, val) def __getitem__(self, key): return self._call(self.raw.__getitem__, key) def __setitem__(self, key, val): self._call(self.raw.__setitem__, key, val) def __call__(self, *args): return self._call(self.raw, *args) @classmethod def _call(cls, fn, *args): """COM calls tend to randomly fail due to thread sync issues. The Microsoft recommended solution is to set up a message filter object to automatically retry failed calls, but this seems prohibitively hard from python, so this is a custom solution to do the same thing. All COM accesses should go through this function. """ ex = AssertionError("this should never be raised!") assert (inspect.isfunction(fn) or inspect.ismethod(fn) or inspect.isbuiltin(fn)), (fn, type(fn)) retries = ([0] * 50) + ([1] * 5) for r in retries: try: try: result = fn(*args) if inspect.ismethod(result) or 'win32com' in str( result.__class__): result = ComObject(result) return result except _com_error as e: msg, _ = _handle_com_error(e) e = WindowsError(msg) # pylint: disable=undefined-variable raise e except (AttributeError, TypeError, OSError) as e: ex = e time.sleep(r) raise ex class DTE(ComObject): def __init__(self, class_string): try: super(DTE, self).__init__(com.DispatchEx(class_string)) except _com_error as e: msg, exc = _handle_com_error(e) raise LoadDebuggerException( '{} [{}]'.format(msg, class_string), orig_exception=exc)