3

ファイル ハンドルをパラメーターとして受け入れるネイティブ メソッドを P/Invokes する Visual Studio 2008 C# .NET 3.5 アプリケーションがあります。もともと、私は FileStream.SafeFileHandle.DangerousGetHandle() を使用してファイル ハンドルを取得していました。しかし、FX COP をオンにした後、CA2001 に関する警告が表示されました。それで、少し調べたところ、「制約付き実行領域」を発見しました。これは私にとって新しいことであり、それに関する多くの情報を見たことがありません。私は、より経験豊富な誰かが見て、これを正しく行ったことを確認できることを望んでいました.

class MyClass
{
    public static bool Write(string filename)
    {
        using (var fs = new System.IO.FileStream(filename, 
            System.IO.FileMode.Create, 
            System.IO.FileAccess.Write, 
            System.IO.FileShare.None))
        {
            bool got_handle;
            bool result;

            System.Runtime.CompilerServices.RuntimeHelpers.PrepareConstrainedRegions();
            try { }
            finally
            {
                fs.SafeFileHandle.DangerousAddRef(ref got_handle);
                result = NativeMethods.Foo(fs.SafeFileHandle.DangerousGetHandle());
                if (got_handle)
                    fs.SafeFileHandle.DangerousRelease();   
            }

            return result;
        }
    }
}

internal sealed class NativeMethods
{
    [DllImport("mylib.dll",
        EntryPoint = "Foo",
        CallingConvention = CallingConvention.StdCall,
        CharSet = CharSet.Unicode,
        ExactSpelling = true, 
        SetLastError = true)]
    public static extern bool Foo(IntPtr hFile);
}

ありがとう、ポールH

4

3 に答える 3

7

あなたはここでいくつかのことをしています。

  1. 安全なコードの実行中に ThreadAbortExceptions が発生しないように、finally ブロックでコードを実行します。

  2. try/finally トリックの前に、PrepareConstrainedRegions を呼び出します。これは、安全なコードが StackOverFlowException によって不意を突かれないように、少なくともいくつかのメソッド呼び出しを行うことができるように、十分なスレッド スタック スペースが存在することを確認すること以外は基本的に何もしません。

そうです、あなたのコードは可能な限り安全に見えます。CERに関する公式ドキュメントには、CLR がこの try/finally ブロックも認識し、追加の対策を講じると記載されています。私が見たところ、CER コードの実行後に OutOfMemoryExceptions も遅延することを除いて、大きな違いはありません。

コードが期待どおりであることを本当に確認するには、これらのテストを作成する必要があります。

  • スタックの枯渇
  • メモリ不足
  • Thread.Abort

信頼性の高いコードを書くことは非常に難しく、BCL クラスのほとんどでさえ、Joe Duffy が説明するようなことに対して強化されていません。コードが失敗しなくても、BCL コードは失敗する可能性があります。BCL コードの大部分が明確に定義された方法でこれらの極端な条件に対処できるようになるまで、追加の利点は得られません。

さようなら、アロイス・クラウス

于 2011-03-16T20:45:22.337 に答える
2

これを処理する真に安全な方法は、IntPtr 参照の代わりに SafeHandle を渡すことです。P/Invoke レイヤーは SafeHandle を認識しており、これが自動的に機能します。これに対する唯一の例外は、ネイティブ API を呼び出してハンドルを閉じる場合です。これは、使用中に SafeHandle が破棄されるためです。

例えば:

[DllImport( "qwave.dll", CallingConvention = CallingConvention.Winapi, SetLastError = true )]
internal static extern bool QOSCreateHandle( ref QosVersion version, out QosSafeHandle qosHandle );

[DllImport( "qwave.dll", CallingConvention = CallingConvention.Winapi, SetLastError = true )]
[ReliabilityContract( Consistency.WillNotCorruptState, Cer.Success )]
internal static extern bool QOSCloseHandle( IntPtr qosHandle );

[DllImport( "qwave.dll", CallingConvention = CallingConvention.Winapi, SetLastError = true )]
internal static extern bool QOSAddSocketToFlow(
    QosSafeHandle qosHandle,
    IntPtr socket,
    byte[] destAddr,
    QosTrafficType trafficType,
    QosFlowFlags flags,
    ref uint flowId
);


/// <summary>
/// Safely stores a handle to the QWave QoS win32 API and ensures the handle is properly 
/// closed when all references to the handle have been garbage collected.
/// </summary>
public class QosSafeHandle : SafeHandle
{
    /// <summary>
    /// Initializes a new instance of the QosSafeHandle class.
    /// </summary>
    public QosSafeHandle() :
        base( IntPtr.Zero, true )
    {
    }

    /// <summary>
    /// Whether or not the handle is invalid.
    /// </summary>
    public override bool IsInvalid
    {
        get { return this.handle == IntPtr.Zero; }
    }

    /// <summary>
    /// Releases the Qos API instance handle.
    /// </summary>
    /// <returns></returns>
    protected override bool ReleaseHandle()
    {
        QosNativeMethods.QOSCloseHandle( this.handle );
        return true;
    }
}

ただし、SafeHandle の実装が構造体のパラメーターとして渡されている場合、または基になるハンドルが IntPtr を超える場合、これは不可能な場合があります。たとえば、Win32 SSPI API は、2 つの IntPtr であるハンドルを使用します。そのような状況に対処するには、CER を手動で実行する必要があります。

CER の使用法が正しくありません。DangerousAddRefまだ失敗する可能性があります。以下は、Microsoft が .Net ソースで使用するパターンです。

public static bool Write( string filename )
{
    using( var fs = new System.IO.FileStream( filename,
        System.IO.FileMode.Create,
        System.IO.FileAccess.Write,
        System.IO.FileShare.None ) )
    {
        bool got_handle;
        bool result;

        // The CER is here to ensure that reference counting on fs.SafeFileHandle is never
        // corrupted. 
        RuntimeHelpers.PrepareConstrainedRegions();
        try
        {
            fs.SafeFileHandle.DangerousAddRef( ref got_handle );
        }
        catch( Exception e )
        {
            if( got_handle )
            {
                fs.SafeFileHandle.DangerousRelease();
            }

            got_handle = false;

            throw;
        }
        finally
        {
            if( got_handle )
            {
                result = NativeMethods.Foo( fs.SafeFileHandle.DangerousGetHandle() );

                fs.SafeFileHandle.DangerousRelease();
            }
        }

        return result;
    }
}

このパターンは、Microsoft リファレンス ソースで実際に確認できます。_SafeNetHandle.csの 2071 行を参照してください。

于 2014-06-24T15:30:32.757 に答える
0

tryブロック内で例外を生成しない限り、問題が発生する可能性はまったくありません。

  • セクション内のコードはfinallyアトミックですか?
  • NativeMethods.Foo()メモリリークやスレッドの中止の可能性はありますか?
于 2011-03-16T20:24:40.987 に答える