简体   繁体   中英

Marshaling array of structs containing array fields from C++ to C#

I want to define a callback function in my C# code and pass it to some native C++ code, then have the C++ code invoke it later. The callback needs to receive a variable-length array of structs, with each struct containing some array fields.

I have had success passing a single struct with an array field, and a variable-length array of structs with scalar fields, but not a variable-length array of structs with array fields.

Here's my C# code. I'm omitting the code that registers the C# callback method with the C++ code, as I don't think that's the issue; it works fine except for the specific problematic case.

The struct:

[StructLayout(LayoutKind.Sequential)]
public struct Foo
{
    [MarshalAs(UnmanagedType.ByValArray, ArraySubType=UnmanagedType.R4, SizeConst = 2)]
    public float[] a;
}

The callback declaration

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    delegate void Callback(Int32 count, 
                          [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] Foo[] foos);

The callback method itself

public void onFoo(Int32 count, Foo[] foos) {
        Debug.Log("in onFoo callback, foo values=" + foo[0].a + ", " + foo[1].a);
}

And here's the C++ code:

First the struct:

typedef struct {
    float a[2];
} Foo;

and the callback invocation:

Foo* foos = new Foo[2];
foos[0].a[0] = 1.11;
foos[0].a[1] = 2.22;
foos[1].a[0] = 3.33;
foos[1].a[1] = 4.44;
onFoo(2, foos);
delete[] foos;

For the problematic case, my callback method is not being invoked (I get no log output). I've done quite a lot of Googling but haven't found anything covering this particular scenario. Do I need a custom marshaler?

This isn't an answer, but an observation of your callback invocation. It's been many years since I've done C++, but shouldn't the following

Foo* foos = new Foo[2];
foos[0].a = 1.11;
foos[1].a = 2.22;
onFoo(2, foos);

Be

Foo* foos = new Foo[2];
foos[0].a[0] = 1.11;
foos[0].a[1] = 1.12;
foos[1].a[0] = 2.22;
foos[1].a[1] = 2.23;
onFoo(2, foos);

OK, answering my own question here with an alternative approach that works. I'll leave it unaccepted in case someone else comes along and solves the originally stated problem.

The workaround is to pass an array of struct pointers to the callback instead of an array of structs. The callback signature changes to

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
delegate void Callback(Int32 count, 
                       [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] IntPtr[] fooPtrs);

and in the C++ code, I allocate an array of Foo*, then allocate and initialize each Foo separately, storing its pointer into the array.

Foo** foos = new Foo*[2];

Foo* foo1 = new Foo();
foo1->a[0] = 1.11;
foo1->a[1] = 2.22;
foos[0] = foo1;

Foo* foo2 = new Foo();
foo2->a[0] = 3.33;
foo2->a[1] = 4.44;
foos[1] = foo2;

onFoo(2, foos);

delete foo1;
delete foo2;
delete[] foos;

Back on the C# side, in the callback method, I allocate a Foo array, loop through the incoming IntPtr array, and marshal each element using Marshal.PtrToStructure, like so:

public void onFoo(Int32 count, IntPtr[] FooPtrs)
{
    Foo[] foos = new Foo[count];
    for (int i=0; i< count; i++)
    {
        foos[i] = (Foo)Marshal.PtrToStructure(ptrs[i], typeof(Foo));
    }
}

The downside to this approach is that there is more memory allocation and copying involved, rather than being able to map the C# struct array directly onto the memory allocated in C++.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM