using constrained execute regions

感情迁移 提交于 2019-12-12 13:38:36

问题


I have a Visual Studio 2008 C# .NET 3.5 application that P/Invokes a native method that accepts a file handle as a parameter. Originally, I was just using FileStream.SafeFileHandle.DangerousGetHandle() to get the file handle. But, after turning on FX COP, I got a CA2001 warning about that. So, after a little research I discovered "Constrained execute regions". This is new to me and I haven't seen a whole lot of information about it. I was hoping somebody more experienced could take a look and verify that I've done this correctly.

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

Thanks, PaulH


回答1:


You are doing several things here.

  1. Execute code in the finally block to prevent ThreadAbortExceptions while your safe code is executing.

  2. Before the try/finally trick you call PrepareConstrainedRegions which does basically nothing except to check that enough thread stack space is present to be sure that at least some method calls can be made so your safe code is not caught off guard by a StackOverFlowException.

So yes you code looks as safe as it can get. In the official docu about CERs there is stated that the CLR does recognize this try/finally blocks as well and takes additional measures. From what I have seen there is not much difference except that OutOfMemoryExceptions are also delayed after your CER code has run.

To be really sure that your code meets your expectations you should create tests for these things.

  • Stack Exhaustion
  • Out Of Memory
  • Thread.Abort

Writing reliable code is really hard and even most of the BCL classes are not hardened against such things as Joe Duffy explains. Even if your code does not fail the BCL code can. You will not get much added benefit until a major part of the BCL code can cope with these extreme conditions in a well defined manner.

Yours, Alois Kraus




回答2:


The truely safe way to handle it is to pass the SafeHandle in place of the IntPtr reference - the P/Invoke layer is SafeHandle-aware and will make this work for you automatically. The only exception to this is when you are calling the native API to close your handle, since the SafeHandle becomes disposed while you are using it.

For instance:

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

However, this may not be possible if the SafeHandle implementation is being passed as a parameter in a struct, or if the underlying handle is more than an IntPtr. For instance, the Win32 SSPI api uses handles that are two IntPtrs. To deal with that situation, you have to do the CER manually.

Your CER usage is incorrect. DangerousAddRef can still fail. The following is the pattern used by Microsoft in their .Net source:

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

You can see this pattern in effect in the Microsoft Reference Source- See _SafeNetHandle.cs, line 2071.




回答3:


I don't see how you could have any problems at all, unless you generate exceptions inside the try block.

  • Is the code inside the finally section atomic?
  • Does NativeMethods.Foo() have any chance of leaking memory or aborting a thread?


来源:https://stackoverflow.com/questions/5330799/using-constrained-execute-regions

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!