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));
}
}
}
1 Answer 1
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);
}
-
\$\begingroup\$ Thanks. So using
[In, Out]
won't require pinning? \$\endgroup\$rhughes– rhughes2019年06月29日 09:02:57 +00:00Commented Jun 29, 2019 at 9:02 -
\$\begingroup\$ @rhughes: No, the marshaler handles that - as far as I know. \$\endgroup\$user73941– user739412019年06月29日 09:30:40 +00:00Commented Jun 29, 2019 at 9:30
extern "C" class
? C only hasstruct
. \$\endgroup\$