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什么时候会在我的线程中运行

WinApi - GetLastError vs. Marshal.GetLastWin32Error

您必须始终使用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的调用之间永远不会调用本机方法,所以是的。还有,为什么不呢?

这个"问题"框架版本相关吗?

当然可以(例如,垃圾收集器中的更改),但不必如此。