如何正确使用Marshal.InitHandle,与SafeHandle子类构造函数有何区别
Marshal.InitHandle的存在意义解答
System.Runtime.InteropServices.Marshal类包含名为InitHandle的方法,根据官方文档说明,该方法的作用是初始化SafeHandle对象的底层句柄。
但该方法的存在意义是什么?难道不能直接使用SafeHandle子类的构造函数来同样轻松地实现该操作吗?
我们日常自定义SafeHandle子类时,确实多数场景可以直接在构造函数内完成句柄赋值,InitHandle不是给普通业务代码日常使用的API,它的存在主要是为了覆盖以下特殊场景:
统一的框架层句柄赋值入口
CLR的P/Invoke封送层、基础类库代码需要一个不依赖具体SafeHandle子类实现的通用句柄初始化方式:不同开发者写的自定义SafeHandle子类构造函数签名千差万别,框架不可能适配所有构造逻辑。而InitHandle是Marshal类提供的标准公共API,不管子类怎么实现,都可以通过这个方法安全给SafeHandle填充原生句柄值。最典型的场景就是当你把SafeHandle作为P/Invoke的输出参数时,底层封送器内部就是调用InitHandle来完成句柄赋值的。原子安全的延迟初始化
部分场景下你无法在SafeHandle实例构造时就拿到有效句柄:比如需要先创建实例占位,后续执行某个异步操作、原生调用后才能获取到实际句柄值。
这时候如果直接调用SafeHandle.SetHandle方法赋值,有两个风险:一是SetHandle不是原子操作,多线程场景下可能出现竞争;二是SetHandle可以直接覆盖已经生效的有效句柄,很容易导致原句柄未被释放造成资源泄漏。而InitHandle是原子操作,只会在当前SafeHandle的句柄还是无效初始值的时候才会赋值,完全避免了上述风险。降低构造阶段的资源泄漏风险
如果你在SafeHandle子类的构造函数中直接调用原生API获取句柄,一旦构造函数执行中途抛出异常,部分初始化的对象可能无法正确走资源释放逻辑,导致原生句柄泄漏。而如果先构造一个持无效句柄的SafeHandle实例,等成功拿到原生句柄后再调用InitHandle完成初始化,就可以完全规避这个问题。
内容的提问来源于stack exchange,提问作者henke37

