I have a matrix class which contains a fixed size float array that I want to expose directly in the C# wrapper class:
class Matrix
{
public:
float data[16];
...
};
Normally, when wrapping this structure in C# I would define the wrapped type as a POD struct that would utilize [StructLayout(LayoutKind.Sequential)] and would allow me to directly marshall the class to and from C++ / C#. However, the c++ class exposes other types (via members or functions) which can't be marshalled in this way so I instead have to use a PIMPL mechanism where my C# class contains an opaque pointer (IntPtr) to the underlying C++ structure. However, I would still like to directly expose the underlying data member of the c++ structure to the C# object. By directly, I mean that modifications to the elements of data in C# will be directly mirrored in the underlying C++ structure (i.e. the C# member can not be a copy of the C++ member). My idea was to expose the data member in C# like this:
public class Matrix
{
protected HandleRef cptr; // opaque reference to equivalent C++ instance
public float [] data
{
get
{
return Get_data(cptr);
}
}
[DllImport(MY_CPP_DLL, EntryPoint = "CSharp_Get_data", CharSet = CHARSET)]
[return: MarshalAs(UnmanagedType.LPArray, SizeConst = 16, ArraySubType = UnmanagedType.R4)]
private static extern float [] Get_Data(HandleRef in_object);
};
Where my C++ dll contains the function CSharp_Get_data which looks like this:
DLLEXPORT float * STDCALL CSharp_Get_data(Matrix *obj)
{
return obj->data;
}
This compiles fine in both C++ and C#, but when I try and do something like this in c#:
Matrix asdf = new Matrix();
asdf.data[0] = 2.0f;
I receive a System.Runtime.InteropServices.MarshalDirectiveException which states Additional information: Cannot marshal 'return value': Invalid managed/unmanaged type combination.
Is there a way I can use this approach but not encounter this error? I know this is a pretty odd use case but I feel as if the approach does not violate any of the marshalling standards described by MS here: https://learn.microsoft.com/en-us/dotnet/framework/interop/default-marshaling-for-arrays.
Thanks for any help.
You can't get a float[] unless .NET allocates the memory. That's true of StructLayout(Sequential), too -- fixed-size buffers such as float[16] are not compatible with float[].
What you can do, though, if you want convenience and efficiency more than you want float[], is to use the new zero-copy buffering types. You can construct a System.Span<float> from a pointer to the first element plus the number of elements, and subscripting that span will give you direct access to the memory. Some refactoring will need to be done, but since Span<T> can access .NET arrays equally well, once you rewrite your code to use Span, it can live in both worlds (C# and C++-allocated data).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With