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

    .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.



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


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

    Поделиться публикацией
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

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

      0
      Вы не поверите, я вчера весь день головой об стенку, писал класс-враппер к dll. А сегодня на Вы выложили ОТЛИЧНЫЕ примеры, опоздав всего на день =)
        +2
        Методы работы с неупраляемым кодом:
        — кнутом,
        — пряником,
        — уволить.
          0
          Отлично… Я думаю еще пригодилось бы: запуск CLR из неуправляемого, кодогенерация управляемого из неуправляемого и было бы полезным что-то типа boost шалонов по работе с управляемым из неуправляемого :)
            0
            Вы свой код отлаживали? Вместо [StructLayout(LayoutKind.Sequential)] надо [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)].

            Впрочем какая отладка, «public char[] str;;» даже не компилируется…
              0
              Среда автоматически использует однобайтные символы, если библиотека не поддерживает Unicode, но если этого не происходит, то да — необходимо будет ручками указывать в аттрибуте.

              Код проверял и отлаживал в Visual Studio 2005, в понедельник могу выложить весь исходный код обоих тестовых проектов.

              На основе этого же принципа, в другом нашем коммерческим продукте, у меня работает вызов библиотеки, структура которой описана в нескольких тысячах строк, используются массивы до 4 уровня многомерности. Передача char[] работает, как от неё и жду. Пожалуйста проверьте, возможно у вас где-то ошибка, поэтому не можете скомпиллировать
                0
                CharSet.Ansi автоматически подставляется для структур только на Windows 98/Me. Что поддерживает unmanaged библиотека .Net Framework узнать никак не может, поэтому выбирает CharSet в зависимости от ОС. Это достаточно ясно написано в документации

                If the CharSet field is set to CharSet.Unicode, all string arguments are converted to Unicode characters (LPWSTR) before being passed to the unmanaged implementation. If the field is set to CharSet.Ansi, the strings are converted to ANSI strings (LPSTR). If the CharSet field is set to CharSet.Auto, the conversion is platform dependent (Unicode on Windows NT, Windows 2000, Windows XP, and Windows Server 2003 family; ANSI on Windows 98 and Windows Me).

                и подтверждается опытом.

                Ошибки никакой у меня нет. Выделенный мной кусок кода «public char[] str;;» гарантированно не компилируется, это можно сказать и наизусть. Две идущие подряд точка с запятой в C# обозначающие пустой statement, в отличие от Си++, не компилируются. Соответственно не ясно откуда вы копировали код.
                  0
                  Вообще-то чарсет «по умолчанию» зависит от компилятора.
                  В частности, C# по умолчанию использует ANSI.
                    0
                    Спасибо за замечание по поводу Charset, внимания большого этому Charset не уделял, т.к. у нас библиотечка не Unicode
                      0
                      Это не правда и это легко проверить экспериментом.
                        +1
                        Вот и убедитесь экспериментально, что это как раз именно так.

                            [StructLayout(LayoutKind.Sequential)]
                            struct S_NoCharset
                            {
                              public int a;
                            }

                            [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
                            struct S_ForceAnsi
                            {
                              public int a;
                            }

                            [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
                            struct S_ForceAuto
                            {
                              public int a;
                            }

                            [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
                            struct S_ForceUnicode
                            {
                              public int a;
                            }


                        * This source code was highlighted with Source Code Highlighter.




                        Более того, можете даже открыть для себя атрибут DefaultCharSet, чтобы задать чарсет по умолчанию для всего модуля.
                          0
                          напишите пожалуйста, где можно установить DefaultCharSet в Ansi… сейчас необходимости в этом нет, но чтобы в случае необходимости, мне можно было установить в Unicode, не вписывая в каждый атрибут по-отдельности.
                            0
                            [module: DefaultCharSet(CharSet.Unicode)]
                      0
                      Удалите лишний символ ';', его там быть не должно, как он вкрался не знаю, скорее всего после манипуляций с «разукрашиванием кода» )))
                      Код вставил из реального тестового проекта, в понедельник размещу и сами исходники, они на рабочем компьютере.

                      PS: VS 2005 Pro не ругается, 2010 Ultimate тоже не ругается, видимо просто игнорирует опечатку двух идущих подряд символов ';'
                  0
                  есть хороший ресурс на эту тему: www.pinvoke.net/
                  там много оберток для разных винапишных и не только функций
                    0
                    для WinApi функций, возможно использовать Windows® API Code Pack for Microsoft® .NET Framework. Microsoft не поддерживает этот проект официально, но заявляет, что оно работает, можно юзать Ribbon, новые фичи Windows 7 и другой WinApi
                    +1
                    Я считаю, что приведение аналогов кода на C — очень удачная идея. Она позволяет более детально вникнуть в суть происходящего. Надеюсь, в следующих частях этот приём так же будет использоваться.
                      0
                      огромные структуры ref просто не мог

                      Пример «огромной» структуры в студию!
                        0
                        Добавил запись и часть кода в свой блог, по поводу той «огромной» структуры, на которую CLR ругнулся, сказав, что не может её упаковать… слишком большая для него o_O
                          0
                          Хм… Я готов опустить вопрос целесообразности гонять структуру размером 370 килобайт туда-сюда.
                          1. Вы пробовали использовать unsafe и просто передавать указатель на структуру? По-моему, это было бы и сильно быстрее, и проще.
                          2. 370 килобайт — это слишком много для стека (структуры ведь размещаются в стеке). Если объявить ее классом (просто заменить struct на class), чтобы она размещалась в куче, то P/Invoke работает как и положено, причем ее даже не нужно передавать как ref (потому что класс — это автоматически reference тип).
                            0
                            Сложность структуры и её размер да… не айс. Но деваться некуда, я и сам много плевался по этому поводу, но деваться мне дали библиотечку и задание «надо работать» )))
                            unsafe решили не использовать по ряду трудностей, которые бы они вызвали у нас при работе, да и не хотелось подписывать сборку, как unsafe.

                            Эта структура только одна часть, другая — динамическая… указатель на массив указателей. Размеры массивов заранее неизвестны, там ни структуры, ни классы не спасут, необходимо ручками выделять и писать в память (подробнее о том, как решил, опишу во 2 части статьи).
                              0
                              М.б. тогда стоило написать Custom Marshaler?
                                0
                                возможно стоило бы… но я не читал про него и не думал о его реализации. Сделал всё на основе стандартных классов (((
                        +1
                        Должен заметить что некоторые библиотеки не удаётся удобно использовать из C# ввиду их API,
                        а точнее структур данных, которые в этом API используются (например Extended MAPI).
                        И тут напомощь приходит Managed C++ на котором можно написать обёртку с нормальным интерфейсом, а её уже использовать из любого языка для .NET.
                          –1
                          C# очень давно руками не трогал, но то что я увидел мне не понравилось

                          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                          Самое читаемое