CA1060: Mueva P/Invokes a la clase NativeMethods

Propiedad Value
Identificador de la regla CA1060
Título Mover P/Invokes a la clase NativeMethods
Categoría Diseño
La corrección es problemática o no problemática Problemático
Habilitado de forma predeterminada en .NET 8 No

Causa

Un método usa servicios de invocación de plataforma para acceder a código no administrado y no es miembro de una de las clases NativeMethods.

Descripción de la regla

Los métodos de invocación de plataforma, como aquellos marcados con el atributo System.Runtime.InteropServices.DllImportAttribute o los métodos definidos mediante la palabra clave Declare en Visual Basic, tienen acceso al código no administrado. Estos métodos deben estar en una de las clases siguientes:

  • NativeMethods: esta clase no suprime los recorridos de la pila para el permiso de código no administrado. (No se debe aplicar System.Security.SuppressUnmanagedCodeSecurityAttribute a esta clase). Esta clase es para métodos que se pueden usar en cualquier lugar, ya que se realizará un recorrido de la pila.

  • SafeNativeMethods: esta clase suprime los recorridos de la pila para el permiso de código no administrado. (Se aplica System.Security.SuppressUnmanagedCodeSecurityAttribute a esta clase). Esta clase es para métodos que son seguros para que los llame cualquier usuario. No se requiere a los llamadores de estos métodos que realicen una revisión de seguridad completa para asegurarse de que el uso es seguro porque los métodos son inofensivos para cualquier llamador.

  • UnsafeNativeMethods: esta clase suprime los recorridos de la pila para el permiso de código no administrado. (Se aplica System.Security.SuppressUnmanagedCodeSecurityAttribute a esta clase). Esta clase es para métodos que son potencialmente peligrosos. Los llamadores de estos métodos deben realizar una revisión de seguridad completa para asegurarse de que el uso es seguro porque no se realizará ningún recorrido de la pila.

Estas clases se declaran como internal (Friend en Visual Basic) y declaran un constructor privado para evitar que se creen instancias. Los métodos de estas clases deben ser static y internal (Shared y Friend en Visual Basic).

Cómo corregir infracciones

Para corregir una infracción de esta regla, mueva el método a la clase NativeMethods adecuada. Para la mayoría de las aplicaciones, basta con mover P/Invoke a una nueva clase denominada NativeMethods.

Aun así, si está desarrollando bibliotecas para usarlas en otras aplicaciones, considere la posibilidad de definir otras dos clases que se llamen SafeNativeMethods y UnsafeNativeMethods. Estas clases se asemejan a la clase NativeMethods, pero se marcan con un atributo especial denominado SuppressUnmanagedCodeSecurityAttribute. Cuando se aplica este atributo, el entorno de ejecución no realiza un recorrido de la pila completo para asegurarse de que todos los llamadores tienen el permiso UnmanagedCode. Normalmente, el entorno de ejecución comprueba este permiso en el inicio. Como no se realiza la comprobación, puede mejorar mucho el rendimiento de las llamadas a estos métodos no gestionados. También habilita el código que tiene permisos limitados para llamar a estos métodos.

Aun así, debe usar este atributo con mucha atención. Puede tener implicaciones de seguridad graves si se implementa incorrectamente.

Para obtener información sobre cómo implementar los métodos, vea el ejemplo de NativeMethods, el ejemplo de SafeNativeMethods y el ejemplo de UnsafeNativeMethods.

Cuándo suprimir las advertencias

No suprima las advertencias de esta regla.

Ejemplo

En el ejemplo siguiente se declara un método que infringe esta regla. Para corregir la infracción, debe moverse el elemento P/Invoke de RemoveDirectory a una clase adecuada que esté diseñada para contener solo elementos P/Invoke.

' Violates rule: MovePInvokesToNativeMethodsClass.
Friend Class UnmanagedApi
    Friend Declare Function RemoveDirectory Lib "kernel32" (
   ByVal Name As String) As Boolean
End Class
// Violates rule: MovePInvokesToNativeMethodsClass.
internal class UnmanagedApi
{
    [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
    internal static extern bool RemoveDirectory(string name);
}

Ejemplo de NativeMethods

Dado que la clase NativeMethods no debe marcarse mediante SuppressUnmanagedCodeSecurityAttribute, los elementos P/Invoke que se colocan en ella necesitarán el permiso UnmanagedCode. Dado que la mayoría de las aplicaciones se ejecutan desde el equipo local con plena confianza, esto no suele ser un problema. Aun así, si está desarrollando bibliotecas reutilizables, considere la posibilidad de definir una clase SafeNativeMethods o UnsafeNativeMethods.

En el ejemplo siguiente se muestra un método Interaction.Beep que encapsula la función MessageBeep de user32.dll. El elemento P/Invoke de MessageBeep se coloca en la clase NativeMethods.

Public NotInheritable Class Interaction

    Private Sub New()
    End Sub

    ' Callers require Unmanaged permission        
    Public Shared Sub Beep()
        ' No need to demand a permission as callers of Interaction.Beep                     
        ' will require UnmanagedCode permission                     
        If Not NativeMethods.MessageBeep(-1) Then
            Throw New Win32Exception()
        End If

    End Sub

End Class

Friend NotInheritable Class NativeMethods

    Private Sub New()
    End Sub

    <DllImport("user32.dll", CharSet:=CharSet.Auto)>
    Friend Shared Function MessageBeep(ByVal uType As Integer) As <MarshalAs(UnmanagedType.Bool)> Boolean
    End Function

End Class
public static class Interaction
{
    // Callers require Unmanaged permission        
    public static void Beep()
    {
        // No need to demand a permission as callers of Interaction.Beep            
        // will require UnmanagedCode permission            
        if (!NativeMethods.MessageBeep(-1))
            throw new Win32Exception();
    }
}

internal static class NativeMethods
{
    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    [return: MarshalAs(UnmanagedType.Bool)]
    internal static extern bool MessageBeep(int uType);
}

Ejemplo de SafeNativeMethods

Los métodos P/Invoke que se pueden exponer de forma segura a cualquier aplicación y que no tienen ningún efecto secundario deben colocarse en una clase denominada SafeNativeMethods. No es necesario que solicites permisos ni que prestes mucha atención a desde qué lugar se les llama.

En el ejemplo siguiente se muestra una propiedad Environment.TickCount que encapsula la función GetTickCount de kernel32.dll.

Public NotInheritable Class Environment

    Private Sub New()
    End Sub

    ' Callers do not require Unmanaged permission       
    Public Shared ReadOnly Property TickCount() As Integer
        Get
            ' No need to demand a permission in place of               
            ' UnmanagedCode as GetTickCount is considered               
            ' a safe method               
            Return SafeNativeMethods.GetTickCount()
        End Get
    End Property

End Class

<SuppressUnmanagedCodeSecurityAttribute()>
Friend NotInheritable Class SafeNativeMethods

    Private Sub New()
    End Sub

    <DllImport("kernel32.dll", CharSet:=CharSet.Auto, ExactSpelling:=True)>
    Friend Shared Function GetTickCount() As Integer
    End Function

End Class
public static class Environment
{
    // Callers do not require UnmanagedCode permission       
    public static int TickCount
    {
        get
        {
            // No need to demand a permission in place of               
            // UnmanagedCode as GetTickCount is considered              
            // a safe method              
            return SafeNativeMethods.GetTickCount();
        }
    }
}

[SuppressUnmanagedCodeSecurityAttribute]
internal static class SafeNativeMethods
{
    [DllImport("kernel32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
    internal static extern int GetTickCount();
}

Ejemplo de UnsafeNativeMethods

Los métodos P/Invoke a los que no se puede llamar de manera segura y que podrían provocar efectos secundarios deben colocarse en una clase denominada UnsafeNativeMethods. Estos métodos deben comprobarse rigurosamente para asegurarse de que no se exponen al usuario de forma involuntaria.

En el ejemplo siguiente se muestra un método Cursor.Hide que encapsula la función ShowCursor de user32.dll.

Public NotInheritable Class Cursor

    Private Sub New()
    End Sub

    Public Shared Sub Hide()
        UnsafeNativeMethods.ShowCursor(False)
    End Sub

End Class

<SuppressUnmanagedCodeSecurityAttribute()>
Friend NotInheritable Class UnsafeNativeMethods

    Private Sub New()
    End Sub

    <DllImport("user32.dll", CharSet:=CharSet.Auto, ExactSpelling:=True)>
    Friend Shared Function ShowCursor(<MarshalAs(UnmanagedType.Bool)> ByVal bShow As Boolean) As Integer
    End Function

End Class
public static class Cursor
{
    public static void Hide()
    {
        UnsafeNativeMethods.ShowCursor(false);
    }
}

[SuppressUnmanagedCodeSecurityAttribute]
internal static class UnsafeNativeMethods
{
    [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
    internal static extern int ShowCursor([MarshalAs(UnmanagedType.Bool)] bool bShow);
}

Consulte también