的SafeHandle和HandleRefSafeHandle、HandleRef

2023-09-03 17:36:16 作者:来一碗小仙女

在阅读有关这两种,包括在这个网站高投的答案,我仍然觉得这有点不清楚。

由于我的事情的理解可能是错误的,我的第一篇文章是我知道这样我就可以,如果我错了,纠正大纲,然后张贴我的具体问题:

有时候编码管理code的时候,我们有一个地址传递给非托管code。这是一个IntPtr是什么。但是,我们尽量确保两个对立的事物:a)保持该指针(一个地址)活着从GC。 B)松开它时,它是没有必要的(即使我们忘记了明确做到这一点)。

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

我的问题:

很显然,我想确认双方。那么,如何才能获得的的是的 功能? (这是最主要的问题。) 从here并从 MSDN (所说的被管理对象),它看起来像只 someObject.Handle 可能 被GC'd,而自由站立的IntPtr 不会。但是,一个的IntPtr 的自身的 是管理! 如何才超出范围一个IntPtr被GC'd(如上文 这里)? 解决方案

我想你混淆指针(的IntPtr 无效* )用手柄(参考Windows对象)。不幸的是,手柄可以重新presented与的IntPtr 类型,它可能会造成混淆。

的SafeHandle 是带手柄专门处理。句柄不是指针,但在系统提供的表上的索引(排序 - 它的意思是不透明的)。例如, 的CreateFile 函数返回一个 HANDLE ,这将是适合搭配 SafeFileHandle 来使用。该的SafeHandle 类本身就是围绕在Windows手柄的包装,它会释放W​​indows的手柄时,的SafeHandle 定稿。所以,你必须确保一个参考的SafeHandle 的对象,只要你想用手柄保持。

开团 美国safety 1st婴幼儿推车,超好推,一键收车 孩子可躺可坐可上飞机,全网最低还送赠品,推车有这一辆就够了 搜狐

一个指针只是一个值。它在存储器中的对象的地址。 的IntPtr 结构结构语义将它按值传递(也就是,每次传递一个的IntPtr 来一个函数,你实际上使副本的的IntPtr )。除非盒装,GC将甚至不知道你的的IntPtr 秒。

HandleRef 文档的重要组成部分,是这样的:

  

HandleRef 构造函数有两个参数:一个对象重presenting包装,以及的IntPtr 重presenting非托管的句柄。封送仅通过手柄非托管code,并保证包装(传递的第一个参数HandleRef的构造函数)仍然活着的通话时长的互操作。

让我们的 MSDN例如的:

 的FileStream FS =新的FileStream(HandleRef.txt,FileMode.Open);
HandleRef HR =新HandleRef(FS,fs.SafeFileHandle.DangerousGetHandle());
StringBuilder的缓冲=新的StringBuilder(5);
INT读= 0;

//平台调用将持有参考HandleRef,直到通话结束

LibWrap.ReadFile(小时,缓冲液,5,读出,0);
Console.WriteLine(读与结构参数:{0},缓冲区);
LibWrap.ReadFile2(小时,缓冲液,5,出来读,NULL);
Console.WriteLine(读与类参数:{0},缓冲区);
 

这是等价于:

 的FileStream FS =新的FileStream(HandleRef.txt,FileMode.Open);
变种HF = fs.SafeFileHandle.DangerousGetHandle();
StringBuilder的缓冲=新的StringBuilder(5);
INT读= 0;

LibWrap.ReadFile(HF,缓冲液,5,读出,0);
Console.WriteLine(读与结构参数:{0},缓冲区);
LibWrap.ReadFile2(HF,缓冲器,5,出来读,NULL);
Console.WriteLine(读与类参数:{0},缓冲区);

//因为我们没有更多的有HandleRef,下面一行是必要的:
GC.KeepAlive(FS);
 

但在这种特殊情况下一个更好的解决办法是:

 使用(的FileStream FS =新的FileStream(HandleRef.txt,FileMode.Open))
{
    StringBuilder的缓冲=新的StringBuilder(5);
    INT读= 0;

    LibWrap.ReadFile(fs.SafeFileHandle,缓冲液,5,读出,0);
    Console.WriteLine(读与结构参数:{0},缓冲区);
    LibWrap.ReadFile2(fs.SafeFileHandle,缓冲器,5,出来读,NULL);
    Console.WriteLine(读与类参数:{0},缓冲区);
}
 

要总结:

有关手柄,使用的SafeHandle ,并确保它的访问,直到你不再需要它了,此时你要么让GC收集它,或者你明确地处理它(通过调用的Dispose()法)。

有关指针,你要确保所指向的内存是固定的整个时间本地code可以访问它。您可以使用固定关键字或固定的GCHandle 来实现这一目标。

的IntPtr 结构,如上所述,所以它不会被GC回收。

这不是的IntPtr 就是收集,这是 HWND 对象,真实揭露它那无法再访问在这一点上,是由收藏在GC。当最终确定,其配置的手柄。

从参考答案的code是:

  HWND A =新的HWND();
IntPtr的H = a.Handle;

// GC能够在这一点上踢,收集HWND,
//因为这条线之后,不是引用。
//如果是这样,HWND的终结器可以运行。
//如果它运行,HWND将配置手柄。
//如果手柄被布置,H将举行一个释放句柄值,
//这是无效的。它仍然具有相同的数值,但
// Windows将已经释放了潜在的对象。
//作为一个价值型,H本身无关,与GC。
//这不是收藏。认为它喜欢它是一个int。

B.SendMessage(h时,...);

//添加GC.KeepAlive(一)在这里解决了这个问题。
 

至于对象可达性规则,一个对象被认为是不再一旦没有更多的可到达的引用的对象使用。在previous例如,在的IntPtr H = a.Handle后; 行,还有的 A 变量,因此假设该对象不再使用,并且可以随时被释放。 GC.KeepAlive(一)创建这样的用法,所以对象仍然活着(真实的东西是有点更复杂,因为使用情况跟踪由JIT做,但,这是很好的足以让这个解释)。

  

的SafeHandle做的没有的包括像HandleRef安全措施。是否正确?

好问题。我假设的P / Invoke编组将保持手柄存活呼叫的持续时间,但其拥有的物品(如 HWND )仍然可以在通话过程中显式地处理它,如果它是最终确定。这是的的安全措施的是 HandleRef 提供的,你不会得到它与的SafeHandle 孤单。您需要确保手柄所有者( HWND 在previous例子)保持活着吧。

不过的首要目标 HandleRef 是包装一个的IntPtr ,这是存储手柄的老方法值。现在,的SafeHandle 是pferred到的IntPtr 的把手存放反正$ P $。你只需要确保手柄所有者将不会在P中明确处置手柄/ Invoke调用。

After reading about both, including a high voted answer on this site, I still find this a bit unclear.

Since my understanding of the matter might be wrong, I'll first post a synopsis of what I know so I can be corrected if I'm wrong, and then post my specific questions:

Sometimes when coding managed code, we have to pass an address to unmanaged code. That's what an IntPtr is for. However, we try to make sure of two opposite things: a) Keep that pointer (to an address) alive from the GC. b) Release it when it's not needed (even if we forget to do that explicitly).

HandleRef does the first, and SafeHandle the second. (I'm actually referring here to derivations of SafeHandle listed here).

My questions:

Obviously, I want to confirm both. So how do I get that functionality? (This is the main question.) From here and From MSDN ("call a managed object") it looks like only someObject.Handle might be GC'd, while a free standing IntPtr will not. But an IntPtr itself is managed! How can an IntPtr be GC'd before it goes out of scope (as mentioned here)?

解决方案

I think you're confusing pointers (IntPtr or void*) with handles (a reference to a Windows object). Unfortunately, handles can be represented with an IntPtr type, which can be confusing.

SafeHandle is for dealing with handles specifically. A handle is not a pointer but an index in a system-provided table (sort of - it's meant to be opaque). For instance, the CreateFile function returns a HANDLE, which would be suitable to use with a SafeFileHandle. The SafeHandle class is itself a wrapper around a Windows handle, it will free the Windows handle when the SafeHandle is finalized. So you have to make sure a reference to the SafeHandle object is kept as long as you want to use the handle.

A pointer is just a value. It's the address of an object in memory. IntPtr is a struct, and the struct semantics will make it be passed by value (that is, every time you pass an IntPtr to a function you actually make a copy of the IntPtr). Unless boxed, the GC won't even know about your IntPtrs.

The important part of the HandleRef docs is this:

The HandleRef constructor takes two parameters: an Object representing the wrapper, and an IntPtr representing the unmanaged handle. The interop marshaler passes only the handle to unmanaged code, and guarantees that the wrapper (passed as the first parameter to the constructor of the HandleRef) remains alive for the duration of the call.

Let's take the 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);

This is equivalent to:

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);

But a better solution in this particular case would be:

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);
}

To sum up:

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;

// 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.

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 does not include a safety measure like HandleRef. Correct?

Good question. I suppose the P/Invoke marshaler will keep the handle alive for the duration of the call, but its owning object (like HWnd) could still dispose it explicitly during the call if it's finalized. This is the safety measure that HandleRef provides, and you won't get it with SafeHandle alone. You need to ensure the handle owner (HWnd in the previous example) is kept alive yourself.

But the primary goal of HandleRef is to wrap an IntPtr, which is the old method of storing a handle value. Now, SafeHandle is preferred to IntPtr for handle storage anyway. You just have to ensure the handle owner won't dispose the handle explicitly during the P/Invoke call.