SafeHandle 和 HandleRef

SafeHandle and HandleRef(SafeHandle 和 HandleRef)

本文介绍了SafeHandle 和 HandleRef的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

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

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

我的问题:

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

解决方案

我认为您将指针(IntPtrvoid*)与句柄(对Windows 对象).不幸的是,句柄可以用 IntPtr 类型表示,这可能会造成混淆.

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

指针只是一个值.它是内存中对象的地址.IntPtr 是一个 struct,而 struct 语义会使其按值传递(即每次传递一个 IntPtr 到一个你实际复制 IntPtr 的函数).除非装箱,否则 GC 甚至不会知道您的 IntPtrs.

HandleRef 文档的重要部分是:

<块引用>

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

让我们以 MSDN 示例为例:

FileStream fs = new FileStream("HandleRef.txt", FileMode.Open);HandleRef hr = new HandleRef(fs, fs.SafeFileHandle.DangerousGetHandle());StringBuilder 缓冲区 = new StringBuilder(5);int 读取 = 0;//平台调用将保持对 HandleRef 的引用,直到调用结束LibWrap.ReadFile(hr, buffer, 5, out read, 0);Console.WriteLine("读取结构参数:{0}", buffer);LibWrap.ReadFile2(hr, buffer, 5, out read, null);Console.WriteLine("读取类参数:{0}", buffer);

这相当于:

FileStream fs = new FileStream("HandleRef.txt", FileMode.Open);var hf = fs.SafeFileHandle.DangerousGetHandle();StringBuilder 缓冲区 = new StringBuilder(5);int 读取 = 0;LibWrap.ReadFile(hf, buffer, 5, out read, 0);Console.WriteLine("读取结构参数:{0}", buffer);LibWrap.ReadFile2(hf, buffer, 5, out read, null);Console.WriteLine("读取类参数:{0}", buffer);//由于我们不再有 HandleRef,因此需要以下行:GC.KeepAlive(fs);

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

using(FileStream fs = new FileStream("HandleRef.txt", FileMode.Open)){StringBuilder 缓冲区 = new StringBuilder(5);int 读取 = 0;LibWrap.ReadFile(fs.SafeFileHandle, buffer, 5, out read, 0);Console.WriteLine("读取结构参数:{0}", buffer);LibWrap.ReadFile2(fs.SafeFileHandle, buffer, 5, out read, null);Console.WriteLine("读取类参数:{0}", buffer);}

总结一下:

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

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

  2. IntPtr 是一个 struct,如上所述,因此它不会被 GC 收集.

  3. 收集的不是 IntPtr,而是暴露它的 HWnd 对象,此时不再可访问,可由 GC 收集.完成后,它会释放句柄.

    参考答案中的代码是:

    HWnd a = new HWnd();IntPtr h = a.Handle;//此时 GC 可以启动并收集 HWnd,//因为在这一行之后没有引用它.//如果是,HWnd 的终结器可以运行.//如果它运行,HWnd 将释放句柄.//如果句柄被释放,h 将持有一个释放的句柄值,//这是无效的.它仍然具有相同的数值,但//Windows 已经释放了底层对象.//作为值类型,h 本身与 GC 无关.//不可收藏.把它想象成一个整数.B.SendMessage(h, ...);//这里添加 GC.KeepAlive(a) 解决了这个问题.

    关于对象可达性规则,一旦不再有对象的可达引用,该对象就被认为不再使用.在前面的例子中,就在 IntPtr h = a.Handle; 行之后,没有其他以后使用 a 变量,因此假设这个对象是 no使用时间更长,可以随时释放.GC.KeepAlive(a) 创建了这样的用法,因此对象保持活动状态(因为使用跟踪是由 JIT 完成的,所以实际情况要复杂一些,但这足以解释这个问题).

<小时><块引用>

SafeHandle 包含像 HandleRef 这样的安全措施.对吗?

好问题.我想 P/Invoke marshaler 将在调用期间保持句柄处于活动状态,但如果它已完成,它的拥有对象(如 HWnd)仍然可以在调用期间显式处理它.这是HandleRef 提供的安全措施,单独使用SafeHandle 是无法做到的.您需要确保句柄所有者(上例中的 HWnd)自己保持活动状态.

HandleRef 的主要目标是包装一个 IntPtr,这是存储句柄值的旧方法.现在,对于句柄存储,SafeHandle 优于 IntPtr.您只需确保句柄所有者不会在 P/Invoke 调用期间显式释放句柄.

After reading about both, including a high voted answer on this site, I still find this a bit unclear.

Since my understanding of the matter might be wrong, I'll first post a synopsis of what I know so I can be corrected if I'm wrong, and then post my specific questions:

Sometimes when coding managed code, we have to pass an address to unmanaged code. That's what an IntPtr is for. However, we try to make sure of two opposite things: a) Keep that pointer (to an address) alive from the GC. b) Release it when it's not needed (even if we forget to do that explicitly).

HandleRef does the first, and SafeHandle the second. (I'm actually referring here to derivations of SafeHandle listed here).

My questions:

  1. Obviously, I want to confirm both. So how do I get that functionality? (This is the main question.)
  2. From here and From MSDN ("call a managed object") it looks like only someObject.Handle might be GC'd, while a free standing IntPtr will not. But an IntPtr itself is managed!
  3. How can an IntPtr be GC'd before it goes out of scope (as mentioned here)?

解决方案

I think you're confusing pointers (IntPtr or void*) with handles (a reference to a Windows object). Unfortunately, handles can be represented with an IntPtr type, which can be confusing.

SafeHandle is for dealing with handles specifically. A handle is not a pointer but an index in a system-provided table (sort of - it's meant to be opaque). For instance, the CreateFile function returns a HANDLE, which would be suitable to use with a SafeFileHandle. The SafeHandle class is itself a wrapper around a Windows handle, it will free the Windows handle when the SafeHandle is finalized. So you have to make sure a reference to the SafeHandle object is kept as long as you want to use the handle.

A pointer is just a value. It's the address of an object in memory. IntPtr is a struct, and the struct semantics will make it be passed by value (that is, every time you pass an IntPtr to a function you actually make a copy of the IntPtr). Unless boxed, the GC won't even know about your IntPtrs.

The important part of the HandleRef docs is this:

The HandleRef constructor takes two parameters: an Object representing the wrapper, and an IntPtr representing the unmanaged handle. The interop marshaler passes only the handle to unmanaged code, and guarantees that the wrapper (passed as the first parameter to the constructor of the HandleRef) remains alive for the duration of the call.

Let's take the 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);

This is equivalent to:

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

But a better solution in this particular case would be:

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

To sum up:

  1. For handles, use SafeHandle and make sure it's reachable until you don't need it anymore, at which point you either let the GC collect it or you dispose it explicitly (by calling the Dispose() method).

    For pointers, you make sure the pointed-to memory is pinned the whole time the native code can access it. You can use the fixed keyword or a pinned GCHandle to achieve this.

  2. IntPtr is a struct, as stated above, so it's not collected by the GC.

  3. It's not the IntPtr that's collected, it's the HWnd object that's exposing it that's no longer reachable at this point and is collectable by the GC. When finalized, it disposes the handle.

    The code from the referenced answer is:

    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.
    

    As for the object reachability rules, an object is considered as no longer used as soon as there's no more reachable references to the object. In the previous example, just after the IntPtr h = a.Handle; line, there is no other later usage of the a variable, therefore it is assumed this object is no longer used and can be freed anytime. GC.KeepAlive(a) creates such an usage, so the object remains alive (the real thing is a bit more involved since usage tracking is done by the JIT but this is good enough for this explanation).


SafeHandle does not include a safety measure like HandleRef. Correct?

Good question. I suppose the P/Invoke marshaler will keep the handle alive for the duration of the call, but its owning object (like HWnd) could still dispose it explicitly during the call if it's finalized. This is the safety measure that HandleRef provides, and you won't get it with SafeHandle alone. You need to ensure the handle owner (HWnd in the previous example) is kept alive yourself.

But the primary goal of HandleRef is to wrap an IntPtr, which is the old method of storing a handle value. Now, SafeHandle is preferred to IntPtr for handle storage anyway. You just have to ensure the handle owner won't dispose the handle explicitly during the P/Invoke call.

这篇关于SafeHandle 和 HandleRef的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!

本文标题为:SafeHandle 和 HandleRef

基础教程推荐