3

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!

5 Answers 5

4

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.

Sign up to request clarification or add additional context in comments.

3 Comments

Hi. Sorry. I didn't follow that at all. Could you please explain.
Google +dllimportattribute +callingconvention. Third hit looks good.
This is infact the correct answer. I had a similar problem and it was solved with this solution. In this case add:[DllImport("SocketAPI.dll", CallingConvention=CallingConvention.Cdecl)]
2

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

Comments

1

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.

1 Comment

This got the array full of 0's again. I'll read more at the link you provided. Thanks.
0

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.

10 Comments

Hi. Thank you for your reply. My preference is to use safe code. However, at this point, I just want it to work. I don't understand how to implement what you wrote in 'Edit 2'. Thanks.
Hmmm... the "[Out]" may have done it. I'll have to do some more testing. This is a hard one to search for on Google. What does [Out] mean? How does it differ from out?
It looks like the first element is returning correctly, after that something goes astray. I'll work with it awhile and see if I have a size wrong somewhere. Thanks!
Did some thorough checking. The first element in the buffer_node array is intact. The rest of the elements are corrupt.
About the [Out] attribute: It means that you want the data marshaled back from the C code, but into your original array. (The runtime doesn't pass a direct pointer; it copies the data in one or both directions.) If you said out, that would mean that you're getting back a pointer to a new array, which isn't the case here. Regarding the size: If you really need an odd size, you have to use the Pack attribute: [StructLayout(LayoutKind.Sequential, Size = 23, Pack = 1)]; otherwise it won't do what you want.
|
0

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.