| # 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) |