问题
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.
Execute code in the finally block to prevent ThreadAbortExceptions while your safe code is executing.
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