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