Действительно безопасный способ справиться с этим - передать 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.