30 September 2010

Вызов native кода из C#. Маршалинг структур

.NET

Все чаще перед разработчиками стала вставать задача вызова native методов из managed кода. В большинстве случаев — вызов сделать достаточно просто, но иногда встречаются неприятные случаи, такие как метод, который содержит структуру с динамическим массивом структур, содержащих динамический массив структур.


Немного о задаче


Есть dll, написанная на С++, с исходным кодом в которой содержаться следующие декларации:
#pragma pack (push, 4)
struct vpsMSR
{
    unsigned __int64 data;   
    unsigned int address;   
};
#pragma pack (pop)

struct vpsConfCounter
{
  int address; 
  int number;  

  vpsMSR *config; 
  unsigned int configCount; 
};

struct vpsConfig
{
  int processorsCount; 

  vpsConfCounter *counters; 
  unsigned int countersCount; 

  bool printToScreen;  
  
  std::wstring activityName;
};

extern "C"
  VPS::ErrorCode InitConfig(vpsConfig conf);

extern "C"
  VPS::ErrorCode ClearConfig(vpsConfig conf);


Задача — реализовать вызов методов из managed кода.

Возможные подходы


Нахрапом решить задачу не получилось. При поиске решений нашелся один HowTo . В нем кратко описывается возможность маршалинга динамического массива структур, но при нем на каждый указатель приходиться много «обслуживающего» кода. В случае, если вы выберете путь маршалинга, описанный в этой статье, то необходимо объявить массивы с ключевым словом fixed, иначе могут быть проблемы.
Суть проблемы в том, что GC может проигнорировать вложенность второго уровня и переместить ваши структуры, после этого действия указатели будут содержать некорректные значения.

Принятое решение


Так как метод, на который дана ссылка выше требует достаточно аккуратной работы и генерирует много лишнего кода, затрудняющего чтение. То было принято решение, использовать временные структуры:
namespace ManagedTemp
{
  #pragma pack (push, 4)
  struct vpsMSR
  {
      unsigned __int64 data;   
      unsigned int address;   
  };
  #pragma pack (pop)

  struct vpsConfCounter
  {
    int address; 
    int number;  

    vpsMSR config[10]; 
    unsigned int configCount; 
  };

  struct vpsConfig
  {
    int processorsCount; 

    vpsConfCounter counters[10]; 
    unsigned int countersCount; 

    bool printToScreen;  
  
    wchar_t* activityName;
  };
}

В этих структурах используются массивы — константной длины. Ограничения — продиктованы предметной областью и на практике не достигаются.
D dll были добавлены два метода, которые приинимают структуры с постоянной длиной массива и пробрасывают их в изначальные методы.
extern "C"
  VPS::ErrorCode InitConfig2(ManagedTemp::vpsConfig* conf);

extern "C"
  VPS::ErrorCode ClearConfig2(ManagedTemp::vpsConfig* conf);


Маршалинг


Приступаем к маршалингу структур, описанных выше:
[StructLayout(LayoutKind.Sequential, Pack = 4)]
   public struct MSR
   {
     [MarshalAs(UnmanagedType.U8)]
     public System.UInt64 data;
     public int adress;
   }

   [StructLayout(LayoutKind.Sequential)]
   public struct ConfCounter
   {
     public int adress;
     public int number;

     [MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)]
     public MSR[] config;

     [MarshalAs(UnmanagedType.U4)]
     public uint configCount;
   }

   [StructLayout(LayoutKind.Sequential), Serializable]
   public struct Config
   {
     public int processorCount;

     [MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)]
     public ConfCounter[] counters;

     [MarshalAs(UnmanagedType.U4)]
     public uint countersCount;

     [MarshalAs(UnmanagedType.Bool)]
     public bool printToScreen;

     [MarshalAs(UnmanagedType.LPWStr)]
     public string activityName;
   }


Маршалинг методов:
     [DllImport(@"..\..\IConfigure.dll", EntryPoint = "InitConfig2")]
     public static extern int InitConfig(ref Config conf);
     
     [DllImport(@"..\..\IConfigure.dll", EntryPoint = "ClearConfig2")]
     public static extern int ClearConfig(ref Config conf);

Так, как в dll на C++ по умолчанию выставлен stdcall, то нет необходимости явно указывать тип вызова через атрибут CallingConvention.

Возникавшие проблемы и как их решать


При реализации маршалинга получаю ошибку/предуреждение StackUnbalanced. Что делать?


Эту ошибку мне приходилось чаще всего видеть — она означает, что сигнатуры импортируемой функции и исходной — не совпадают.
Причины:
1. Некорректный машалинг типов.
2. Разная конвенция вызова.
Способы диагностики: Сначала проверить соответствие конвенций вызова, потом попробовать передавать структуру поле за полем и смотреть на каком поле вылетает ошибка.

Невозможность приведения int к __int64


PInvoke не приводит int к int64. Поэтому пришлось использовать тип System.Int64.

Этот материал основан на опыте работы в лаборатории ITLab при ННГУ. В процессе использовалась статья HowTo и информация о маршалинге на msdn.
Tags:маршалингnative.net
Hubs: .NET
+22
13k 54
Comments 13
Popular right now
Ведущий программист .net
from 70,000 to 120,000 ₽Мечел-СервисЧелябинскRemote job
Разработчик .NET
from 60,000 to 120,000 ₽GMCSТулаRemote job
Разработчик .NET
from 60,000 ₽GMCSКазаньRemote job
.NET C#/Blazor Developer
from 3,000 to 4,000 $Hand2NoteRemote job
Разработчик .Net Core
from 90,000 ₽ГК InnoSTageRemote job