WinApi - GetLastError vs. Marshal.GetLastWin32Error

58,668

Solution 1

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.

Solution 2

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 ;-)

Solution 3

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.

Share:
58,668
Bitterblue
Author by

Bitterblue

Since Stackoverflow sucks, I'm gonna be egoistic and only look for answers. No more help from me. Thanks! Bye!

Updated on October 16, 2021

Comments

  • Bitterblue
    Bitterblue over 2 years

    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 ?
  • Bitterblue
    Bitterblue almost 11 years
    I checked GC but can't see any problem yet. See edit in my example code.
  • Bitterblue
    Bitterblue almost 11 years
    I couldn't create a wrong value returned by GetLastError. See my edit and example code.
  • cremor
    cremor almost 11 years
    @mini-me GC was just an example, I have no idea if it actually could cause problems. But it can for sure run in your thread if concurrent GC is disabled. Don't only think about automatic and/or background operations in the framework. Maybe you will one time call some (managed) framework API that internally calls a native API between your calls to the native API and GetLastError. Do you really want to analyze the implementation of all calls you do before calling GetLastError?
  • Bitterblue
    Bitterblue almost 11 years
    No, but the first thing to do after calling a WinApi function is to call GetLastError not to call another WinApi function or play a round of Tetris. Besides Marshal.GetLastWin32Error is just the same value on a different layer. If the CLR is bad designed and calls other WInApi functions who can guarantee that it is not overwriting this value by another call of a SetLastError = true flagged import ?
  • Bitterblue
    Bitterblue almost 11 years
    Well, your example kinda proves you wrong. If you exchange them you get the same unwanted behavior from Marshal.GetLastWin32Error. But this is actually bad programming. Like when you overwrite a variable and expect it to have the old value still. From what I understand the dllimport thing is well designed in .NET and the use of GetLastError is save although people try to scare you off using it.
  • Aster Veigas
    Aster Veigas about 10 years
    your ans should be accepted as the correct ans. The GetLastError article was an eye opener :) Thanks
  • Jonathan Gilbert
    Jonathan Gilbert over 8 years
    The CLR does not have, as a design goal, not calling other Windows API functions between two of your P/Invoke calls. Thus, if it does this under any circumstances, it is not an indication of bad design. There are a number of situations where the additional handling required to support managed code can result in extra processing behind the scenes in between two of your statements. This is by design, and the fact that it does it is not a flaw but actually allows your code to work the way you expect. Marshal.GetLastWin32Error was added specifically to make it still possible to work with APIs.
  • Jonathan Gilbert
    Jonathan Gilbert over 8 years
    Note that .NET has a guarantee that none of that hidden processing will overwrite Marshal.GetLastWin32Error's stashed value. Thus, if you make a call to a P/Invoke function with SetLastError = true, and you do no other P/Invoke calls on the same thread (which also means not calling library functions that might themselves P/Invoke), then Marshal.GetLastWin32Error will return the value of GetLastError as it was at the moment of your original P/Invoke call's return. The managed world can control when Marshal.GetLastWin32Error changes, but not when the GetLastError API function does.
  • BatteryBackupUnit
    BatteryBackupUnit about 8 years
    I agree with @Bitterblue that the example is wrong or misleading at least.. Presumably the new FileStream either produces a new error or resets it. It's expected that the result returned by both Marshal.GetLastWin32Error and GetLastError both return a "new" result at that time.
  • Jochen Kalmbach
    Jochen Kalmbach about 6 years
    Hi @BatteryBackupUnit: The function is only there, to point out, that WE do not know which Win32 API might be called between the unmanaged and the managed part of the code... we did not write the CLR, so we need to assume that there might be other Win32 API calls is some special situations (like GC or other scenarios) which we do not control. Therefor we need to use "SetLastError" in order to be sure that we get the correct error code.
  • BatteryBackupUnit
    BatteryBackupUnit about 6 years
    @JochenKalmbach Sure thing. However, your example does not prove that. If you'd wanted to prove that, you'd have to perform a Marshal.GetLastWin32Error() and GetLastError() without any other calls in between. Now, if the two calls would return a different result you'd have proven that there's a difference. With your example the difference might just be that new FileStream(...) is performing a Win32 call. It might even be a DllImport with SetLastError = true!
  • Casey
    Casey almost 6 years
    Anyone working with PInvoke should read both this question and answer in full. Not setting 'SetLastError' to true causes all kinds of grief.
  • IInspectable
    IInspectable almost 2 years
    "The main problem is the garbage collector." - I doubt this is accurate. The garbage collector runs on its own, dedicated thread, and the last error code is recorded per thread. That is, the garbage collector will not have an effect on the last error code stored on any client thread.
  • Jochen Kalmbach
    Jochen Kalmbach almost 2 years
    First: There are two kind of garbage collectors (server and workstation). Second: The dot net runtime also envolved over the last couple of years... so they also might improve/solve the problems they had ;)