Как маршалировать произвольные структуры и объединения между C # и C?

#c# #c #interop #marshalling

#c# #c #взаимодействие #маршалирование

Вопрос:

У меня есть программа на C, скомпилированная в DLL, которую я загружаю в C # (используя kernel32.dll LoadLibrary и FreeLibrary). Используя GetProcAddress, я могу получить указатель на некоторые данные в памяти, и я хочу преобразовать эти данные в эквивалентную структуру в C #.

Моей первой идеей было создать структуры на C #, которые отображались бы непосредственно на структуры C (используя атрибуты StructLayout и MarshalAs ), а затем просто использовать Marshal класс (в System.Runtime.InteropServices ). Это хорошо работает для «более простых» структур, но не работает для многомерных массивов и объединений массивов.

Структуры данных в C выглядят следующим образом (например):

 struct MyStruct 
{
    union MyUnion my_data[16][16];
}
union MyUnion
{
    uint32_t ints[2];
    uint8_t bytes[8];
}
  

Т. е. он содержит произвольно вложенные структуры и объединения и их многомерные массивы.

Есть ли встроенный способ в C # для обработки маршалинга этих типов структур данных?


Редактировать:

Мои требования таковы:

  • Объединения C # должны работать так же, как и объединения C, т. Е. запись в bytes поле также должна изменять ints поле (afaik, используя явный макет структуры, вы можете создавать объединения только встроенных типов значений в C #). (Это скорее проблема C #, чем проблема маршалинга)
  • Структуры C # должны быть маршалируемыми для C / C и обратно

Как отметил Селвин в комментариях, можно сгладить многомерные массивы в одно измерение, а затем получить доступ с помощью [x y * 16]. Однако я предпочитаю, чтобы исходные размеры были сохранены, а метод доступа такой же, как в C.

Мой ответ ниже делает то, что я хочу, но поскольку маршалинг между C # и C настолько распространен, кажется, что должен быть более простой способ достижения этого результата.

Комментарии:

1. Добро пожаловать в SO! Обратите внимание, что формулировка » Каков наилучший способ … » может привести к тому, что ваш вопрос скоро будет закрыт, потому что прямой ответ, скорее всего, будет больше основан на мнениях, чем на фактах. Я отредактировал ваш вопрос, потому что в общем виде вы даже не просите об этом.

2. я почти уверен, что my_data[256] это должно работать с такими доступом, как my_data[x y*16]

3. Здесь есть несколько хороших примеров .

4. Взгляните на классы маршалинга, структуры и объединения . Там вы найдете несколько примеров. Другой способ, который я предпочитаю, особенно для сложных структур c и / или вызова нескольких функций простой C DLL, — это создание C / CLI -библиотеки DLL-оболочки.

5. Спасибо за ответы. Моя самая большая проблема — это маршалирование объединений массивов, которое встроенному маршалл действительно не нравится. Для объединения в моем примере выше запись в bytes поле также должна изменять значения в ints поле. Может быть, это отдельный вопрос? Как говорит @Selvin, многомерные массивы можно свести к одному массиву. Я пробовал это, и это работает нормально, но я бы предпочел использовать тот же метод доступа к значениям, который используется в структурах C, для согласованности.

Ответ №1:

Мое текущее решение заключается в создании класса-оболочки вокруг необработанных данных byte[] с средствами доступа, которые имитируют структуру базовых данных.

Например, для простой структуры в C:

 struct MySimpleStruct
{
    uint32_t my_ints[16];
}
  

Я создаю оболочку:

 public class MySimpleStruct
{

    private byte[] _underlying;
    public UintAccessor[] MyInts { get; }

    public MySimpleStruct(byte[] data) 
    {
        _underlying = data;
        MyInts = new UintAccessor[16];
        // for-loop to initialize each accessor with the right index
    }
}
public class UintAccessor
{
    public uint Value 
    {
        get => BitConverter.ToUint32(_underlying, _idx);
        set => BitConverter.GetBytes(value).CopyTo(_underlying, _idx);
    }
    public UintAccessor(byte[] data, int index) { /* ... */ }
}
  

и оболочку можно использовать следующим образом

 var myStruct = new MySimpleStruct(theData);
myStruct.MyInts[5].Value = 123;
  

Эти классы-оболочки могут стать довольно сложными, однако мы автоматически генерируем структуры C, и мы будем генерировать эти оболочки C # одновременно, поэтому сложность возникает только при генерации кода.