Search code examples
c#c++unmanagedmanaged

How to correctly pass a vector of struct from C++ to C#?


Here is my code in C++

struct myStruct
{
    int cid;
    float c;
    float r;
};

int Predict(myStruct* p, const char* path2D)
{
    std::vector<myStruct> result = SomeFunction(path2D);//e.g., result.size() = 2 here
    for(unsigned int i = 0; result.size(); i++)
    {
        p[i] = result[i];
    }
    return 0;
}
extern "C"{
   int __declspec(dllexport) Predict(myStruct* predictions, const char* path2D);
          }

call inside the C#

[StructLayout(LayoutKind.Sequential)]
public struct myStruct
{
    public int cid;
    public float c;
    public float r;
}
    
[DllImport("mydll.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int Predict([MarshalAs(UnmanagedType.LPArray)] ref myStruct[] results, [MarshalAs(UnmanagedType.LPStr)] string path2Image);

This code fails on execution. Any idea / suggestion would be helpful what might be wrong here?

Also when i debug the code it goes through C++ function Predict until return 0; after which it throws error.

Fatal error. Internal CLR error. (0x80131506)

Solution

  • Your code have several problems and suboptimal points.

    1. You have an infinite loop for(unsigned int i = 0; result.size(); i++)

    2. You do not need to copy your array (in native), instead you want to keep your array as a global object, and return the pointer to the data() of the vector and then marshal it. This is the most general schema, in particular, if you have unblittable types and arrays, you can't do it differently.

    3. You are actually not just passing a struct from native to managed, but an array of struct, which is a bit more demanding.

    If you are passing just one struct from native to managed, you only only need Marshal.PtrToStructure<myStruct>(pointer);

    If you need to get a vector of structures, I advise you to marshal the structs one at a time on the managed side.

    Here a functionnal revised code:

    native:

    struct myStruct
    {
        int cid;
        float c;
        float r;
    };
    
    std::vector<myStruct> SomeFunction(const char* path_2d)
    {
        std::vector<myStruct> lresult{};
        for (int i=0; i <10; i++)
        {
            myStruct o{};
            o.cid = i;
            o.c = 1.f / (float)i;
            o.r = 1.f - o.c;
            lresult.push_back(o);
        }
        return lresult;
    }
    
    std::vector<myStruct> result{};
    
    extern "C" __declspec(dllexport) myStruct* Predict(int* size, const char* path2D)
    {
        result = SomeFunction(path2D);
        *size = result.size();
        return result.data();
    }
    

    managed:

    [StructLayout(LayoutKind.Sequential)]
    public struct myStruct
    {
        public int cid;
        public float c;
        public float r;
        
        public override string ToString() => $"cid,r,c: {cid} - {r} - {c}";
    }
    
    public static class NativeLibrary
    {
        [DllImport("Native.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern IntPtr Predict(out int size, [MarshalAs(UnmanagedType.LPStr)] string path2Image);
    
        public static List<myStruct> GetPredict(string path = "somepath")
        {
            var ptr = Predict(out int size, path);
            List<myStruct> results = new List<myStruct>();
            var structSize = Marshal.SizeOf(typeof(myStruct));
            for (var i = 0; i < size; i++)
            {
                var o = Marshal.PtrToStructure<myStruct>(ptr);
                results.Add(o);
                ptr += structSize;
            }
            return results;
        }
    }
    
    class Program
    {
        static void Main()
        {
            var result = NativeLibrary.GetPredict();
            Console.WriteLine($"list of {result.Count} structures");
            foreach (var o in result)
                Console.WriteLine(o);
        }
    }
    

    result:

    list of 10 structures
    cid,r,c: 0 - -∞ - ∞
    cid,r,c: 1 - 0 - 1
    cid,r,c: 2 - 0,5 - 0,5
    cid,r,c: 3 - 0,6666666 - 0,33333334
    cid,r,c: 4 - 0,75 - 0,25
    cid,r,c: 5 - 0,8 - 0,2
    cid,r,c: 6 - 0,8333333 - 0,16666667
    cid,r,c: 7 - 0,85714287 - 0,14285715
    cid,r,c: 8 - 0,875 - 0,125
    cid,r,c: 9 - 0,8888889 - 0,11111111
    

    remarks:

    1. you need to have a global instance, in order to keep the object alive; the alternative is to use the pointer of a class instance created by new MyClass().

    2. the functions of your managed structure are not taken into account for your structure size; and with blittable types, the structure size is the same in managed and native.