По 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
да. А почему бы и нет?
связана ли эта версия фреймворка" проблема"?
Это определенно может быть (например, изменения в сборщике мусора), но это не обязательно.