Skip to content

Provide error code to .NET exception mapping #1644

@MichaelGrafnetter

Description

@MichaelGrafnetter

Is your feature request related to a problem? Please describe.
.NET framework contains built-in mapping of HRESULT to Exception in Marshal.ThrowExceptionForHR.
Similar functionality is missing in CsWin32 for WIN32_ERROR, RPC_STATUS, and NTSTATUS, prompting every implementer to write their own mapping, making the behavior highly inconsistent.

Describe the solution you'd like
It would be nice to get built-in error to exception mapping. My C# projects typically contain copy-pasted code like this:

internal static class WindowsErrorMapper
{
extension(WIN32_ERROR error)
{
    [SuppressMessage("Usage", "CA2201:Do not raise reserved exception types", Justification = "Code for the OutOfMemoryException is returned by the system.")]
    public void ThrowOnFailure()
    {
        switch (error)
        {
            case WIN32_ERROR.ERROR_SUCCESS:
            case WIN32_ERROR.ERROR_MORE_DATA:
                // No error occurred, so exit gracefully.
                return;
        }

        var genericException = new Win32Exception(unchecked((int)error));
        Exception exceptionToThrow;

        // We will try to translate the generic Win32 exception to a more specific built-in exception.
        switch (error)
        {
            case WIN32_ERROR.ERROR_APP_INIT_FAILURE:
                exceptionToThrow = new ApplicationException(genericException.Message, genericException);
                break;
            case WIN32_ERROR.ERROR_DS_INVALID_DN_SYNTAX:
            case WIN32_ERROR.ERROR_INVALID_PARAMETER:
            case WIN32_ERROR.ERROR_INVALID_NAME:
            case WIN32_ERROR.ERROR_BAD_ARGUMENTS:
            case WIN32_ERROR.ERROR_INVALID_FLAG_NUMBER:
            case WIN32_ERROR.ERROR_INVALID_ADDRESS:
                exceptionToThrow = new ArgumentException(genericException.Message, genericException);
                break;
            case WIN32_ERROR.ERROR_ARITHMETIC_OVERFLOW:
                exceptionToThrow = new ArithmeticException(genericException.Message, genericException);
                break;
            case WIN32_ERROR.ERROR_BAD_EXE_FORMAT:
                exceptionToThrow = new BadImageFormatException(genericException.Message, genericException);
                break;
            case WIN32_ERROR.ERROR_BAD_FORMAT:
            case WIN32_ERROR.ERROR_SXS_MANIFEST_PARSE_ERROR:
            case WIN32_ERROR.ERROR_INVALID_DATA:
            case WIN32_ERROR.ERROR_DATATYPE_MISMATCH:
                exceptionToThrow = new FormatException(genericException.Message, genericException);
                break;
            case WIN32_ERROR.ERROR_MOD_NOT_FOUND:
                exceptionToThrow = new DllNotFoundException(genericException.Message, genericException);
                break;
            case WIN32_ERROR.ERROR_HANDLE_EOF:
                exceptionToThrow = new EndOfStreamException(genericException.Message, genericException);
                break;
            case WIN32_ERROR.ERROR_INVALID_ORDINAL:
                exceptionToThrow = new EntryPointNotFoundException(genericException.Message, genericException);
                break;
            case WIN32_ERROR.ERROR_OPERATION_ABORTED:
            case WIN32_ERROR.ERROR_CANCELLED:
                exceptionToThrow = new OperationCanceledException(genericException.Message, genericException);
                break;
            case WIN32_ERROR.ERROR_FILE_NOT_FOUND:
                exceptionToThrow = new FileNotFoundException(genericException.Message, genericException);
                break;
            case WIN32_ERROR.ERROR_PATH_NOT_FOUND:
            case WIN32_ERROR.ERROR_INVALID_DRIVE:
            case WIN32_ERROR.ERROR_DIRECTORY:
                exceptionToThrow = new DirectoryNotFoundException(genericException.Message, genericException);
                break;
            case WIN32_ERROR.ERROR_FILENAME_EXCED_RANGE:
                exceptionToThrow = new PathTooLongException(genericException.Message, genericException);
                break;
            case WIN32_ERROR.ERROR_ACCESS_DENIED:
            case WIN32_ERROR.ERROR_DS_DRA_ACCESS_DENIED:
            case WIN32_ERROR.ERROR_WRONG_PASSWORD:
            case WIN32_ERROR.ERROR_PASSWORD_EXPIRED:
            case WIN32_ERROR.ERROR_NOACCESS:
            case WIN32_ERROR.ERROR_ELEVATION_REQUIRED:
            case WIN32_ERROR.ERROR_PRIVILEGE_NOT_HELD:
            case WIN32_ERROR.ERROR_ACCESS_DISABLED_BY_POLICY:
                exceptionToThrow = new UnauthorizedAccessException(genericException.Message, genericException);
                break;
            case WIN32_ERROR.ERROR_ALREADY_EXISTS:
            case WIN32_ERROR.ERROR_FILE_EXISTS:
            case WIN32_ERROR.ERROR_SHARING_VIOLATION:
            case WIN32_ERROR.ERROR_LOCK_VIOLATION:
            case WIN32_ERROR.ERROR_FILE_CORRUPT:
                exceptionToThrow = new IOException(genericException.Message, genericException);
                break;
            case WIN32_ERROR.ERROR_NOT_ENOUGH_MEMORY:
            case WIN32_ERROR.ERROR_OUTOFMEMORY:
            case WIN32_ERROR.ERROR_DS_DRA_OUT_OF_MEM:
                exceptionToThrow = new OutOfMemoryException(genericException.Message, genericException);
                break;
            case WIN32_ERROR.ERROR_FILE_INVALID:
                exceptionToThrow = new FileLoadException(genericException.Message, genericException);
                break;
            case WIN32_ERROR.ERROR_DLL_INIT_FAILED:
                exceptionToThrow = new TypeInitializationException(null, genericException);
                break;
            case WIN32_ERROR.ERROR_INVALID_INDEX:
                exceptionToThrow = new ArgumentOutOfRangeException(null, genericException.Message);
                break;
            case WIN32_ERROR.ERROR_INVALID_DATATYPE:
                exceptionToThrow = new InvalidCastException(genericException.Message, genericException);
                break;
            case WIN32_ERROR.ERROR_INVALID_FILTER_PROC:
                exceptionToThrow = new InvalidFilterCriteriaException(genericException.Message);
                break;
            case WIN32_ERROR.ERROR_INVALID_VARIANT:
                exceptionToThrow = new InvalidOleVariantTypeException(genericException.Message);
                break;
            case WIN32_ERROR.ERROR_INVALID_OPERATION:
                exceptionToThrow = new InvalidOperationException(genericException.Message, genericException);
                break;
            case WIN32_ERROR.ERROR_INVALID_FUNCTION:
                exceptionToThrow = new InvalidProgramException(genericException.Message);
                break;
            case WIN32_ERROR.ERROR_PROC_NOT_FOUND:
                exceptionToThrow = new MissingMethodException(genericException.Message);
                break;
            case WIN32_ERROR.ERROR_RESOURCE_NAME_NOT_FOUND:
            case WIN32_ERROR.ERROR_RESOURCE_DATA_NOT_FOUND:
                exceptionToThrow = new MissingManifestResourceException(genericException.Message, genericException);
                break;
            case WIN32_ERROR.ERROR_BUFFER_OVERFLOW:
            case WIN32_ERROR.ERROR_MARSHALL_OVERFLOW:
                exceptionToThrow = new OverflowException(genericException.Message, genericException);
                break;
            case WIN32_ERROR.ERROR_OLD_WIN_VERSION:
                exceptionToThrow = new PlatformNotSupportedException(genericException.Message, genericException);
                break;
            case WIN32_ERROR.ERROR_STACK_OVERFLOW:
                exceptionToThrow = new StackOverflowException(genericException.Message);
                break;
            case WIN32_ERROR.ERROR_NOT_LOCKED:
                exceptionToThrow = new SynchronizationLockException(genericException.Message);
                break;
            case WIN32_ERROR.ERROR_THREAD_1_INACTIVE:
                exceptionToThrow = new ThreadStateException(genericException.Message);
                break;
            case WIN32_ERROR.ERROR_INVALID_DLL:
            case WIN32_ERROR.ERROR_INVALID_MODULETYPE:
                exceptionToThrow = new TypeLoadException(genericException.Message, genericException);
                break;
            case WIN32_ERROR.ERROR_NOT_SUPPORTED:
                exceptionToThrow = new NotSupportedException(genericException.Message, genericException);
                break;
            case WIN32_ERROR.ERROR_CALL_NOT_IMPLEMENTED:
                exceptionToThrow = new NotImplementedException(genericException.Message, genericException);
                break;
            case WIN32_ERROR.ERROR_INVALID_HANDLE:
            case WIN32_ERROR.ERROR_INVALID_TARGET_HANDLE:
                exceptionToThrow = new ObjectDisposedException(null, genericException.Message);
                break;
            case WIN32_ERROR.ERROR_NO_LOGON_SERVERS:
                exceptionToThrow = new SocketException((int)error);
                break;
            case WIN32_ERROR.ERROR_NO_SUCH_DOMAIN:
            case WIN32_ERROR.ERROR_DS_OBJ_NOT_FOUND:
            case WIN32_ERROR.ERROR_DS_NAME_ERROR_NO_MAPPING:
            case WIN32_ERROR.ERROR_SOME_NOT_MAPPED:
            // This error code means either a non-existing DN or Access Denied.
            case WIN32_ERROR.ERROR_DS_DRA_BAD_DN:
                exceptionToThrow = new DirectoryObjectNotFoundException(null, genericException);
                break;
            case WIN32_ERROR.ERROR_GEN_FAILURE:
            default:
                // We were not able to translate the Win32Exception to a more specific type.
                exceptionToThrow = genericException;
                break;
        }
        throw exceptionToThrow;
    }

    public HRESULT ToHResult()
    {
        // HRESULT_FROM_WIN32(unsigned long x) { return (HRESULT)(x) <= 0 ? (HRESULT)(x) : (HRESULT) (((x) & 0x0000FFFF) | (FACILITY_WIN32 << 16) | 0x80000000);}
        return PInvoke.HRESULT_FROM_WIN32(error);
    }

    public static WIN32_ERROR GetLastError()
    {
        int lastErrorCode = Marshal.GetLastWin32Error();
        return (WIN32_ERROR)lastErrorCode;
    }

    public static void ThrowLastError()
    {
        WIN32_ERROR lastError = WIN32_ERROR.GetLastError();
        lastError.ThrowOnFailure();
    }
}

extension(RPC_STATUS status)
{
    [SuppressMessage("Usage", "CA2201:Do not raise reserved exception types", Justification = "Code for the OutOfMemoryException is returned by the system.")]
    public void ThrowOnFailure()
    {
        if (status == RPC_STATUS.RPC_S_OK)
        {
            // No error occurred, so exit gracefully.
            return;
        }

        var genericException = new Win32Exception((int)status);
        Exception exceptionToThrow;

        switch (status)
        {
            case RPC_STATUS.RPC_S_OBJECT_NOT_FOUND:
                exceptionToThrow = new DirectoryObjectNotFoundException(objectIdentifier:null, genericException);
                break;
            case RPC_STATUS.RPC_S_OUT_OF_MEMORY:
                exceptionToThrow = new OutOfMemoryException(genericException.Message, genericException);
                break;
            case RPC_STATUS.RPC_S_ZERO_DIVIDE:
            case RPC_STATUS.RPC_S_FP_DIV_ZERO:
                exceptionToThrow = new DivideByZeroException(genericException.Message, genericException);
                break;
            case RPC_STATUS.RPC_S_FP_OVERFLOW:
            case RPC_STATUS.RPC_S_FP_UNDERFLOW:
                exceptionToThrow = new NotFiniteNumberException(genericException.Message);
                break;
            case RPC_STATUS.RPC_S_ACCESS_DENIED:
                exceptionToThrow = new UnauthorizedAccessException(genericException.Message, genericException);
                break;
            case RPC_STATUS.RPC_S_CALL_CANCELLED:
                exceptionToThrow = new OperationCanceledException(genericException.Message, genericException);
                break;
            case RPC_STATUS.RPC_S_INVALID_BINDING:
            case RPC_STATUS.RPC_S_INVALID_STRING_BINDING:
            case RPC_STATUS.RPC_S_INVALID_NET_ADDR:
                exceptionToThrow = new ArgumentException(genericException.Message, genericException);
                break;
            case RPC_STATUS.RPC_S_INVALID_TIMEOUT:
                exceptionToThrow = new ArgumentOutOfRangeException(null, genericException.Message);
                break;
            case RPC_STATUS.RPC_S_WRONG_KIND_OF_BINDING:
            case RPC_STATUS.RPC_S_INVALID_ASYNC_HANDLE:
            case RPC_STATUS.RPC_S_CALL_IN_PROGRESS:
            case RPC_STATUS.RPC_S_ADDRESS_ERROR:
                exceptionToThrow = new InvalidOperationException(genericException.Message, genericException);
                break;
            case RPC_STATUS.RPC_S_UNSUPPORTED_TRANS_SYN:
            case RPC_STATUS.RPC_S_UNSUPPORTED_TYPE:
            case RPC_STATUS.RPC_S_UNKNOWN_AUTHN_TYPE:
            case RPC_STATUS.RPC_S_UNKNOWN_AUTHN_SERVICE:
            case RPC_STATUS.RPC_S_UNKNOWN_AUTHZ_SERVICE:
                exceptionToThrow = new NotSupportedException(genericException.Message, genericException);
                break;
            case RPC_STATUS.RPC_S_SERVER_UNAVAILABLE:
            case RPC_STATUS.RPC_S_CALL_FAILED:
            case RPC_STATUS.RPC_S_CALL_FAILED_DNE:
            case RPC_STATUS.RPC_S_SERVER_TOO_BUSY:
                exceptionToThrow = new SocketException((int)status);
                break;
            default:
                // We were not able to translate the exception to a more specific type.
                exceptionToThrow = genericException;
                break;
        }

        throw exceptionToThrow;
    }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions