Getting more information than the exception class provides

Getting more information than the exception class provides

  • Comments 9

We recently had a question about how to get more information than an exception’s type provides. The developer was trying to copy a file and didn’t know why the copy was failing. File copies can fail for many reasons, almost all of them what Eric Lippert calls “exogenous conditions”. The developer was catching System.IOException and parsing the error message. It worked on his machine, but failed during test passes.

Unfortunately, the .NET Framework doesn’t always directly provide the reason that the file copy failed. If an error results from an IO operation the .NET Framework will call GetLastError to find out why the operation failed, then throw an IOException with a message explaining the problem: sharing violation, path too long, etc. The error message is formatted in such a way that you can expose it directly to your user: it clearly states the problem and gets translated into the user’s OS language.

This is a little annoying if you’re trying to programmatically respond to the error. For example, if there’s a sharing violation you might want to wait for a few seconds then try again. How do you tell that this IOException comes from a sharing violation as opposed to a disconnected USB drive or a network failure?

You do NOT want to parse the exception message because exception messages get translated into the user’s OS language. You can’t programmatically parse a localized string without doing a lot of unnecessary work. Moreover, the exception message could change slightly between different versions of the .NET Framework. While we don’t rewrite exception messages just for fun, one day someone could complain that the error is slightly misleading and we’d fix it, breaking your parsing code. So what can you do if you want a more robust way to get more information than the exception code gives you?

The answer is pretty simple: call Marshal.GetLastWin32Error yourself in your catch block and look up the system error code on MSDN. Reading the error doesn’t clear it so you can call GetLastWin32Error even after the Framwork API does.

Here’s some sample code that illustrates how to use GetLastWin32Error. It relies on the fact that the default file handle on Windows is an exclusive handle so you can’t call File.Open twice on the same file without having closed it between the two calls.

using System;

using System.IO;

using System.Runtime.InteropServices;

 

class Program

{

    static void Main()

    {

        File.Open("foo.txt", FileMode.OpenOrCreate);

 

        try

        {

            // This call will fail due to a sharing violation.

            File.Open("foo.txt", FileMode.Open);

        }

        catch (IOException e)

        {

            // A P/Invoke here would overwrite last the error.

            int error = Marshal.GetLastWin32Error();

            Console.WriteLine("Exception message is: " + e.Message);

            Console.WriteLine("Win32 system error code is " + error);

        }

    }

}

This code prints out two pieces of information: first, that the process cannot access the file “foo.txt” because it is being used by another process, and second, that the Win32 error code is 32. Looking it up online, 32 means ERROR_SHARING_VIOLATION. If you’re passing this information on to your end user, the exception error message is already formatted for you. If you need to know the error programmatically, you can switch on the error code. (Note to the nitpickers: yes, the file’s being used by the same process, not another process as the error code says. The BCL team believes that you’re smart enough not to try to open the same file twice in your own code so they’re giving you the benefit of the doubt and blaming some other process.  But this is a fantastic example of why you shouldn’t parse error codes: maybe we’ll fix this one to say that “this process or another process” has the file open already.)

This is a simple answer, but it’s also a bit too simple. GetLastWin32Error returns the error code of the last native function executed on the thread. (Technically, the last native function that sets the error code, but let’s assume they all do.) If the call to File.Open was the most recent native function call on this thread, then everything is cool. If you P/Invoke—or if a library function that you call P/Invokes—then you won’t get the error code from the File.Open call.

The code I showed above will work fine because it is so simple. But because exceptions can propagate far out of the context from where they were raised you can easily run into problems. For example, say that instead of calling File.Open yourself, you call a function that will open the file’s directory before opening the file—call it OpenFileInDirectory. When the exception is raised, OpenFileInDirectory’s finally clause executes and closes the directory. Now the finally clause has reset the Win32 error code so when you call GetLastWin32Error in your catch clause it returns success—the error value of closing the directory.

So what should you do to get more information from an exception than its type tells you? Parsing the exception message is definitely the wrong thing to do. We’ve had suggestions on Connect that we provide exception subtypes that add more information to an exception. Essentially, when we call GetLastError, we’d pass that error on to the programmer through something like Exception.SubType or Exception.Win32Error. This is a great suggestion, and it's something the BCL team is considering for a future release.

If you find an exception code that doesn’t give you all the information you need, enter a suggestion on Connect. If there’s one there already, vote it up. The BCL team reads all Connect issues and tries hard to get the top-rated suggestions into future releases. There's always more work to do than there is time available so we try to prioritize the things that matter most. If this issue matters to you, let the team know!

Andrew Pardoe

Program Manager, CLR EH team

Leave a Comment
  • Please add 5 and 2 and type the answer here:
  • Post
  • PingBack from http://www.csharphacker.com/technicalblog/index.php/2009/06/20/need-access-to-native-os-errors-from-net-marshalgetlastwin32error-is-your-friend/

  • I use Marshal.GetHRForException(e) instead. I was using FileStream and WebClient to open IO streams. Both throw completely different exceptions for the "same" issue. I created a function that would look at the exception and tell me if it was a file not found, authorization, sharing issue, etc. Obviously GetLastWin32Error wouldn't work because I wanted to look at a specific exception.

  • Thank you for submitting this cool story - Trackback from DotNetShoutout

  • int error = Marshal.GetLastWin32Error();

    Is better then parsing text of course. However it relies on the fact that at least BCL itself will never call another API which will reset the code.

    This effectively relies on the internals of BCL not to change and it also would be a possible breaking change in the future.

    For example you may decide to call some other APIs to get more information about the source of the issue.

    I wouldn't recommend this solution either.

    In short, I think Marshal.GetLastWin32Error() is just a tiny bit better than parsing text.

    Full solution is for BCL to subsclass exceptions or at the very least to expose Win32Code on the exception thrown

  • To nitpick a little, in the code sample since the first FileStream result is not used anywhere else in the code in Release compilation it could be collected and finalized (and therefore the file closed).

    I know it is just a simple code sample + it's very unlikely to have the file closed by the time the second File.Open gets called.

    I just could not resist nitpicking :)

  • Certainly if you want to differentiate errors, you  create inherited exception class. One would have thought CLR team knows the guidelines of programming CLR.

    I cant wait to laugh when you realize Win32Code is not detailed enough for some scenarios, and you will add one more differentiator like Win32SubCode. Then Win64SubCode, Ole32Code and whatelse.

    And if you're wary of changing class hierarchies, didn't you already add Dictionary-typed property to Exception?

  • >> I know it is just a simple code sample + it's very unlikely to have the file closed

    >> by the time the second File.Open gets called.

    Well, since the first FileStream instance is not cached in a variable, it is as likely to get nicked in Debug mode as in Release.

    I do not think that is a nitpicking at all. It is rather likely the whole problem of requiring Win32Code was in fact sucked out of that bizarre code style. Let me explain.

    Imagine, somebody uses File.Open this childish way:

    var f = File.Open(...);

    f.Read(...);

    f.Close();

    Obviously, sometimes File.Open may fail by host of practical reasons: USB stick is pulled out in the middle of write, CD is too scratchy, network gone dead, actual intrinsic code logic error not related to I/O. Nobody really cares about the reason in that situation: you just can't recover.

    However, that file is left opened. That is where 'derivative' error started to happen when accessing the same path. If Garbage Collector was lazy enough to not clean the file handle in time.

    And only that 'derivative' error is recoverable. Surely, if you sit and wait long enough, GC breaks in, start collecting dead bodies and switching off life machines of those poor comatose guys who do not have living relations or next-of-kin to remember them. Alas, waiting may help when you've been dirty not cleaning up your own stuff, and there's GC willing to clean anyway.

    Why don't I think that file sharing issue is genuine need? Because in real life blocked file

    is as much likely to get released, as scratched CD get read be second attempt, or network get reconnected after you walk out of the underground walkway. So if you genuinely do want to do several File.Open attempts, you would want to do that not only for blocked shared file.

    I know that's kind of psychic debugging, but common sense and logic give strong suggestion I've guessed it right.

    In general, there's no justified reason to have this feature. Of course there's 'dirty coding' reason, but should BCL get messy and more complex to help somebody hack their way through bugs? Don't think so.

    If they do want to keep to their muddy paths, why don't they learn how to use PInvoke and get files opened with kernel32.dll/CreateFile?

  • It would be nice if the Framework used HResult consistently.  Sometimes the framework uses HResult to communicate the last win32 error, sometimes it doesn't.

    It would actually be more useful if the Framework used IOException consistently, i.e. don't use IOException for invalid-operation-like exceptions (e.g. Directory.Move when source and dest don't have same root.  That's not an IO exception...)

  • Let me direct your attention to the following feedback entry in Connect:

    "IOException should provide more differentiated error information"

    https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=94306

    What surprised me is its age: it was created in 2005, and nothing seems to have happened since.

    Please add some new IOException-derived types to .Net 4.0 - this is long overdue (especially SharingViolationException)!

Page 1 of 1 (9 items)