7

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:

  1. Obviously, I want to confirm both. So how do I get that functionality? (This is the main question.)
  2. From here and From MSDN ("call a managed object") it looks like only someObject.Handle might be GC'd, while a free standing IntPtr will not. But an IntPtr itself is managed!
  3. How can an IntPtr be GC'd before it goes out of scope (as mentioned here)?
Community
  • 1
  • 1
ispiro
  • 26,556
  • 38
  • 136
  • 291
  • 1
    `IntPtr` can't be GC'ed before it goes out of scope, but an object owning the actual resource can, thus invalidating the pointer. – Anton Savin Dec 07 '14 at 21:47
  • @AntonSavin Write a method creating a million IntPtr's and call it a thousand times - you won't have an out of memory exception. Why? Because they're GC'd. No? – ispiro Dec 07 '14 at 21:49
  • 1
    I was talking about different thing - the object pointed to by IntPtr may no longer exist because its owner was GC'ed, but IntPtr may be still alive at that point. – Anton Savin Dec 07 '14 at 21:52
  • Handles are not pointers so the question rambles heavily. By far the most common way to pass a pointer is by using *ref* or *out* or by passing an object. An IntPtr cannot be garbage collected. The pinvoke marshaller is very good at automatic pinning. – Hans Passant Dec 07 '14 at 22:57

1 Answers1

10

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 IntPtrs.

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:

  1. 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.

  2. IntPtr is a struct, as stated above, so it's not collected by the GC.

  3. 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.

Community
  • 1
  • 1
Lucas Trzesniewski
  • 50,214
  • 11
  • 107
  • 158
  • Thanks. I'm still trying to understand the answer. But why did you write "h will hold an invalid value" - didn't you just say that it's passed by value? Nothing will change that now that `a` is collected. It should still be valid. – ispiro Dec 07 '14 at 22:17
  • Do I understand correctly that the answer to 3 is that things _can_ be GC'd even if they didn't "officially" go out of scope, when the GC sees they'll never be accessed again? – ispiro Dec 07 '14 at 22:19
  • `So you have to make sure a reference to the SafeHandle object is kept as long as you want to use the handle` - So you're saying I understood correctly that SafeHandle does _not_ include a safety measure like HandleRef. Correct? – ispiro Dec 07 '14 at 22:22
  • Little a bit lately. Everything is correct, but many ways under some situations are acceptable. HandleRef is good for do not forget keep reference. But exactly same effect can be done with GC.KeepAlive or anything other. SafeHandle are only way to have guarantee that handle will be released even if threaded aborted deep inside pinvoke (i.e. before you got result from method in managed world), and in same time **release-handle-api should be thread-free**. But SafeHandle did not provide anything useful for you, for example if you try wrap HWND ('cause HWND can't be destroyed from random thread). – Dmitry Azaraev Apr 09 '15 at 18:15
  • I'm revisiting this old question of mine now that I understand this stuff a little better. To sum up your answer - One should use `SafeHandle` and keep a reference to the referenced object, and all should be well. Did I understand you correctly? (According to [this](http://stackoverflow.com/a/526792/939213) answer - perhaps keeping the reference is not even needed - `Prior to the introduction of SafeHandle, ...prevented from being GC'ed was the responsibility of the programmer`.) – ispiro Nov 05 '15 at 20:26
  • @ispiro `SafeHandle` ensures that if it's disposed while being used in a P/Invoke call in another thread, the handle will be released *after* the call returns ([docs](https://msdn.microsoft.com/en-us/library/4h5yzadk.aspx)), so you're safe using it. That's at least what I understand from *"Calling the Close or Dispose method allows the resources to be freed. This might not happen immediately if other threads are using the same safe handle object, but will happen as soon as that is no longer the case."*. – Lucas Trzesniewski Nov 05 '15 at 22:29