0

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?

3
  • Can you use the List container, since lists by definition are variable length? Commented Nov 14, 2016 at 17:57
  • Do you mean C++'s std::list? How would that get marshalled to C#? Commented Nov 14, 2016 at 19:58
  • Not sure, it was a thought. I don't work with c++ anymore. Commented Nov 15, 2016 at 4:09

2 Answers 2

1

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);
Sign up to request clarification or add additional context in comments.

1 Comment

yes, good catch, that was a transcription error. I updated my question, thanks.
0

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++.

Comments

Your Answer

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.