Использование ограниченных областей исполнения
У меня есть приложение 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);
}
Спасибо, Пол
3 ответа
Вы делаете несколько вещей здесь.
Выполните код в блоке finally, чтобы предотвратить исключения ThreadAbortExceptions во время выполнения безопасного кода.
Перед трюком try/finally вы вызываете PrepareConstrainedRegions, который практически ничего не делает, кроме проверки наличия достаточного пространства стека потоков, чтобы быть уверенным, что по крайней мере некоторые вызовы методов могут быть выполнены, чтобы ваш безопасный код не застал врасплох StackOverFlowException.
Так что да, ваш код выглядит максимально безопасным. В официальном документе о CER указано, что CLR также распознает эти блоки try/finally и принимает дополнительные меры. Из того, что я видел, нет большой разницы, за исключением того, что исключения OutOfMemoryException также задерживаются после выполнения кода CER.
Чтобы быть уверенным, что ваш код соответствует вашим ожиданиям, вы должны создать тесты для этих вещей.
- Истощение стека
- Недостаточно памяти
- Thread.Abort
Написание надежного кода действительно сложно, и даже большинство классов BCL не защищены от таких вещей, как объясняет Джо Даффи. Даже если ваш код не выходит из строя, код BCL может. Вы не получите большого дополнительного преимущества, пока большая часть кода BCL не сможет справиться с этими экстремальными условиями четко определенным образом.
С уважением, Алоис Краус
Действительно безопасный способ справиться с этим - передать 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.
Я не понимаю, как у вас могут возникнуть какие-либо проблемы, если только вы не генерируете исключения внутри try
блок.
- Код внутри
finally
раздел атомный? - Есть ли
NativeMethods.Foo()
есть ли вероятность утечки памяти или прерывания потока?