Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WinApi - GetLastError vs. Marshal.GetLastWin32Error

You must always use the Marshal.GetLastWin32Error. The main problem is the garbage collector. If it runs between the call of SetVolumeLabel and the call of GetLastError then you will receive the wrong value, because the GC has surely overwritten the last result.

Therefore you always need to specify the SetLastError=true in the DllImport-Attribute:

[DllImport("kernel32.dll", SetLastError=true)]
static extern bool SetVolumeLabel(string lpRootPathName, string lpVolumeName);

This ensures that the marhsallling stub calls immediately after the native function the "GetLastError" and stores it in the local thread.

And if you have specified this attribute then the call to Marshal.GetLastWin32Error will always have the correct value.

For more info see also "GetLastError and managed code" by Adam Nathan.

Also other function from .NET can change the windows "GetLastError". Here is an example which produces different results:

using System.IO;
using System.Runtime.InteropServices;

public class ForceFailure
{
  [DllImport("kernel32.dll")]
  public static extern uint GetLastError();

  [DllImport("kernel32.dll", SetLastError = true)]
  private 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());
      try
      {
        using (new FileStream("sdsdafsdfsdfs sdsd ", FileMode.Open)) {}
      }
      catch
      {
      }
      System.Console.WriteLine(GetLastError());
    }
  }
}

Also it seems that this is depended on the CLR which you are using! If you compile this with .NET2, it will produce "2 / 0"; if you switch to .NET 4, it will output "2 / 2"...

So it is depended on the CLR version, but you should not trust the native GetLastError function; always use the Marshal.GetLastWin32Error.


TL;DR

  • Do use [DllImport(SetLastError = true)] and Marshal.GetLastWin32Error()
  • perform the Marshal.GetLastWin32Error() immediately after a failing Win32 call and on the same thread.

Argumentation

As i read it, the official explanation why you need Marshal.GetLastWin32Error can be found here:

The common language runtime can make internal calls to APIs that overwrite the GetLastError maintained by the operating system.

To say it in other words:

Between your Win32 call which sets the error, the CLR may "insert" other Win32 calls which could overwrite the error. Specifying [DllImport(SetLastError = true)] makes sure that the CLR retrieves the error code before the CLR executes any unexpected Win32 calls. To access that variable we need to use Marshal.GetLastWin32Error.

Now what @Bitterblue found is that these "inserted calls" don't happen often - he couldn't find any. But that's not really surpising. Why? Because it's extremely difficult to "black box test" whether GetLastError works reliably:

  • you can detect unreliability only if a CLR-inserted Win32 call actually fails in the meantime.
  • failure of these calls may be dependent on internal/external factors. Such as time/timing, memory pressure, devices, state of computer, windows version...
  • insertion of Win32 calls by CLR may be dependent on external factors. So under some circumstances the CLR inserts a Win32 call, under others it doesn't.
  • behavior can change with different CLR versions as well

There's is one specific component - the Garbage collector (GC) - which is known to interrupt a .net thread if there's memory pressure and do some processing on that thread (see What happens during a garbage collection). Now if the GC were to execute a failing Win32 call, this would break your call to GetLastError.

To sum it up, you have a plethora of unknown factors which can influence the reliability of GetLastError. You'll most likely not find an unreliability problem when developing/testing, but it might blow up in production at any time. So do use [DllImport(SetLastError = true)] and Marshal.GetLastWin32Error() and improve your sleep quality ;-)


in [DllImport("kernel32.dll", SetLastError = true)] does the SetLastError attribute make the Framework store the error code for the use of Marshal.GetLastWin32Error() ?

Yes, as is documented in DllImportAttribute.SetLastError Field

is there an example where plain GetLastError fails to give the correct result ?

As documented in Marshal.GetLastWin32Error Method, if the framework itself (e.g. the garbage collector) calls any native method that sets an error value between your calls to the native method and GetLastError you would get the error value of the framework's call instead of your call.

do I really HAVE to use Marshal.GetLastWin32Error() ?

Since you can't ensure that the framework will never call a native method between your call and the call to GetLastError, yes. Also, why not?

is this "problem" Framework version related ?

It could definitely be (e.g. changes in the garbage collector), but it doesn't have to.