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)로 선언되고 private 생성자를 선언하여 새 인스턴스가 생성되지 않게 합니다. 이러한 클래스의 메서드는 staticinternal(Visual Basic에서 SharedFriend)이어야 합니다.

위반 문제를 해결하는 방법

이러한 규칙 위반 문제를 해결하려면 메서드를 적절한 NativeMethods 클래스로 이동합니다. 대부분의 애플리케이션에서는 P/Invoke를 NativeMethods라는 새 클래스로 이동하기만 해도 됩니다.

그러나 다른 애플리케이션에서 사용할 라이브러리를 개발하는 경우 SafeNativeMethodsUnsafeNativeMethods라는 두 개의 다른 클래스를 정의하는 것이 좋습니다. 이러한 클래스는 NativeMethods 클래스와 유사합니다. 그러나 SuppressUnmanagedCodeSecurityAttribute라는 특수 특성을 사용하여 표시됩니다. 이러한 특성이 적용되면 런타임은 모든 호출자에게 UnmanagedCode 권한이 있는지 확인하기 위해 전체 스택 워크를 수행하지 않습니다. 런타임은 일반적으로 시작할 때 해당 권한을 확인합니다. 검사 수행되지 않으므로 관리되지 않는 메서드에 대한 호출의 성능이 크게 향상될 수 있습니다. 또한 이러한 메서드를 호출할 수 있는 권한이 제한된 코드를 사용할 수 있습니다.

그러나 해당 특성은 매우 주의해서 사용해야 합니다. 잘못 구현된 경우 심각한 보안 영향을 미칠 수 있습니다.

메서드를 구현하는 방법에 대한 자세한 내용은 NativeMethods 예제, SafeNativeMethods 예제 및 UnsafeNativeMethods 예를 참조하세요.

경고를 표시하지 않는 경우

이 규칙에서는 경고를 표시해야 합니다.

예시

다음 예제에서는 이 규칙을 위반하는 메서드를 선언합니다. 위반 문제를 해결하기 위해 P/Invoke만 포함하도록 고안된 적절한 클래스로 RemoveDirectory 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 클래스를 정의하는 것이 좋습니다.

다음 예제에서는 user32.dll에서 MessageBeep 함수를 래핑하는 Interaction.Beep 메서드를 보여 줍니다. 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라는 클래스에 배치해야 합니다. 당신은 그들이 어디에서 호출되는지에 많은 관심을 지불 할 필요가 없습니다.

다음 예제에서는 kernel32.dll에서 GetTickCount 함수를 래핑하는 Environment.TickCount 속성을 보여 줍니다.

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라는 클래스에 배치해야 합니다. 이러한 메서드는 사용자에게 실수로 노출되지 않도록 엄격하게 확인해야 합니다.

다음 예제에서는 user32.dll에서 ShowCursor 함수를 래핑하는 Cursor.Hide 메서드를 보여 줍니다.

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

참고 항목