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
0 comments