传递结构时可以将指针编组到数组吗?

Posted

技术标签:

【中文标题】传递结构时可以将指针编组到数组吗?【英文标题】:Can a pointer be marshalled to an array when passing a struct? 【发布时间】:2018-12-25 05:00:47 【问题描述】:

在下面的示例中,C++ 中定义的dataArray 如果定义为数组,则可以工作,但不能作为指针(只是变成垃圾数据)。是否有另一种方法来编组 C# 数组,以便将指针作为数组读取?

C#

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct CSharpFoo
    int alpha;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 5]
    public int[] dataArray;
    int beta;

C++

struct CPPFoo
    int alpha;
    //int* dataArray; //Doesn't work, even though initialized to an array elsewhere
    int dataArray[5];
    int beta;

通过这样的函数传递 C#

[DllImport("MyDll.dll", CallingConvention = CallingConvention.Cdecl, BestFitMapping = false, ThrowOnUnmappableChar = true)]
public extern static bool InitializeDLL([MarshalAs(UnmanagedType.FunctionPtr)] ResultCallback callbackPointer);
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate void ResultCallback(CSharpFoo value);

C++

//Callback
typedef void(__stdcall * ResultCallback)(CPPFoo);
__declspec(dllexport) bool InitializeDLL(ResultCallback callback);

提前致谢!

编辑:: 因为“初始化到别处的数组”不清楚:

CPPFoo(int dummy) //Constructor
    alpha = 32;
    dataArray = new int[5];
    for (int i = 0; i < 5; i++)
        dataArray[i] = i;
    
    beta = 13;

//dataArray C++ 0,1,2,3,4
//alpha C# 32
//dataArray C# Total random garbage (dataArray[3] is 13!)
//beta C# 0

PS,CPPFoo 结构是一个来自 DLL 的复杂结构,所以我无法更改它。现在,为了让事情正常进行,我将它复制到一个更合适的数组中,比如 NonCreature0714 的答案,但这会导致所有数据都被复制 - 两次。这是我试图避免的双重副本。

另一个编辑: 虽然看起来对于包含单个数组的结构,值可以正确传递,但对于复杂的结构,垃圾会被扔进去。 我更新了代码以反映更复杂的结构!

【问题讨论】:

请定义“不起作用”。 垃圾数据。现在将编辑 int* dataArray; -- 对我们来说,这是垃圾数据。我们不知道这个指针指向什么,因为它是未初始化的。也许您认为您将其设置为一个局部变量(因此不再存在)。 在别处初始化为数组?我从一个单独的 DLL 接收它,文档将它称为指向数组的指针。 @NonCreature0714 因为它不仅仅是 1 个数组,它是一个复杂的结构,包含从 DLL 接收的数据(所以我无法更改它) 【参考方案1】:

就我对您的代码的理解而言,我在本地机器上做了几个测试。我对以下代码没有任何问题

c#端

struct Foo

    public int alpha;

    public IntPtr Data;

    public int beta;

    public void GetData(ref int[] buffer,int length)
    
        Marshal.Copy(Data,buffer,0,length);
    


类程序

    [DllImport("MyPtr.dll",EntryPoint = "InitializeDLL", CallingConvention = CallingConvention.Cdecl)]
    public static extern bool InitializeDLL([MarshalAs(UnmanagedType.FunctionPtr)]ResultCallback callbackPointer);

    [UnmanagedFunctionPointer(CallingConvention.StdCall)]
    public delegate void ResultCallback(ref Foo value);

    static void CallBackMe(ref Foo value)
    
        var buffer = new int[5];
        value.GetData(ref buffer,buffer.Length);
    
    static void Main(string[] args)
    
        InitializeDLL(CallBackMe);
    

C++端

struct CPPFoo 
    int* dataArrayPtr;
    CPPFoo()
    
        dataArrayPtr = new int[5];
        for (int i = 0; i < 5; i++) 
            dataArrayPtr[i] = i;
        
    
;

typedef void(__stdcall * ResultCallback)(CPPFoo);
extern "C" __declspec(dllexport) bool InitializeDLL(ResultCallback callback)

    CPPFoo f;
    callback(f);
    return true;

代码在行动

【讨论】:

这很有趣!但它没有用。等我更新问题 @Mars,你能告诉我什么不工作吗?我有这段代码没有任何问题 我更新了代码。请使用更复​​杂的结构重试 :) @Mars,是的,数组变成了垃圾值 @Mars,我刚刚点击了你的个人资料,希望能得到一些关于你住在哪里的信息,因为你说你在我离开时要离开办公室?原来我们都在日本【参考方案2】:

我在 C# 方面的经验非常有限,但似乎它可以与 int dataArray[5]; 一起使用,因为代码符合 CSharpFoo 和 CPPFoo 的类型。两者的大小都是五个整数。当您将类型更改为指针时,一个是五个整数的大小,另一个是一个小于五个整数的单个指针。

很可能传递一个动态大小的数组需要一个大小,并且要获得一个指针,您可能需要使用不安全的代码做一些事情,但我不是用 C# 编写的,所以这只是一个猜测。

struct CPPFoo 
    int* dataArray = nullptr;
    int size = 0;

【讨论】:

谢谢。将其作为固定大小的数组传递并非不可能,但这意味着将数据复制两次,这不太理想 我在想你可以传递原始数组的地址,这就是我想到 C# 不安全块的原因。能否分配地址和数组的大小。然后 C++ 代码可以将其推入 vector 类型。老实说,我有点猜测。我确实认为为了所有权需要有一份副本。如果没有复制,那么必须保证 int 数组一直保留到 C++ 代码不再引用它。 一个可以考虑的选项,但目前unsafe 没有启用,我不知道客户端会允许它 会像 int[] = new int[size]; 抱歉,我还没准备好就按了最后一条评论的输入。我认为 [MarshalAs(UnmanagedType.ByValArray, SizeConst = 5] 可能是需要更改的。可以在 C# 中使用类似以下的内容吗?没有元帅会发生什么...? public struct CSharpFoo public int[] dataArray;公共整数数据大小;【参考方案3】:

使用向量。

#include <vector>

struct CPPFoo 
    std::vector<int> dataArray;
;

Vector 更具可读性和安全性。它只是略微增加了代码的大小,并且只比使用原始数组稍微慢一点。您也不必担心保留一个大小变量,并在每次更改时对其进行更新。

【讨论】:

以上是关于传递结构时可以将指针编组到数组吗?的主要内容,如果未能解决你的问题,请参考以下文章

将结构数组从 C#(.NET Core) 传递到 C++(未管理)

如何在 C# 中编组结构数组?

包含字节数组的编组结构

C:为啥你可以通过值传递(给函数)一个结构,而不是一个数组?

P/Invoke 编组和解组 C# 和非托管 DLL 之间的二维数组、结构和指针

在 C#/C++ 之间编组复杂结构