56

我测试了很多。但是我发现这两个没有缺点!
但请参阅接受的答案。


我在这里 读到调用GetLastError托管代码是不安全的,因为框架可能会在内部“覆盖”最后一个错误。我从来没有遇到过任何明显的问题,GetLastError在我看来,.NET Framework 足够聪明,不会覆盖它。因此,我对该主题有几个问题:

  • 该属性[DllImport("kernel32.dll", SetLastError = true)]是否SetLastError使框架存储错误代码以供使用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 在我的线程中运行?
4

3 回答 3

81

您必须始终使用Marshal.GetLastWin32Error. 主要问题是垃圾收集器。如果它在调用 ofSetVolumeLabel和调用之间运行,GetLastError那么您将收到错误的值,因为 GC 肯定会覆盖最后的结果。

因此,您始终需要SetLastError=trueDllImport-Attribute 中指定:

[DllImport("kernel32.dll", SetLastError=true)]
static extern bool SetVolumeLabel(string lpRootPathName, string lpVolumeName);

这确保 marhsallling 存根在本机函数“GetLastError”之后立即调用并将其存储在本地线程中。

如果您已指定此属性,则调用Marshal.GetLastWin32Error将始终具有正确的值。

有关详细信息,另请参阅Adam Nathan 的“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”;如果您切换到 .NET 4,它将输出“2 / 2”...

所以它依赖于 CLR 版本,但你不应该相信原生GetLastError函数;始终使用Marshal.GetLastWin32Error.

于 2013-07-29T07:56:57.463 回答
15

TL;博士

  • 使用[DllImport(SetLastError = true)]Marshal.GetLastWin32Error()
  • 在调用Marshal.GetLastWin32Error()失败后立即在Win32同一线程上执行。

论证

Marshal.GetLastWin32Error当我阅读它时,可以在此处找到您需要的官方解释:

公共语言运行时可以对覆盖操作系统维护的 GetLastError 的 API 进行内部调用。

换句话说:

在设置错误的 Win32 调用之间,CLR 可能会“插入”其他可能覆盖错误的 Win32 调用。指定[DllImport(SetLastError = true)]可确保 CLR 在 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()改善你的睡眠质量;-)

于 2016-02-19T09:47:32.707 回答
5

在 [DllImport("kernel32.dll", SetLastError = true)] 中,SetLastError 属性是否使框架存储错误代码以供 Marshal.GetLastWin32Error() 使用?

是的,如DllImportAttribute.SetLastError 字段中所述

有没有一个简单的 GetLastError 无法给出正确结果的例子?

正如Marshal.GetLastWin32Error Method中所述,如果框架本身(例如垃圾收集器)调用任何在您对本机方法的调用之间设置错误值的本机方法,GetLastError那么您将获得框架调用的错误值而不是您的调用。

我真的必须使用 Marshal.GetLastWin32Error() 吗?

由于您无法确保框架永远不会在您的调用和对 的调用之间调用本机方法GetLastError,所以是的。还有,为什么不呢?

这个“问题”框架版本是否相关?

它肯定是(例如垃圾收集器中的更改),但它不是必须的。

于 2013-07-29T07:57:53.850 回答