gpt4 book ai didi

c# - 安全句柄和句柄引用

转载 作者:太空狗 更新时间:2023-10-29 23:07:03 24 4
gpt4 key购买 nike

在阅读了两者(包括本网站上的高票答案)之后,我仍然觉得这有点不清楚。

由于我对这件事的理解可能是错误的,所以我会先把我所知道的提纲贴出来,如果我错了可以纠正我,然后贴出我的具体问题:

有时在编写托管代码时,我们必须将地址传递给非托管代码。这就是 IntPtr 的用途。然而,我们试图确保两件相反的事情:a) 保持该指针(指向地址)在 GC 中处于事件状态。 b) 在不需要时释放它(即使我们忘记明确地这样做)。

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

我的问题:

  1. 显然,我想确认两者。那么我如何得到那个功能? (这是主要问题。)
  2. 来自 here来自 MSDN (“调用托管对象”)看起来只有 someObject.Handle 可能会被 GC,而独立的 IntPtr 不会。但是 IntPtr 本身得到管理!
  3. 如何在 IntPtr 超出范围之前对其进行 GC(如前所述 here )?

最佳答案

我认为您将指针(IntPtrvoid*)与句柄(对 Windows 对象的引用)混淆了。不幸的是,句柄可以用 IntPtr 类型表示,这可能会造成混淆。

SafeHandle 专门用来处理句柄的。句柄不是指针,而是系统提供的表中的索引(有点——它意味着不透明)。例如,CreateFile函数返回一个 HANDLE,它适合与 SafeFileHandle 一起使用。 SafeHandle 类本身是 Windows 句柄的包装器,它会在 SafeHandle 完成时释放 Windows 句柄。因此,只要您要使用句柄,就必须确保保留对 SafeHandle 对象的引用。

指针只是一个值。它是内存中对象的地址。 IntPtr 是一个structstruct 语义将使它按值传递(即每次传递一个IntPtr 到一个函数,你实际上复制了 IntPtr)。除非装箱,否则 GC 甚至不知道您的 IntPtr

HandleRef 文档的重要部分是:

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.

让我们以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);
}

总结:

  1. 对于句柄,使用 SafeHandle 并确保它可以访问,直到您不再需要它为止,此时您要么让 GC 收集它,要么显式处理它(通过调用Dispose() 方法)。

    对于指针,您要确保指向的内存在 native 代码可以访问它的整个过程中都被固定。您可以使用 fixed 关键字或固定 GCHandle 来实现此目的。

  2. IntPtr 是一个 struct,如上所述,因此它不会被 GC 收集。

  3. 收集的不是 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,这是存储句柄值的旧方法。现在,SafeHandleIntPtr 更适合句柄存储。您只需确保句柄所有者不会在 P/Invoke 调用期间显式处置句柄。

关于c# - 安全句柄和句柄引用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27348119/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com