在阅读了这两篇文章之后,包括在这个网站上的一个高投票的答案,我仍然觉得这有点不清楚。
由于我对这件事的理解可能是错误的,我将首先发布我所知道的内容的概要,这样如果我错了,我就可以得到纠正,然后发布我的具体问题:
有时,在编写托管代码时,我们必须将一个地址传递给非托管代码。这就是IntPtr的作用所在。但是,我们试图确保两件相反的事情: a)将指向地址的指针保持在GC中。( b)在不需要时释放它(即使我们忘记显式地这样做)。
HandleRef做第一个,SafeHandle做第二个。(这里我实际上指的是SafeHandle listed 这里的派生词)。
我的问题:
发布于 2014-12-07 21:53:04
我认为您将指针(IntPtr或void*)与句柄(对Windows的引用)混淆在一起。不幸的是,句柄可以用IntPtr类型表示,这可能会让人感到困惑。
SafeHandle专门用于处理句柄。句柄不是指针,而是系统提供的表中的索引(某种程度上说,它意味着不透明)。例如,CreateFile函数返回一个HANDLE,它适合与SafeFileHandle一起使用。SafeHandle类本身就是Windows句柄的包装器,当SafeHandle完成时,它将释放SafeHandle句柄。因此,只要您想要使用句柄,就必须确保保留对SafeHandle对象的引用。
指针只是一个值。它是内存中对象的地址。IntPtr是一个struct,struct语义将使它通过值传递(也就是说,每次将IntPtr传递给函数时,您实际上都会复制IntPtr)。除非被装箱,否则GC甚至不会知道您的IntPtr。
HandleRef文档的重要部分是:
HandleRef构造函数接受两个参数:表示包装器的Object和表示非托管句柄的IntPtr。互操作封送拆收器只将句柄传递给非托管代码,并保证包装器(作为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);
}总括而言:
SafeHandle并确保它是可访问的,直到您不再需要它为止,此时您要么让GC收集它,要么显式地释放它(通过调用Dispose()方法)。
对于指针,确保将指向内存固定在本机代码可以访问它的整个时间内。您可以使用fixed关键字或固定的GCHandle来实现这一点。IntPtr是一个struct,所以它不是GC收集的。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,这是存储句柄值的旧方法。现在,在句柄存储方面,SafeHandle比IntPtr更可取。您只需确保句柄所有者不会在P/Invoke调用期间显式地释放句柄。
https://stackoverflow.com/questions/27348119
复制相似问题