2014-12-07 30 views
4

在阅读了关于这两个,包括在这个网站上的高票表决答案后,我仍然觉得有点不清楚。SafeHandle和HandleRef

因为我的问题的理解可能是错误的,我将首先发布的是什么,我知道这样我就可以,如果我错了,纠正大纲,然后发布我的具体问题:编码时

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

HandleRef做第一个,SafeHandle做第二个。 (我实际上在这里指的是SafeHandle的列表here)。

我的问题:

  1. 很明显,我想确认两者。那么我该如何获得 的功能? (这是最主要的问题。)
  2. here和从MSDN(“呼叫管理对象”),它看起来像只someObject.Handle可能 被GC'd,而独立IntPtr不会。但IntPtr本身 管理!
  3. IntPtr在超出范围之前如何GC'd(如提到的 here)?
+0

'IntPtr'在超出范围之前不能被GC化,但是拥有实际资源的对象可以使指针失效。 – 2014-12-07 21:47:36

+0

@AntonSavin编写一个方法创建一百万个IntPtr并调用它一千次 - 你不会有内存不足的例外。为什么?因为他们是GC'd。没有? – ispiro 2014-12-07 21:49:13

+0

我在谈论不同的事情 - 由IntPtr指向的对象可能不再存在,因为它的所有者已被GC化,但IntPtr可能在此时仍然存在。 – 2014-12-07 21:52:50

回答

4

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

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

指针只是一个值。这是内存中对象的地址。 IntPtrstruct,并且struct语义将使其通过值传递(即每次将IntPtr传递给实际制作IntPtr副本的函数)。除非装箱,GC甚至不知道您的IntPtr s。

HandleRef文档的重要组成部分,是这样的:

HandleRef构造函数有两个参数:一个Object代表的包装,以及代表非托管手柄的IntPtr。互操作封送拆收器仅将句柄传递给非托管代码,并确保在调用期间封装(作为HandleRef的构造函数的第一个参数传递)保持活动状态。

让我们以MSDN example

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); 
} 

综上所述up:

  1. 对于手柄,使用SafeHandle并确保它的到达,直到你不需要它了,此时你要么让GC收集它,或者你处置它明确(通过调用Dispose()方法)。

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

  2. IntPtrstruct,如上所述,所以它不被GC收集。

  3. 这不是收集到的IntPtr,而是HWnd这个对象,暴露了它在此时不再可达并且可被GC收集的对象。完成后,它将处理手柄。

    referenced answer的代码是:

    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;行之后,a变量没有其他用法,因此假定该对象不再使用并且可以随时释放。 GC.KeepAlive(a)创建了这种用法,因此该对象保持活动状态(由于使用跟踪是由JIT完成的,所以实际情况会更复杂一些,但对于此解释来说这已足够好)。


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

好问题。我认为P/Invoke封送拆收器会在调用期间保持句柄活动,但它的拥有对象(如HWnd)仍然可以在调用期间明确地处理它,如果它已完成。这是HandleRef提供的安全措施10,您不会单独使用SafeHandle。您需要确保手柄所有者(上例中的HWnd)保持自己的状态。

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

+0

谢谢。我仍然试图理解答案。但是,为什么你写“h将会有一个无效的价值” - 你不只是说它是按价值传递的?现在收集'a'后,没有什么会改变。它应该仍然有效。 – ispiro 2014-12-07 22:17:50

+0

我是否正确理解对3的回答是,即使GC没有“正式”超出范围,GC看到它们再也不会被访问时,它们可以GC_d了? – ispiro 2014-12-07 22:19:50

+0

'所以你必须确保只要你想使用句柄就保持对SafeHandle对象的引用。 - 所以你说我正确理解SafeHandle并不包含像HandleRef这样的安全措施。正确? – ispiro 2014-12-07 22:22:33

相关问题