一、类SafeHandle的定义
public abstract partial class SafeHandle : CriticalFinalizerObject, IDisposable
该类继承了抽象类CriticalFinalizerObject,并实现了IDisposable接口
二、类SafeHandle中的字段
///SafeHandle类中包装的handle
protected IntPtr handle;
/// 将引用计数和closed/disposed标志组合在一起的一个标志位(so we can atomically modify them)
private volatile int _state;
///我们是否可以释放当前的handle的标记
private readonly bool _ownsHandle;
/// 构造是否完成的标记
private volatile bool _fullyInitialized;
/// _state的位标志的含义
/// 31 2 1 0
/// +-----------------------------------------------------------+---+---+
/// | Ref count | D | C |
/// +-----------------------------------------------------------+---+---+
///D=1表示Dispose已经被执行,C=1表示顶层的handle已经被释放或者马上将被关闭释放
private static class StateBits
{//_state取值的封装类
//获取closed的掩码
public const int Closed = 0b01;//这里的0b表示后面的数值时二进制形式
//获取Disposed的掩码
public const int Disposed = 0b10;
//获取引用计数的掩码
public const int RefCount = unchecked(~0b11); // 2 bits reserved for closed/disposed; ref count gets 30 bits
//表示只有一个引用
public const int RefCountOne = 1 << 2;
}
三、SafeHandle的构造函数
protected SafeHandle(IntPtr invalidHandleValue, bool ownsHandle) {
handle = invalidHandleValue;
//引用计数为1,并且未closed或disposed
_state = StateBits.RefCountOne; // Ref count 1 and not closed or disposed.
_ownsHandle = ownsHandle;
if (!ownsHandle){
//如果不允许释放,则不让GC调用其finalizer
GC.SuppressFinalize(this);
}
_fullyInitialized = true;
}
四、SafeHandle的引用计数增加(AddRef)
以安全的方式(通过cas的方式保证原子操作)来增加引用计数,源码详解如下:
public void DangerousAddRef(ref bool success){
/*
1、为了避免回收安全攻击,我们必须严格保证不能在被释放的handle上调用AddRef。
为了实现这一点,我们保证不会在被标记为closed的handle上新增引用(AddRef),
也不会在一个handle的引用数不为零的情况下将一个Handle标记为closed。为此,
想要做到线程安全我们必须要查看(或更新)引用数和handle状态这两个值的时候
是同一个原子操作,所以我们将这两者存储在同一个整型变量(_state)中,并且
通过Interloced中的cas更新操作修改_state的值。
2、另外,我们必须解决Dispose操作带来的问题,我们必须将设该方法可能会被暴露给
不信任的调用者,并且存在恶意(malicious)的调用者会尝试调用最基本的Realease
来使得引用计数极少为0,然后释放该handle(实际上还有其他人在使用该handle),
为了解决该问题,我们通过只允许一个Dispose对给定的安全句柄进行操作来
(这将平衡创建操作,因为Dispose取消了终结)。我们记录了这样一个事实,
即在ref count和closed状态相同的状态字段中请求了Dispose。
*/
//确保当前SafeHandle初始是成功的
Debug.Assert(_fullyInitialized);
//程序运行到这表示当前handle没有关闭,可以执行AddRef
do{// cas增加引用计数
// First step is to read the current handle state. We use this as a
// basis to decide whether an AddRef is legal and, if so, to propose an
// update predicated on the initial state (a conditional write).
// Check for closed state.
oldState = _state;//第一次获取handle的状态_state
if ((oldState & StateBits.Closed) != 0){
//如果已经关闭则直接抛出异常
throw new ObjectDisposedException(nameof(SafeHandle), SR.ObjectDisposed_SafeHandleClosed);
}
newState = oldState + StateBits.RefCountOne;//引用计数加1
//while中表示原子更新_state的值
} while (Interlocked.CompareExchange(ref _state, newState, oldState) != oldState);
//程序运行到这里表示我们成功完成了AddRef操作
success = true;
}
// Used by internal callers to avoid declaring a bool to pass by ref
internal void DangerousAddRef(){
bool success = false;
DangerousAddRef(ref success);
}
五、SafeHandle的Dispose()和引用计数减少(Release)
SafeHandle的Dispose的内部是通过最终调用InternalRelease(disposeOrFinalizeOperation: false)完成的,源码详解如下:
public void Dispose(){
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing){
Debug.Assert(_fullyInitialized);
//不管传输的disposing是什么,都调用InternalRelease(true)
InternalRelease(disposeOrFinalizeOperation: true);
}
public void DangerousRelease() => InternalRelease(disposeOrFinalizeOperation: false);
private void InternalRelease(bool disposeOrFinalizeOperation){
Debug.Assert(_fullyInitialized || disposeOrFinalizeOperation);
//减少引用计数,如果减少引用计数为0,则同时设置closed标志
bool performRelease = false;
int oldState, newState;
do{//cas减少引用计数
oldState = _state;//第一次获取handle的状态_state
// If this is a Dispose operation we have additional requirements (to
// ensure that Dispose happens at most once as the comments in AddRef
// detail). We must check that the dispose bit is not set in the old
// state and, in the case of successful state update, leave the disposed
// bit set. Silently do nothing if Dispose has already been called.
if (disposeOrFinalizeOperation && ((oldState & StateBits.Disposed) != 0)){
//需要释放并且当前handle已标记为Disposed则直接返回
return;
}
//引用计数不可能为0,但是可能出现引用计数不为0但是被标记为closed的情况(调用了SetHandleAsInvalid)
if ((oldState & StateBits.RefCount) == 0){
//如果引用计数已为零直接抛出异常
throw new ObjectDisposedException(nameof(SafeHandle), SR.ObjectDisposed_SafeHandleClosed);
}
// If we're proposing a decrement to zero and the handle is not closed
// and we own the handle then we need to release the handle upon a
// successful state update. If so we need to check whether the handle is
// currently invalid by asking the SafeHandle subclass. We must do this before
// transitioning the handle to closed, however, since setting the closed
// state will cause IsInvalid to always return true.
//如果_state同时满足以下4个条件,则可以执行release操作(performRelease = true):
//1、计数值为1;2、未被标记为Closed;3、可以释放handle( _ownsHandle=true);4、并且当前handle未失效
performRelease = ((oldState & (StateBits.RefCount | StateBits.Closed)) == StateBits.RefCountOne) &&
_ownsHandle &&
!IsInvalid;
// Attempt the update to the new state, fail and retry if the initial
// state has been modified in the meantime. Decrement the ref count by
// substracting StateBits.RefCountOne from the state then OR in the bits for
// Dispose (if that's the reason for the Release) and closed (if the
// initial ref count was 1).
newState = oldState - StateBits.RefCountOne;//引用计数减一
if ((oldState & StateBits.RefCount) == StateBits.RefCountOne){
//如果计数器减一之前的计数为1,则在新的_state中设置closed标志位置
newState |= StateBits.Closed;
}
if (disposeOrFinalizeOperation){
//如果需要释放,则设置disposed标志位
newState |= StateBits.Disposed;
}
} while (Interlocked.CompareExchange(ref _state, newState, oldState) != oldState);
// If we get here we successfully decremented the ref count. Additionally we
// may have decremented it to zero and set the handle state as closed. In
// this case (providng we own the handle) we will call the ReleaseHandle
// method on the SafeHandle subclass.
if (performRelease){
//如果performRelease为true则执行ReleaseHandle()
int lastError = Marshal.GetLastPInvokeError();
ReleaseHandle();//该方法是一个抽象方法
Marshal.SetLastPInvokeError(lastError);
}
}
//该方法是一个抽象方法,子类需要使用SafeHandle时需要重写该方法
protected abstract bool ReleaseHandle();
如果一个类希望使用SafeHandle提供的方法,则需要重写ReleaseHandle方法。另外,如果SetHandleAsInvalid可能会使得_state中的引用计数不为0但是被标记为closed的情况。
public void SetHandleAsInvalid(){
Debug.Assert(_fullyInitialized);
// Set closed state (low order bit of the _state field).
//在_state中设置closed标志位,该操作可能会使得_state的计数不为0,但是closed标志位为1
Interlocked.Or(ref _state, StateBits.Closed);
GC.SuppressFinalize(this);
}