CA1060: переместите P/Invokes в класс NativeMethods

Свойство Значение
Идентификатор правила CA1060
Заголовок Переместите методы P/Invoke в класс NativeMethods
Категория Проектирование
Исправление является критическим или не критическим Критическое
Включен по умолчанию в .NET 8 No

Причина

Метод использует службы вызова платформы для доступа к неуправляемому коду и не является членом одного из классов NativeMethods.

Описание правила

Методы PInvoke, например методы, помеченные атрибутом System.Runtime.InteropServices.DllImportAttribute, или методы, определенные с помощью ключевого слова Declare в Visual Basic, обращаются к неуправляемому коду. Эти методы должны находиться в одном из следующих классов:

  • NativeMethods — этот класс не подавляет обход стека для разрешения неуправляемого кода. (System.Security.SuppressUnmanagedCodeSecurityAttribute не следует применять к этому классу.) Этот класс предназначен для методов, которые можно использовать в любом месте, так как будет выполнен обход стека.

  • SafeNativeMethods — этот класс подавляет обход стека для разрешения неуправляемого кода. (System.Security.SuppressUnmanagedCodeSecurityAttribute применяется к этому классу.) Этот класс предназначен для методов, которые являются надежными для любого вызова. Вызывающим объектам этих методов не требуется выполнять полную проверку безопасности, чтобы обеспечить безопасность использования, так как методы являются безопасными для любого вызывающего объекта.

  • UnsafeNativeMethods — этот класс подавляет обход стека для разрешения неуправляемого кода. (System.Security.SuppressUnmanagedCodeSecurityAttribute применяется к этому классу.) Этот класс предназначен для методов, которые являются потенциально опасными. Любой вызывающий объект этих методов должен выполнить полную проверку безопасности, чтобы обеспечить безопасность использования, так как обход стека не будет выполнен.

Эти классы объявляются как internal (Friend в Visual Basic) и объявляют закрытый конструктор, чтобы предотвратить создание новых экземпляров. Методы в этих классах должны быть static и internal (Shared и Friend в Visual Basic).

Устранение нарушений

Чтобы устранить нарушение этого правила, переместите метод в соответствующий класс NativeMethods. Для большинства приложений достаточно перемещения P/Invoke в новый класс с именем NativeMethods.

Однако при разработке библиотек для использования в других приложениях следует рассмотреть возможность определения двух других классов, которые называются SafeNativeMethods и UnsafeNativeMethods. Эти классы похожи на класс NativeMethods. Однако они помечаются с помощью специального атрибута с именем SuppressUnmanagedCodeSecurityAttribute. При применении этого атрибута среда выполнения не выполняет полный обход стека, чтобы убедиться, что все вызывающие объекты имеют разрешение UnmanagedCode. Среда выполнения обычно проверяет наличие этого разрешения при запуске. Так как проверка не выполняется, это может значительно повысить производительность вызовов этих неуправляемых методов. Он также позволяет коду с ограниченными разрешениями вызывать эти методы.

Однако этот атрибут следует использовать с большой осторожностью. Он может иметь серьезные последствия для безопасности, если он реализован неправильно.

Сведения о том, как реализовать методы, см. в примере NativeMethods, примере SafeNativeMethods и примере UnsafeNativeMethods.

Когда лучше отключить предупреждения

Для этого правила отключать вывод предупреждений не следует.

Пример

В следующем примере объявляется метод, нарушающий это правило. Чтобы устранить нарушение, RemoveDirectory P/Invoke следует переместить в соответствующий класс, предназначенный для хранения только 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);
}

Пример NativeMethods

Так как класс NativeMethods не должен быть помечен с помощью атрибута SuppressUnmanagedCodeSecurityAttribute, P/Invoke, которые помещаются в него, потребует разрешения UnmanagedCode. Поскольку большинство приложений выполняются с локального компьютера и работают совместно с полным доверием, обычно это не проблема. Однако при разработке многократно используемых библиотек следует рассмотреть возможность определения класса SafeNativeMethods или UnsafeNativeMethods.

В следующем примере показан метод Interaction.Beep, который создает оболочку для функции MessageBeep из user32.dll. MessageBeep P/Invoke помещается в класс 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);
}

Пример SafeNativeMethods

Методы P/Invoke, которые могут быть безопасно предоставлены для любого приложения и не имеют побочных эффектов, должны быть размещены в классе с именем SafeNativeMethods. Вам не нужно уделять много внимания тому, откуда они вызываются.

В следующем примере показано свойство Environment. TickCount, которое служит оболочкой для функции GetTickCount из 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();
}

Пример UnsafeNativeMethods

Методы P/Invoke, которые не могут быть безопасно вызваны и могут вызвать побочные эффекты, должны быть размещены в классе с именем UnsafeNativeMethods. Эти методы должны быть тщательно проверены, чтобы исключить случайно предоставление доступа пользователю.

В следующем примере показан метод Cursor.Hide, который заключает в оболочку функцию ShowCursor из 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);
}

См. также