Click here to Skip to main content
Email Password   helpLost your password?

Introduction

This article describes a very short helper class to work with unmanaged memory that must be pinned to prevent the garbage collector from moving it. This is a particular requirement when working with the Win32 API's asynchronous I/O methods, which I found myself doing when implementing a USB device interface.

There is no download; simply copy the code from this article--please do not remove the copyright.

The Code

There really isn't much to this code:

Here's the code:

// (c) 2007 Marc Clifton

using System;
using System.Runtime.InteropServices;

namespace Clifton.Tools.Interop
{
  /// <summary>

  /// A helper class for pinning a managed structure so that it is suitable for

  /// unmanaged calls. A pinned object will not be collected and will not be moved

  /// by the GC until explicitly freed.

  /// </summary>

  public class PinnedObject<T> : IDisposable where T : struct
  {
    protected T managedObject;
    protected GCHandle handle;
    protected IntPtr ptr;
    protected bool disposed;

    public T ManangedObject
    {
      get 
      {
        return (T)handle.Target;
      }
      set
      {
        Marshal.StructureToPtr(value, ptr, false);
      }
    }

    public IntPtr Pointer
    {
      get { return ptr; }
    }

    public PinnedObject()
    {
      handle = GCHandle.Alloc(managedObject, GCHandleType.Pinned);
      ptr = handle.AddrOfPinnedObject();
    }

    ~PinnedObject()
    {
      Dispose();
    }

    public void Dispose()
    {
      if (!disposed)
      {
        handle.Free();
        ptr = IntPtr.Zero;
        disposed = true;
      }
    }
  }
}

Example

Using Unsafe Pointers In C#

This example illustrates manipulating a PinnedObject using C++ style pointer syntax within an unsafe code block. The code illustrates:

using System;

using Clifton.Tools.Interop;

namespace pintest
{
  public struct TestStruct
  {
    public int a;
  }

  public static class Test
  {
    public static void Main()
    {
      PinnedObject<TestStruct> pin = new PinnedObject<TestStruct>();
      TestStruct ts = new TestStruct();
      ts.a = 1;
      pin.ManangedObject = ts;

      unsafe
      {
        TestStruct* p = (TestStruct*)pin.Pointer;
        ++p->a;
      }

    Console.WriteLine(pin.ManangedObject.a);
    }
  }
}

Conclusion

A simple class but hopefully you will find it useful for the strong type management and functionality that it encapsulates.

You must Sign In to use this message board.
 
 
Per page   
 FirstPrevNext
GeneralPinning copy of the structure
Lesheniuk
6:04 2 Mar '07  
try this at the end of code

Console.WriteLine(ts.a);

and you'll see that you pin wrong object.

Actually in this line

pin.ManagedObject = ts;

a copy of the structure is created and passed as value to the property.

And even if you'd use directly

GCHandle.Alloc(ts, GSHandleType.Pinned)

then a copy of the struct would be created, boxed in object, passed to the Alloc method,
and handle to this box-object would be returned.
GeneralRe: Pinning copy of the structure
Marc Clifton
6:18 2 Mar '07  
Lesheniuk wrote:
a copy of the structure is created and passed as value to the property.

That's correct. To acquire the current state of the managed object, you have to do:

ts = pin.ManangedObject;
Console.WriteLine(ts.a);

But that's the way structures work, they're not passed by reference.

for example:

...
Change(ts);
Console.WriteLine(ts.a);
...

public static void Change(TestStruct ts)
{
ts.a = 3;
}

Does not change the caller's value to 3.

Marc

Thyme In The Country
Interacx

People are just notoriously impossible. --DavidCrow
There's NO excuse for not commenting your code. -- John Simmons / outlaw programmer
People who say that they will refactor their code later to make it "good" don't understand refactoring, nor the art and craft of programming. -- Josh Smith


Questionfixed statement
rvpilot
11:48 13 Feb '07  
I am currently using c# and an unmanaged DLL that need structs and byte buffers passing to them (FTDI's D2XX.DLL USB API), and I suspect that it may be the reason for some recent unpredictable behaviour !!

How does your helper compare to the 'fixed (...) { ... }' statement and in what situation would you use one instead of the other ?


Regards,
Simon.
AnswerRe: fixed statement
Marc Clifton
12:06 13 Feb '07  
rvpilot wrote:
How does your helper compare to the 'fixed (...) { ... }' statement and in what situation would you use one instead of the other ?

That's a good question and I should have addressed it in the article. The "fixed" statement is useful when you are pinning something for the lifetime of the code within the "fixed" block. My pinned object is useful especially in a threaded scenario when you read or write some data asynchronously and later come back and work with the data or write out more data.

So, basically, anytime you need an object pinned beyond the lifetime of a "fixed" block, would be when to use a different mechanism, such as the PinnedObject to pin the object.

Marc

Thyme In The Country

People are just notoriously impossible. --DavidCrow
There's NO excuse for not commenting your code. -- John Simmons / outlaw programmer
People who say that they will refactor their code later to make it "good" don't understand refactoring, nor the art and craft of programming. -- Josh Smith


GeneralRe: fixed statement
rvpilot
12:14 13 Feb '07  
Thanks Marc,

Sounds like your PinnedObject is the way to go for my app !!

Simon
GeneralTesting
Luc Pattyn
13:07 12 Feb '07  
Hi Marc,

it's me again.
I forgot to ask: how do you test such a feature, i.e. how can you be sure
the object really got pinned (inside my using block) or no longer is
pinned (outside my using block) ?

It is clear if pinning is required, but not applied, and the object got moved,
all kinds of things may happen, but can we make an object move on purpose in such
a way that it will actually change position (unless pinned) ??

Greetings,


Luc Pattyn

GeneralRe: Testing
Marc Clifton
14:28 12 Feb '07  
Luc Pattyn wrote:
but can we make an object move on purpose in such
a way that it will actually change position (unless pinned) ??

I experienced this with the USB stuff I was using, and it was very predictable. However, I never tried reducing it down to a repeatable test case. You'd have to do a bunch of allocations to fragment the heap and then force garbage collection, I imagine.

Marc

Thyme In The Country

People are just notoriously impossible. --DavidCrow
There's NO excuse for not commenting your code. -- John Simmons / outlaw programmer
People who say that they will refactor their code later to make it "good" don't understand refactoring, nor the art and craft of programming. -- Josh Smith


GeneralMy .NET 1.1 approach, without generics
Luc Pattyn
12:27 12 Feb '07  
Hi Marc,

your article got me thinking. I had a simple scheme where I pass an object (typically
a byte array) to the constructor of an LP_Pinning object. I did not implement the
equivalent of your ManangedObject property, but then I also never needed it
since I always use the using(...) pattern.

So now I tested my approach for other things, and it seems to accept a struct of
bittable things (say ints); of course it rejects some others when GCHandle.Alloc
complains with "Object contains non-primitive or non-blittable data."

So here is my entire class, please feel free to comment on it:

using System;
using System.Runtime.InteropServices;          // GCHandle

namespace LP_Core {
     /// <summary>
     /// An LP_Pinning instance pins an object for as long as it is alive.
     /// </summary>
     /// <remarks>
     /// This is the preferred use when the pointer value is not needed:
     ///          using (new LP_Pinning(objectThatNeedsPinning)) {
     ///               .. use the object here, it is pinned now ...
     ///          }
     /// This is the preferred use when the pointer value is needed:
     ///          using (LP_Pinning pinning=new LP_Pinning(objectThatNeedsPinning)) {
     ///                IntPtr ptr=pinning.Ptr;
     ///               .. use the object here, it is pinned now ...
     ///          }
     /// </remarks>
     public class LP_Pinning : IDisposable {
          private GCHandle handle;
          private bool disposed;
          private IntPtr ptr;

          /// <summary>
          /// Creates an instance op LP_Pinning, and pins the argument.
          /// </summary>
          /// <param name="obj"></param>
          public LP_Pinning(object obj) {
               handle=GCHandle.Alloc(obj, GCHandleType.Pinned);
               ptr=handle.AddrOfPinnedObject();
          }

          /// <summary>
          /// Undoes the pinning.
          /// </summary>
          ~LP_Pinning() {
               Dispose();
          }

          public void Dispose() {
               if (!disposed) {
                    disposed=true;
                    handle.Free();
                    ptr=IntPtr.Zero;
               }
          }

          /// <summary>
          /// Returns the pointer to the pinned object.
          /// </summary>
          public IntPtr Ptr {get {return ptr;}}
     }    
}

Regards,

Luc Pattyn


Last Updated 11 Feb 2007 | Advertise | Privacy | Terms of Use | Copyright © CodeProject, 1999-2010