c# - WinApi-GetLastError vs. Marshal.GetLastWin32Error
marshalling unmanaged (3)
en [DllImport ("kernel32.dll", SetLastError = true)] hace que el atributo SetLastError haga que el Framework almacene el código de error para el uso de Marshal.GetLastWin32Error ()?
Sí, como está documentado en el SetLastError=true
¿Hay algún ejemplo en el que GetLastError simple no proporcione el resultado correcto?
Como se documenta en el Marshal.GetLastWin32Error , si el marco (por ejemplo, el recolector de basura) llama a cualquier método nativo que establezca un valor de error entre sus llamadas al método nativo y GetLastError
, obtendrá el valor de error de la llamada del marco en lugar de su llamada .
¿realmente TENGO que usar Marshal.GetLastWin32Error ()?
Como no puede asegurarse de que el marco nunca llamará a un método nativo entre su llamada y la llamada a GetLastError
, sí. Además, ¿por qué no?
¿Está relacionado este "problema" con la versión del Marco?
Definitivamente podría ser (por ejemplo, cambios en el recolector de basura), pero no tiene por qué ser así.
Probé mucho. ¡Pero no encontré desventajas de esos 2!
Pero mira la respuesta aceptada.
GetLastError
en el código administrado no es seguro porque el Framework podría "sobreescribir" el último error. Nunca he tenido ningún problema notable con GetLastError
y me parece que .NET Framework es lo suficientemente inteligente como para no sobrescribirlo. Por lo tanto, tengo algunas preguntas sobre ese tema: - en
[DllImport("kernel32.dll", SetLastError = true)]
hace que el atributoSetLastError
haga que el Framework almacene el código de error para el uso deMarshal.GetLastWin32Error()
? - ¿Hay
GetLastError
ejemplo en el queGetLastError
simple no proporcione el resultado correcto? - ¿ realmente TENGO que usar
Marshal.GetLastWin32Error()
? - ¿Está relacionado este "problema" con la versión del Marco?
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());
}
}
}
Produciendo errores:
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
¡No veo ninguna diferencia! Ambos se comportan igual excepto Marshal.GetLastWin32Error
almacena resultados de App-> CLR-> WinApi también y GetLastError
almacena solo resultados de aplicaciones-> llamadas de WinApi.
- GetLastError es seguro para subprocesos. SetLastError almacena un código de error para cada hilo que lo llame.
- ¿desde cuándo funcionaría GC en mis hilos?
TL; DR
- Utilice
[DllImport(SetLastError = true)]
yMarshal.GetLastWin32Error()
- realizar el
Marshal.GetLastWin32Error()
inmediatamente después de una llamadaWin32
falla y en el mismo hilo.
Argumentación
Mientras lo leo, la explicación oficial de por qué necesita Marshal.GetLastWin32Error
se puede encontrar here :
Si desea acceder a este código de error, debe llamar a GetLastWin32Error en lugar de escribir su propia definición de invocación de plataforma para GetLastError y llamarlo. El tiempo de ejecución de lenguaje común puede realizar llamadas internas a las API que sobrescriben el GetLastError mantenido por el sistema operativo.
Para decirlo en otras palabras:
Entre su llamada de Win32 que establece el error, el CLR puede "insertar" otras llamadas de Win32 que podrían sobrescribir el error. Al especificar [DllImport(SetLastError = true)]
se asegura de que CLR recupere el código de error antes de que CLR ejecute llamadas Win32 inesperadas. Para acceder a esa variable, necesitamos usar Marshal.GetLastWin32Error
.
Ahora, lo que @Bitterblue encontró es que estas "llamadas insertadas" no ocurren a menudo, no pudo encontrar ninguna. Pero eso no es realmente sorprendente. ¿Por qué? Porque es extremadamente difícil hacer una "prueba de caja negra" para saber si GetLastError
funciona de manera confiable:
- puede detectar falta de fiabilidad solo si una llamada Win32 insertada CLR realmente falla mientras tanto.
- la falla de estas llamadas puede depender de factores internos / externos. Tales como tiempo / tiempo, presión de memoria, dispositivos, estado de la computadora, versión de Windows ...
- la inserción de llamadas Win32 por CLR puede depender de factores externos. Entonces, en algunas circunstancias, el CLR inserta una llamada de Win32, en otras no.
- el comportamiento también puede cambiar con diferentes versiones de CLR
Hay un componente específico, el recolector de basura (GC), que se sabe que interrumpe un hilo de .NET si hay presión de memoria y hace algún procesamiento en ese hilo (consulte Qué sucede durante una recolección de basura ). Ahora, si el GC fuera a ejecutar una llamada Win32 GetLastError
, esto interrumpiría su llamada a GetLastError
.
Para resumir, tiene una plétora de factores desconocidos que pueden influir en la fiabilidad de GetLastError
. Lo más probable es que no encuentre un problema de falta de fiabilidad al desarrollar / probar, pero puede explotar en producción en cualquier momento. Entonces use [DllImport(SetLastError = true)]
y Marshal.GetLastWin32Error()
y mejore la calidad de su sueño ;-)
Siempre debe usar Marshal.GetLastWin32Error
. El problema principal es el recolector de basura. Si se ejecuta entre la llamada de SetVolumeLabel
y la llamada de GetLastError
, recibirá un valor incorrecto, porque el GC seguramente ha sobrescrito el último resultado.
Por lo tanto, siempre debe especificar SetLastError=true
en DllImport-Attribute:
[DllImport("kernel32.dll", SetLastError=true)]
static extern bool SetVolumeLabel(string lpRootPathName, string lpVolumeName);
Esto asegura que el stub marhsallling llama inmediatamente después de la función nativa al "GetLastError" y lo almacena en el hilo local.
Y si ha especificado este atributo, la llamada a Marshal.GetLastWin32Error
siempre tendrá el valor correcto.
Para obtener más información, consulte también here
También otra función de .NET puede cambiar las ventanas "GetLastError". Aquí hay un ejemplo que produce diferentes resultados:
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());
}
}
}
¡También parece que esto depende del CLR que estás usando! Si compila esto con .NET2, producirá "2/0"; si cambia a .NET 4, obtendrá "2/2" ...
Por lo tanto, depende de la versión de CLR, pero no debe confiar en la función nativa GetLastError
; siempre use Marshal.GetLastWin32Error
.