- c - 在位数组中找到第一个零
- linux - Unix 显示有关匹配两种模式之一的文件的信息
- 正则表达式替换多个文件
- linux - 隐藏来自 xtrace 的命令
在阅读了两者(包括本网站上的高票答案)之后,我仍然觉得这有点不清楚。
由于我对这件事的理解可能是错误的,所以我会先把我所知道的提纲贴出来,如果我错了可以纠正我,然后贴出我的具体问题:
有时在编写托管代码时,我们必须将地址传递给非托管代码。这就是 IntPtr 的用途。然而,我们试图确保两件相反的事情:a) 保持该指针(指向地址)在 GC 中处于事件状态。 b) 在不需要时释放它(即使我们忘记明确地这样做)。
HandleRef 做第一个,SafeHandle 做第二个。 (我实际上指的是这里列出的 SafeHandle 的派生 here )。
我的问题:
最佳答案
我认为您将指针(IntPtr
或 void*
)与句柄(对 Windows 对象的引用)混淆了。不幸的是,句柄可以用 IntPtr
类型表示,这可能会造成混淆。
SafeHandle
专门用来处理句柄的。句柄不是指针,而是系统提供的表中的索引(有点——它意味着不透明)。例如,CreateFile
函数返回一个 HANDLE
,它适合与 SafeFileHandle
一起使用。 SafeHandle
类本身是 Windows 句柄的包装器,它会在 SafeHandle
完成时释放 Windows 句柄。因此,只要您要使用句柄,就必须确保保留对 SafeHandle
对象的引用。
指针只是一个值。它是内存中对象的地址。 IntPtr
是一个struct
,struct
语义将使它按值传递(即每次传递一个IntPtr
到一个函数,你实际上复制了 IntPtr
)。除非装箱,否则 GC 甚至不知道您的 IntPtr
。
HandleRef
文档的重要部分是:
The
HandleRef
constructor takes two parameters: anObject
representing the wrapper, and anIntPtr
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.
让我们以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);
}
总结:
对于句柄,使用 SafeHandle
并确保它可以访问,直到您不再需要它为止,此时您要么让 GC 收集它,要么显式处理它(通过调用Dispose()
方法)。
对于指针,您要确保指向的内存在 native 代码可以访问它的整个过程中都被固定。您可以使用 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
变量的其他后续使用,因此假定此对象是 no使用时间更长,可以随时释放。 GC.KeepAlive(a)
创建了这样的用法,因此对象保持事件状态(由于使用跟踪是由 JIT 完成的,所以实际情况要复杂一些,但这足以解释这个问题)。
SafeHandle does not include a safety measure like HandleRef. Correct?
好问题。我想 P/Invoke 编码(marshal)拆收器将在调用期间保持句柄处于事件状态,但它拥有的对象(如 HWnd
)仍然可以在调用期间显式处置它(如果它已完成)。这是HandleRef
提供的安全措施,您无法单独使用SafeHandle
。您需要自己确保句柄所有者(上例中的 HWnd
)保持事件状态。
但是 HandleRef
的主要目标是包装一个 IntPtr
,这是存储句柄值的旧方法。现在,SafeHandle
比 IntPtr
更适合句柄存储。您只需确保句柄所有者不会在 P/Invoke 调用期间显式处置句柄。
关于c# - 安全句柄和句柄引用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27348119/
我设置了 Helm 柄和 Helm 柄。我有tiller-deploy。昨天,我可以定期运行了。但今天我收到此错误消息 Error: could not find a ready tiller pod
我以前已将分er安装到特定的 namespace 中。 我设置了一个环境变量来设置'tiller'命名空间-但我不记得该环境变量的名称-而且似乎无法通过网络搜索找到它。 这是什么 key ? 最佳答案
当我在 View 模型中使用如下界面时 class MainViewModel @ViewModelInject constructor( private val trafficImagesR
我正在尝试找到如何在某个 fragment 相关场景中定义 Hilt 的解决方案。我有以下设置: Activity 父 fragment 1 子 fragment 1 子 fragment 2 ...
Hilt 指出如果没有@Provides 注解就不能提供这个接口(interface): interface PlannedListRepository { fun getAllLists()
我的问题非常简单明了:两个注释/示例之间有什么区别: 例子一 @Singleton class MySingletonClass() {} @Module @InstallIn(FragmentCom
我是一名优秀的程序员,十分优秀!