Как стать автором
Обновить

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

Время на прочтение17 мин
Количество просмотров6.2K
.NET и работа с неуправляемым кодом. Часть 2

Тем, кто не читал первую часть — читать

После выполнения первой части работ, создания структур, мне задачу усложнили. Библиотечка с которой мне нужно было работать — выделяла массив, размер которых не был известен до момента запуска. Он был динамический, т.е. чем больше данных — тем больше массив. Тут задача стала поинтереснее, т.к. старый способ, когда было достаточно использовать структуру, в которой этот размер был указан — уже не подходил.

Тогда я стал дальше изучать маршалинг и нашел еще несколько методов у класса Marshal, которые помогли мне решить задачу.

И так, собственно сама задача:
— библиотечка на входе принимает указатель на массив указателей (void*) o_O
— содержимое массива — этой указатели на другие массивы, например на массив символов, где указан адрес файла, указатели на массивы int, в которых содержаться некоторые данные

Т.к. мне часто пришлось бы вызывать некоторый код, я создал специальный статический класс UnMemory, его код ниже

  /// <summary>
  /// Класс для очистки блоков неуправляемой памяти
  /// </summary>
  public static class UnMemory
  {
    /// <summary>
    /// Очередь для высвобождения блоков памяти
    /// </summary>
    private static Queue<IntPtr> queue = new Queue<IntPtr>();

    public static void Enqueue(IntPtr ptr)
    {
      queue.Enqueue(ptr);
    }

    private static void FreeIntPtr(IntPtr ptr)
    {
      if (ptr != IntPtr.Zero)
        Marshal.FreeCoTaskMem(ptr);
    }

    /// <summary>
    /// Освобождение блоков памяти в неуправляемом пространстве
    /// </summary>
    public static void FreeMemory()
    {
      while (queue.Count > 0)
      {
        IntPtr temp = queue.Dequeue();
        // освобождаем то, что записано в памяти
        Marshal.FreeCoTaskMem(temp);
      }
    }
  }


* This source code was highlighted with Source Code Highlighter.


В этом классе я определил очередь, в которую буду добавлять все указатели на сгенерированную неуправляемую память, чтобы в конце вызывать его статический метод FreeMomory() для полной очистки выделенной памяти. Для добавления указателя в очередь, необходимо вызвать UnMemory.Enqueue(ptr);
Мне показалось это удобнее, чем обращаться к указателям, чтобы очистить каждый из них. Ведь так можно что-то пропустить и получим утечку памяти.

Также, мне понадобился еще один класс, который я назвад UnMemory, который будет выделять в неуправляемой памяти место и заполняеть его данными, ну и конечно читать из неё.
Его код ниже
  /// <summary>
  /// Класс для работы неуправляемой памятью
  /// </summary>
  /// <typeparam name="T">Структурный тип данных</typeparam>
  public static class UnMemory<T>
    where T : struct
  {

    /// <summary>
    /// Получить указатель на структуру в неуправляемом куску памяти
    /// </summary>
    /// <param name="memory_object">Объект для сохранения</param>
    /// <param name="ptr">Указатель</param>
    /// <typeparam name="T">Структурный тип данных</typeparam>
    public static void SaveInMem(T memory_object, ref IntPtr ptr)
    {
      if (default(T).Equals(memory_object))
      {
        // объявляем указатель на кусок памяти
        ptr = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(T)));
        UnMemory.Enqueue(ptr);
        return;
      }

      if (ptr == IntPtr.Zero)
      {
        // объявляем указатель на кусок памяти
        ptr = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(T)));

        // записываем в память данные структуры
        Marshal.StructureToPtr(memory_object, ptr, false);
      }
      else
      {
        // записываем в память данные структуры
        Marshal.StructureToPtr(memory_object, ptr, true);
      }

      UnMemory.Enqueue(ptr);
    }

    /// <typeparam name="T">IntPtr, int, float</typeparam>
    /// <exception cref="System.ArgumentException">Параметр #1 должен быть массивом IntPtr, int, float</exception>
    public static void SaveInMem2(T[] managedArray, ref IntPtr pnt)
    {
      Debug.Assert(managedArray != null, "Объект не должен быть Null");
      Debug.Assert(managedArray.Length != 0, "Объект не может иметь длину массива 0");

      if (pnt == IntPtr.Zero)
      {
        // объявляем указатель на кусок памяти. Размер = размер одного элемента * количество
        //int size = Marshal.SizeOf(typeof(T)) * managedArray.Length;
        int size = Marshal.SizeOf(managedArray[0]) * managedArray.Length;
        pnt = Marshal.AllocCoTaskMem(size);
      }

      // в зависимости от типа массива, мы вызываем соответствующий метод в Marshal.Copy
      if (typeof(T) == typeof(int))
      {
        int[] i = managedArray as int[];
        Marshal.Copy(i, 0, pnt, i.Length);
      }
      else if (typeof(T) == typeof(byte))
      {
        byte[] b = managedArray as byte[];
        Marshal.Copy(b, 0, pnt, b.Length);
      }
      else if (typeof(T) == typeof(float))
      {
        float[] f = managedArray as float[];
        Marshal.Copy(f, 0, pnt, f.Length);
      }
      else if (typeof(T) == typeof(char))
      {
        // читаем массив байтов и переводим в текущую кодировку
        byte[] b = Encoding.Default.GetBytes(managedArray as char[]);
        Marshal.Copy(b, 0, pnt, b.Length);
      }
      else if (typeof(T) == typeof(IntPtr))
      {
        IntPtr[] p = managedArray as IntPtr[];
        Marshal.Copy(p, 0, pnt, p.Length);
      }
      else
        throw new ArgumentException("Параметр #1 должен быть массивом IntPtr, int, float или char");

      // запоминаем указатель, чтобы потом его почистить
      UnMemory.Enqueue(pnt);
    }

    /// <summary>
    /// Чтение структуры из неуправляемой памяти
    /// </summary>
    /// <param name="ptr">Указатель</param>
    /// <param name="type">Тип данных для чтения</param>
    /// <returns>Структура из памяти</returns>
    public static T ReadInMem(IntPtr ptr)
    {
      return (T)Marshal.PtrToStructure(ptr, typeof(T));
    }

    public static T[] ReadInMem2(IntPtr ptr, int size)
    {
      if (typeof(T) == typeof(int))
      {
        int[] memInt = new int[size];
        Marshal.Copy(ptr, memInt, 0, size);
        return memInt as T[];
      }
      else if (typeof(T) == typeof(byte))
      {
        byte[] memByte = new byte[size];
        Marshal.Copy(ptr, memByte, 0, size);
        return memByte as T[];
      }
      else if (typeof(T) == typeof(float))
      {
        float[] memFloat = new float[size];
        Marshal.Copy(ptr, memFloat, 0, size);
        return memFloat as T[];
      }
      else if (typeof(T) == typeof(IntPtr))
      {
        IntPtr[] memIntPtr = new IntPtr[size];
        Marshal.Copy(ptr, memIntPtr, 0, size);
        return memIntPtr as T[];
      }
      else
        throw new ArgumentException("Параметр #1 должен быть массивом int, float или char");
    }

    /// <summary>
    /// Класс переводит массивы
    /// </summary>
    public static class UnArray
    {
      /// <summary>
      /// Перевод одномерного массива в двумерный
      /// </summary>
      /// <typeparam name="T">Тип исходного массива</typeparam>
      /// <param name="array">Исходный массив</param>
      /// <returns>Двумерный массив</returns>
      public static T[,] Rank1_Rank2(T[] array, int x, int y)
      {
        T[,] res = new T[x, y];
        int size = Buffer.ByteLength(array);
        Buffer.BlockCopy(array, 0, res, 0, size);
        return res;
      }

      /// <summary>
      /// Перевод двумерного в одномерный массив
      /// </summary>
      /// <typeparam name="T">Тип исходного массива</typeparam>
      /// <param name="array">Исходный массив</param>
      /// <returns>Одномерный массив</returns>
      public static T[] ToRank1(T[,] array, int x, int y)
      {
        T[] res = new T[x * y];
        int size = Buffer.ByteLength(array);
        Buffer.BlockCopy(array, 0, res, 0, size);
        return res;
      }

      /// <summary>
      /// Перевод одномерного массива в трехмерный
      /// </summary>
      /// <typeparam name="T">Тип исходного массива</typeparam>
      /// <param name="array">Исходный массив</param>
      /// <returns>Трехмерный массив</returns>
      public static T[, ,] Rank1_Rank3(T[] array, int x, int y, int z)
      {
        T[, ,] res = new T[x, y, z];
        int size = Buffer.ByteLength(array);
        Buffer.BlockCopy(array, 0, res, 0, size);
        return res;
      }

      /// <summary>
      /// Перевод трехмерного массива в одномерный
      /// </summary>
      /// <typeparam name="T">Тип исходного массива</typeparam>
      /// <param name="array">Исходный массив</param>
      /// <returns>Одномерный массив</returns>
      public static T[] ToRank1(T[, ,] array, int x, int y, int z)
      {
        T[] res = new T[x * y * z];
        int size = Buffer.ByteLength(array);
        Buffer.BlockCopy(array, 0, res, 0, size);
        return res;
      }

      /// <summary>
      /// Перевод одномерного массива в четырехмерный
      /// </summary>
      /// <typeparam name="T">Тип исходного массива</typeparam>
      /// <param name="array">Исходный массив</param>
      /// <returns>Четырехмерный массив</returns>
      public static T[, , ,] Rank1_Rank4(T[] array, int x, int y, int z, int w)
      {
        T[, , ,] res = new T[x, y, z, w];
        int size = Buffer.ByteLength(array);
        Buffer.BlockCopy(array, 0, res, 0, size);
        return res;
      }

      /// <summary>
      /// Перевод четырехмерного массива в одномерный
      /// </summary>
      /// <typeparam name="T">Тип исходного массива</typeparam>
      /// <param name="array">Исходный массив</param>
      /// <returns>Одномерный массив</returns>
      public static T[] ToRank1(T[, , ,] array, int x, int y, int z, int w)
      {
        T[] res = new T[x * y * z * w];
        int size = Buffer.ByteLength(array);
        Buffer.BlockCopy(array, 0, res, 0, size);
        return res;
      }
    }
  }


* This source code was highlighted with Source Code Highlighter.


Этот класс обобщенный, что позволило не создавать множество методов для каждого типа отдельно. Смысл этого класса, он повереводит массивы из управляемой памяти в неуправляемую и наоборот. Метод SaveInMem сохраняет структуру, которую мы ему передадим, метод SaveInMem2 сохранит массив, таких как intPtr, Int, float… Ограничение представляет сам метод Marshal.Copy, в котором реализовано копирование отдельно для int, отдельно для byte и некоторых других. Поэтмоу нужно было для каждого типа реализовать свой свой вызов, такой как if (typeof(T) == typeof(int))

Методы ReadInMem и ReadInMem2 для чтения структур и массивов, аналогично SaveMem и SaveMem2.

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

И так, теперь переходим непосредственно к самому вызову.
Из С, вызов выгладит так:
extern "C" int __import TkzIvc(void *mpGS[]);

* This source code was highlighted with Source Code Highlighter.


Для C# этот вызов будет выглядить так:
[DllImport(@"DllTkzIvc.dll")]
private static extern int _TkzIvc([In] IntPtr mpGS);


* This source code was highlighted with Source Code Highlighter.


Осталось самое простое. Сделать обертку для использования

[StructLayout(LayoutKind.Sequential)]
public class mpSh_Struct : IDisposable
{
  private IntPtr[] mpSh = new IntPtr[5];
  private int size_vetv; // число ветвей

  /// <summary>
  /// Путь к файлам схемы
  /// </summary>
  /// <value>Строка размером до 255 символов</value>
  /// <exception cref="System.ArgumentOutOfRangeException">Возникает, если строка больше, чем 255 символов</exception>
  public string PathSh
  {
    get
    {
      return Marshal.PtrToStringAnsi(this.mpSh[0]);
    }
    set
    {
      if (String.IsNullOrEmpty(value) || value.Length > 255)
        throw new ArgumentOutOfRangeException("Данные должны быть обязательно заданы и размером не более 255 символов");
      this.mpSh[0] = Marshal.StringToHGlobalAnsi(value);
    }
  }
  
  ///<summary>
  /// указатель массива int[]
  ///</summary>
  public int[] TypeV
  {
    get { return UnMemory<int>.ReadInMem2(this.mpSh[1], this.size_vetv); }
    set { UnMemory<int>.SaveInMem2(value, ref this.mpSh[1]); }
  }
  
  /// <summary>
  /// указатель массива char u1[][6]
  /// </summary>
  /// <value>Массив из строк размером до 5 символов</value>
  public string[] u1
  {
    get
    {
      // читаем массив байт из памяти
      byte[] mem = UnMemory<byte>.ReadInMem2(this.mpSh[2], this.size_vetv * 6);
      // определяем, каким же должен быть результирующий массив string[]
      int length = this.size_vetv;
      // генерируем новый массив
      string[] res = new string[length];
      for (int i = 0; i < length; i++)
      {
        // в цикле обрабатываем массив байт, который переводим в String и обрезаем символы конца
        res[i] = Encoding.Default.GetString(mem, i * 6, 6).TrimEnd('\0');
      }
      return res;
    }
    set
    {
      // генерируем массив char[]
      char[] res = new char[value.Length * 6];
      for (int i = 0; i < value.Length; i++)
      {
        if (value[i] != null)
        value[i].CopyTo(0, res, i * 6, value[i].Length);
      }
      // сохраняем
      UnMemory<char>.SaveInMem2(res, ref this.mpSh[2]);
    }
  }
  
  /// <summary>
  /// указатель массива float[]
  /// </summary>
  public float[] EK1B1
  {
    get { return UnMemory<float>.ReadInMem2(this.mpSh[4], this.size_vetv); }
    set { UnMemory<float>.SaveInMem2(value, ref this.mpSh[4]); }
  }
  
  public int[] ParamSh
  {
    get { return UnMemory<int>.ReadInMem2(this.mpSh[3], 6); }
    set
    {
      this.size_vetv = value[0]; // число ветвей (ParamSh[0])
      UnMemory<int>.SaveInMem2(value, ref this.mpSh[3]);
    }
  }
  
  /// <summary>
  /// Вызов библиотеки на обновление данных
  /// </summary>
  public bool Read(out string errorText)
  {
    try
    {
      IntPtr t = new IntPtr();
      UnMemory<IntPtr>.SaveInMem2(this.mpSh, ref t);

      _TkzIvc(t);

      mpSh = UnMemory<IntPtr>.ReadInMem2(t, this.mpSh.Length);

      int[] paramSh = this.ParamSh; // параметры схемы
      this.size_vetv = paramSh[0]; // число ветвей
      errorText = String.Empty;
      return true;
    }
    catch (DllNotFoundException)
    {
      errorText = "Целость программы нарушена. Пожалуйста переустановите программу или обратитесь к администратору за помощью";
      return false;
    }
    catch (Exception exp)
    {
      errorText = exp.Message;
      return false;
    }
  }
  
  public void Dispose()
  {
    // очищаем память для выделенных объектов
    UnMemory.FreeMemory();

    // очищаем то, что выделялось библиотечкой
    for (int i = 1; i < 5; i++)
    {
      IntPtr ptr = mpSh[i];
      // чистим
      UnMemory.FreeIntPtr(ptr);
    }
  }
}

* This source code was highlighted with Source Code Highlighter.


ОБРАТИТЕ ВНИМАНИЕ, что для простого текста, строки, я использовал Marshal.StringToHGlobalAnsi(text). Т.к. это специальный метод класса Marshal для чтения и записи обычной текстовой строки, которая оканчивается символом '\0'

Массив указателей сохраняется в mpSh, а для всех полей реализованы методы get и set, которые почти прозрачно от самого класса пишут и читают неуправляемую память. Метод Read переносит массив указателей в неуправляемую память, чтобы потом прочесть данные из неё (при вызове библиотеки, эти данные заполняются). После вызова Read, мы сможем прочесть нужную нам переменную просто обратившись к нужному полю.

Правда есть одно ограничение… лучше получить ссылку на массив ( int[] paramSh = this.ParamSh ), а уже потом работать с ним, чем обращаться к полю постоянно ( this.size_vetv = this.ParamSh[0] ). Потому как при обращении, данные заново будут читаться из неуправляемой памяти. Несмотря, что это проиходит быстро, если вызовов много, это может занять достаточное время.

PS: в рамках данной статьи не был рассмотрен CustomMarshaling. Суть его в том, что Вы можете польностью настраивать, как должна сохраняться определенная структура, с его помощью Вы можете поддерживать различные версии своих библиотек. Например, сохранять DateTime как String в определенном формате и пр. Такие случаи бывают реже, поэтому этому можно посветить отдельную статью.

К сожалению приложить исходники к статье не могу, т.к. это не одобрит руководство :) Но я постарался привести максимум кода, чтобы без исходников было понятно, как работать с маршалингом. Если вам будем что-то непонятно, пожалуйста напишите и я отвечу на ваши вопросы.
Теги:
Хабы:
0
Комментарии4

Публикации

Изменить настройки темы

Истории

Работа

.NET разработчик
74 вакансии

Ближайшие события

Weekend Offer в AliExpress
Дата20 – 21 апреля
Время10:00 – 20:00
Место
Онлайн
Конференция «Я.Железо»
Дата18 мая
Время14:00 – 23:59
Место
МоскваОнлайн