首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >SafeHandle和HandleRef

SafeHandle和HandleRef
EN

Stack Overflow用户
提问于 2014-12-07 21:35:25
回答 1查看 2.8K关注 0票数 7

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

由于我对这件事的理解可能是错误的,我将首先发布我所知道的内容的概要,这样如果我错了,我就可以得到纠正,然后发布我的具体问题:

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

HandleRef做第一个,SafeHandle做第二个。(这里我实际上指的是SafeHandle listed 这里的派生词)。

我的问题:

  1. 显然,我想确认这两件事。那么我如何获得功能呢?(这是主要问题。)
  2. 这里MSDN (“调用托管对象”)来看,似乎只有someObject.Handle可能是GC的,而独立的IntPtr则不会。但是IntPtr本身是被管理的!
  3. 如何在IntPtr超出范围(如这里)之前对其进行GC处理?
EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2014-12-07 21:53:04

我认为您将指针(IntPtrvoid*)与句柄(对Windows的引用)混淆在一起。不幸的是,句柄可以用IntPtr类型表示,这可能会让人感到困惑。

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

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

HandleRef文档的重要部分是:

HandleRef构造函数接受两个参数:表示包装器的Object和表示非托管句柄的IntPtr。互操作封送拆收器只将句柄传递给非托管代码,并保证包装器(作为HandleRef构造函数的第一个参数传递)在调用期间仍然有效。

我们来看看MSDN示例

代码语言:javascript
复制
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);

这相当于:

代码语言:javascript
复制
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);

但在这种情况下,更好的解决办法是:

代码语言:javascript
复制
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收集它,要么显式地释放它(通过调用Dispose()方法)。 对于指针,确保将指向内存固定在本机代码可以访问它的整个时间内。您可以使用fixed关键字或固定的GCHandle来实现这一点。
  2. 正如上面所述,IntPtr是一个struct,所以它不是GC收集的。
  3. 收集的不是IntPtr,而是公开它的HWnd对象,这个对象在此时无法访问,并且可以由GC收集。当最后确定时,它会处理手柄。 参考答案的代码是: HWnd a=新的HWnd();IntPtr h= a.Handle;// GC可以在此时启动并收集HWnd,//因为该行后面没有引用它。//如果是的话,HWnd的终结器就可以运行。//如果它运行,HWnd将释放句柄。//如果释放了句柄,h将保存一个已释放的句柄值,//该值无效。它仍然具有相同的数值,但是// Windows已经释放了基础对象。//作为一个值类型,h本身与GC无关。//这是不能收藏的。把它想成是个整数。B.SendMessage(h,.);//在这里添加GC.KeepAlive(a)解决了这个问题。 至于对象可达性规则,一旦没有更多可访问的对象引用,对象就被视为不再使用。在前面的示例中,就在IntPtr h = a.Handle;行之后,没有其他其他a变量的使用,因此假设这个对象不再被使用,可以随时释放。GC.KeepAlive(a)创建了这样的用法,因此对象仍然处于活动状态(由于使用情况跟踪是由JIT完成的,所以真正的对象需要更多的参与,但对于这个解释来说,这已经足够了)。

SafeHandle不包括像HandleRef这样的安全措施。对,是这样?

问得好。我认为P/Invoke封送拆收器将在调用期间保持句柄活动,但是它的拥有对象(如HWnd)仍然可以在调用期间显式地释放它,如果它已经完成。这是HandleRef提供的安全措施,您不能单独使用SafeHandle。您需要确保句柄所有者(在前面的示例中是HWnd)自己保持活动状态。

但是HandleRef的主要目标是包装一个IntPtr,这是存储句柄值的旧方法。现在,在句柄存储方面,SafeHandleIntPtr更可取。您只需确保句柄所有者不会在P/Invoke调用期间显式地释放句柄。

票数 9
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/27348119

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档