1# DExTer : Debugging Experience Tester 2# ~~~~~~ ~ ~~ ~ ~~ 3# 4# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 5# See https://llvm.org/LICENSE.txt for license information. 6# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 7"""Communication via the Windows COM interface.""" 8 9import inspect 10import time 11import sys 12 13# pylint: disable=import-error 14import win32com.client as com 15import win32api 16# pylint: enable=import-error 17 18from dex.utils.Exceptions import LoadDebuggerException 19 20_com_error = com.pywintypes.com_error # pylint: disable=no-member 21 22 23def get_file_version(file_): 24 try: 25 info = win32api.GetFileVersionInfo(file_, '\\') 26 ms = info['FileVersionMS'] 27 ls = info['FileVersionLS'] 28 return '.'.join( 29 str(s) for s in [ 30 win32api.HIWORD(ms), 31 win32api.LOWORD(ms), 32 win32api.HIWORD(ls), 33 win32api.LOWORD(ls) 34 ]) 35 except com.pywintypes.error: # pylint: disable=no-member 36 return 'no versioninfo present' 37 38 39def _handle_com_error(e): 40 exc = sys.exc_info() 41 msg = win32api.FormatMessage(e.hresult) 42 try: 43 msg = msg.decode('CP1251') 44 except AttributeError: 45 pass 46 msg = msg.strip() 47 return msg, exc 48 49 50class ComObject(object): 51 """Wrap a raw Windows COM object in a class that implements auto-retry of 52 failed calls. 53 """ 54 55 def __init__(self, raw): 56 assert not isinstance(raw, ComObject), raw 57 self.__dict__['raw'] = raw 58 59 def __str__(self): 60 return self._call(self.raw.__str__) 61 62 def __getattr__(self, key): 63 if key in self.__dict__: 64 return self.__dict__[key] 65 return self._call(self.raw.__getattr__, key) 66 67 def __setattr__(self, key, val): 68 if key in self.__dict__: 69 self.__dict__[key] = val 70 self._call(self.raw.__setattr__, key, val) 71 72 def __getitem__(self, key): 73 return self._call(self.raw.__getitem__, key) 74 75 def __setitem__(self, key, val): 76 self._call(self.raw.__setitem__, key, val) 77 78 def __call__(self, *args): 79 return self._call(self.raw, *args) 80 81 @classmethod 82 def _call(cls, fn, *args): 83 """COM calls tend to randomly fail due to thread sync issues. 84 The Microsoft recommended solution is to set up a message filter object 85 to automatically retry failed calls, but this seems prohibitively hard 86 from python, so this is a custom solution to do the same thing. 87 All COM accesses should go through this function. 88 """ 89 ex = AssertionError("this should never be raised!") 90 91 assert (inspect.isfunction(fn) or inspect.ismethod(fn) 92 or inspect.isbuiltin(fn)), (fn, type(fn)) 93 retries = ([0] * 50) + ([1] * 5) 94 for r in retries: 95 try: 96 try: 97 result = fn(*args) 98 if inspect.ismethod(result) or 'win32com' in str( 99 result.__class__): 100 result = ComObject(result) 101 return result 102 except _com_error as e: 103 msg, _ = _handle_com_error(e) 104 e = WindowsError(msg) # pylint: disable=undefined-variable 105 raise e 106 except (AttributeError, TypeError, OSError) as e: 107 ex = e 108 time.sleep(r) 109 raise ex 110 111 112class DTE(ComObject): 113 def __init__(self, class_string): 114 try: 115 super(DTE, self).__init__(com.DispatchEx(class_string)) 116 except _com_error as e: 117 msg, exc = _handle_com_error(e) 118 raise LoadDebuggerException( 119 '{} [{}]'.format(msg, class_string), orig_exception=exc) 120