Search code examples
c#winapimarshallingunmanagedmanaged

WinApi - GetLastError vs. Marshal.GetLastWin32Error


I tested a lot. But I found no disadvantages of those 2!
But see the accepted answer.


I read here that calling GetLastError in managed code is unsafe because the Framework might internally "overwrite" the last error. I have never had any noticeable problems with GetLastError and it seems for me that the .NET Framework is smart enough not to overwrite it. Therefore I have a few questions on that topic:

  • in [DllImport("kernel32.dll", SetLastError = true)] does the SetLastError attribute make the Framework store the error code for the use of Marshal.GetLastWin32Error() ?
  • is there an example where plain GetLastError fails to give the correct result ?
  • do I really HAVE to use Marshal.GetLastWin32Error() ?
  • is this "problem" Framework version related ?

public class ForceFailure
{
    [DllImport("kernel32.dll")]
    static extern uint GetLastError();
    [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
        {
            // the first last error check is fine here:
            System.Console.WriteLine(GetLastError());
            System.Console.WriteLine(Marshal.GetLastWin32Error());
        }
    }
}


Producing errors:

if (SetVolumeLabel("XYZ:\\", "My Imaginary Drive "))
    Console.WriteLine("It worked???");
else
{
    // bad programming but ok GetlLastError is overwritten:
    Console.WriteLine(Marshal.GetLastWin32Error());
    try
    {
        using (new FileStream("sdsdafsdfsdfs sdsd ", FileMode.Open)) { }
    }
    catch { }
    Console.WriteLine(GetLastError());
}

if (SetVolumeLabel("XYZ:\\", "My Imaginary Drive "))
    Console.WriteLine("It worked???");
else
{
    // bad programming and Marshal.GetLastWin32Error() is overwritten as well:
    Console.WriteLine(GetLastError());
    try
    {
        using (new FileStream("sdsdafsdfsdfs sdsd ", FileMode.Open)) { }
    }
    catch { }
    Console.WriteLine(Marshal.GetLastWin32Error());
}

// turn off concurrent GC
GC.Collect(); // doesn't effect any of the candidates

Console.WriteLine(" -> " + GetLastError());
Console.WriteLine(" -> " + GetLastError());
Console.WriteLine(Marshal.GetLastWin32Error());
Console.WriteLine(Marshal.GetLastWin32Error());
// when you exchange them -> same behaviour just turned around

I don't see any difference! Both behave the same except Marshal.GetLastWin32Error stores results from App->CLR->WinApi calls as well and GetLastError stores only results from App->WinApi calls.


Garbage Collection seems not to call any WinApi functions overwriting the last error code

  • GetLastError is thread-safe. SetLastError stores an error code for each thread calling it.
  • since when would GC run in my threads ?

Solution

  • 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.