2009-03-23 14 views
18

He estado escribiendo mucho código recientemente que involucra interoperabilidad con la API Win32 y he empezado a preguntarme cuál es la mejor forma de lidiar con errores nativos (no administrados) causados ​​por llamadas a funciones API de Windows .Lanzando una excepción Win32Exception

Actualmente, las llamadas a funciones nativas tienen el siguiente aspecto:

// NativeFunction returns true when successful and false when an error 
// occurred. When an error occurs, the MSDN docs usually tell you that the 
// error code can be discovered by calling GetLastError (as long as the 
// SetLastError flag has been set in the DllImport attribute). 
// Marshal.GetLastWin32Error is the equivalent managed function, it seems. 
if (!WinApi.NativeFunction(param1, param2, param3)) 
    throw new Win32Exception(); 

La línea que plantea la excepción se puede reescribir de manera equivalente, como tal, yo creo:

throw new Win32Exception(Marshal.GetLastWin32Error()); 

Ahora bien, esto es todo bien porque arroja una excepción que contiene adecuadamente el código de error Win32 que se estableció, así como una descripción (generalmente) legible para humanos del error como la propiedad Message del objeto Exception. Sin embargo, he estado pensando que sería aconsejable modificar/ajustar al menos algunas de estas excepciones, si no todas, para que den un mensaje de error ligeramente más orientado al contexto, es decir, una más significativa en cualquier situación en que se encuentre el código nativo. siendo utilizado. He considerado varias alternativas para esto:

  1. Especificación de un mensaje de error personalizado en el constructor de Win32Exception.

    throw new Win32Exception(Marshal.GetLastWin32Error(), "My custom error message."); 
    
  2. Envolver la Win32Exception en otro objeto excepción de modo que tanto el código de error original y el mensaje se conservan (la Win32Exception es ahora la InnerException de la excepción de los padres).

    throw new Exception("My custom error message.", Win32Exception(Marshal.GetLastWin32Error())); 
    
  3. El mismo que 2, excepto que usando otro Win32Exception como la excepción envoltura.

  4. Lo mismo que 2, excepto que se usa una clase personalizada derivada de Exception como la excepción de envoltura.

  5. Igual que 2, excepto que se usa una excepción BCL (Biblioteca de clases base) como padre cuando es apropiado. No estoy seguro si es apropiado incluso configurar el InnerException en el Win32Exception en este caso (tal vez para un contenedor de bajo nivel pero no una interfaz de nivel superior/abstraída que no hace obvio que la interoperabilidad de Win32 ocurre detrás de las escenas).

Esencialmente lo que quiero saber es: ¿cuál es la práctica recomendada para tratar los errores de Win32 en .NET? Lo veo hecho en código abierto en todo tipo de formas diferentes, pero tenía curiosidad de si había alguna guía de diseño. Si no, me interesarían sus preferencias personales aquí. (¿Tal vez incluso no utiliza ninguno de los métodos anteriores?)

Respuesta

4

Esto no es realmente específico para las excepciones Win32; la pregunta es, ¿cuándo deberían identificarse dos casos de error diferentes por dos tipos distintos de excepciones y cuándo deberían arrojar el mismo tipo con diferentes valores almacenados en él?

Lamentablemente, es imposible responder sin saber de antemano todas las situaciones en las que se llamará su código. :) Este es el problema con solo poder filtrar excepciones por tipo. En términos generales, si tiene una fuerte sensación de que sería útil tratar dos casos de error de manera diferente, arroje diferentes tipos.

De lo contrario, es frecuente que la cadena devuelta por Exception.Message solo necesite registrarse o mostrarse al usuario.

Si hay información adicional, ajuste Win32Exception con algo más de alto nivel. Por ejemplo, intenta hacer algo con un archivo y el usuario bajo el que se ejecuta no tiene permiso para hacerlo. Capture la excepción Win32Exception, envuélvala en una clase de excepción propia, cuyo mensaje proporcione el nombre de archivo y la operación que se intenta, seguido del mensaje de la excepción interna.

+2

+1 para la información adicional: hay poco, si acaso, más frustrante que obtener una excepción de "Archivo no encontrado" sin referencia a qué archivo no se puede encontrar. –

+0

@Earwicker: Haces un punto justo ... Además, estoy haciendo esta pregunta específicamente en relación con Win32Exception porque a menudo (aunque no siempre) tiende a ser bastante vago e inútil, como señala Harper. – Noldorin

+0

Para aclarar su último párrafo, ¿quiere decir que es mejor adjuntar también el mensaje de error interno * como para envolverlo como la excepción interna? – Noldorin

3

Mi opinión siempre ha sido que la forma adecuada de manejar esto depende de la audiencia objetivo y de cómo va a usarse su clase.

Si la persona que llama de su clase/método se va a dar cuenta de que están llamando a Win32 de una forma u otra, yo usaría la opción 1) que usted ha especificado. Esto me parece el más "claro". (Sin embargo, si este es el caso, nombraría su clase de forma que quede claro que la API de Win32 se usará directamente). Dicho esto, hay excepciones en el BCL que realmente subclasifican a Win32Exception para que sea más claro, en lugar de simplemente envolverlo. Por ejemplo, SocketException deriva de Win32Exception. Nunca he usado ese enfoque personalmente, pero parece una manera potencialmente limpia de manejar esto.

Si la persona que llama de su clase no va a tener idea de que está llamando directamente a la API de Win32, manejaría la excepción y usaría una excepción más descriptiva y personalizada que usted define. Por ejemplo, si estoy usando su clase, y no hay indicación de que esté usando la API de Win32 (ya que la está usando internamente por alguna razón específica no obvia), no tendría motivos para sospechar que puede necesitar manejar una Win32Exception. Siempre podría documentar esto, pero me parece más razonable atraparlo y dar una excepción que tendría más significado en su contexto comercial específico. En este caso, podría envolver la Win32Exception inicial como una excepción interna (es decir, su caso 4), pero dependiendo de qué causó la excepción interna, es posible que no.

Además, hay muchas ocasiones en las que se lanzaría una Win32Exception desde una llamada nativa, pero hay otras excepciones en el BCL que son más relevantes. Este es el caso cuando llama a una API nativa que no está ajustada, pero hay funciones similares que están incluidas en el BCL. En ese caso, probablemente atraparía la excepción, me aseguraré de que sea lo que estoy esperando, pero luego lanzaré la excepción BCL estándar en su lugar. Un buen ejemplo de esto sería usar SecurityException en lugar de lanzar Win32Exception.

En general, sin embargo, evitaría las opciones 2 y 3 que enumeró.

La opción dos arroja un tipo de excepción general: recomendaría evitarlo por completo. Parece irracional ajustar una excepción específica a una más general.

La tercera opción parece redundante - No hay realmente ninguna ventaja sobre la opción 1.

+0

Gracias por las sugerencias. Estoy pensando que puede ser mejor en mi situación lanzar Win32Exception directamente, ya que la clase que contiene el código es realmente un envoltorio de cosas que manipulan otros procesos/hilos/memoria compartida. – Noldorin

+0

(cont.) ¿Lanzar excepciones BCL (y envolviendo Win32?) También podría ser apropiado. La opción 4 parece ser la mejor cuando se quiere ocultar el hecho de que se está utilizando la interoperabilidad Win32 (es decir, una interfaz de nivel relativamente alto). – Noldorin

2

Personalmente me gustaría hacer # 2 o # 4 ... Preferiblemente # 4. Envuelva Win32Exception dentro de su excepción, que es sensible al contexto. De esta forma:

void ReadFile() 
{ 
    if (!WinApi.NativeFunction(param1, param2, param3)) 
     throw MyReadFileException("Couldn't read file", new Win32Exception()); 
} 

De esta forma, si alguien detecta la excepción, tendrá una muy buena idea de dónde ocurrió el problema. No haría el n. ° 1 porque requiere la captura para interpretar el mensaje de error de texto. Y el # 3 realmente no da ninguna información adicional.

+0

De acuerdo. La opción 4 es probablemente la que usaré cuando quiera ocultar las excepciones de Win32, posiblemente usando excepciones de BCL en lugar de las personalizadas cuando corresponda. – Noldorin

Cuestiones relacionadas