CA1060: P/Invoke を NativeMethods クラスに移動します

プロパティ
ルール ID CA1060
Title 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) として宣言し、新しいインスタンスが作成されないようにプライベート コンストラクターを宣言します。 これらのクラスのメソッドは、static および internal (Visual Basic では Shared および Friend) である必要があります。

違反の修正方法

この規則の違反を修正するには、メソッドを適切な NativeMethods クラスに移動します。 ほとんどのアプリケーションでは、P/Invoke を NativeMethods という名前の新しいクラスに移動するだけで十分です。

ただし、他のアプリケーションで使用するためのライブラリを開発している場合は、SafeNativeMethods および UnsafeNativeMethods という 2 つのクラスを定義することを検討する必要があります。 これらのクラスは 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 クラスを定義することを検討する必要があります。

次の例では、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);
}

関連項目