microsoft / microsoft/CsWin32

Provide error code to .NET exception mapping

Open
#1,644 3 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

enhancement
Dominant language
C#
Stars
2.5k
Forks
124
Avg merge
1d 3h
Merged PRs (30d)
9

Description

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;
    }
}

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

No files or tests are named in the issue. Start by reviewing the proposed use of Marshal.ThrowExceptionForHR and the WIN32_ERROR, RPC_STATUS, and NTSTATUS entry points, including GetLastError and ThrowLastError. Done means CsWin32 provides a consistent built-in error-to-exception mapping for the requested status types.

Written by the indexing model from the issue text.

Assessment

Tech stack
csharp
Domain
operating-systems, tooling
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
30/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.