Skip to content

Methods to bridge the gap between Integer error codes and HRESULT values represented as signed 32-bit hexadecimal #665

@junkmd

Description

@junkmd

New users of this package may find themselves deeply confused when encountering a COMError.

COMError is an exception defined in the ctypes internal implementation, _ctypes, and is part of the Python standard library. However, it remained undocumented for many years. To address this, I opened an issue and submitted a pull request to add documentation for this error to the official Python documentation.

Another problem with COMError is that most COM references typically represent error codes as signed 32-bit hexadecimal values, like the ones below, while the error code assigned to its hresult attribute is an int, making them less 'googlable'.

Additionally, converting between signed 32-bit hexadecimal strings and integer values requires some extra effort.
For instance, when I was less familiar with HRESULT, I mistakenly tried to derive the integer value for E_NOINTERFACE using int("0x80004002", 16).

To handle these conversions properly, a utility function like the following is necessary:

def signed32bithex_to_int(value: str, /) -> int:
    """
    Examples:

        >>> from comtypes import hresult as hr
        >>> signed32bithex_to_int('0x00000000') == hr.S_OK
        True
        >>> signed32bithex_to_int('0x00000001') == hr.S_FALSE
        True
        >>> signed32bithex_to_int('0x8000FFFF') == hr.E_UNEXPECTED
        True
    """
    val = int(value, 16)
    if val < 0x80000000:
        return val
    return val - 0x100000000


def int_to_signed32bithex(value: int, /) -> str:
    """
    Examples:

        >>> import comtypes.hresult as hr
        >>> int_to_signed32bithex(hr.S_OK)
        '0x00000000'
        >>> int_to_signed32bithex(hr.S_FALSE)
        '0x00000001'
        >>> int_to_signed32bithex(hr.E_UNEXPECTED)
        '0x8000FFFF'

        >>> from comtypes import CoCreateInstance
        >>> from comtypes import shelllink, automation
        >>> CLSID_ShellLink = shelllink.ShellLink().IPersist_GetClassID()
        >>> p = CoCreateInstance(CLSID_ShellLink)
        >>> p.QueryInterface(shelllink.IShellLinkA)  # doctest: +ELLIPSIS
        <POINTER(IShellLinkA) ptr=0x... at ...>
        >>> p.QueryInterface(automation.IDispatch)  # doctest: +ELLIPSIS
        Traceback (most recent call last):
            ...
        _ctypes.COMError: (-2147467262, ..., (None, None, None, 0, None))
        >>> int_to_signed32bithex(-2147467262)  # E_NOINTERFACE
        '0x80004002'
    """
    # it is simpler than using `hex(value & 0xFFFFFFFF)`
    return f"0x{value & 0xFFFFFFFF:08X}"

For example, let’s consider the case where you encounter an COMError: (-2146233083, ..., ...).
Searching for -2146233083 alone will primarily return information about COR_E_TIMEOUT, which is related to .NET.
However, searching for "0x80131505", which can be derived using int_to_signed32bithex(-2146233083), makes it easier to find information about UIA_E_TIMEOUT, which is related to UIAutomation.

I am considering adding such a function to comtypes/hresult.py with appropriate docstrings.
Doing so will likely reduce the number of inquiries related to error handling in the future, preventing burnout among the community and maintainers.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestshared_infouse cases, tips and troubleshoots

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions