我想你混淆指针(IntPtr
或void*
)与手柄(到Windows对象的引用)。不幸的是,句柄可以用IntPtr
类型来表示,这可能会令人困惑。
SafeHandle
是专门用于处理句柄的。一个句柄不是一个指针,而是系统提供的表中的一个索引(排序 - 它意味着不透明)。例如,CreateFile
函数返回HANDLE
,这将适用于SafeFileHandle
。 SafeHandle
类本身是一个Windows句柄的包装,它将在完成SafeHandle
时释放Windows句柄。因此,只要您想使用手柄,就必须确保提及SafeHandle
对象。
指针只是一个值。这是内存中对象的地址。 IntPtr
是struct
,并且struct
语义将使其通过值传递(即每次将IntPtr
传递给实际制作IntPtr
副本的函数)。除非装箱,GC甚至不知道您的IntPtr
s。
的HandleRef
文档的重要组成部分,是这样的:
的HandleRef
构造函数有两个参数:一个Object
代表的包装,以及代表非托管手柄的IntPtr
。互操作封送拆收器仅将句柄传递给非托管代码,并确保在调用期间封装(作为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);
}
综上所述up:
对于手柄,使用SafeHandle
并确保它的到达,直到你不需要它了,此时你要么让GC收集它,或者你处置它明确(通过调用Dispose()
方法)。
对于指针,可以确保指向的内存在本机代码可以访问的整个过程中被锁定。您可以使用fixed
关键字或固定的GCHandle
来实现此目的。
IntPtr
是struct
,如上所述,所以它不被GC收集。
这不是收集到的IntPtr
,而是HWnd
这个对象,暴露了它在此时不再可达并且可被GC收集的对象。完成后,它将处理手柄。
从referenced answer的代码是:
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
变量没有其他用法,因此假定该对象不再使用并且可以随时释放。 GC.KeepAlive(a)
创建了这种用法,因此该对象保持活动状态(由于使用跟踪是由JIT完成的,所以实际情况会更复杂一些,但对于此解释来说这已足够好)。
的SafeHandle不不包括安全措施像HandleRef。正确?
好问题。我认为P/Invoke封送拆收器会在调用期间保持句柄活动,但它的拥有对象(如HWnd
)仍然可以在调用期间明确地处理它,如果它已完成。这是HandleRef
提供的安全措施10,您不会单独使用SafeHandle
。您需要确保手柄所有者(上例中的HWnd
)保持自己的状态。
但HandleRef
的主要目标是包装一个IntPtr
,这是存储句柄值的旧方法。现在,无论如何,SafeHandle
优于IntPtr
用于手柄存储。您只需确保句柄所有者不会在P/Invoke调用期间显式处理句柄。
'IntPtr'在超出范围之前不能被GC化,但是拥有实际资源的对象可以使指针失效。 – 2014-12-07 21:47:36
@AntonSavin编写一个方法创建一百万个IntPtr并调用它一千次 - 你不会有内存不足的例外。为什么?因为他们是GC'd。没有? – ispiro 2014-12-07 21:49:13
我在谈论不同的事情 - 由IntPtr指向的对象可能不再存在,因为它的所有者已被GC化,但IntPtr可能在此时仍然存在。 – 2014-12-07 21:52:50