我认为你把指针(
IntPtr
或
void*
)和句柄(对Windows对象的引用)混淆了。不幸的是,句柄可以用
IntPtr
类型表示,这可能会令人困惑。
SafeHandle
用于处理句柄。句柄不是指针,而是系统提供的表中的索引(有点像——它应该是不透明的)。例如,
CreateFile
函数返回一个
HANDLE
,适合与
SafeFileHandle
一起使用。
SafeHandle
类本身是一个包装器,用于包装Windows句柄,当
SafeHandle
被完成时,它将释放Windows句柄。因此,您必须确保保持对
SafeHandle
对象的引用,只要您想使用该句柄。
指针只是一个值。它是内存中对象的地址。
IntPtr
是一个
struct
,并且
struct
语义将使其按值传递(也就是说,每次将
IntPtr
传递给函数时,实际上都会复制
IntPtr
)。除非装箱,否则GC甚至不知道你的
IntPtr
。
HandleRef
文档的重要部分是这样的:
HandleRef
构造函数接受两个参数:表示包装器的Object
和表示未托管句柄的IntPtr
。Interop marshaler仅传递句柄到未托管代码,并保证在调用期间包装器(作为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;
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);
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);
}
总之:
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.
IntPtr
is a struct
, as stated above, so it's not collected by the GC.
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;
B.SendMessage(h, ...);
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没有像HandleRef
那样的安全措施,对吗?
好问题。我想P/Invoke marshaler将在调用期间保持句柄的存活状态,但其拥有对象(如HWnd
)仍然可能在调用期间显式地处理它。这是HandleRef
提供的安全措施,你不能只使用SafeHandle
来获得它。你需要确保句柄所有者(前面示例中的HWnd
)自己保持存活。
但是HandleRef
的主要目标是包装IntPtr
,这是存储句柄值的旧方法。现在,SafeHandle
已经成为句柄存储的首选方法。你只需要确保句柄所有者不会在P/Invoke调用期间显式处理句柄。
IntPtr
在超出作用域之前无法进行垃圾回收,但拥有实际资源的对象可以进行垃圾回收,从而使指针失效。 - Anton Savin