Есть ли способ декодировать числовые коды ошибок COM в pywin32 - PullRequest
25 голосов
/ 06 февраля 2009

Вот часть трассировки стека из недавнего запуска ненадежного приложения, написанного на Python, который управляет другим приложением, написанным в Excel:

pywintypes.com_error: (-2147352567, 'Exception occurred.', (0, None, None, None, 0, -2146788248), None)

Очевидно, что что-то пошло не так ... но что? [1] Эти коды ошибок COM кажутся чрезмерно загадочными.

Как я могу декодировать это сообщение об ошибке? Есть ли где-нибудь таблица, которая позволяет мне преобразовать этот числовой код ошибки в нечто более значимое?

[1] Я действительно знаю, что пошло не так в этом случае, он пытался получить доступ к свойству Name объекта Range, у которого не было свойства Name ... не все ошибки найти так просто!

Ответы [ 5 ]

39 голосов
/ 10 февраля 2009

Вы не делаете ничего плохого. Первый элемент в вашей трассировке стека (число) - это код ошибки, возвращаемый объектом COM. Второй элемент - это описание, связанное с кодом ошибки, который в данном случае называется «Исключение произошло». pywintypes.com_error уже называется для вас эквивалентом win32api.FormatMessage (errCode). Мы посмотрим на второе число через минуту.

Кстати, вы можете использовать утилиту «Поиск ошибок», которая поставляется в Visual Studio (C: \ Program Files \ Microsoft Visual Studio 9.0 \ Common7 \ Tools \ ErrLook.exe) в качестве панели быстрого запуска для проверьте коды ошибок COM. Эта утилита также вызывает FormatMessage для вас и отображает результат. Не все коды ошибок будут работать с этим механизмом, но многие будут. Это обычно моя первая остановка.

Обработка ошибок и создание отчетов в COM немного грязно. Я постараюсь дать вам немного фона.

Все вызовы метода COM возвращают числовой код, называемый HRESULT, который может указывать на успех или неудачу. Все формы сообщений об ошибках в COM основываются на этом.

Коды обычно выражаются в шестнадцатеричном виде, хотя иногда вы увидите их в виде больших 32-битных чисел, как в вашей трассировке стека. Существуют все виды предопределенных кодов возврата для общих результатов и проблем, или объект может возвращать пользовательские числовые коды для особых ситуаций. Например, значение 0 (называемое S_OK) универсально означает «Нет ошибок», а 0x80000002 - E_OUTOFMEMORY. Иногда коды HRESULT возвращаются объектом, иногда инфраструктурой COM.

COM-объект также может предоставить гораздо более подробную информацию об ошибках, реализуя интерфейс IErrorInfo. Когда объект реализует IErrorInfo, он может предоставить всевозможные подробности о том, что произошло, например подробное настраиваемое сообщение об ошибке и даже имя файла справки, описывающего проблему. В VB6 и VBA. объект Err позволяет получить доступ ко всей этой информации (Err.Description и т. д.).

Чтобы усложнить ситуацию, объекты COM с поздней привязкой (которые используют механизм, называемый COM Automation или IDispatch) добавляют некоторые слои, которые необходимо отделить для получения информации. Excel обычно манипулирует поздним связыванием.

Теперь давайте снова посмотрим на вашу ситуацию. В качестве первого числа вы получаете довольно общий код ошибки: DISP_E_EXCEPTION. Примечание: обычно вы можете узнать официальное название HRESULT, набрав номер в Google, хотя иногда вам придется использовать шестнадцатеричную версию, чтобы найти что-нибудь полезное.

Ошибки, которые начинаются с DISP_, являются кодами ошибок IDISPATCH. Эта ошибка означает, что объект «выбросил исключение COM», с дополнительной информацией, упакованной в другом месте (хотя я не совсем знаю, где; мне придется ее искать).

Из того, что я понимаю о pywintypes.com_error, последнее число в вашем сообщении - это фактический код ошибки, который был возвращен объектом во время исключения. Это фактический числовой код, который вы получите из Err.Number.

VBA.

К сожалению, второй код -2146788248 (0x800A9C68) находится в диапазоне, зарезервированном для пользовательских сообщений об ошибках, определяемых приложением (в VBA: VbObjectError + someCustomErrorNumber), поэтому централизованное значение отсутствует. Одно и то же число может означать совершенно разные вещи для разных программ.

В этом случае мы зашли в тупик:

Код ошибки «пользовательский», и приложение должно задокументировать, что это такое, за исключением того, что в Excel нет. Кроме того, Excel (или фактический источник ошибки), по-видимому, не предоставляет больше информации через IErrorInfo.

Excel печально известен (по крайней мере, мне) за загадочные коды ошибок из автоматизации и непонятные ситуации, которые их вызывают. Это особенно верно для ошибок, которые можно рассматривать как «ошибки времени разработки» («вы должны были знать лучше, чем вызывать метод, который не существует в объекте»). Вместо красивого «Не удалось прочитать свойство Name» вы получите « Ошибка времени выполнения« 1004 »: ошибка приложения или объекта » (которую я только что получил пытаясь получить доступ к свойству имени в диапазоне, из VBA в Excel). Это НЕ очень полезно.

Проблема не направлена ​​на Python или его интерфейс к Excel. Сам Excel не объясняет, что произошло, даже с VBA.

Однако общая процедура, описанная выше, остается в силе. Если в будущем вы получите сообщение об ошибке Excel, вы можете получить лучшее сообщение об ошибке, которое вы можете отследить таким же образом.

Удачи!

9 голосов
/ 16 марта 2011

Сделай так:

try:
    [whatever code]
except pythoncom.com_error as error:
    print(win32api.FormatMessage(error.excepinfo[5]))

Дополнительная информация о переваривании объекта pythoncom.com_error здесь: http://docs.activestate.com/activepython/3.2/pywin32/com_error.html

7 голосов
/ 06 февраля 2009

Да, попробуйте модуль win32api:

import win32api
e_msg = win32api.FormatMessage(-2147352567)

Вы можете получить любые коды, возвращенные из исключения, и передать их в FormatMessage. В вашем примере было 2 кода ошибки.

3 голосов
/ 17 мая 2012

Специально для pythoncom коды ошибок, которые в результате получаются более чем загадочными. Это связано с тем, что pythoncom представляет их внутренне как 32-битное целое число со знаком, когда правильное представление представляет собой 32-битное целое число без знака . В результате преобразование, которое вы видите в трассировке стека, является неправильным.

В частности, ваше исключение, согласно pythoncom, составляет -2147352567, а ваш (из-за отсутствия лучшего слова) Err.Number -2146788248.

Это, однако, вызывает некоторые проблемы при поиске конкретных ошибок, как показано ниже:

DISP_E_EXCEPTION = 0x80020009
#...
#except pywintypes.com_error as e:
#    print repr(e)
#    #pywintypes.com_error: (-2147352567, 'Exception occurred.', (0, None, None, None, 0, -2146788248), None)
#    hr = e.hresult

hr = -2147352567
if hr == DISP_E_EXCEPTION:
    pass #This never occurs
else:
    raise

Чтобы понять, почему это вызывает проблемы, давайте рассмотрим следующие коды ошибок:

>>> DISP_E_EXCEPTION = 0x80020009
>>> DISP_E_EXCEPTION
2147614729L
>>> my_hr = -2147352567
>>> my_hr == DISP_E_EXCEPTION
False

Опять же, это потому, что python видит константу, объявленную как положительную, а неправильное объявление pythoncom интерпретирует ее как отрицательную. Конечно, самое очевидное решение не помогает:

>>> hex(my_hr)
'-0x7ffdfff7'

Решение состоит в том, чтобы правильно интерпретировать число. К счастью, представление pythoncom является обратимым. Нам нужно интерпретировать отрицательное число как 32-разрядное целое число со знаком, а затем интерпретировать , что , как целое число без знака:

def fix_com_hresult(hr):
    import struct
    return struct.unpack("L", struct.pack("l", hr))[0]

>>> DISP_E_EXCEPTION = 0x80020009
>>> my_hr = -2147352567
>>> my_hr == DISP_E_EXCEPTION
False
>>> fixed_hr = fix_com_hresult(my_hr)
>>> fixed_hr
2147614729L
>>> fixed_hr == DISP_E_EXCEPTION
True

Итак, чтобы собрать все это вместе, вам нужно запускать fix_com_hresult () для этого результата из pythoncom, по сути, постоянно.

Так как обычно вам нужно делать это при проверке исключений, я создал следующие функции:

def fix_com_exception(e):
    e.hresult = fix_com_hresult(e.hresult)
    e.args = [e.hresult] + list(e.args[1:])
    return e

def fix_com_hresult(hr):
    import struct
    return struct.unpack("L", struct.pack("l", hr))[0]

, который затем можно использовать, как вы ожидаете:

DISP_E_EXCEPTION = 0x80020009
try:
    #failing call
except pywintypes.com_error as e:
    print repr(e)
    #pywintypes.com_error: (-2147352567, 'Exception occurred.', (0, None, None, None, 0, -2146788248), None)
    fix_com_exception(e)
    print repr(e)
    #pywintypes.com_error: (2147614729L, 'Exception occurred.', (0, None, None, None, 0, -2146788248), None)
    if e.hresult == DISP_E_EXCEPTION:
        print "Got expected failure"
    else:
        raise

Мне не удалось найти документ MSDN со списком всех HRESULT, но я нашел это: http://www.megos.ch/support/doserrors_e.txt

Кроме того, поскольку у вас это есть, fix_com_hresult () также следует запускать для вашего расширенного кода ошибки (-2146788248), но, как сказал Euro Micelli, в данном конкретном случае это не поможет:)

1 голос
/ 17 марта 2015

Никто еще не упомянул атрибут strerror pywintypes.com_error Исключение . Это возвращает результат FormatMessage для кода ошибки. Так что вместо того, чтобы делать это самостоятельно, как это

try:
    [whatever code]
except pythoncom.com_error as error:
    print(win32api.FormatMessage(error.excepinfo[5]))

Вы можете просто сделать это:

try:
    [whatever code]
except pythoncom.com_error as error:
    print(error.strerror)

Обратите внимание, что он вернет None, если у вас нестандартный HRESULT: (

...