Marshalling structure from Unnty to C++ and backward

I am trying to marshal a structure from managed code to unmanaged, perform operations on it in C++ and return the result back by out parameter.

The structure has a double array and an int array (it’s just a Mesh-structure with two arrays of double and int). This approach worked fine when it was a dll for net.core and C++, but when I moved the MyMesh.cs structure from the net.core application to the unity project, I get the following error when marshaling.

Structure field of type Double[] can't be marshalled as LPArray

What is the best way to transfer a structure to a C++ DLL, perform operations on it in C++ code, and screw the new structure into the managed code in the Unity script?

The code that I use in C# and C++ is below:

MyMesh.cs

public enum BooleanType
{
	Union,
	Inter,
	Dif
}

public struct MyMesh
{
	private IntPtr floatsPtr;
	private IntPtr indexesPtr;

	private int floatsLength;
	private int indexesLength;

	[NonSerialized]
	private double[] floats;
	[NonSerialized]
	private int[] indexes;

	public MyMesh(double[] floats, int[] indexes)
	{
		this.floats = floats;
		floatsLength = floats.Length;
		floatsPtr = Marshal.AllocHGlobal(floatsLength * sizeof(double));
		Marshal.Copy(floats, 0, floatsPtr, floatsLength);

		this.indexes = indexes;
		indexesLength = indexes.Length;
		indexesPtr = Marshal.AllocHGlobal(indexesLength * sizeof(int));
		Marshal.Copy(indexes, 0, indexesPtr, indexesLength);
	}

	private void LoadAndClear()
	{
		floats = new double[floatsLength];
		Marshal.Copy(floatsPtr, floats, 0, floatsLength);

		indexes = new int[indexesLength];
		Marshal.Copy(indexesPtr, indexes, 0, indexesLength);

		ClearMyMeshExtern(this);
		floatsPtr = IntPtr.Zero;
		indexesPtr = IntPtr.Zero;
	}

	private void ClearLocal()
	{
		Marshal.FreeHGlobal(floatsPtr);
		Marshal.FreeHGlobal(indexesPtr);
	}

	public static bool Load(string path, out MyMesh myMesh)
	{
		LoadExtern(path, out myMesh);
		myMesh.LoadAndClear();
		return true;
	}

	[DllImport(pathDll, CallingConvention = CallingConvention.Cdecl)]
	private static extern void ClearMyMeshExtern(MyMesh input);

	[DllImport(pathDll, CallingConvention = CallingConvention.Cdecl)]
	private static extern int LoadExtern(string path, out MyMesh output);

	private const string pathDll = "MyMeshNative.dll";
}

And Unmanaged-part:

MyMesh.h

#pragma once

struct MyMesh {
    double* floatsPtr;
    int* indexesPtr;

    int floatsLength;
    int indexesLength;
};

MyMesh.cpp

__declspec(dllexport) int LoadExtern(const char* path, MyMesh* output) {
    Mesh mesh;
    LoadMesh(path, mesh);
    ConvertToMyMesh(mesh, output);

    return 0;
}

Thanks in advance to everyone who can help!

Solved!

Replaced [NonSereliazed] to [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)]

[MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)]
private double[] listDouble;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)]
private int[] listInt;
1 Like

That doesn’t seem like a right fix. That will always treat the array as having exactly 1 element in it. Did you try using UnmanagedType.LPArray instead, without a SizeConst?

No, I haven’t tried it. In fact, I don’t need to marshal these two arrays at all, they should only be in managed code, but as I understand it, the NonSerialized attribute is not supported either in Unity or in NetStandart in the context of marshalling. If there is a more preferable way to exclude these two arrays from the marshaling process inside the MyMesh structure, they will be very glad to see them!

.NET doesn’t have an option to not marshal types in a structure passed in to native code. The usual solution is to create a second structure that’s used just for passing it to native:

struct MyMesh
{
    private double[] floats;
    private int[] indexes;

    struct MyMeshNative
    {
        public IntPtr floatsPtr;
        public IntPtr indexesPtr;

        public int floatsLength;
        public int indexesLength;
    }

    public static bool Load(string path, out MyMesh myMesh)
    {
        LoadExtern(path, out var myMeshNative);

        var myMesh = new MyMesh();
        myMesh.floats = new double[myMeshNative.floatsLength];
        Marshal.Copy(myMeshNative.floatsPtr, myMesh.floats, 0, myMeshNative.floatsLength);

        myMesh.indexes = new int[myMeshNative.indexesLength];
        Marshal.Copy(myMeshNative.indexesPtr, myMesh.indexes, 0, myMeshNative.indexesLength);

        ClearMyMeshExtern(myMeshNative);
        return true;
    }

    [DllImport(pathDll, CallingConvention = CallingConvention.Cdecl)]
    private static extern void ClearMyMeshExtern(MyMeshNative input);

    [DllImport(pathDll, CallingConvention = CallingConvention.Cdecl)]
    private static extern int LoadExtern(string path, out MyMeshNative output);
2 Likes