/// <summary> /// Turns HRESULT errors into the appropriate exception (that maps with existing .NET behavior as much as possible). /// There are additional IOException derived errors for ease of client error handling. /// </summary> public static Exception GetIoExceptionForHResult(HRESULT hr, string path = null) { string message = path == null ? $"{HResultToString(hr)}" : $"{HResultToString(hr)} '{path}'"; switch (hr) { case HRESULT.E_ACCESSDENIED: return(new UnauthorizedAccessException(message)); case HRESULT.E_INVALIDARG: return(new ArgumentException(message)); default: if (ErrorMacros.HRESULT_FACILITY(hr) == Facility.WIN32) { return(WindowsErrorToException((WindowsError)ErrorMacros.HRESULT_CODE(hr), message, path)); } else { return(new IOException(message, (int)hr)); } } }
// .NET's Win32Exception impements the error code lookup on FormatMessage using FORMAT_MESSAGE_FROM_SYSTEM. // It won't handle Network Errors (NERR_BASE..MAX_NERR), which come from NETMSG.DLL. public static string FormatMessage( uint messageId, IntPtr source, FormatMessageFlags flags, params string[] args) { using (StringBuffer buffer = new StringBuffer()) { // Don't use line breaks flags |= FormatMessageFlags.FORMAT_MESSAGE_MAX_WIDTH_MASK; if (args == null || args.Length == 0) { flags |= FormatMessageFlags.FORMAT_MESSAGE_IGNORE_INSERTS; } WindowsError lastError = WindowsError.ERROR_INSUFFICIENT_BUFFER; uint capacity = byte.MaxValue; uint result = 0; while (lastError == WindowsError.ERROR_INSUFFICIENT_BUFFER && capacity <= short.MaxValue) { buffer.EnsureCharCapacity(capacity); result = Imports.FormatMessageW( dwFlags: flags, lpSource: source, dwMessageId: messageId, // Do the default language lookup dwLanguageId: 0, lpBuffer: buffer.DangerousGetHandle(), nSize: buffer.CharCapacity, Arguments: args); if (result == 0) { lastError = Errors.GetLastError(); capacity = (uint)Math.Min(capacity * 2, short.MaxValue); } else { buffer.Length = result; return(buffer.ToString()); } } throw new IOException("Failed to get error string.", (int)ErrorMacros.HRESULT_FROM_WIN32(lastError)); } }
private static Exception WindowsErrorToException(WindowsError error, string message, string path) { switch (error) { case WindowsError.ERROR_FILE_NOT_FOUND: return(new FileNotFoundException(message, path)); case WindowsError.ERROR_PATH_NOT_FOUND: return(new DirectoryNotFoundException(message)); case WindowsError.ERROR_ACCESS_DENIED: // Network access doesn't throw UnauthorizedAccess in .NET case WindowsError.ERROR_NETWORK_ACCESS_DENIED: return(new UnauthorizedAccessException(message)); case WindowsError.ERROR_FILENAME_EXCED_RANGE: return(new PathTooLongException(message)); case WindowsError.ERROR_INVALID_DRIVE: // Not available in Portable libraries // return new DriveNotFoundException(message); goto default; case WindowsError.ERROR_OPERATION_ABORTED: return(new OperationCanceledException(message)); case WindowsError.ERROR_NOT_READY: return(new DriveNotReadyException(message)); case WindowsError.FVE_E_LOCKED_VOLUME: return(new DriveLockedException(message)); case WindowsError.ERROR_INVALID_PARAMETER: return(new ArgumentException(message)); case WindowsError.ERROR_NOT_SUPPORTED: case WindowsError.ERROR_NOT_SUPPORTED_IN_APPCONTAINER: return(new NotSupportedException(message)); case WindowsError.ERROR_ALREADY_EXISTS: case WindowsError.ERROR_SHARING_VIOLATION: case WindowsError.ERROR_FILE_EXISTS: default: return(new IOException(message, (int)ErrorMacros.HRESULT_FROM_WIN32(error))); } }
/// <summary> /// Try to get the string for an HRESULT /// </summary> public static string HResultToString(HRESULT hr) { string message; if (ErrorMacros.HRESULT_FACILITY(hr) == Facility.WIN32) { // Win32 Error, extract the code message = ErrorMethods.FormatMessage( messageId: (uint)ErrorMacros.HRESULT_CODE(hr), source: IntPtr.Zero, flags: FormatMessageFlags.FORMAT_MESSAGE_FROM_SYSTEM); } else { // Hope that we get a rational IErrorInfo Exception exception = Marshal.GetExceptionForHR((int)hr); message = exception.Message; } return($"HRESULT {(int)hr:D} [0x{(int)hr:X}]: {message}"); }