3

我有一个 Visual Studio 2008 C# .NET 3.5 应用程序,它 P/Invokes 一个接受文件句柄作为参数的本机方法。最初,我只是使用 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);
}

谢谢,保罗

4

3 回答 3

7

你在这里做几件事。

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

  2. 在 try/finally 技巧之前,您调用 PrepareConstrainedRegions,它基本上什么都不做,除了检查是否存在足够的线程堆栈空间以确保至少可以进行一些方法调用,这样您的安全代码就不会被 StackOverFlowException 措手不及。

所以是的,你的代码看起来尽可能安全。在关于 CER 的官方文档中指出,CLR 确实也承认了这个 try/finally 块并采取了额外的措施。从我所看到的情况来看,除了 OutOfMemoryExceptions 在您的 CER 代码运行后也会延迟之外,没有太大区别。

要真正确保您的代码符合您的期望,您应该为这些东西创建测试。

  • 堆栈耗尽
  • 内存不足
  • 线程中止

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

你的,阿洛伊斯克劳斯

于 2011-03-16T20:45:22.337 回答
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 行。

于 2014-06-24T15:30:32.757 回答
0

我看不出你怎么会遇到任何问题,除非你在try块内生成异常。

  • 部分内的代码是finally原子的吗?
  • 是否NativeMethods.Foo()有可能泄漏内存或中止线程?
于 2011-03-16T20:24:40.987 回答