SafeHandle和HandleRef
在阅读了这两个内容后,包括在这个网站上的高投票答案,我仍然觉得这有点不清楚。
由于我对此事的理解可能是错误的,我将首先发布我所知道的概要,以便我可以纠正如果我错了,然后发布我的具体问题:
有时在编写托管代码时,我们必须将地址传递给非托管代码。 这就是IntPtr的用途。 但是,我们尝试确保两个相反的事情:a)保持指针(到一个地址)从GC中存活。 b)在不需要时释放它(即使我们忘记明确地这样做)。
HandleRef执行第一个,SafeHandle执行第二个。 (我实际上是指这里列出的SafeHandle的派生词)。
我的问题:
- 显然,我想确认两者。 那么我该如何获得该function呢? (这是主要问题。)
- 从这里和从MSDN (“调用一个托管对象”)看起来只有
someObject.Handle
可能是GC’d,而一个独立的IntPtr
不会。 但IntPtr
本身是管理的! - IntPtr如何在超出范围之前进行GC(如此处所述)?
我认为你把指针( 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编组程序仅将句柄传递给非托管代码,并保证包装器(作为第一个参数传递给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; // 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); }
总结一下:
-
对于句柄,使用
SafeHandle
并确保它不再SafeHandle
,直到您不再需要它为止,此时您要么让GC收集它,要么显式处理它(通过调用Dispose()
方法)。对于指针,您确保指向的内存在本机代码可以访问它的整个时间内固定。 您可以使用
fixed
关键字或固定GCHandle
来实现此目的。 -
IntPtr
是一个struct
,如上所述,因此它不是由GC收集的。 -
它不是收集的
IntPtr
,它是暴露它的HWnd
对象,此时不再可以到达并且可由GC收集。 最终确定时,它会处理手柄。引用答案的代码是:
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;
line,之后没有其他a
变量使用,因此假设此对象不再使用,可以随时释放。GC.KeepAlive(a)
创建了这样的用法,因此对象保持活动状态(由于JIT完成了使用情况跟踪,因此实际情况更为复杂,但这对于此解释来说已经足够了)。
SafeHandle不包括像HandleRef这样的安全措施。 正确?
好问题。 我想P / Invoke编组器会在调用期间保持句柄处于活动状态,但是如果它已经完成,它的拥有对象(如HWnd
)仍然可以在调用期间显式处理它。 这是HandleRef
提供的安全措施 ,单独使用SafeHandle
就不会得到它。 您需要确保句柄所有者(上一个示例中的HWnd
)自己保持活动状态。
但是HandleRef
的主要目标是包装一个IntPtr
,这是存储句柄值的旧方法。 现在,无论如何, SafeHandle
比IntPtr
适合处理存储。 您只需确保句柄所有者在P / Invoke调用期间不会显式处理句柄。
上述就是C#学习教程:SafeHandle和HandleRef分享的全部内容,如果对大家有所用处且需要了解更多关于C#学习教程,希望大家多多关注—计算机技术网(www.ctvol.com)!
本文来自网络收集,不代表计算机技术网立场,如涉及侵权请联系管理员删除。
ctvol管理联系方式QQ:251552304
本文章地址:https://www.ctvol.com/cdevelopment/1022256.html