使用约束执行区域

时间:2011-03-16 19:37:41

标签: c# pinvoke handle cer

我有一个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);
}

谢谢, PaulH

3 个答案:

答案 0 :(得分:6)

你在这里做了几件事。

  1. 在您执行安全代码时,执行finally块中的代码以防止ThreadAbortExceptions。

  2. 在try / finally技巧之前你调用PrepareConstrainedRegions,除了检查是否存在足够的线程堆栈空间以确保至少可以进行一些方法调用以确保您的安全代码没有措手不及通过StackOverFlowException。

  3. 所以是的,你的代码看起来很安全。在有关CERs的官方docu中,有人说CLR确实识别了这个try / finally块并采取了额外的措施。从我所看到的情况来看,除了在运行CER代码后OutOfMemoryExceptions也被延迟之外没有太大区别。

    要确定您的代码符合您的期望,您应该为这些事情创建测试。

    • Stack Exhaustion
    • 内存不足
    • Thread.Abort

    编写可靠的代码非常困难,甚至大多数BCL类都不是hardened反对Joe Duffy解释的事情。即使您的代码没有失败,BCL代码也可以。在BCL代码的主要部分能够以明确定义的方式应对这些极端条件之前,您不会获得太多额外的好处。

    此致,    Alois Kraus

答案 1 :(得分:2)

处理它的真正安全的方法是传递SafeHandle代替IntPtr引用 - 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使用两个IntPtrs的句柄。要处理这种情况,您必须手动执行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行。

答案 2 :(得分:0)

除非您在try块内生成异常,否则我看不出您有任何问题。

  • finally部分内的代码是原子?
  • NativeMethods.Foo()是否有泄漏内存或中止线程的可能性?