Pull to refresh

.NET и работа с неуправляемым кодом. Часть 1

.NET *
.NET и работа с неуправляемым кодом. Часть 1

.NET и работа с неуправляемым кодом

Сегодня я хочу показать один из способов работы с неуправляемым кодом, посредством специального класса Marshal. Большинство методов, определенных в этом классе, обычно используются разработчиками, которым нужно обеспечить сопряжение между моделями управляемого и неуправляемого программирования.

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

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


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

Код на C

struct test
{
  struct innerstr
  {
   char str[300];
   int Int;
   int* in_pInt;
  } in;
  char str[2][50];
  int IntArr[10];
  int* pInt;
  innerstr* pStruct;
  int* ptr;
};


* This source code was highlighted with Source Code Highlighter.



Код на C#

[StructLayout(LayoutKind.Sequential)]
public struct Test
{
  public Innerstr _in;
  [MarshalAs(UnmanagedType.ByValArray, SizeConst = 50 * 2)]
  public char[] str;;
  [MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)]
  public int[] IntArr;
  public IntPtr pInt;
  public IntPtr pStruct;
  public IntPtr ptr;

  [StructLayout(LayoutKind.Sequential)]
  public struct Innerstr
  {
   [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 300)]
   internal string str;
   public int _Int;
   public IntPtr in_pInt;
  }
}


* This source code was highlighted with Source Code Highlighter.



Как можете заметить, все указатели из C были заменения на тип IntPtr из C#. Двумерные массивы — на одномерные, аналогичной длины. А сама структура подписана аттрибутом [StructLayout]. Значение LayoutKind параметра Sequential используется для принудительного последовательного размещения членов в порядке их появления.

Для массивов необходимо указать их тип как UnmanagedType.ByValArray и сразу же указать их точный размер. Даже если размер самой переменной будет отличаться — при передаче, он автоматически будет уравнен в необходимый размер.

Вызов неуправляемого кода

Код на C
extern "C" __declspec(dllexport) int ExpFunc(test* s, bool message)

* This source code was highlighted with Source Code Highlighter.



Код на C#:
  [return:MarshalAs(UnmanagedType.I4)]
  [DllImport("TestTkzDLL.dll")]
  public static extern int ExpFunc([In, Out] IntPtr c, [In]bool message);


* This source code was highlighted with Source Code Highlighter.



Как вы наверное заметили, перед вызово необходимо сначало объявить все IntPtr. Для этого необходимо использовать примерно следующий код:

  Test test = new Test();

  ...

  // для получения указателя на => int* pInt
  int _pInt = 2010; // значение числа
  IntPtr _pInt_buffer = Marshal.AllocCoTaskMem(Marshal.SizeOf(_pInt)); // выделили кусочек памяти
  Marshal.StructureToPtr(_pInt, _pInt_buffer, false); // записали содержимое
  test.pInt = _pInt_buffer; // сохранили


* This source code was highlighted with Source Code Highlighter.


По аналогии, и для innerstr* pStruct, и для всех остальных указателей.

Test.Innerstr inner2 = new Test.Innerstr();
IntPtr _pStruct_buffer = Marshal.AllocCoTaskMem(Marshal.SizeOf(inner2));
Marshal.StructureToPtr(inner2, _pStruct_buffer, false);
test.pStruct = _pStruct_buffer;


* This source code was highlighted with Source Code Highlighter.



Вот и всё, всё просто. Теперь осталось из кода вызвать метод

  ///////////////////////////////////////<br ?>
  // ГЕНЕРИРУЕМ ЗАПРОС (способ с маршилингом данных в память, затем передачей ссылки)
  /////////////////////////////////////
  IntPtr ptr1 = Marshal.AllocCoTaskMem(Marshal.SizeOf(test));
  Marshal.StructureToPtr(test, ptr1, false);
  int retInt = ExpFunc(ptr1, false); // вызов ветода
  test = (StartClass.Test)Marshal.PtrToStructure(ptr1, typeof(StartClass.Test)); /// получаем наше значение обратно из неуправляемого кода

* This source code was highlighted with Source Code Highlighter.



В данном случае я перенес всю структуру в неуправляемую память, а затем передал ссылку на этот кусок, который затем в C был прочитан. Этого можно не делать, если передавать по ref, но я столкнулся с тем, что огромные структуры ref просто не мог перенести в неуправляемую память, а способ передавать ссылку работает всегда

Затем не забудьте почистить память. В отличие от управляемого кода, сборщик мусора не может чистить неуправляемый. Поэтому необходимо вызвать Marshal.FreeCoTaskMem(ptr); для всех ссылок IntPtr

PS: добавлено позже… аттрибут [StructLayout(LayoutKind.Sequential)], также может указывать на используемую таблицу символов, ANSI или UNICODE. Для этого необходимо написать [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)], [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] или [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]. По-умолчанию используется ANSI.



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


Обновлено
Добавлены исходники тестового проекта. Скачать

Tags:
Hubs:
Total votes 53: ↑38 and ↓15 +23
Views 27K
Comments Comments 24