WinApi - GetLastError vs. Marshal.GetLastWin32Error
本文关键字:Marshal GetLastWin32Error vs GetLastError WinApi | 更新日期: 2023-09-27 18:25:22
我测试了很多但我没有发现这两个的缺点
但请看公认的答案。
我在这里读到,在托管代码中调用
GetLastError
是不安全的,因为框架可能会在内部"覆盖"最后一个错误。我从来没有遇到过GetLastError
的任何明显问题,对我来说,.NET Framework足够聪明,不会覆盖它。因此,我对这个主题有几个问题:
- 在
[DllImport("kernel32.dll", SetLastError = true)]
中,SetLastError
属性是否使Framework存储用于使用Marshal.GetLastWin32Error()
的错误代码 - 有没有一个简单的
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());
}
}
}
产生错误:
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
我看不出有什么不同!除了Marshal.GetLastWin32Error
存储来自App->CLR->WinApi调用的结果以及GetLastError
仅存储来自App->WinApi呼叫的结果之外,两者的行为相同。
垃圾回收似乎没有调用任何WinApi函数来覆盖最后一个错误代码
- GetLastError是线程安全的。SetLastError为调用它的每个线程存储一个错误代码
- GC什么时候会在我的线程中运行
您必须始终使用Marshal.GetLastWin32Error
。主要问题是垃圾收集器。如果它在SetVolumeLabel
的调用和GetLastError
的调用之间运行,那么您将收到错误的值,因为GC肯定已经覆盖了最后的结果。
因此,您总是需要在DllImport
-属性:中指定SetLastError=true
[DllImport("kernel32.dll", SetLastError=true)]
static extern bool SetVolumeLabel(string lpRootPathName, string lpVolumeName);
这确保了marhsalling存根在本机函数之后立即调用";GetLastError";并将其存储在本地线程中。
如果指定了此属性,那么对Marshal.GetLastWin32Error
的调用将始终具有正确的值。
欲了解更多信息,请参阅";GetLastError和托管代码";亚当·内森。
另外,来自.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());
}
}
}
此外,这似乎取决于您正在使用的CLR!如果你用.NET2编译它;2/0";;如果切换到.NET4;2/2";。。。
因此它取决于CLR版本,但您不应该信任本机的GetLastError
函数;始终使用CCD_ 17。
TL;DR
- 务必使用
[DllImport(SetLastError = true)]
和Marshal.GetLastWin32Error()
- 在CCD_ 21调用失败后立即在同一线程上执行CCD_
论证
当我读到它的时候,你为什么需要Marshal.GetLastWin32Error
的官方解释可以在这里找到:
公共语言运行库可以对覆盖操作系统维护的GetLastError的API进行内部调用
换句话说:
在设置错误的Win32调用之间,CLR可能会"插入"其他Win32调用,这些调用可能会覆盖错误。指定[DllImport(SetLastError = true)]
可以确保CLR在执行任何意外的Win32调用之前检索错误代码。要访问该变量,我们需要使用Marshal.GetLastWin32Error
。
现在@Bitterblue发现,这些"插入电话"并不经常发生——他找不到任何电话。但这并不是真的令人讨厌。为什么?因为"黑匣子测试"GetLastError
是否可靠工作极其困难:
- 只有在CLR插入的Win32调用同时实际失败时,才能检测到不可靠性
- 这些呼叫的失败可能取决于内部/外部因素。例如时间/计时、内存压力、设备、计算机状态、windows版本
- CLR插入Win32调用可能取决于外部因素。因此,在某些情况下,CLR会插入Win32调用,而在其他情况下则不会
- 不同的CLR版本也会改变行为
有一个特定的组件-垃圾回收器(GC)-已知它会在内存压力下中断.net线程,并对该线程进行一些处理(请参阅垃圾回收过程中会发生什么)。现在,如果GC执行一个失败的Win32调用,这将中断对GetLastError
的调用。
综上所述,有很多未知因素会影响GetLastError
的可靠性。在开发/测试时,您很可能不会发现不可靠性问题,但它可能随时在生产中爆发。所以一定要使用[DllImport(SetLastError = true)]
和Marshal.GetLastWin32Error()
来改善你的睡眠质量;-)
在[DllImport("kernel32.dll",SetLastError=true)]中,SetLastError属性是否使Framework存储错误代码以供Marshal.GetLastWin32Error()使用?
是,如DllImportAttribute.SetLastError字段中所述
有没有一个简单的GetLastError无法给出正确结果的例子?
如Marshal.GetLastWin32Error Method中所述,如果框架本身(例如垃圾收集器)调用任何在对本机方法的调用和GetLastError
之间设置错误值的本机方法,则将获得框架调用的错误值,而不是您的调用。
我真的必须使用Marshal.GetLastWin32Error()吗?
因为您不能确保框架在您的调用和对GetLastError
的调用之间永远不会调用本机方法,所以是的。还有,为什么不呢?
这个"问题"框架版本相关吗?
当然可以(例如,垃圾收集器中的更改),但不必如此。