4
\$\begingroup\$

I have an array allocated in C# which I am passing into unmanaged code to be modified. The following code works, but I am not sure if there are any other more 'correct' or 'efficient' methods that I am not aware of.

Code.c

extern "C" {
 class MyObject
 {
 public:
 int32_t X;
 };
 __declspec(dllexport) void ArrayTest(MyObject* a, int length);
 void ArrayTest(MyObject* a, int length)
 {
 for (int i = 0; i < length; i++)
 {
 a[i].X = i + 1;
 }
 }
}

Code.cs

class Program
{
 [StructLayout(LayoutKind.Sequential)]
 public struct MyObject
 {
 public Int32 X;
 }
 [DllImport(@"MyDll.dll", CallingConvention = CallingConvention.Cdecl)]
 public extern static void ArrayTest(IntPtr objects, int length);
 static void Main(string[] args)
 {
 var objects = new MyObject[3];
 var handle = GCHandle.Alloc(objects, GCHandleType.Pinned);
 var ptr = GCHandle.ToIntPtr(handle);
 ArrayTest(ptr, objects.Length);
 for (int i = 0; i < objects.Length; i++)
 {
 var offset = Marshal.SizeOf<MyObject>() * i;
 objects[i] = Marshal.PtrToStructure<MyObject>(IntPtr.Add(ptr, offset));
 }
 }
}
asked Jun 29, 2019 at 3:17
\$\endgroup\$
1
  • \$\begingroup\$ extern "C" class? C only has struct. \$\endgroup\$ Commented Jun 29, 2019 at 10:04

1 Answer 1

5
\$\begingroup\$

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<MyObject>() * i;

Instead you can calculate offset once:

var offset = Marshal.Sizeof<MyObject>();

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 < 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);
 }
answered Jun 29, 2019 at 8:57
\$\endgroup\$
2
  • \$\begingroup\$ Thanks. So using [In, Out] won't require pinning? \$\endgroup\$ Commented Jun 29, 2019 at 9:02
  • \$\begingroup\$ @rhughes: No, the marshaler handles that - as far as I know. \$\endgroup\$ Commented Jun 29, 2019 at 9:30

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.