CA1060: mover P/Invokes para a classe NativeMethods

Property Valor
ID da regra CA1060
Título Mova P/Invokes para a classe NativeMethods
Categoria Projetar
Correção interruptiva ou sem interrupção Quebra
Habilitado por padrão no .NET 8 Não

Causa

Um método usa os Serviços de Invocação de Plataforma para acessar código não gerenciado e não é membro de uma das classes NativeMethods.

Descrição da regra

Métodos de invocação de plataforma, como os que são marcados usando o atributo System.Runtime.InteropServices.DllImportAttribute, ou métodos definidos com a palavra-chave Declare no Visual Basic, acessam código não gerenciado. Esses métodos devem estar em uma das seguintes classes:

  • NativeMethods – essa classe não suprime a movimentação da pilha para permissão de código não gerenciado. (System.Security.SuppressUnmanagedCodeSecurityAttribute não deve ser aplicado a essa classe.) Essa classe é para métodos que podem ser usados em qualquer lugar porque uma movimentação da pilha será executada.

  • SafeNativeMethods – essa classe suprime movimentações da pilha para permissão de código não gerenciado. (System.Security.SuppressUnmanagedCodeSecurityAttribute é aplicado a essa classe.) Essa classe serve para métodos seguros para qualquer pessoa chamar. Os chamadores desses métodos não são obrigados a executar a análise de segurança completa para garantir que o uso é seguro, pois os métodos são inofensivos para qualquer chamador.

  • UnsafeNativeMethods – essa classe suprime a movimentação da pilha para permissão de código não gerenciado. (System.Security.SuppressUnmanagedCodeSecurityAttribute é aplicado a essa classe.) Essa classe é para métodos potencialmente perigosos. Qualquer chamador desses métodos deve executar uma análise de segurança completa para garantir que o uso seja seguro, pois nenhuma movimentação de pilha será executada.

Essas classes são declaradas internal (Friend no Visual Basic) e declaram um construtor privado para impedir a criação de instâncias. Os métodos nessas classes devem ser static e internal (Shared e Friend no Visual Basic).

Como corrigir violações

Para corrigir uma violação dessa regra, mova o método para a classe NativeMethods apropriada. Na maioria dos aplicativos, mover P/Invokes para uma nova classe chamada NativeMethods é suficiente.

No entanto, se você estiver desenvolvendo bibliotecas para uso em outros aplicativos, considere definir duas outras classes chamadas SafeNativeMethods e UnsafeNativeMethods. Essas classes se assemelham à classe NativeMethods; no entanto, elas são marcadas usando um atributo especial chamado SuppressUnmanagedCodeSecurityAttribute. Quando esse atributo é aplicado, o runtime não executa a movimentação completa da pilha para garantir que todos os chamadores tenham a permissão UnmanagedCode. Normalmente, o runtime verifica essa permissão na inicialização. Como a verificação não é executada, ela pode melhorar muito o desempenho das chamadas desses métodos não gerenciados. Também permite que o código, que tem permissões limitadas, chame esses métodos.

No entanto, você deve usar esse atributo com muito cuidado. Haverá sérias implicações de segurança se ele for implementado incorretamente.

Para obter informações sobre como implementar os métodos, confira os exemplos de NativeMethods, SafeNativeMethods e UnsafeNativeMethods.

Quando suprimir avisos

Não suprima um aviso nessa regra.

Exemplo

O exemplo a seguir declara um método que viola essa regra. Para corrigir a violação, o P/Invoke RemoveDirectory deve ser movido para uma classe apropriada projetada para conter apenas P/Invokes.

' 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);
}

Exemplo de NativeMethods

Como a classe NativeMethods não deve ser marcada usando SuppressUnmanagedCodeSecurityAttribute, P/Invokes colocados nela exigirão a permissão UnmanagedCode. Como a maioria dos aplicativos é executada no computador local e com total confiança, isso geralmente não é um problema. No entanto, se você estiver desenvolvendo bibliotecas reutilizáveis, considere definir uma classe SafeNativeMethods ou UnsafeNativeMethods.

O exemplo a seguir mostra um método Interaction.Beep que encapsula a função MessageBeep do user32.dll. O P/Invoke MessageBeep é colocado na classe 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);
}

Exemplo de SafeNativeMethods

Os métodos P/Invoke que podem ser expostos com segurança a qualquer aplicativo e que não têm efeitos colaterais devem ser colocados em uma classe chamada SafeNativeMethods. Você não precisa prestar muita atenção de onde eles são chamados.

O exemplo a seguir mostra uma propriedade Environment.TickCount que encapsula a função GetTickCount do 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();
}

Exemplo de UnsafeNativeMethods

Os métodos P/Invoke que não podem ser chamados com segurança e que podem causar efeitos colaterais devem ser colocados em uma classe chamada UnsafeNativeMethods. Esses métodos devem ser rigorosamente verificados para garantir que eles não sejam expostos ao usuário sem querer.

O exemplo a seguir mostra um método Cursor.Hide que encapsula a função ShowCursor do 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);
}

Confira também