SafeHandle和HandleRef

在阅读了这两个内容后,包括在这个网站上的高投票答案,我仍然觉得这有点不清楚。

由于我对此事的理解可能是错误的,我将首先发布我所知道的概要,以便我可以纠正如果我错了,然后发布我的具体问题:

有时在编写托管代码时,我们必须将地址传递给非托管代码。 这就是IntPtr的用途。 但是,我们尝试确保两个相反的事情:a)保持指针(到一个地址)从GC中存活。 b)在不需要时释放它(即使我们忘记明确地这样做)。

HandleRef执行第一个,SafeHandle执行第二个。 (我实际上是指这里列出的SafeHandle的派生词)。

我的问题:

  1. 显然,我想确认两者。 那么我如何获得function呢? (这是主要问题。)
  2. 从这里和从MSDN (“调用一个托管对象”)看起来只有someObject.Handle可能是GC’d,而一个独立的IntPtr不会。 但IntPtr 本身是管理的!
  3. IntPtr如何在超出范围之前进行GC(如此处所述)?

我认为你把指针( IntPtrvoid* )与句柄(对Windows对象的引用)混淆了。 不幸的是,句柄可以用IntPtr类型表示,这可能会令人困惑。

SafeHandle专门用于处理句柄。 句柄不是指针,而是系统提供的表中的索引(有点 – 它意味着不透明)。 例如, CreateFile函数返回一个HANDLE ,它适合与SafeFileHandle一起使用。 SafeHandle类本身就是Windows句柄的包装器,它将在SafeHandle完成时释放Windows句柄。 因此,只要您想使用句柄,就必须确保保留对SafeHandle对象的引用。

指针只是一个值。 它是内存中对象的地址。 IntPtr是一个structstruct语义将使它通过值传递(也就是说,每次将IntPtr传递给函数时,实际上都是IntPtr的副本)。 除非装箱,否则GC甚至不会知道您的IntPtr

HandleRef文档的重要部分是:

HandleRef构造函数接受两个参数:表示包装器的Object和表示非托管句柄的IntPtr 。 interop编组程序仅将句柄传递给非托管代码,并保证包装器(作为第一个参数传递给HandleRef的构造函数)在调用期间保持活动状态。

我们来看MSDN示例 :

 FileStream fs = new FileStream("HandleRef.txt", FileMode.Open); HandleRef hr = new HandleRef(fs, fs.SafeFileHandle.DangerousGetHandle()); StringBuilder buffer = new StringBuilder(5); int read = 0; // platform invoke will hold reference to HandleRef until call ends LibWrap.ReadFile(hr, buffer, 5, out read, 0); Console.WriteLine("Read with struct parameter: {0}", buffer); LibWrap.ReadFile2(hr, buffer, 5, out read, null); Console.WriteLine("Read with class parameter: {0}", buffer); 

这相当于:

 FileStream fs = new FileStream("HandleRef.txt", FileMode.Open); var hf = fs.SafeFileHandle.DangerousGetHandle(); StringBuilder buffer = new StringBuilder(5); int read = 0; LibWrap.ReadFile(hf, buffer, 5, out read, 0); Console.WriteLine("Read with struct parameter: {0}", buffer); LibWrap.ReadFile2(hf, buffer, 5, out read, null); Console.WriteLine("Read with class parameter: {0}", buffer); // Since we no more have a HandleRef, the following line is needed: GC.KeepAlive(fs); 

但在这种特殊情况下,更好的解决方案是:

 using(FileStream fs = new FileStream("HandleRef.txt", FileMode.Open)) { StringBuilder buffer = new StringBuilder(5); int read = 0; LibWrap.ReadFile(fs.SafeFileHandle, buffer, 5, out read, 0); Console.WriteLine("Read with struct parameter: {0}", buffer); LibWrap.ReadFile2(fs.SafeFileHandle, buffer, 5, out read, null); Console.WriteLine("Read with class parameter: {0}", buffer); } 

总结一下:

  1. 对于句柄,使用SafeHandle并确保它不再SafeHandle ,直到您不再需要它为止,此时您要么让GC收集它,要么显式处理它(通过调用Dispose()方法)。

    对于指针,您确保指向的内存在本机代码可以访问它的整个时间内固定。 您可以使用fixed关键字或固定GCHandle来实现此目的。

  2. IntPtr是一个struct ,如上所述,因此它不是由GC收集的。

  3. 它不是收集的IntPtr ,它是暴露它的HWnd对象,此时不再可以到达并且可由GC收集。 最终确定时,它会处理手柄。

    引用答案的代码是:

     HWnd a = new HWnd(); IntPtr h = a.Handle; // The GC can kick in at this point and collect HWnd, // because it's not referenced after this line. // If it does, HWnd's finalizer could run. // If it runs, HWnd will dispose the handle. // If the handle is disposed, h will hold a freed handle value, // which is invalid. It still has the same numerical value, but // Windows will already have freed the underlying object. // Being a value type, h itself has nothing to do with the GC. // It's not collectable. Think of it like it were an int. B.SendMessage(h, ...); // Adding GC.KeepAlive(a) here solves this issue. 

    对于对象可达性规则,只要对象没有更多可达参考,就认为对象不再使用。 在前面的例子中,就在IntPtr h = a.Handle; line,之后没有其他a变量使用,因此假设此对象不再使用,可以随时释放。 GC.KeepAlive(a)创建了这样的用法,因此对象保持活动状态(由于JIT完成了使用情况跟踪,因此实际情况更为复杂,但这对于此解释来说已经足够了)。


SafeHandle不包括像HandleRef这样的安全措施。 正确?

好问题。 我想P / Invoke编组器会在调用期间保持句柄处于活动状态,但是如果它已经完成,它的拥有对象(如HWnd )仍然可以在调用期间显式处理它。 这是HandleRef提供的安全措施 ,单独使用SafeHandle就不会得到它。 您需要确保句柄所有者(上一个示例中的HWnd )自己保持活动状态。

但是HandleRef的主要目标是包装一个IntPtr ,这是存储句柄值的旧方法。 现在,无论如何, SafeHandleIntPtr适合处理存储。 您只需确保句柄所有者在P / Invoke调用期间不会显式处理句柄。