在阅读有关这两种,包括在这个网站高投的答案,我仍然觉得这有点不清楚。
由于我的事情的理解可能是错误的,我的第一篇文章是我知道这样我就可以,如果我错了,纠正大纲,然后张贴我的具体问题:
有时候编码管理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手柄的包装,它会释放Windows的手柄时,的SafeHandle
定稿。所以,你必须确保一个参考的SafeHandle
的对象,只要你想用手柄保持。
一个指针只是一个值。它在存储器中的对象的地址。 的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 onlysomeObject.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 IntPtr
s.
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.