Udostępnij za pośrednictwem


GetLastError and managed code

In the Win32 world, calling the GetLastError API is often the mechanism to get additional error information when an API call fails. When calling these same Win32 APIs in managed code via PInvoke or via "It Just Works (IJW)" in managed C++, the rules are slightly different. That's because the CLR itself could be making calls to the operating system as the call returns from unmanaged to managed code (when it unmarshals parameters, for instance) and these calls could easily invalidate the last error.

For PInvoke, the solution is two-fold:

1) Mark the relevant PInvoke signature with SetLastError=true. This makes the CLR call GetLastError immediately after it calls the target unmanaged API and save the result.

2) Call Marshal.GetLastWin32Error (in System.Runtime.InteropServices) to retrieve the value that the CLR saved.

Therefore, never define a PInvoke signature for GetLastError from kernel32.dll. If managed code calls such a method, it will not get reliable results.

Here's a C# example that retrieves the last error correctly. It forces a failure by passing an invalid drive name (XYZ) to the SetVolumeLabel API:

  using System.Runtime.InteropServices;

  public class ForceFailure

  {

    [DllImport("kernel32.dll", SetLastError=true)]

    static extern bool SetVolumeLabel(

      string lpRootPathName, string lpVolumeName);

    public static void Main ()

    {

      if (SetVolumeLabel("XYZ:\\", "My

Imaginary Drive

"))

        System.Console.WriteLine("It worked???");

      else

       System.Console.WriteLine(Marshal.GetLastWin32Error());

    }

  }

This outputs "123" to the console. Of course, this isn't a very satisfactory error message. In the Win32 world, you'd probably call the FormatMessage API next to get a meaningful message describing what error 123 means. You could definitely do the same thing in managed code with a PInvoke call to FormatMessage, but the .NET Framework provides a much simpler way to do this.

The System.Component model namespace defines a Win32Exception class that internally calls FormatMessage for you! So if you change the previous code to the following:

  if (SetVolumeLabel("XYZ:\\", "My

Imaginary Drive

"))

    System.Console.WriteLine("It worked???");

  else

    throw new Win32Exception(Marshal.GetLastWin32Error());

Then running the code gives an easy-to-understand unhandled exception:

  Unhandled Exception: System.ComponentModel.Win32Exception: The filename, directory name, or volume label syntax is incorrect

   at ForceFailure.Main()

Even more slick (although perhaps confusing to see in code) is that the default constructor for Win32Exception calls Marshal.GetLastWin32Error for you! So you could actually change the code to the following to get the same result:

  if (SetVolumeLabel("XYZ:\\", "My

Imaginary Drive

"))

    System.Console.WriteLine("It worked???");

  else

    throw new Win32Exception();

Note that you don't have to throw the exception in order to exercise the FormatMessage functionality, since this is done in the exception object's constructor. Once the exception object is constructed, you could check its Message property and see the result of the FormatMessage call.

If you're a VB.NET programmer, note that when you use Declare statements, the compiler emits PInvoke signatures that automatically set SetLastError to true. (C# forces you to opt-in because having the CLR track the last error is slightly slower than not tracking it.) So all you need to worry about is calling Marshal.GetLastWin32Error, or Err.LastDllError. The latter is defined in the Microsoft.VisualBasic assembly for backwards compatibility with earlier versions of Visual Basic. It does the exact same thing as Marshal.GetLastWin32Error.

 

For managed C++ and IJW, the story is a little different. This is important for you C++ programmers out there, so I hope you're still reading! If you use DllImport explicitly in C++, the same rules apply as with C#. But when you call unmanaged APIs directly from managed C++ code, neither GetLastError nor Marshal.GetLastWin32Error will work reliably. GetLastError won't work for the same reason that a PInvoke call to it wouldn't work in C#. Marshal.GetLastWin32Error won't work because the implicit PInvoke goop emitted by the compiler doesn't set SetLastError to true. To fix this, you can use #pragma unmanaged to keep code that relies on GetLastError functionality as unmanaged code.

 

Finally, don't forget about the rules of GetLastError, which are not specific to managed code but still apply:

1) Not all Win32 APIs make use of this mechanism. Check MSDN to find out which ones do and which don't.

2) Even if an API supports SetLastError/GetLastError, the value returned by GetLastError is only meaningful if the API you just called actually fails. How that failure is indicated depends on the API.

Comments

  • Anonymous
    April 26, 2003
    Even if an API supports SetLastError/GetLastError, the value returned by GetLastError is only meaningful if the API you just called actually fails.  How that failure is indicated depends on the API.Except for a couple of braindead API/situation combinations, like Impersonate* with the new SeImpersonatePrivilege: the API can return a success code even if it fails due to lack of privilege, leaving you with only an identification token. Then you have to call GetLastError () to find out why the nominally-successful API call failed....
  • Anonymous
    March 19, 2008
    PingBack from http://blog.euphemos.com/2005/04/18/hresults-and-win32-error-codes-introduction/