C# SafeHandle详解

一、类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);
} 
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值