Подтвердить что ты не робот

WinApi - GetLastError против Marshal.GetLastWin32Error

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


Я читаю здесь, что вызов GetLastError в управляемом коде небезопасен, потому что Framework может внутренне "перезаписать" последнюю ошибку. У меня никогда не было заметных проблем с GetLastError, и мне кажется, что .NET Framework достаточно умен, чтобы не перезаписывать его. Поэтому у меня есть несколько вопросов по этой теме:
  • в [DllImport("kernel32.dll", SetLastError = true)] ли атрибут SetLastError делает Framework хранилищем код ошибки для использования Marshal.GetLastWin32Error()?
  • Есть ли пример, когда plain GetLastError не дает правильный результат?
  • Я использую реально использовать Marshal.GetLastWin32Error()?
  • является ли эта "проблема" связана с версией Framework?

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 запускается в моих потоках?
4b9b3361

Ответ 1

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

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

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

Это гарантирует, что marhsallling stub вызывает сразу после нативной функции "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"; если вы переключитесь на .NET 4, он выведет "2/2"...

Таким образом, это зависит от версии CLR, но вы не должны доверять встроенной функции GetLastError; всегда используйте Marshal.GetLastWin32Error.

Ответ 2

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

Да, как описано в поле DllImportAttribute.SetLastError

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

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

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

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

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

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

Ответ 3

TL; DR

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

Аргументация

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

Если вы хотите получить доступ к этому коду ошибки, вы должны вызвать GetLastWin32Error вместо того, чтобы писать собственное определение вызова платформы для GetLastError и вызывать его. Среда выполнения общего языка может выполнять внутренние вызовы API, которые перезаписывают GetLastError, поддерживаемые операционной системой.

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

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

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

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

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

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