Передача массива структуры в C ++ DLL из C # - PullRequest
3 голосов
/ 30 декабря 2010

Я пытаюсь передать массив структуры в C ++ DLL и сталкиваюсь с проблемами.Я пытался понять это в течение нескольких дней безрезультатно.Я могу получить данные из C ++, я просто сталкиваюсь с проблемами, когда пытаюсь получить массив struct с помощью .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);

Моя структура - это 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());
}

Еще раз спасибо.Любая помощь будет принята с благодарностью, потому что то, что я считаю простой задачей, действительно заставляет меня замолчать!

Ответы [ 4 ]

4 голосов
/ 30 декабря 2010

Вам придется использовать свойство CallingConvention в атрибуте [DllImport].По умолчанию используется StdCall, вам нужен Cdecl, поскольку объявление C ++ не использовало __stdcall.

1 голос
/ 30 декабря 2010

Если 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 .

0 голосов
/ 14 февраля 2018

У меня была такая же проблема с необходимостью передать пустой массив из C # в функцию C в DLL.Затем функция возвращает указатель, указывающий на первый элемент массива, заполненный структурами.

Так я объявляю внешнюю функцию:

[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);
}
0 голосов
/ 30 декабря 2010

Те с ref и out не работают, потому что они передают указатель на ссылку, а не указатель на первый элемент.


Редактировать 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 ничего не изменится, если вы не измените выравнивание, потому что структура будет дополнена для достижения выравнивания по умолчанию.

...