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!
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.
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.More info on how arrays are mashalled at MSDN.
The ones with
ref
andout
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 afixed byte[]
, then use this code:(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?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.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:
The struct in question:
This is how I call the function and how I cast the IntPtr to my struct: