По WinAPI - и getlasterror и Маршал.GetLastWin32Error

я много тестировал. но я не нашел никаких недостатков этих 2!
Но посмотрите на принятый ответ.


Я читаю здесь что вызов GetLastError в управляемом коде небезопасно, потому что платформа может внутренне "перезаписать" последнюю ошибку. У меня никогда не было никаких заметных проблем с GetLastError и мне кажется, что .NET Framework достаточно умен, чтобы не перезаписывать его. Поэтому у меня есть несколько вопросов на эту тему:
  • in [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());
        }
    }
}


производить ошибки:
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 не вижу никакой разницы! Оба ведут себя одинаково, за исключением Marshal.GetLastWin32Error сохраняет результаты из приложения - >CLR - >WinApi звонки, а также и GetLastError сохраняет только результаты из App - >WinApi вызовов.


Вывоз Мусора кажется, не вызывает никаких функций WinApi, перезаписывающих последний код ошибки
  • GetLastError является потокобезопасным. SetLastError хранит код ошибки для каждого потока, вызывающего его.
  • С каких пор GC будет работать в моих потоках ?

3 ответов


вы всегда должны использовать Marshal.GetLastWin32Error. Основная проблема-сборщик мусора. Если он работает между вызовом SetVolumeLabel и GetLastError тогда вы получите неправильное значение, потому что GC наверняка перезаписал последний результат.

поэтому вам всегда нужно указать SetLastError=true в атрибуте DllImport -:

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

это гарантирует, что заглушка marhsallling вызывает сразу после собственной функции "GetLastError" и сохраняет его в локальной нити.

и если вы указали этот атрибут, то вызов Marshal.GetLastWin32Error всегда будет иметь правильное значение.

для получения дополнительной информации см. Также GetLastError и управляемый код

также другая функция от .NET может изменить windows "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, но вы не должны доверять родной


TL; DR

  • используйте [DllImport(SetLastError = true)] и Marshal.GetLastWin32Error()
  • выполнить Marshal.GetLastWin32Error() сразу после провала Win32 вызов и в том же потоке.

Аргументы

как я прочитал, официальное объяснение, почему вам нужно Marshal.GetLastWin32Error можно найти здесь:

общеязыковая среда выполнения может выполнять внутренние вызовы API, которые перезаписывают GetLastError, поддерживаемый операционная система.

сказать это другими словами:

между вызовом Win32, который устанавливает ошибку, среда CLR может "вставить" другие вызовы Win32, которые могут перезаписать ошибку. Указание [DllImport(SetLastError = true)] гарантирует, что среда CLR извлекает код ошибки перед выполнением любых непредвиденных вызовов Win32. Получить доступ к этой переменной нужно использовать Marshal.GetLastWin32Error.

теперь @Bitterblue обнаружил, что эти "вставленные вызовы" происходят не часто - он не мог найди любого. Но в этом нет ничего удивительного. Почему? Потому что чрезвычайно сложно "проверить черный ящик", является ли GetLastError работает:

  • вы можете обнаружить ненадежность, только если CLR-вставленный вызов Win32 на самом деле не в то же время.
  • сбой этих вызовов может зависеть от внутренних/внешних факторов. Такие как время / время, давление памяти, устройства, состояние компьютера, версия windows...
  • вставка вызовов Win32 CLR может зависеть от внешних факторов. Поэтому при некоторых обстоятельствах среда CLR вставляет вызов Win32, а при других-нет.
  • поведение может меняться с различными версиями CLR, а также

существует один конкретный компонент-сборщик мусора ( GC), который, как известно, прерывает поток .net, если есть давление памяти и выполняет некоторую обработку в этом потоке (см. что происходит во время сборки мусора). Теперь, если GC должен был выполнить сбой вызова Win32, это нарушит ваш вызов GetLastError.

подводя итог, у вас есть множество неизвестных факторов, которые могут повлиять на надежность GetLastError. Скорее всего, вы не найдете проблемы с ненадежностью при разработке/тестировании, но она может взорваться в производстве в любое время. Так что используйте [DllImport(SetLastError = true)] и Marshal.GetLastWin32Error() и улучшить качество вашего сна ;-)


в [атрибута DllImport("на kernel32.dll", SetLastError = true)] атрибут SetLastError делает фреймворк хранилищем кода ошибки для использования Маршала.GetLastWin32Error() ?

Да, как описано в DllImportAttribute.Функции Setlasterror Поле

есть ли пример, когда простой GetLastError не может дать правильный результат ?

как описано в Маршал.Способ GetLastWin32Error, если сама платформа (например, сборщик мусора) вызывает любой собственный метод, который устанавливает значение ошибки между вашими вызовами собственного метода и GetLastError вы получите значение ошибки вызова фреймворка вместо вашего вызова.

мне действительно нужно использовать Marshal.GetLastWin32Error() ?

поскольку вы не можете гарантировать, что платформа никогда не вызовет собственный метод между вашим вызовом и вызовом GetLastError да. А почему бы и нет?

связана ли эта версия фреймворка" проблема"?

Это определенно может быть (например, изменения в сборщике мусора), но это не обязательно.