如何正确将 C++ 中的结构体向量传递到 C#?

huangapple go评论71阅读模式
英文:

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

问题

这段代码在执行时出现了错误。在调试时,代码能够执行 C++ 中的 Predict 函数,但在执行完 return 0; 后,出现了内部 CLR 错误 (0x80131506)。

英文:

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)

答案1

得分: 2

你的代码存在一些问题和不够优化的地方。

  1. 你有一个无限循环 for(unsigned int i = 0; result.size(); i++)

  2. 在本地代码中,你其实不需要复制数组,相反你想要将数组保留为全局对象,然后返回指向矢量的data()的指针,然后进行封送。这是最一般的模式,特别是如果你有不可平坦化的类型和数组,你不能以不同的方式处理它。

  3. 你实际上不只是从本地传递一个结构到托管,而是一个结构数组,这要求更多的处理。

如果你只是从本地传递一个结构到托管,你只需要 Marshal.PtrToStructure<myStruct>(pointer);

如果你需要获取结构的向量,我建议你在托管端逐个封送结构。

这是一个功能上修订的代码:

在本地:

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 / static_cast<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();
}

在托管端:

[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);
    }
}

结果:

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
英文:

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&lt;myStruct&gt;(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&lt;myStruct&gt; SomeFunction(const char* path_2d)
{
    std::vector&lt;myStruct&gt; lresult{};
    for (int i=0; i &lt;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&lt;myStruct&gt; result{};

extern &quot;C&quot; __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() =&gt; $&quot;cid,r,c: {cid} - {r} - {c}&quot;;
}

public static class NativeLibrary
{
    [DllImport(&quot;Native.dll&quot;, CallingConvention = CallingConvention.Cdecl)]
    public static extern IntPtr Predict(out int size, [MarshalAs(UnmanagedType.LPStr)] string path2Image);

    public static List&lt;myStruct&gt; GetPredict(string path = &quot;somepath&quot;)
    {
        var ptr = Predict(out int size, path);
        List&lt;myStruct&gt; results = new List&lt;myStruct&gt;();
        var structSize = Marshal.SizeOf(typeof(myStruct));
        for (var i = 0; i &lt; size; i++)
        {
            var o = Marshal.PtrToStructure&lt;myStruct&gt;(ptr);
            results.Add(o);
            ptr += structSize;
        }
        return results;
    }
}

class Program
{
    static void Main()
    {
        var result = NativeLibrary.GetPredict();
        Console.WriteLine($&quot;list of {result.Count} structures&quot;);
        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.

答案2

得分: 1

你的p/invoke声明对应一个双指针。删除ref关键字。通过值传递数组引用已经允许被调用者更改数组内容。

英文:

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.

huangapple
  • 本文由 发表于 2023年4月19日 21:49:31
  • 转载请务必保留本文链接:https://go.coder-hub.com/76055324.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定