有没有办法在 pywin32 中解码数字 COM 错误代码

Posted

技术标签:

【中文标题】有没有办法在 pywin32 中解码数字 COM 错误代码【英文标题】:Is there a way to decode numerical COM error-codes in pywin32 【发布时间】:2010-10-06 00:36:19 【问题描述】:

这是最近运行的一个用 Python 编写的不可靠应用程序的堆栈跟踪的一部分,该应用程序控制着另一个用 Excel 编写的应用程序:

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

显然出了点问题……但是什么?[1] 这些 COM 错误代码似乎过于神秘。

如何解码此错误消息?是否有一个表格可以让我将此数字错误代码转换为更有意义的内容?

[1] 我实际上知道在这种情况下出了什么问题,它试图访问没有 Name 属性的 Range 对象上的 Name 属性...并非所有错误都这么容易找到!

【问题讨论】:

【参考方案1】:

你没有做错任何事。堆栈跟踪中的第一项(数字)是 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 自动化或 IDispatch 的机制)添加了一些需要剥离的层才能获取信息。 Excel 通常通过后期绑定进行操作。

现在让我们再看看你的情况。作为第一个数字,您得到的是一个相当通用的错误代码:DISP_E_EXCEPTION。 注意:您通常可以通过谷歌搜索数字来找出 HRESULT 的正式名称,尽管有时您必须使用十六进制版本才能找到有用的信息。

以 DISP_ 开头的错误是 IDISPATCH 错误代码。该错误大致意味着“对象抛出了一个 COM 异常”,更多信息包含在其他地方(虽然我不太清楚在哪里;我必须查一下)。

根据我对 pywintypes.com_error 的了解,您消息中的最后一个数字是异常期间对象返回的实际错误代码。这是您从 VBA 的Err.Number 中得到的实际数字代码。

不幸的是,第二个代码 -2146788248 (0x800A9C68) 在为自定义应用程序定义的错误消息保留的范围内(在 VBA 中:VbObjectError + someCustomErrorNumber),因此没有集中的含义。对于不同的程序,相同的数字可能意味着完全不同的东西。

在这种情况下,我们走到了死胡同:

错误代码是“自定义”,应用程序需要记录它是什么,除了 Excel 没有。此外,Excel(或错误的实际来源)似乎没有通过 IErrorInfo 提供更多信息。

Excel 因来自自动化的神秘错误代码和导致它们的晦涩情况而臭名昭著(至少对我而言)。对于可以考虑“设计时错误”(“您应该比调用对象中不存在的方法”)的错误尤其如此。你得到的是“Run-time error '1004': Application defined or object-defined error”,而不是一个很好的“无法读取 Name 属性”(我只是通过尝试从 Excel 中的 VBA 访问 Range 上的 Name 属性获得)。这不是很有帮助。

问题不是在 Python 上路由,也不是在 Excel 接口上。 Excel 本身并没有解释发生了什么,甚至对 VBA 也是如此。

但是,上述一般程序仍然有效。如果您以后从 Excel 中收到错误,您可能会收到一条更好的错误消息,您可以通过相同的方式进行跟踪。

祝你好运!

【讨论】:

非常感谢!对于使用 Visual Studio 10.0+ 的任何人,您可以从:microsoft.com/en-us/download/details.aspx?id=100432 下载错误查找。然后使用终端(cmd / powershell)执行。您可以搜索错误代码,例如“./Err_.exe /winerror.h -21473525627”,这将返回错误。【参考方案2】:

这样做:

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

更多关于在此处消化 pythoncom.com_error 对象的信息:https://web.archive.org/web/20170831073447/http://docs.activestate.com/activepython/3.2/pywin32/com_error.html

【讨论】:

链接已损坏。 通过回程机固定链接【参考方案3】:

是的,试试 win32api 模块:

import win32api
e_msg = win32api.FormatMessage(-2147352567)

您可以获取从异常返回的任何代码并将它们传递给 FormatMessage。您的示例有 2 个错误代码。

【讨论】:

第一条消息是“发生异常”,第二条消息引发错误:系统在 %2 的消息文件中找不到消息号 0x%1 的消息文本。我做错了吗?【参考方案4】:

特别是对于 pythoncom,产生的错误代码不仅仅是神秘的。这是因为当正确的表示是 32 位 unsigned 整数时,pythoncom 在内部将它们表示为 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 位有符号整数,然后将 that 解释为无符号整数:

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

因此,综合起来,您需要对来自 pythoncom 的结果运行 fix_com_hresult(),基本上是一直运行。

由于在检查异常时通常需要这样做,所以我创建了这些函数:

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

我找不到列出所有 HRESULT 的 MSDN 文档,但我找到了:http://www.megos.ch/support/doserrors_e.txt

另外,既然你有它,fix_com_hresult() 也应该在你的扩展错误代码 (-2146788248) 上运行,但正如 Euro Micelli 所说,它在这种特定情况下对你没有帮助:)

【讨论】:

【参考方案5】:

还没有人提到pywintypes.com_error Exception 的strerror 属性。这将返回错误代码的 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)

请注意,如果您有非标准的HRESULT,它将返回None :(

【讨论】:

error.strerror 只返回第一个代码的字符串-2147352567,已经在问题中了。

以上是关于有没有办法在 pywin32 中解码数字 COM 错误代码的主要内容,如果未能解决你的问题,请参考以下文章

如果某个任务正在任务管理器中运行,我如何在 python 中使用 pywin 或 win32com.client 检查?

pywin32 - Python 在Windows COM 编程模块

有没有使用 pywin32 createprocessasuser 并获取输出的好例子?

python调用pywin32打开记事本只打开了notepad.exe进程没有打开记事本编辑窗口?

在Windows下安装scrapy

python win32gui模块下载