12 February 2010

.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:Cнеуправляемый кодMarshalмаршалинг
Hubs: .NET
+23
24.6k 72
Comments 24
Popular right now
.NET C#/Blazor Developer
from 3,000 to 4,000 $Hand2NoteRemote job
Разработчик .NET
from 60,000 ₽GMCSКазаньRemote job
Ведущий программист .net
from 70,000 to 120,000 ₽Мечел-СервисЧелябинскRemote job
Senior Backend Developer (C#, .net)
from 200,000 ₽Wärtsilä Digital TechnologiesСанкт-Петербург
SharePoint Developer C# .NET
from 80,000 to 100,000 ₽Витро СофтRemote job