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