использование ограниченных областей исполнения - PullRequest
3 голосов
/ 16 марта 2011

У меня есть приложение Visual Studio 2008 C # .NET 3.5, которое P / вызывает собственный метод, который принимает дескриптор файла в качестве параметра.Первоначально я просто использовал 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 ]

6 голосов
/ 16 марта 2011

Здесь вы делаете несколько вещей.

  1. Выполните код в блоке finally, чтобы исключить исключения ThreadAbortException во время выполнения вашего безопасного кода.

  2. Перед трюком try / finally вы вызываете PrepareConstrainedRegions, который практически ничего не делает, кроме проверки наличия достаточного пространства стека потоков, чтобы быть уверенным, что по крайней мере некоторые вызовы методов могут быть выполнены, чтобы ваш безопасный код не застал врасплох StackOverFlowException.

Так что да, ваш код выглядит максимально безопасным.В официальном документе о CER указано, что CLR также распознает блоки try / finally и принимает дополнительные меры.Из того, что я видел, нет большой разницы, за исключением того, что исключения OutOfMemoryException также задерживаются после выполнения кода CER.

Чтобы быть уверенным, что ваш код соответствует вашим ожиданиям, вы должны создать тесты для этих вещей.

  • Исчерпание стека
  • Недостаточно памяти
  • Thread.Abort

Написание надежного кода действительно сложно, и даже большая часть BCLклассы не закалены против таких вещей, как объясняет Джо Даффи.Даже если ваш код не выходит из строя, код BCL может.Вы не получите много дополнительных преимуществ, пока большая часть кода BCL не сможет четко справиться с этими экстремальными условиями.

С уважением, Алоис Краус

2 голосов
/ 24 июня 2014

Действительно безопасный способ справиться с этим - передать 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. Например, API-интерфейс Win32 SSPI использует дескрипторы, которые являются двумя 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.

0 голосов
/ 16 марта 2011

Я не вижу, как у вас могут возникнуть какие-либо проблемы, если только вы не генерируете исключения внутри блока try.

  • Является ли код в секции finally атомарным?
  • Есть ли у NativeMethods.Foo() шансы утечки памяти или прерывания потока?
...