Есть ли способ декодировать числовые коды ошибок COM в pywin32

StackOverflow https://stackoverflow.com/questions/521759

Вопрос

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

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

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

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

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

Это было полезно?

Решение

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

Кстати, вы можете использовать утилиту "Поиск ошибок", которая поставляется в Visual Studio (C:\Program Files\Microsoft Visual Studio 9.0\Common7 ools\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, погуглив номер, хотя иногда вам придется использовать шестнадцатеричную версию, чтобы найти что-нибудь полезное.

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

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

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

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

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

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

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

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

Удачи вам!

Другие советы

Сделай это вот так:

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

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

import win32api
e_msg = win32api.FormatMessage(-2147352567)

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

В частности, для 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, в котором перечислены все HRESULTs, но я нашел это: http://www.megos.ch/support/doserrors_e.txt

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

Никто еще не упомянул о 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 :(

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top