WinApi - GetLastError vs. Marshal.GetLastWin32Error
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)]
andMarshal.GetLastWin32Error()
- perform the
Marshal.GetLastWin32Error()
immediately after a failingWin32
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.
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, 2021Comments
-
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 callingGetLastError
in managed code is unsafe because the Framework might internally "overwrite" the last error. I have never had any noticeable problems withGetLastError
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 theSetLastError
attribute make the Framework store the error code for the use ofMarshal.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 andGetLastError
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 ?
- in
-
Bitterblue almost 11 yearsI checked GC but can't see any problem yet. See edit in my example code.
-
Bitterblue almost 11 yearsI couldn't create a wrong value returned by
GetLastError
. See my edit and example code. -
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 callingGetLastError
? -
Bitterblue almost 11 yearsNo, 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 aSetLastError = true
flagged import ? -
Bitterblue almost 11 yearsWell, 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 ofGetLastError
is save although people try to scare you off using it. -
Aster Veigas about 10 yearsyour ans should be accepted as the correct ans. The GetLastError article was an eye opener :) Thanks
-
Jonathan Gilbert over 8 yearsThe 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 over 8 yearsNote 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 withSetLastError = 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), thenMarshal.GetLastWin32Error
will return the value ofGetLastError
as it was at the moment of your original P/Invoke call's return. The managed world can control whenMarshal.GetLastWin32Error
changes, but not when theGetLastError
API function does. -
BatteryBackupUnit about 8 yearsI 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 about 6 yearsHi @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 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()
andGetLastError()
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 thatnew FileStream(...)
is performing a Win32 call. It might even be aDllImport
withSetLastError = true
! -
Casey almost 6 yearsAnyone working with PInvoke should read both this question and answer in full. Not setting 'SetLastError' to true causes all kinds of grief.
-
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 almost 2 yearsFirst: 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 ;)