C# WinApi - GetLastError 与 Marshal.GetLastWin32Error
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/17918266/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
WinApi - GetLastError vs. Marshal.GetLastWin32Error
提问by Bitterblue
I tested a lot.But I found no disadvantages of those 2!
But see the accepted answer.
我测试了很多。但是我没有发现那两个的缺点!
但请参阅已接受的答案。
我读 here这里调用
GetLastError
GetLastError
托管代码是不安全的,因为框架可能会在内部“覆盖”最后一个错误。我从来没有遇到过任何明显的问题,GetLastError
GetLastError
在我看来,.NET Framework 足够聪明,不会覆盖它。因此,我对该主题有几个问题:- 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 HAVEto use
Marshal.GetLastWin32Error()
? - is this "problem" Framework version related ?
- 在
[DllImport("kernel32.dll", SetLastError = true)]
它的SetLastError
属性使框架存储错误代码的使用Marshal.GetLastWin32Error()
? - 有没有一个例子,plain
GetLastError
不能给出正确的结果? - 我真的必须使用
Marshal.GetLastWin32Error()
吗? - 这个“问题”框架版本是否相关?
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.
我看不出有什么不同!除了Marshal.GetLastWin32Error
存储来自 App->CLR->WinApi 调用的GetLastError
结果并且仅存储来自 App->WinApi 调用的结果之外,两者的行为相同。
Garbage Collection垃圾收集似乎没有调用任何覆盖最后一个错误代码的 WinApi 函数
- GetLastError is thread-safe. SetLastError stores an error code for each thread calling it.
- since when would GC run in my threads ?
- GetLastError 是线程安全的。SetLastError 为调用它的每个线程存储一个错误代码。
- 从什么时候开始 GC 会在我的线程中运行?
采纳答案by Jochen Kalmbach
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.
您必须始终使用Marshal.GetLastWin32Error
. 主要问题是垃圾收集器。如果它在调用SetVolumeLabel
和调用之间运行,GetLastError
那么您将收到错误的值,因为 GC 肯定会覆盖最后一个结果。
Therefore you always need to specify the SetLastError=true
in the DllImport-Attribute:
因此,您始终需要SetLastError=true
在 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.
这确保了 marhsallling 存根在本机函数之后立即调用“GetLastError”并将其存储在本地线程中。
And if you have specified this attribute then the call to Marshal.GetLastWin32Error
will always have the correct value.
如果您已指定此属性,则调用Marshal.GetLastWin32Error
将始终具有正确的值。
For more info see also GetLastError and managed code
有关更多信息,另请参阅GetLastError 和托管代码
Also other function from .NET can change the windows "GetLastError". Here is an example which produces different results:
.NET 的其他函数也可以更改窗口“GetLastError”。这是一个产生不同结果的示例:
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"...
此外,这似乎取决于您使用的 CLR!如果你用 .NET2 编译它,它会产生“2 / 0”;如果您切换到 .NET 4,它将输出“2 / 2”...
So it is depended on the CLR version, but you should not trust the native GetLastError
function; always use the Marshal.GetLastWin32Error
.
所以它依赖于 CLR 版本,但你不应该相信原生GetLastError
函数;始终使用Marshal.GetLastWin32Error
.
回答by cremor
in [DllImport("kernel32.dll", SetLastError = true)] does the SetLastError attribute make the Framework store the error code for the use of Marshal.GetLastWin32Error() ?
在 [DllImport("kernel32.dll", SetLastError = true)] 中,SetLastError 属性是否使框架存储错误代码以供使用 Marshal.GetLastWin32Error() ?
Yes, as is documented in DllImportAttribute.SetLastError Field
是的,如DllImportAttribute.SetLastError 字段中所述
is there an example where plain GetLastError fails to give the correct result ?
有没有简单的 GetLastError 无法给出正确结果的例子?
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.
如Marshal.GetLastWin32Error Method 中所述,如果框架本身(例如垃圾收集器)调用任何在您对本机方法的调用之间设置错误值的本机方法,GetLastError
那么您将获得框架调用的错误值,而不是您的调用。
do I really HAVE to use Marshal.GetLastWin32Error() ?
我真的必须使用 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?
由于您无法确保框架永远不会在您的调用和对 的调用之间调用本机方法GetLastError
,是的。还有,为什么不呢?
is this "problem" Framework version related ?
这个“问题”框架版本是否相关?
It could definitely be (e.g. changes in the garbage collector), but it doesn't have to.
绝对可以(例如垃圾收集器中的更改),但并非必须如此。
回答by BatteryBackupUnit
TL;DR
TL; 博士
- Do use
[DllImport(SetLastError = true)]
andMarshal.GetLastWin32Error()
- perform the
Marshal.GetLastWin32Error()
immediately after a failingWin32
call and on the same thread.
- 使用
[DllImport(SetLastError = true)]
和Marshal.GetLastWin32Error()
- 执行
Marshal.GetLastWin32Error()
一个失败后立即Win32
通话,并在同一个线程。
Argumentation
论证
As i read it, the official explanation why you need Marshal.GetLastWin32Error
can be found here:
当我阅读它时,Marshal.GetLastWin32Error
可以在此处找到您需要的官方解释:
The common language runtime can make internal calls to APIs that overwrite the GetLastError maintained by the operating system.
公共语言运行时可以对覆盖操作系统维护的 GetLastError 的 API 进行内部调用。
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
.
在设置错误的 Win32 调用之间,CLR 可能会“插入”其他可能覆盖错误的 Win32 调用。指定[DllImport(SetLastError = true)]
可确保 CLR 在 CLR 执行任何意外的 Win32 调用之前检索错误代码。要访问该变量,我们需要使用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:
现在@Bitterblue 发现这些“插入的电话”并不经常发生——他找不到任何。但这并不令人惊讶。为什么?因为“黑盒测试”是否GetLastError
可靠工作是极其困难的:
- you can detect unreliability only if a CLR-inserted Win32 call actually failsin 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
- 只有当 CLR 插入的 Win32 调用实际上在此期间失败时,您才能检测到不可靠性。
- 这些调用的失败可能取决于内部/外部因素。例如时间/时序、内存压力、设备、计算机状态、Windows 版本...
- CLR 插入 Win32 调用可能取决于外部因素。因此,在某些情况下,CLR 会插入 Win32 调用,而在其他情况下则不会。
- 行为也会随着不同的 CLR 版本而改变
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
.
有一个特定的组件 - 垃圾收集器 (GC) - 如果存在内存压力,它会中断 .net 线程并在该线程上执行一些处理(请参阅垃圾收集期间会发生什么)。现在,如果 GC 执行失败的 Win32 调用,这将中断您对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 ;-)
总而言之,您有大量未知因素会影响GetLastError
. 在开发/测试时,您很可能不会发现不可靠性问题,但它可能随时在生产中爆炸。所以一定要使用[DllImport(SetLastError = true)]
并Marshal.GetLastWin32Error()
改善您的睡眠质量 ;-)