Quiz: Gotcha with Exceptions and HRESULTs
The C# code below, when executed, prints the following:
0x80004002
0x80004002
Who can figure out why the second line printed isn't 0x80004003?
using System;
using System.Runtime.InteropServices;
public class Quiz
{
const int E_NOINTERFACE = unchecked((int)0x80004002);
const int E_POINTER = unchecked((int)0x80004003);
public static void Main ()
{
try
{
Marshal.ThrowExceptionForHR(E_NOINTERFACE);
}
catch (Exception ex)
{
Console.WriteLine("0x{0:x}", Marshal.GetHRForException(ex));
}
try
{
Marshal.ThrowExceptionForHR(E_POINTER);
}
catch (Exception ex)
{
Console.WriteLine("0x{0:x}", Marshal.GetHRForException(ex));
}
}
}
Comments
- Anonymous
June 13, 2003
Well, both E_NOINTERFACE and E_POINTER HRESULT are mapped to an InvalidCast exception by ThrowExceptionForHR. Once an InvalidCast exception is thrown, the same HRESULT will be returned from GetHRForException (they're the same exception at that point). - Anonymous
June 13, 2003
The reason that 0x8004002 is displayed both times is that the error object is not being reset between the two try/catch blocks. before the first try/catch the error object associated with the logical thread is empty, after the try/catch the error object's HRESULT value is set to 0x8004002. Now the second try/catch block runs and the GetErrorInfo() is run to set the COMException.HRESULT value to the value from the error object (which is already set to 0x8004002), so the COMException.HRESULT value is set to 0x8004002. Let me see if I can come up with an example... - Anonymous
June 13, 2003
The comment has been removed - Anonymous
June 14, 2003
Yep. Egg is on my face. A simple test proved that I was wrong. - Anonymous
June 15, 2003
Interesting. In my test, I also tried Marshal.GetLastWin32Error after the TryInterface(), CallExcel() and TryPointer() functions but it returned 0. I also read somewhere that managed code should never call GetLastError() directly since the results from the GetLastError() call may not be accurate because the CLR may have made additional calls that would modify the logical thread error object.Adam, could you give some background on how you found this bug/feature?Also, did my example demonstrate what's going on, or is there another reason why this is happening? - Anonymous
June 15, 2003
I don't have much else to add but one thing I noticed is that in both cases the code is throwing the same instance of the exception. Also, if you throw each exception in it's own thread, then it prints the expected result. - Anonymous
June 15, 2003
Very good observations, everyone!Ordinarily, E_NOINTERFACE is mapped to InvalidCastException and E_POINTER is mapped to NullReferenceException. But as Fernando pointed out, both exceptions thrown in this example are InvalidCastException. That explains why GetHRForException returns E_NOINTERFACE in both cases, but why do we get an InvalidCastException twice?The ThrowExceptionForHR method throws an exception whose type corresponds to the input HRESULT value (or a COMException containing the HRESULT if the input HRESULT isn't in the CLR's mapping table). But it does more than just that: it calls the Win32 GetErrorInfo API to attempt to retrieve an error object available on the current thread. It uses this information to give the exception a customized error message, source, and so on. ThrowExceptionForHR also has an overload with a second System.IntPtr parameter that represents a pointer to an IErrorInfo interface, so you can pass this information directly. Passing IntPtr.Zero means that you want the API to act just like the single-parameter overload (and call GetErrorInfo), and passing -1 disables the extra info retrieval altogether.The GetHRForException method retrieves the HRESULT value for any managed exception. This is the value of the exception's protected "HResult" property, which you usually can't access directly. But this method has a side effect: it calls the Win32 SetErrorInfo API, passing the CCW for exception (which implements IErrorInfo appropriately). It does this to make it easy to mimic the marshaler's behavior when manually exposing an exception as an HRESULT and IErrorInfo to COM.In this example, the first call to GetHRForException causes the CLR to call SetErrorInfo with the wrapped InvalidCastException object. Because of this, the second call to ThrowExceptionForHR picks up this information when it calls GetErrorInfo, as Phil pointed out. The reason this affects the type of the thrown exception - despite the fact that IErrorInfo doesn't have a mechanism for retrieving an HRESULT - is that the CLR can detect when the error object retrieved from SetErrorInfo originated as a managed exception and just reuse that original exception. All of this means that no matter what HRESULT is passed into the ThrowExceptionForHR method the second time (as long as it's a failure HRESULT), the thrown exception is always an InvalidCastException.To get the behavior you'd ordinarily expect, you can use the overload of ThrowExceptionForHR that ignores IErrorInfo:using System;using System.Runtime.InteropServices;public class Quiz{ const int E_NOINTERFACE = unchecked((int)0x80004002); const int E_POINTER = unchecked((int)0x80004003);public static void Main() { try { Marshal.ThrowExceptionForHR(E_NOINTERFACE); } catch (Exception ex) { Console.WriteLine("0x{0:x}", Marshal.GetHRForException(ex)); }
}}This new code prints:0x800040020x80004003Phil asked why Marshal.GetLastWin32Error always returned 0 no matter where he inserted it. That's because GetLastError/SetLastError is a separate mechanism from GetErrorInfo/SetErrorInfo. The former mechanism is never involved here. And, yes, managed code should never call GetLastError directly. See: http://blogs.gotdotnet.com/anathan/PermaLink.aspx/6d021f27-72c8-4420-b6ad-1c5f0690bde8try{ // Ignore IErrorInfo Marshal.ThrowExceptionForHR(E_POINTER, new IntPtr(-1));}catch (Exception ex){ Console.WriteLine("0x{0:x}", Marshal.GetHRForException(ex));}
- Anonymous
June 20, 2003
I am sorry that my guestion is not related to the subject.I would like to know that how can I gather information of one page for example get the html code of one special page in my ASP or ASP.NET apllication and use it in my application.for exaple I get the html code of "www.anysite.com " and then use this code in my page by programming.thanks - Anonymous
July 27, 2004
The comment has been removed - Anonymous
July 30, 2004
The comment has been removed - Anonymous
March 25, 2005
Getting information on a COM failure - Anonymous
March 19, 2008
PingBack from http://blog.euphemos.com/2005/03/25/getting-information-on-a-com-failure/