CA1060:将 P/Invoke 移动到 NativeMethods 类

属性
规则 ID CA1060
标题 将 P/Invoke 移动到 NativeMethods 类
类别 设计
修复是中断修复还是非中断修复 重大
在 .NET 8 中默认启用

原因

方法使用平台调用服务访问非托管代码,不是 NativeMethods 类之一的成员。

规则说明

平台调用方法(如使用 System.Runtime.InteropServices.DllImportAttribute 属性标记的方法)或在 Visual Basic 中使用 Declare 关键字定义的方法可访问非托管代码。 这些方法应是处于以下一个类中:

  • NativeMethods - 此类不会对非托管代码权限取消堆栈审核。 (System.Security.SuppressUnmanagedCodeSecurityAttribute 不得应用于此类。)此类用于可在任何位置使用的方法,因为会执行堆栈审核。

  • SafeNativeMethods - 此类会对非托管代码权限取消堆栈审核。 (System.Security.SuppressUnmanagedCodeSecurityAttribute 应用于此类。)此类用于可供任何人安全调用的方法。 这些方法的调用方不需要执行完整安全评审以确保使用是安全的,因为这些方法对于任何调用方都无害。

  • UnsafeNativeMethods - 此类会对非托管代码权限取消堆栈审核。 (System.Security.SuppressUnmanagedCodeSecurityAttribute 应用于此类。)此类用于有潜在危险的方法。 这些方法的任何调用方都必须执行完整安全检查,以确保使用是安全的,因为不会执行任何堆栈审核。

这些类声明为 internal(在 Visual Basic 中为 Friend),并声明一个私有构造函数来阻止创建新实例。 这些类中的方法应是 staticinternal(在在 Visual Basic 中是 SharedFriend)。

如何解决冲突

若要解决与此规则的冲突,请将方法移动到合适的 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 方法,它可包装来自 user32.dll 的 MessageBeep 函数 。 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 属性,它可包装来自 kernel32.dll 的 GetTickCount 函数 。

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 方法,它可包装来自 user32.dll 的 ShowCursor 函数 。

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

另请参阅