0

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)
Soleil
  • 6,404
  • 5
  • 41
  • 61
sulabh
  • 41
  • 7
  • 3
    Your title refers to passing `std::vector` to C# (which can't be done anyway), but your code doesn't attempt to do that. Maybe you should change the title of this thread? – PaulMcKenzie Apr 19 '23 at 13:59
  • 1
    Yes I agree with your point, I am not even sure how to frame it? my main objective is to read vector of struct in my c# code. You are welcome to provide suggestions. – sulabh Apr 19 '23 at 14:04
  • Maybe consider [converting vector to array](https://stackoverflow.com/questions/2923272/how-to-convert-vector-to-array)? That should resolve the issue of types being mismatched. When that array gets to the C# layer then you have the full capacities of LINQ and constructors to turn it into whatever collection you want – Narish Apr 19 '23 at 14:10
  • Have a look at [C++/cli](https://www.codeproject.com/Articles/19354/Quick-C-CLI-Learn-C-CLI-in-less-than-10-minutes). In most cases you end up ccopying data between C++ and C# anyway unless you want to manually pin managed pointers etc... – Pepijn Kramer Apr 19 '23 at 14:17
  • `This code fails on execution` - which code? There isn't executable C# code. – GSerg Apr 19 '23 at 14:27
  • I believe pinvoke marshals arrays to a pointer and a length parameter. If you lack the length parameter there is no way for your c++ code to avoid potential out of bounds access. See [Documentation](https://learn.microsoft.com/en-us/cpp/dotnet/how-to-marshal-arrays-using-pinvoke?view=msvc-170). But I would probably just go for C++/Cli to avoid having to learn all the marshalling rules. – JonasH Apr 19 '23 at 14:47
  • @JonasH: You are correct that the C++ code needs to someone know the size of the buffer, but p/invoke doesn't automatically marshal a length parameter, the C# code needs to set that up explicitly. – Ben Voigt Apr 19 '23 at 15:26
  • You also need to pass the correct length: C++ can't work out the size of your array – Charlieface Apr 19 '23 at 15:59
  • 1
    not knowing C# too good, but I cannot see any code providing the memory for that adressed array "mystruct[]" called "p". its just a pointer to a single mystruct, but nothing more, so I suspect (!!!) that p[i] = result[i]; just splashes pointers into the void. – Synopsis Apr 19 '23 at 16:00
  • 1
    @Synopsis: The code actually calling the function isn't shown, so it's very possible that a null pointer is being passed in. What it isn't, is a pointer to a single struct. The type is "array of struct". – Ben Voigt Apr 19 '23 at 16:19
  • 1
    You have infinite loop: `for(unsigned int i = 0; result.size(); i++)` – Soleil Apr 19 '23 at 16:38
  • @BenVoigt you got it correct, If i change ref myStruct[] to ref myStruct then i get the struct, but just a struct not as an array. However I wish to get array of struct in my c# code. So i tried using ref myStruct[] however first of all it fails as mentioned by Ben. I followed his comment and removed ref, the code ran through, however results was not filled. – sulabh Apr 19 '23 at 16:41

2 Answers2

2

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.

Soleil
  • 6,404
  • 5
  • 41
  • 61
1

Your p/invoke declaration corresponds to a double pointer. Lose the ref keyword. Passing an array reference by value already allows the callee to change the array contents.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • Yes that solved the error problem however the values were not updated. – sulabh Apr 19 '23 at 16:45
  • 1
    https://learn.microsoft.com/en-us/dotnet/framework/interop/default-marshalling-for-arrays "In contrast, the interop marshaller passes an array as In parameters by default." So you need to apply `OutAttribute`. e.g. `[Out] myStruct[] results` – Ben Voigt Apr 19 '23 at 16:50