C# 安全手柄和手柄

C# 安全手柄和手柄,c#,.net,garbage-collection,unmanaged,C#,.net,Garbage Collection,Unmanaged,在阅读了这两个问题后,包括在这个网站上的一个高投票率的答案,我仍然觉得这有点不清楚 由于我对这件事的理解可能是错误的,我将首先发布我所知道的内容的概要,这样如果我错了,我就可以纠正,然后发布我的具体问题: 有时在编写托管代码时,我们必须将地址传递给非托管代码。这就是IntPtr的用途。然而,我们试图确保两件相反的事情:a)保持GC中的指针(指向地址)处于活动状态。b) 在不需要时释放它(即使我们忘记显式地这样做) HandleRef做第一个,SafeHandle做第二个。(这里我实际上指的是列出

在阅读了这两个问题后,包括在这个网站上的一个高投票率的答案,我仍然觉得这有点不清楚

由于我对这件事的理解可能是错误的,我将首先发布我所知道的内容的概要,这样如果我错了,我就可以纠正,然后发布我的具体问题:

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

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

我的问题:

  • 显然,我想确认这两个。那么我怎样才能得到那份 功能?(这是主要问题。)
  • From和From(“调用托管对象”)看起来只有
    someObject。Handle
    可能 是GC'd,而独立的
    IntPtr
    不会。但是
    IntPtr
    本身 管理好了
  • IntPtr如何在超出范围(如前所述)之前进行GC )?

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

    SafeHandle
    专门用于处理句柄。句柄不是指针,而是系统提供的表中的索引(某种程度上,它是不透明的)。例如,该函数返回一个
    句柄
    ,该句柄适合与
    安全文件句柄
    一起使用。
    SafeHandle
    类本身就是一个围绕Windows句柄的包装器,当
    SafeHandle
    完成时,它将释放Windows句柄。因此,您必须确保只要您想使用句柄,就会保留对
    SafeHandle
    对象的引用

    指针只是一个值。它是内存中对象的地址
    IntPtr
    是一个
    struct
    ,而
    struct
    语义将使其按值传递(也就是说,每次将
    IntPtr
    传递给函数时,实际上是复制
    IntPtr
    )。除非装箱,否则GC甚至不知道您的
    IntPtr
    s

    HandleRef
    文档的重要部分是:

    HandleRef
    构造函数接受两个参数:表示包装器的
    Object
    和表示非托管句柄的
    IntPtr
    。互操作封送拆收器仅将句柄传递给非托管代码,并确保包装器(作为第一个参数传递给HandleRef的构造函数)在调用期间保持活动状态

    让我们来看看:

    这相当于:

    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()
    方法)

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

  • IntPtr
    是一个
    struct
    ,如上所述,因此GC不会收集它

  • 收集的不是
    IntPtr
    ,而是暴露它的
    HWnd
    对象,此时不再可以访问它,GC可以收集它。完成后,它将处理句柄

    来自的代码是:

    至于对象可访问性规则,只要不再存在对对象的可访问引用,对象就被视为不再使用。在前面的示例中,就在
    IntPtr h=a.Handle之后行,以后不再使用
    a
    变量,因此假定此对象不再使用,并且可以随时释放
    GC.KeepAlive(a)
    创建这样的用法,因此对象保持活动状态(因为用法跟踪是由JIT完成的,所以真正的事情要复杂一些,但这对于这个解释已经足够了)


  • SafeHandle不包括HandleRef这样的安全措施。对吗

    好问题。我假设P/Invoke封送拆收器将在调用期间保持句柄的活动状态,但是它所拥有的对象(如
    HWnd
    )仍然可以在调用期间显式地处理它(如果它已完成)。这是
    HandleRef
    提供的安全措施,单凭
    SafeHandle
    是无法实现的。您需要确保句柄所有者(
    HWnd
    在上一个示例中)自己保持活动状态


    但是
    HandleRef
    的主要目标是包装
    IntPtr
    ,这是存储句柄值的旧方法。现在,
    SafeHandle
    IntPtr
    更适合用于句柄存储。您只需确保句柄所有者不会在P/Invoke调用期间显式地处置句柄。

    IntPtr
    不能在其超出范围之前进行GC'ed,但拥有实际资源的对象可以,因此,指针无效。@AntonSavin编写一个方法,创建一百万个IntPtr,并调用它一千次-不会出现内存不足异常。为什么?因为它们是GC的。不是吗?我说的是另一件事——IntPtr指向的对象可能不存在了,因为它的所有者是GC'ed,但IntPtr在那一点上可能还活着。句柄不是指针,所以问题很复杂。到目前为止,传递指针最常用的方法是使用ref或out或传递对象。IntPtr不能被垃圾回收。pinvoke marshaller非常擅长自动固定。谢谢。我仍然是tr
    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);
    }
    
    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.