C# 安全手柄和手柄
在阅读了这两个问题后,包括在这个网站上的一个高投票率的答案,我仍然觉得这有点不清楚 由于我对这件事的理解可能是错误的,我将首先发布我所知道的内容的概要,这样如果我错了,我就可以纠正,然后发布我的具体问题: 有时在编写托管代码时,我们必须将地址传递给非托管代码。这就是IntPtr的用途。然而,我们试图确保两件相反的事情:a)保持GC中的指针(指向地址)处于活动状态。b) 在不需要时释放它(即使我们忘记显式地这样做) HandleRef做第一个,SafeHandle做第二个。(这里我实际上指的是列出的SafeHandle的派生) 我的问题:C# 安全手柄和手柄,c#,.net,garbage-collection,unmanaged,C#,.net,Garbage Collection,Unmanaged,在阅读了这两个问题后,包括在这个网站上的一个高投票率的答案,我仍然觉得这有点不清楚 由于我对这件事的理解可能是错误的,我将首先发布我所知道的内容的概要,这样如果我错了,我就可以纠正,然后发布我的具体问题: 有时在编写托管代码时,我们必须将地址传递给非托管代码。这就是IntPtr的用途。然而,我们试图确保两件相反的事情:a)保持GC中的指针(指向地址)处于活动状态。b) 在不需要时释放它(即使我们忘记显式地这样做) HandleRef做第一个,SafeHandle做第二个。(这里我实际上指的是列出
someObject。Handle
可能
是GC'd,而独立的IntPtr
不会。但是IntPtr
本身
管理好了李>
我认为您将指针(
IntPtr
或void*
)与句柄(对Windows对象的引用)混淆了。不幸的是,句柄可以用IntPtr
类型表示,这可能会造成混淆
SafeHandle
专门用于处理句柄。句柄不是指针,而是系统提供的表中的索引(某种程度上,它是不透明的)。例如,该函数返回一个句柄
,该句柄适合与安全文件句柄
一起使用。SafeHandle
类本身就是一个围绕Windows句柄的包装器,当SafeHandle
完成时,它将释放Windows句柄。因此,您必须确保只要您想使用句柄,就会保留对SafeHandle
对象的引用
指针只是一个值。它是内存中对象的地址IntPtr
是一个struct
,而struct
语义将使其按值传递(也就是说,每次将IntPtr
传递给函数时,实际上是复制IntPtr
)。除非装箱,否则GC甚至不知道您的IntPtr
s
HandleRef
文档的重要部分是:
HandleRef
构造函数接受两个参数:表示包装器的Object
和表示非托管句柄的IntPtr
。互操作封送拆收器仅将句柄传递给非托管代码,并确保包装器(作为第一个参数传递给HandleRef的构造函数)在调用期间保持活动状态
让我们来看看:
这相当于:
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可以收集它。完成后,它将处理句柄
来自的代码是:
至于对象可访问性规则,只要不再存在对对象的可访问引用,对象就被视为不再使用。在前面的示例中,就在IntPtr h=a.Handle之后代码>行,以后不再使用a
变量,因此假定此对象不再使用,并且可以随时释放GC.KeepAlive(a)
创建这样的用法,因此对象保持活动状态(因为用法跟踪是由JIT完成的,所以真正的事情要复杂一些,但这对于这个解释已经足够了)
SafeHandle不包括HandleRef这样的安全措施。对吗 好问题。我假设P/Invoke封送拆收器将在调用期间保持句柄的活动状态,但是它所拥有的对象(如
HWnd
)仍然可以在调用期间显式地处理它(如果它已完成)。这是HandleRef
提供的安全措施,单凭SafeHandle
是无法实现的。您需要确保句柄所有者(HWnd
在上一个示例中)自己保持活动状态
但是
HandleRef
的主要目标是包装IntPtr
,这是存储句柄值的旧方法。现在,SafeHandle
比IntPtr
更适合用于句柄存储。您只需确保句柄所有者不会在P/Invoke调用期间显式地处置句柄。IntPtr
不能在其超出范围之前进行GC'ed,但拥有实际资源的对象可以,因此,指针无效。@AntonSavin编写一个方法,创建一百万个IntPtr,并调用它一千次-不会出现内存不足异常。为什么?因为它们是GC的。不是吗?我说的是另一件事——IntPtr指向的对象可能不存在了,因为它的所有者是GC'ed,但IntPtr在那一点上可能还活着。句柄不是指针,所以问题很复杂。到目前为止,传递指针最常用的方法是使用ref或out或传递对象。IntPtr不能被垃圾回收。pinvoke marshaller非常擅长自动固定。谢谢。我仍然是tr
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);
}
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.