将结构体数组传递到 C++来自 C# 的 DLL

发布于 2024-10-09 19:16:20 字数 3239 浏览 2 评论 0原文

我试图将结构数组传递到 C++ DLL 中并遇到问题。我已经尝试了好几天了,但没有成功。我可以从 C++ 中获取数据,但当我尝试使用 .NET 获取结构数组时,我遇到了问题。

C++ 原型是:

static __declspec(dllexport) int SocketAPI::api_get_data(int iSize, buffer_node *data); 

在我的 C# 代码中,我将函数定义为:

[DllImport("SocketAPI.dll")]
static extern int api_get_data(int iSize, buffer_node[] data);

我的 Struct 是 buffer_node,其定义为:

[StructLayout(LayoutKind.Sequential, Size = 23), Serializable]
public struct header
{
    // HEADER
    public UInt16 h_type; 
    public UInt32 frame_num;  
    public UInt16 count_1pps;   
    public byte data_options;   
    public byte project_type;   
    public byte tile_num;     
    public byte tile_set;          
    public byte total_rows;     
    public byte total_cols;      
    public byte num_rows;         
    public byte num_cols;       
    public byte first_row;        
    public byte first_col;      
    public UInt16 num_sensors;      
    public UInt16 num_data_bytes; 
    public byte h_checksum;
}

[StructLayout(LayoutKind.Sequential, Size = 25), Serializable]
public struct footer
{
    // FOOTER
    public UInt16 f_type;  
    public byte ts_len;           
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)]
    public byte[] ts_array;
    public byte frame_status;
    public byte f_checksum;     
}

[StructLayout(LayoutKind.Sequential, Size = 51), Serializable]
public struct buffer_node
{
    // HEADER
    public header data_header;

    // DATA
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
    public byte[] data;

    // FOOTER
    public footer data_footer;
}

如果尝试以下导入:

// See buffer, but everything is 0 - ie. not being populated
unsafe static extern int api_get_data(int iSize, buffer_node[] data); 

// fails somewhere in the API
static extern int api_get_data(int iSize, out buffer_node[] data); 

static extern int api_get_data(int iSize, ref buffer_node[] data);

我的 C# GetData 程序当前如下所示:

// Get current data size
int iSize = api_is_data_available();

// Create buffer to hold the data
buffer_node[] buf_data = new buffer_node[iSize];

for (int i = 0; i < iSize; i++)
{
    buf_data[i].data = new byte[3];
    buf_data[i].data_footer.ts_array = new byte[20];
}

// Get the data
//int iStructSize = Marshal.SizeOf(buf_data[0]);
//IntPtr bufNodePtr = IntPtr.Zero;
//IntPtr buffer = Marshal.AllocHGlobal(iStructSize * iSize);
//api_get_data(iSize, buffer);
//for (int i = 0; i < iSize; i++)
//{
//    IntPtr ptr = new IntPtr(buffer.ToInt64() + iStructSize * i);
//    buf_data[i] = (buffer_node)Marshal.PtrToStructure(ptr, typeof(buffer_node));
//}

//api_get_data(iSize, buf_data); // See buffer, but everything is 0 - ie. not being populated
// api_get_data(iSize, out buf_data); // fails no error
api_get_data(iSize, ref buf_data); // fails no error
// api_get_data(iSize, ref buf_data);

// Print the data
for (int i = 0; i < iSize; i++)
{
    StringBuilder sb = new StringBuilder();
    sb.Append("Tile Number: " + Convert.ToString(buf_data[i].data_header.tile_num));
    AppendTextBox(sb.ToString());
}

再次感谢。任何帮助将不胜感激,因为我认为这是一项简单的任务,但实际上却让我陷入了困境!

I'm trying to pass a struct array into a C++ DLL and running into issues. I've been trying to figure it out for several days with no avail. I can get the data fine from from C++, I just run into problems when I try to get the struct array using .NET.

The C++ prototype is:

static __declspec(dllexport) int SocketAPI::api_get_data(int iSize, buffer_node *data); 

In my C# code, I defined the function as:

[DllImport("SocketAPI.dll")]
static extern int api_get_data(int iSize, buffer_node[] data);

My Struct is buffer_node which is defined as:

[StructLayout(LayoutKind.Sequential, Size = 23), Serializable]
public struct header
{
    // HEADER
    public UInt16 h_type; 
    public UInt32 frame_num;  
    public UInt16 count_1pps;   
    public byte data_options;   
    public byte project_type;   
    public byte tile_num;     
    public byte tile_set;          
    public byte total_rows;     
    public byte total_cols;      
    public byte num_rows;         
    public byte num_cols;       
    public byte first_row;        
    public byte first_col;      
    public UInt16 num_sensors;      
    public UInt16 num_data_bytes; 
    public byte h_checksum;
}

[StructLayout(LayoutKind.Sequential, Size = 25), Serializable]
public struct footer
{
    // FOOTER
    public UInt16 f_type;  
    public byte ts_len;           
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)]
    public byte[] ts_array;
    public byte frame_status;
    public byte f_checksum;     
}

[StructLayout(LayoutKind.Sequential, Size = 51), Serializable]
public struct buffer_node
{
    // HEADER
    public header data_header;

    // DATA
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
    public byte[] data;

    // FOOTER
    public footer data_footer;
}

If tried the following Imports:

// See buffer, but everything is 0 - ie. not being populated
unsafe static extern int api_get_data(int iSize, buffer_node[] data); 

// fails somewhere in the API
static extern int api_get_data(int iSize, out buffer_node[] data); 

static extern int api_get_data(int iSize, ref buffer_node[] data);

My C# GetData program currently looks like this:

// Get current data size
int iSize = api_is_data_available();

// Create buffer to hold the data
buffer_node[] buf_data = new buffer_node[iSize];

for (int i = 0; i < iSize; i++)
{
    buf_data[i].data = new byte[3];
    buf_data[i].data_footer.ts_array = new byte[20];
}

// Get the data
//int iStructSize = Marshal.SizeOf(buf_data[0]);
//IntPtr bufNodePtr = IntPtr.Zero;
//IntPtr buffer = Marshal.AllocHGlobal(iStructSize * iSize);
//api_get_data(iSize, buffer);
//for (int i = 0; i < iSize; i++)
//{
//    IntPtr ptr = new IntPtr(buffer.ToInt64() + iStructSize * i);
//    buf_data[i] = (buffer_node)Marshal.PtrToStructure(ptr, typeof(buffer_node));
//}

//api_get_data(iSize, buf_data); // See buffer, but everything is 0 - ie. not being populated
// api_get_data(iSize, out buf_data); // fails no error
api_get_data(iSize, ref buf_data); // fails no error
// api_get_data(iSize, ref buf_data);

// Print the data
for (int i = 0; i < iSize; i++)
{
    StringBuilder sb = new StringBuilder();
    sb.Append("Tile Number: " + Convert.ToString(buf_data[i].data_header.tile_num));
    AppendTextBox(sb.ToString());
}

Thank you again. Any help would be greatly appreciated, as what I though would be a simple task is really throwing me for a loop!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

雨轻弹 2024-10-16 19:16:20

您必须使用 [DllImport] 属性中的 CallingConvention 属性。默认是StdCall,这里需要Cdecl,因为C++声明没有使用__stdcall。

You will have to use the CallingConvention property in the [DllImport] attribute. The default is StdCall, you need Cdecl here since the C++ declaration didn't used __stdcall.

可爱暴击 2024-10-16 19:16:20

对 buffer_node[] 数据参数使用 [In, Out] 属性:

[DllImport("SocketAPI.dll")]
static extern int api_get_data(int iSize, [In, Out] buffer_node[] data);

Use [In, Out] attributes for buffer_node[] data parameter:

[DllImport("SocketAPI.dll")]
static extern int api_get_data(int iSize, [In, Out] buffer_node[] data);
勿忘心安 2024-10-16 19:16:20

如果 int iSize 是数组元素的大小(例如 data.Length),请尝试使用 MarshallAs.SizeParamIndex。这将告诉编组器数据中应包含多少个元素。

[DllImport("SocketAPI.dll")]
static extern int api_get_data(int iSize, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex=0)]  buffer_node[] data);

有关数组如何聚合的更多信息,请访问 MSDN。

If int iSize is the size of the array in elements (e.g. data.Length), try using MarshallAs.SizeParamIndex. That will tell the marshaller how many elements should be in data.

[DllImport("SocketAPI.dll")]
static extern int api_get_data(int iSize, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex=0)]  buffer_node[] data);

More info on how arrays are mashalled at MSDN.

蓝海 2024-10-16 19:16:20

带有 refout 的不起作用,因为它们传递的是指向引用的指针,而不是指向第一个元素的指针。


编辑1:我刚刚注意到,你不能像现在一样传递数组——结构内的托管数组通常不会按照你想要的方式进行编组。当我想到一个解决方案时,我会写一个解决方案,但我认为你必须手动整理事情。


编辑 2:如果您能够使用不安全的代码,那么这应该可以解决问题:将 ByValArray 中的所有内容更改为 fixed byte[],然后使用此代码:(

[StructLayout(LayoutKind.Sequential, Size = 23), Serializable]
public struct header
{
    // HEADER
    public UInt16 h_type; 
    public UInt32 frame_num;  
    public UInt16 count_1pps;   
    public byte data_options;   
    public byte project_type;   
    public byte tile_num;     
    public byte tile_set;          
    public byte total_rows;     
    public byte total_cols;      
    public byte num_rows;         
    public byte num_cols;       
    public byte first_row;        
    public byte first_col;      
    public UInt16 num_sensors;      
    public UInt16 num_data_bytes; 
    public byte h_checksum;
}

[StructLayout(LayoutKind.Sequential, Size = 25), Serializable]
public struct footer
{
    // FOOTER
    public UInt16 f_type;  
    public byte ts_len;           
    public unsafe fixed byte ts_array[20];
    public byte frame_status;
    public byte f_checksum;     
}

[StructLayout(LayoutKind.Sequential, Size = 51), Serializable]
public struct buffer_node
{
    // HEADER
    public header data_header;

    // DATA
    public unsafe fixed byte data[3];

    // FOOTER
    public footer data_footer;
}

unsafe static extern int api_get_data(int iSize, buffer_node* pData);


//...

// Get current data size
int iSize = api_is_data_available();

// Create buffer to hold the data
buffer_node[] buf_data = new buffer_node[iSize];

unsafe
{
    fixed (buffer_node* pBufData = buf_data)
    {
        api_get_data(iSize, pBufData); // fails no error
    }
}

您必须将声明更改为指向元素的指针。)


编辑 3:我刚刚注意到...您是否尝试过像这样说 [Out]

[DllImport("SocketAPI.dll")]
static extern int api_get_data(int iSize, [Out] buffer_node[] data);

这可能会起作用,而不需要像我上面所做的那样痛苦。

旁注:除非您还更改对齐方式,否则说 Size = 23 不会执行任何操作,因为结构将被填充以达到默认对齐方式。

The ones with ref and out don't work, because they pass a pointer to the reference, not a pointer to the first element.


Edit 1: I just noticed, you can't pass arrays around like you're doing right now -- managed arrays inside structs don't usually get marshaled the way you want them. I'll write a solution when I think of one, but I think you're going to have to marshal things by hand.


Edit 2: If you're able to use unsafe code, then this should fix the problem: Change everything from a ByValArray to a fixed byte[], then use this code:

[StructLayout(LayoutKind.Sequential, Size = 23), Serializable]
public struct header
{
    // HEADER
    public UInt16 h_type; 
    public UInt32 frame_num;  
    public UInt16 count_1pps;   
    public byte data_options;   
    public byte project_type;   
    public byte tile_num;     
    public byte tile_set;          
    public byte total_rows;     
    public byte total_cols;      
    public byte num_rows;         
    public byte num_cols;       
    public byte first_row;        
    public byte first_col;      
    public UInt16 num_sensors;      
    public UInt16 num_data_bytes; 
    public byte h_checksum;
}

[StructLayout(LayoutKind.Sequential, Size = 25), Serializable]
public struct footer
{
    // FOOTER
    public UInt16 f_type;  
    public byte ts_len;           
    public unsafe fixed byte ts_array[20];
    public byte frame_status;
    public byte f_checksum;     
}

[StructLayout(LayoutKind.Sequential, Size = 51), Serializable]
public struct buffer_node
{
    // HEADER
    public header data_header;

    // DATA
    public unsafe fixed byte data[3];

    // FOOTER
    public footer data_footer;
}

unsafe static extern int api_get_data(int iSize, buffer_node* pData);


//...

// Get current data size
int iSize = api_is_data_available();

// Create buffer to hold the data
buffer_node[] buf_data = new buffer_node[iSize];

unsafe
{
    fixed (buffer_node* pBufData = buf_data)
    {
        api_get_data(iSize, pBufData); // fails no error
    }
}

(You'll have to change the declaration to be a pointer to an element.)


Edit 3: I just noticed... have you tried saying [Out] like this?

[DllImport("SocketAPI.dll")]
static extern int api_get_data(int iSize, [Out] buffer_node[] data);

That might just work, without the pain of doing what I did above.

Side note: Saying Size = 23 won't do anything unless you also change the alignment, because the structure will be padded to reach the default alignment.

长途伴 2024-10-16 19:16:20

我遇到了同样的问题,必须将一个空数组从 C# 传递到 dll 中的 C 函数。然后,该函数将返回指向填充结构的数组的第一个元素的指针。

这是我声明外部函数的方式:

[DllImport(LIB_NAME, CallingConvention = CallingConvention.StdCall, EntryPoint = "getData")]
unsafe extern void getData(IntPtr data, ref UInt32 dataLen);

有问题的结构:

[StructLayout(LayoutKind.Sequential)]
internal struct DataC
{
    internal UInt16 xRes, yRes;
    internal fixed float rot[9];
}

这是我调用该函数以及将 IntPtr 转换为我的结构的方式:

unsafe
{
    UInt32 dataLen = 10;
    IntPtr dataPtr = Marshal.AllocHGlobal((int)dataLen * Marshal.SizeOf(typeof(DataC)));
    getData(dataPtr, ref dataLen);
    // check here for null, obviously
    DataC* dataArr = (DataC*)dataPtr;
    for (int i = 0; i < dataLen; i++)
    {
        DataC data = dataArr[i];
        // I fill a managed class/struct with the unmanaged data and add it to a List or whatever
        result.Add(new Data(data->xRes, data->yRes, data->rot[0], ...));
    }
    // As we have the data in managed memory now, we free the allocated space
    Marshal.FreeHGlobal(dataPtr);
}

I had the same problem with having to pass an empty array from C# to a C function in a dll. The function would then return the pointer pointing to the first element of the array filled with structs.

This is how I declare the external function:

[DllImport(LIB_NAME, CallingConvention = CallingConvention.StdCall, EntryPoint = "getData")]
unsafe extern void getData(IntPtr data, ref UInt32 dataLen);

The struct in question:

[StructLayout(LayoutKind.Sequential)]
internal struct DataC
{
    internal UInt16 xRes, yRes;
    internal fixed float rot[9];
}

This is how I call the function and how I cast the IntPtr to my struct:

unsafe
{
    UInt32 dataLen = 10;
    IntPtr dataPtr = Marshal.AllocHGlobal((int)dataLen * Marshal.SizeOf(typeof(DataC)));
    getData(dataPtr, ref dataLen);
    // check here for null, obviously
    DataC* dataArr = (DataC*)dataPtr;
    for (int i = 0; i < dataLen; i++)
    {
        DataC data = dataArr[i];
        // I fill a managed class/struct with the unmanaged data and add it to a List or whatever
        result.Add(new Data(data->xRes, data->yRes, data->rot[0], ...));
    }
    // As we have the data in managed memory now, we free the allocated space
    Marshal.FreeHGlobal(dataPtr);
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文