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 запускается в моих потоках?
Ответы
Ответ 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()
и улучшите качество сна; -)