Passing Array into Unmanaged Code for Modification
You must definitely call handle.Free() when finished using it so GC can do the cleaning.



Note that GCHandle.Alloc(objects, GCHandleType.Pinned); only works for structs with pure primitive or to be more precise: blittable types. String fields etc. must be handled differently.



A little optimization:

You repeatedly calculate this 


  var offset = Marshal.SizeOf&lt;MyObject&gt;() * i;


Instead you can calculate offset once:

var offset = Marshal.Sizeof&lt;MyObject&gt;();


and in the loop do:

ptr = IntPtr.Add(ptr, offset);




If you are willing/allowed to run in unsafe mode you can do:

unsafe public void RunUnsafe()
{
  var objects = new MyObject[3];

  var handle = GCHandle.Alloc(objects, GCHandleType.Pinned);
  var ptr = GCHandle.ToIntPtr(handle);

  ArrayTest(ptr, objects.Length);
  MyObject* pobj = (MyObject*)ptr;

  for (int i = 0; i &lt; objects.Length; i++, pobj++)
  {
    Console.WriteLine((*pobj).X);
  }

  handle.Free();
}




If you are entitled to modify the signature of ArrayTest, you can changed it to:

    public extern static void ArrayTest([In, Out] MyObject[] objects, int length);


The [In, Out] attributes determines that the array should be marshaled both ways so that ArrayTest can work on the provided instances in the array [In], and changes made in ArrayTest are reflected in the objects when the function returns [Out].

Then your method can be simplified to:

  var objects = new MyObject[3];

  ArrayTest(objects, objects.Length);

  foreach (var obj in objects)
  {
    Console.WriteLine(obj.X);
  }