SafeHandle和HandleRef

时间:2014-12-07 21:35:26

标签: c# .net garbage-collection unmanaged

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

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

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

HandleRef执行第一个操作,SafeHandle执行第二个操作。 (我实际上在这里指的是SafeHandle列出here)的推导。

我的问题:

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

1 个答案:

答案 0 :(得分:6)

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

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

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

HandleRef文档的重要部分是:

  

HandleRef构造函数有两个参数:表示包装器的Object和表示非托管句柄的IntPtr。 interop编组程序仅将句柄传递给非托管代码,并保证包装器(作为第一个参数传递给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);
}

总结一下:

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

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

  2. 如上所述,
  3. GCHandleIntPtr,因此GC不会收集它。

  4. 这不是收集的struct,而是IntPtr对象暴露它,此时不再可以访问,并且可由GC收集。完成后,它会处理句柄。

    referenced answer的代码是:

    HWnd

    对于对象可达性规则,只要对象没有更多可达参考,就认为对象不再使用。在前面的示例中,在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创建了这样的用法,因此对象保持活动状态(由于JIT完成了使用情况跟踪,因此实际情况会更复杂一些,但这对于此解释来说已经足够了。)


  5.   

    SafeHandle 包含像HandleRef这样的安全措施。正确的吗?

    好问题。我想P / Invoke封送程序会在调用期间保持句柄处于活动状态,但是如果它已经完成,它的拥有对象(如GC.KeepAlive(a))仍然可以在调用期间显式处理它。这是HWnd提供的安全措施,您无法单独使用HandleRef。您需要确保句柄所有者(前一个示例中的SafeHandle)自己保持活动状态。

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