Форум программистов, компьютерный форум, киберфорум
C# для начинающих
Войти
Регистрация
Восстановить пароль
Карта форума Темы раздела Блоги Сообщество Поиск Заказать работу  
 
Рейтинг 4.85/599: Рейтинг темы: голосов - 599, средняя оценка - 4.85
Почетный модератор
Эксперт .NET
8721 / 3673 / 404
Регистрация: 14.06.2010
Сообщений: 4,513
Записей в блоге: 9
1

Класс Marshal, использование PInvoke, небезопасный код (unsafe)

15.08.2011, 14:04. Показов 120922. Ответов 0

Author24 — интернет-сервис помощи студентам
Все комментарии и вопросы просьба оставлять в этой теме.



1. Введение

Начнём с небольшой теории. Большинство стандартных типов в .NET являются объектами, жизненным циклом каждого из которых управляет CLR, и нам, программистам, не нужно заботиться о выделении и освобождении памяти для каждого из объектов. Всё это берет на себя CLR. Но бывают такие моменты когда возможностей .NET может не хватать (скорость алгоритмов [кривые руки не учитываются], взаимодействие с ОС на более низком уровне и т.п.) или некоторые решения уже есть но на языке отличном от C#. Данную проблему можно решить путём взаимодействия с библиотекой написанной на native языке (Platform Invoke) - где CLR будет выступать в виде средства взаимодействия (выполнять нужные преобразования над передаваемой информацией), или же использование небезопасного кода в приложении - для прямого (без контроля CLR) обращения к памяти, выделенной под объекты. Об этом и пойдет речь в данной статье.



2. System.IntPtr

Тип данных System.IntPtr используется для представления указателя (void *) на участок памяти. Размер данного типа будет различаться в зависимости от разрядности приложения: PE32 - 4 байта, PE64 - 8 байт. Начиная с .NET 4.0 у данной структуры появилась возможность использования арифметических операций "+" и "-".



3. System.Runtime.InteropServices.Marshal

Использовать функции предоставляемые данным классом нужно в тех случаях, когда есть необходимость в применении неуправляемого кода или PInvoke. В данной статье будут рассмотрены наиболее важные (по моему мнению) и часто используемые функции.

3.1. Функции выделения и освобождения неуправляемой памяти

3.1.1. Marshal.AllocHGlobal - выделение в куче участка памяти, инициализированного изначально мусором. Внутренняя реализация представляет из себя обёртку над WinAPI функцией LocalAlloc, с параметром uFlags равным LMEM_FIXED (выделяемая память фиксирована в куче). Если во время вызова LocalAlloc произошел сбой, то функция выдаст исключение OutOfMemoryException.

3.1.2. Marshal.ReAllocHGlobal - изменение размера выделенного функцией AllocHGlobal участка памяти, в большую или меньшую сторону. Внутренняя реализация является обёрткой над WinAPI LocalReAlloc. Если функция завершилась корректно, то будет возвращён указатель на участок выделенной памяти, он может совпадать с тем что был до вызова ReAllocHGlobal или нет, в этом случае участок памяти выделенный ранее, с помощью AllocHGlobal, будет скопирован (нужного размера) по новому адресу, а старый участок будет освобождён. В случае некорректного завершения LocalReAlloc, функция выдаст исключение OutOfMemoryException.

3.1.3. Marshal.FreeHGlobal - освобождает память выделенную вызовом AllocHGlobal. Внутренняя реализация является обёрткой над WinAPI LocalFree. В случае некорректного завершения LocalFree, FreeHGlobal выдаст исключение COMException с кодом ошибки.

3.1.4. Пример использования функций:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
using System;
using System.Runtime.InteropServices;
 
...
 
static void AllocationExample ( ) {
    const int NEW_MEM_SIZE = 0x200;
 
    byte[] buff = new byte[0x400];
    // Выделение 1024 байт
    IntPtr pMem = Marshal.AllocHGlobal( 0x400 );
 
    for ( int i = 0; i < buff.Length; ++i )
        buff[i] = (byte)i;
 
    // Копируем массив в выделенный участок памяти
    Marshal.Copy( buff, 0, pMem, buff.Length );
    // Уменьшаем размер выделенной области памяти в 2 раза
    pMem = Marshal.ReAllocHGlobal( pMem, (IntPtr)NEW_MEM_SIZE );
    // Очищаем массив
    for ( int i = 0; i < buff.Length; ++i )
        buff[i] = 0;
    // Копируем значения из измененного участка памяти
    Marshal.Copy( pMem, buff, 0, NEW_MEM_SIZE );
    /* Если скопировать участок памяти больший по объему
     * чем выделено, то есть шанс получить ошибку
     * Access Violation - это значит что доступ к участку
     * памяти заблокирован. Если же исключения не будет, то
     * скорее всего будет скопирован мусор.
     */
 
    // Обязательно освобождение выделенной памяти!
    Marshal.FreeHGlobal( pMem );
}
3.2. Копирование управляемой памяти в неуправляемую и наоборот

Marshal.Copy предоставляет множество перегрузок для копирования массивов всех стандартных типов из неуправляемой памяти в управляемую и наоборот.

Пример обработки изображения ускоренным методом (относительно GetPixel/SetPixel):
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
using System;
using System.Runtime.InteropServices;
using System.Drawing;
using System.Drawing.Imaging;
 
...
 
static void CopyExample ( ) {
    using ( var bmp = new Bitmap( 100, 100 ) ) {
        // Фиксируем изображение в памяти
        var bd  = bmp.LockBits(
            new Rectangle( 0, 0, 100, 100 ),
            ImageLockMode.ReadWrite,
            bmp.PixelFormat
        );
        // Буфер под размер изображения
        var buffer = new byte[bd.Stride * bd.Height];
        // Копируем байтовое представление изображения
        // в выделенный буфер
        Marshal.Copy( bd.Scan0, buffer, 0, buffer.Length );
 
        /*
         * Выполнение некоторых модификаций над буфером
         */
 
        // Копируем буфер обратно по адресу расположения
        // изображения в памяти
        Marshal.Copy( buffer, 0, bd.Scan0, buffer.Length );
        // Разблокируем изображение
        bmp.UnlockBits( bd );
    }
}
3.3. Определение размера занимаемой объектом (или типом) неуправляемой памяти

Перед выделением памяти под объект нужно знать, сколько байт необходимо выделить. Для этого есть функция Marshal.SizeOf, которая может получить от CLR размер объекта или типа в неуправляемой памяти.
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
 
...
 
struct Example
{
    public int i1;
    public long l1;
}
 
static void SizeOfExample ( ) {
    // Вывод размера структуры и типа в окно Output
    Debug.WriteLine(
        "Object: {0}\r\nType: {1}",
        Marshal.SizeOf( new Example() ),
        Marshal.SizeOf( typeof( Example ) )
    );
    /* Object: 16
     * Type: 16
     */
}
Внимание!Если в примере заменить struct на class, то во время выполнения кода будет выдано исключение ArgumentException, в котором будет сказано что невозможно представить объект в виде неуправляемого, т.к. невозможного рассчитать его размер и смещение полей. Чтобы исправить эту ситуацию достаточно добавить атрибут LayoutKind.Sequential при объявлении класса (Об использовании атрибута StructLayout речь пойдет в разделе (4.3)).


3.4. Определение смещения поля в типе данных

Функция Marshal.OffsetOf позволяет рассчитать смещение поля относительно начала размещения структуры в неуправляемой памяти. Это может понадобиться когда размер структуры меняется в зависимости от ОС и/или её разрядности. Функция позволяет автоматизировать расчет смещения, а не высчитывать его каждый раз самостоятельно.
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
 
...
 
struct Example
{
    public int i1;
    public long l1;
}
 
static void OffsetOfExample ( ) {
    // Вывод смещения поля l1 относительно
    // начала размещения структуры в памяти
    Debug.WriteLine(
        Marshal.OffsetOf( typeof( Example ), "l1" ),
        "Offset"
    );
    // Offset: 8
}
3.5. Чтение/запись из/в неуправляемую память

После того как память выделена с ней можно выполнять операции чтения/записи, для этого есть функции Marshal.ReadX и Marshal.WriteX (вместо X нужно подставить название стандартного типа заданного в CLR). Каждая функция имеет набор перегрузок, которые позволяют писать данные нужного типа в начало объекта или по определенному адресу (с указанием смещения, если это необходимо). Эти методы лучше всего использовать когда запись в память должна происходить небольшими фрагментами, если же есть необходимость писать или читать память большими кусками, то для этого лучше использовать функцию Marshal.Copy, т.к. запись большого участка памяти значительно быстрее чем побайтовая запись при помощи данных функций.
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
using System;
using System.Runtime.InteropServices;
 
...
 
static void ReadWriteExample ( ) {
    using ( var bmp = new Bitmap( 100, 100 ) ) {
        var bd  = bmp.LockBits(
            new Rectangle( 0, 0, 100, 100 ),
            ImageLockMode.ReadWrite,
            bmp.PixelFormat
        );
 
        int length = bd.Stride * bd.Height;
        byte temp  = 0;
 
        for ( int i = 0; i < length; ++i )
            /* Читаем 1 байт смещению i относительно
             * адреса Scan0, записываем его в temp
             * и сравниваем со значением 127 */
            if ( ( temp = Marshal.ReadByte( bd.Scan0, i ) ) > 127 )
                // Пишем по смещению i относительно
                // адреса Scan0 значение i
                Marshal.WriteByte( bd.Scan0, i, (byte)i );
            else
                // Пишем по смещению i относительно
                // адреса Scan0 значение temp xor 7
                Marshal.WriteByte( bd.Scan0, i, (byte)( temp ^ 7 ) );
 
        bmp.UnlockBits( bd );
    }
}
3.6. Получение адреса объекта в памяти

3.6.1. Marshal.StructureToPtr и Marshal.PtrToStructure

Чтобы обращаться к объекту через неуправляемую память нужно получить адрес по которому объект располагается в памяти. Для этого служит функция Marshal.StructureToPtr. Принцип её работы состоит в том, что весь объект копируется (даже если это ссылочный тип) в заранее выделенный фрагмент памяти. В этой функции важное значение играет 3-й параметр типа Boolean, который отвечает за освобождение скопированных туда ранее данных. При первом вызове данной функции (сразу после выделения фрагмента памяти) этот параметр должен быть false, т.к. очищать нечего, но при последующих вызовах с использованием указателя на один и тот же участок памяти следует установить параметр в true, чтобы избежать "невидимых" утечек памяти.

Для того чтобы получить нужный нам объект из неуправляемой памяти существует функция Marshal.PtrToStructure, которая также выполняет полное копирование объекта из неуправляемой памяти в управляемую.

Пример использования:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
using System;
using System.Runtime.InteropServices;
 
...
 
struct Example
{
    public int i1;
    public long l1;
}
 
static void StructureToPtrExample ( ) {
    // Создаем объект
    var ex = new Example();
    // Выделяем под него память
    var ptr = Marshal.AllocHGlobal( Marshal.SizeOf( typeof( Example ) ) );
    /* Копируем созданный объект в выделенный
     * участок памяти, и при этом не хотим
     * чтобы блок памяти перед этим очищался */
    Marshal.StructureToPtr( ex, ptr, false );
    // Записываем в первые четыре байта выделенной
    // памяти, т.е. в поле i1, значение 10
    Marshal.WriteInt32( ptr, 0xA );
    // Копируем структуру обратно в ex
    ex = (Example)Marshal.PtrToStructure( ptr, typeof(Example) );
    // Освобождаем память!
    Marshal.FreeHGlobal( ptr );
}
3.6.2. GCHandle, краткое описание

Чтобы избежать копирования ссылочных типов в неуправляемую память, нужно закрепить объект в управляемой памяти и не давать его очистить сборщику мусора. Для этого существует структура System.Runtime.InteropServices.GCHandle. Получить адрес объекта можно вызвав статический метод Alloc этой структуры, который принимает 2 параметра: 1-й это нужный объект адрес которого нужно получить, а второй это один из вариантов перечисления GCHandleType, который определяет какой тип дескриптора будет создан и как будет обращаться с объектом сборщик мусора, в рамках данной статьи нас интересует только GCHandleType.Pinned - объект будет закреплен в управляемой памяти и не будет удален сборщиком мусора, также появляется возможность получить адрес объекта вызвав AddrOfPinnedObject созданного экземпляра структуры GCHandle.
После того как работа с объектом, используя полученный адрес, завершена, следует обязательно вызвать метод Free созданного экземпляра структуры GCHandle, иначе объект будет навсегда закреплен в памяти и сборщик мусора к нему не притронется, что повлечет утечку памяти.
Внимание!Закрепленный объект необходимо сразу освободить, как только в нём больше нет необходимости. Закрепление объектов в памяти может сказаться на производительности всего приложения.


Пример использования:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
using System;
using System.Runtime.InteropServices;
 
...
 
// О атрибуте StructLayout описано в разделе 4.3
[StructLayout(LayoutKind.Sequential)]
class Example
{
    public int i1;
    public long l1;
}
 
static void GCHandleExample ( ) {
    // Создаём объект
    var example = new Example();
    // Фиксируем его в памяти
    var gch = GCHandle.Alloc( example, GCHandleType.Pinned );
    {
        // Пишем значение 4 в поле i1 напрямую, т.е.
        // значение в example будет изменено сразу
        // без копирования объектов
        Marshal.WriteInt32( gch.AddrOfPinnedObject(), 0x4 );
    }
    gch.Free(); // Освобождаем объект от закрепления
}
3.7. Копирование строк из управляемой памяти в неуправляемую и наоборот

Для копирования строки в неуправляемую память существуют функции Marshal.StringToHGlobalAnsi и Marshal.StringToHGlobalUni. Первая выполняет копирование строки в неуправляемую память в кодировке ANSI, а вторая в Unicode.
Для копирования строки из неуправляемой памяти служат функции Marshal.PtrToStringAnsi и Marshal.PtrToStringUni.
После того как работа с выделенным под строку фрагментом памяти завершена, необходимо её освободить вызвав Marshal.FreeHGlobal.

Пример использования:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
using System;
using System.Runtime.InteropServices;
 
...
 
static void AnsiStringCopyExample ( ) {
    var example = "Some text here";
    // Копируем example в неуправляемую память
    var pMem    = Marshal.StringToHGlobalAnsi( example );
    // Пишем в память NULL взамен второго пробела
    Marshal.WriteByte( pMem + 9, 0 );
    // Копируем строку обратно, и получаем
    // "Some text", т.к. будет скопирован участок
    // памяти до первого символа \0 (NULL)
    example = Marshal.PtrToStringAnsi( pMem );
    // Освобождаем память!
    Marshal.FreeHGlobal( pMem );
}
 
static void UniStringCopyExample ( ) {
    var example = "Some text here";
    // Копируем example в неуправляемую память
    var pMem    = Marshal.StringToHGlobalUni( example );
    // Пишем в память NULL взамен второго пробела
    // но теперь смещение мы берем в 2 раза больше
    // т.к. юникод это 2-х байтовая кодировка
    Marshal.WriteByte( pMem + 18, 0 ); // Обнуляем 19-й
    Marshal.WriteByte( pMem + 19, 0 ); // и 20-й байты
    // Копируем строку обратно, и получаем "Some text"
    example = Marshal.PtrToStringUni( pMem );
    // Освобождаем память!
    Marshal.FreeHGlobal( pMem );
}


4. Platform Invoke (PInvoke)

Множество алгоритмов и всевозможных решений в настоящее время существуют в виде DLL библиотек, что позволяет их использовать в приложениях путем вызова экспортируемых функций из них. В CLR для этого встроен специальный "сервис": Platform Invocation Service который позволяет легко вызывать находящиеся в DLL функции, а также выполнять необходимые преобразования параметров для передачи их из управляемого кода в неуправляемый. Для взаимодействия с этим сервисом существуют специальные атрибуты: DllImport, MarshalAs и StructLayout. Они позволяют настроить сервис необходимым образом, чтобы при вызове неуправляемой функции все параметры передавались нужным образом.

4.1. DllImport

Атрибут DllImport служит для указания из какой библиотеки и какую функцию следует импортировать для использования в приложении. У данного атрибута есть несколько параметров, которые позволяют настроить поиск функции в библиотеке, указать способ передачи параметров и их преобразование.
Первым параметром при объявлении данного атрибута является имя DLL, в которой будет происходить поиск функции.
Прототип импортируемой функции должен быть объявлен с использованием ключевых слов static и extern.

Рассмотрим некоторые свойства данного атрибута:
  1. EntryPoint - позволяет указать имя импортируемой функции или её порядковый номер. Если данный параметр установлен, то имя прототипа функции может быть любым.
  2. CharSet - указывает преобразование строк в параметрах функции, а также позволяет CLR выбирать какую версию функции импортировать (Unicode или ANSI), подставляя к имени функции окончание A или W, если это не указано явно.
    • CharSet.None - это значение в настоящее время устарело и CLR вместо него использует CharSet.Ansi.
    • CharSet.Ansi - преобразовывает строки к однобайтовому (ANSI) представлению.
    • CharSet.Unicode - преобразовывает строки к двухбайтовому (Unicode) представлению.
    • CharSet.Auto - использует тип преобразования в зависимости от ОС. ANSI на Win 98 и ME, и Unicode на остальных. По умолчанию в C# используется CharSet.Ansi.
  3. SetLastError - данный параметр очень важен при импорте WinAPI функций, т.к. он указывает CLR что после вызова функции необходимо вызвать WinAPI функцию GetLastError и сохранить в памяти результат её работы.
    • Этот момент важен, т.к. при вызове функции, CLR может внутри себя вызывать другие WinAPI функции, в результате чего вызов GetLastError напрямую может вернуть совсем не то значение которое ожидалось.
    После того как WinAPI функция вернула управление нашей программе, то узнать результат выполнения функции GetLastError можно вызвав Marshal.GetLastWin32Error.
    По умолчанию значение параметра SetLastError равно false.
  4. ExactSpelling - указывает является ли имя функции установленное в параметре EntryPoint или в прототипе, конечным именем. Если данный параметр установлен, то CLR не будт модифицировать название функции (подставлять A или W), а будет пытаться найти указанное имя.
    По умолчанию в C# значение данного параметра равно false.
  5. CallingConvention - соглашение о порядке и способе передачи параметров в неуправляемую функцию.
    По умолчанию параметр имеет значение CallingConvention.StdCall.

Примеры построения некоторых прототипов WinAPI функций:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// Получение заголовка окна из приложения "Калькулятор"
 
[DllImport( "USER32.DLL", SetLastError = true )]
static extern IntPtr FindWindow (
    string lpClassName,
    string lpWindowName
    );
 
[DllImport( "USER32.DLL", SetLastError = true, EntryPoint = "GetWindowText"/*,
    ExactSpelling = true */)]
static extern int gwt (
    IntPtr hWnd,
    StringBuilder lpString,
    int nMaxCount
    );
 
// Тоже самое что и пример выше
//[DllImport( "USER32.DLL", SetLastError = true )]
//static extern int GetWindowText (
//    IntPtr hWnd,
//    StringBuilder lpString,
//    int nMaxCount
//    );
 
static void Main ( string[] args ) {
    // Создаем буфер длиной 256 символов
    var sb = new StringBuilder( 256 );
    // Получаем текст из приложения
    gwt(
        // Ищем окно с классом CalcFrame
        FindWindow( "CalcFrame", null ),
        sb, // Буфер
        sb.Capacity // Размер буфера
    );
    // Если вызов GetWindowText завершился некорректно
    // то выводим ошибку
    if ( Marshal.GetLastWin32Error() != 0 )
        Console.WriteLine("Error: " + Marshal.GetLastWin32Error() );
    else
        Console.WriteLine( sb );
    Console.ReadLine();
}
Если немного изменить данный пример, раскомментировав параметр ExactSpelling и запустив приложение, выполнение программы прервётся на месте вызова функции gwt с ошибкой EntryPointNotFoundException: "Unable to find an entry point named 'GetWindowText' in DLL 'USER32.DLL'.".

Пример использования CallingConvention: (скачать проект - DllImportExample.rar)
C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// Библиотека TestC.dll на языке C с экспортируемой функцией Add
 
#include "main.h"
 
EXPORT
int __cdecl
Add( 
    char* pFirst, 
    char* pSecond,
    char* pResult, 
    int count 
    )
{
    int i;
    
    if ( pFirst == NULL || pSecond == NULL || pResult == NULL )
        return 1;
    if ( count < 1 )
        return 2;
 
    for ( i = 0; i < count; ++i )
        pResult[i] = pFirst[i] + pSecond[i];
 
    return 0;
}
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
        [DllImport( "TestC.dll", CallingConvention = CallingConvention.Cdecl )]
        static extern int Add (
            byte[] first,
            byte[] second,
            byte[] result,
            int count
            );
 
        static void Main ( string[] args ) {
            const int COUNT = 25;
            byte[] f = new byte[COUNT],
                s = new byte[COUNT],
                r = new byte[COUNT];
 
            for ( int i =0; i < COUNT; ++i )
                f[i] = s[i] = (byte)i;
 
            if ( Add( f, s, r, COUNT ) == 0 )
                for ( int i = 0; i < COUNT; ++i )
                    Console.WriteLine( r[i] );
        }
Закомментировав параметр CallingConvention, при вызове функции будет выдано исключение PInvokeStackImbalance, что говорит о неверном способе передачи параметров в неуправляемую функцию.

Пример вызова функции с разным окончанием (A или W) в зависимости от установленного CharSet: (скачать проект - DllImportExample.rar)
C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include "main.h"
 
EXPORT
char __cdecl
GetMethodNameA(
    char *pBuff
    )
{
    if ( pBuff == NULL )
        return 1;
 
    strcpy( pBuff, "ANSI sample method" );
    return 0;
}
 
EXPORT
char __cdecl
GetMethodNameW( 
    wchar_t *pBuff 
    )
{
    if ( pBuff == NULL )
        return 1;
 
    wcscpy( pBuff, L"Unicode sample method" );
    return 0;
}
C#
1
2
3
4
5
6
7
8
9
10
11
12
[DllImport( "TestC.dll", //CharSet = CharSet.Unicode,
    CallingConvention = CallingConvention.Cdecl )]
static extern byte GetMethodName (
    StringBuilder sb
    );
 
static void Main ( string[] args ) {
    var sb = new StringBuilder( 256 );
    GetMethodName( sb );
    Console.WriteLine( sb );
    Console.ReadLine();
}
Запустив скомпилированное приложение с закомментированным параметром CharSet и потом с раскомментированным, будет видно как параметр CharSet влияет на вызов функций.

4.2. MarshalAs

Атрибут MarshalAs позволяет указать способ преобразования передаваемых параметров и возвращаемых значений при вызове неуправляемой функции. Также данный атрибут можно применять к полям класса или структуры.
Внимание!Данный атрибут не является обязательным, т.к. в большинстве случаев CLR может понять как преобразовывать значения. Его следует использовать когда базовое представление управляемого объекта в памяти может не соответствовать тому, которое требуется.

Рассмотрим параметры атрибута:
  • UnmanagedType - позволяет указать тип неуправляемого объекта, к которому при необходимости управляемый объект будет преобразован. Данный параметр является перечислением и имеет множество значений которые понятны по названию. Рассмотрим наиболее важные:
    • Строковые преобразования - для преобразования строк есть 7 значений:
      • AnsiBStr - строка в кодировке ANSI, используется для COM взаимодействия;
      • BStr - тоже что и AnsiBStr, только строка в кодировке Unicode;
      • TBStr - кодировка строк зависит от ОС;
      • LPStr - используется для передачи строк в ANSI кодировке;
      • LPWStr - применяется для передачи строк в кодировке Unicode;
      • LPTStr - применяется для передачи строк в кодировке, которая используется ОС по умолчанию (тоже самое что CharSet.Auto);
        Внимание!Значения выше имеют более высокий приоритет относительно параметра CharSet.
      • ByValTStr - используется для передачи строк фиксированной длины (в символах). Если установленно данное значение, то в атрибуте MarshalAs обязательно должен быть использован параметр SizeConst отвечающий за длину строки. Кодировка строки зависит от параметра CharSet.
        Примечание: Ввиду того что при машалинге CLR преобразует строки к C-стилю (null-terminated strings), т.е. строки с \0 (или \0\0 в Unicode) на конце, то действительная длина строки будет SizeConst - 1, т.к. последний символ будет заменён на \0.
    • Для преобразования целых чисел и чисел с плавающей точкой используются значения I1, I2, I4, I8, U1, U2, U4, U8 и R4, R6 соответсвенно.
    • Для передачи массивов в неуправляемый код используются значения UnmanagedType.LPArray для параметров функций и UnmanagedType.ByValArray для полей структур и классов. Эти параметры стоит использовать вместе с параметром SizeConst атрибута MarshalAs, чтобы устанавливать массивы фиксированной длины.
  • SizeConst - указывает количество элементов (не байт) в массиве или строке фиксированной длины. Используется совместно с параметрами UnmanagedType.ByValArray, UnmanagedType.ByValTStr. Значение данного параметра ограничено до 536870912 (512Мб).
  • ArraySubType - указывает тип элементов массива, который передаётся как UnmanagedType.ByValArray или UnmanagedType.LPArray.
Пример использования:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
// Получение информации о состоянии подключенной батареи
using System;
using System.Runtime.InteropServices;
using System.Reflection;
 
enum POWER_INFORMATION_LEVEL : uint
{
    AdministratorPowerPolicy = 9,
    LastSleepTime = 15,
    LastWakeTime = 14,
    ProcessorInformation = 11,
    ProcessorPowerPolicyAc = 18,
    ProcessorPowerPolicyCurrent = 22,
    ProcessorPowerPolicyDc = 19,
    SystemBatteryState = 5,
    SystemExecutionState = 16,
    SystemPowerCapabilities = 4,
    SystemPowerInformation = 12,
    SystemPowerPolicyAc = 0,
    SystemPowerPolicyCurrent = 8,
    SystemPowerPolicyDc = 1,
    SystemReserveHiberFile = 10,
    VerifyProcessorPowerPolicyAc = 20,
    VerifyProcessorPowerPolicyDc = 21,
    VerifySystemPolicyAc = 2,
    VerifySystemPolicyDc = 3
}
 
struct SYSTEM_BATTERY_STATE
{
    // Используем MarshalAs, т.к. размер типа bool
    // равен 4 байта
    [MarshalAs( UnmanagedType.I1 )]
    public bool AcOnLine;
    [MarshalAs( UnmanagedType.I1 )]
    public bool BatteryPresent;
    [MarshalAs( UnmanagedType.I1 )]
    public bool Charging;
    [MarshalAs( UnmanagedType.I1 )]
    public bool Discharging;
    [MarshalAs( UnmanagedType.ByValArray, SizeConst = 4, ArraySubType = UnmanagedType.I1 )]
    public bool[] Spare1; //можно просто заменить на Int32, или тип схожего размера
    public uint MaxCapacity;
    public uint RemainingCapacity;
    public uint Rate;
    public uint EstimatedTime;
    public uint DefaultAlert1;
    public uint DefaultAlert2;
}
 
[DllImport( "PowrProf.dll", SetLastError = true )]
static extern uint CallNtPowerInformation (
    POWER_INFORMATION_LEVEL InformationLevel,
    IntPtr lpInputBuffer,
    int nInputBufferSize,
    ref SYSTEM_BATTERY_STATE lpOutputBuffer,
    int nOutputBufferSize
    );
 
static void Main ( string[] args ) {
    var sbs = new SYSTEM_BATTERY_STATE();
    if ( CallNtPowerInformation(
        POWER_INFORMATION_LEVEL.SystemBatteryState,
        IntPtr.Zero,
        0,
        ref sbs,
        Marshal.SizeOf( sbs )
        ) == 0 ) {
        // Чтобы не писать значение каждого поля в
        // структуре SYSTEM_BATTERY_STATE, получим
        // их с помощью отражения (Reflection)
        var fields = 
            typeof( SYSTEM_BATTERY_STATE ).GetFields( BindingFlags.Public | BindingFlags.Instance );
        if ( fields != null )
            for ( int i = 0; i < fields.Length; ++i )
                Console.WriteLine(
                    "{0}: {1}",
                    fields[i].Name,
                    fields[i].GetValue( sbs )
                );
    }
    Console.ReadLine();
}
4.3. StructLayout

Атрибут StructLayout позволяет указать способ расположения полей объекта в памяти. Данный атрибут применим как для структур, так и для классов.

Рассмотрим параметры данного атрибута:
  • LayoutKind - это первый, и обязательный параметр атрибута StructLayout. Указывает на то, как поля будут распологаться в памяти:
    • Sequential - поля структуры или класса будут располагаться в памяти в том порядке, в котором объявлены в коде, с учётом выравнивания (параметр Pack).
    • Explicit - поля будут располагаться в памяти в соответсвии с указанным смещением. Смещение указывается атрибутом FieldOffset для каждого объявленого поля в структуре или классе. Данный параметр позволяет создавать объединение из нескольких типов, таким образом, что они будут расположены по одному адресу в памяти (аналогично union в C/C++).
    • Auto - CLR сама выстраивает поля объекта в наиболее оптимальном по её мнению порядке.
      Внимание!Для структур компилятор C# по умолчанию использует значение LayoutKind.Sequential, для классов LayoutKind.Auto.
  • CharSet - данный параметр отвечает за кодировку строк в строковых полях объекта.
    Pack - позволяет указать выравние полей в памяти, значение параметра должны быть числом, являющимся степенью двойки (https://www.cyberforum.ru/cgi-bin/latex.cgi?{2}^{n}) и не превышать 128.
    Size - устанавливает размер занимаемой объектом памяти, может большим или равным физическому размеру объекта. Если значение меньше, то параметр будет проигнорирован.

Пример использования использования классов с атрибутом StructLayout, а также создание объединений (union):
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
[StructLayout( LayoutKind.Sequential, Pack = 1 )]
struct BLOCK
{
    public IntPtr hMem;
    /* Если здесь заменить эти 3 параметра на массив с
        * атрибутом MarshalAs, то во время выполнения будет
        * выдано исключение TypeLoadException, это связано
        * с тем что при создании объединения CLR не может
        * создать требуемую структуру, возможно это баг CLR
        */
#if !ERROR
    public uint dwReserved1;
    public uint dwReserved2;
    public uint dwReserved3;
#else
    [MarshalAs( UnmanagedType.ByValArray, SizeConst = 3 )]
    public uint[] dwReserved;
#endif
}
 
[StructLayout( LayoutKind.Sequential, Pack = 1 )]
struct REGION
{
    public uint dwCommittedSize;
    public uint dwUnCommittedSize;
    public IntPtr lpFirstBlock;
    public IntPtr lpLastBlock;
}
 
[StructLayout( LayoutKind.Explicit )]
struct UNION
{
    // Создаём объединение из 2-х структур
    [FieldOffset( 0 )]
    public BLOCK Block;
    [FieldOffset( 0 )]
    public REGION Region;
}
 
[StructLayout( LayoutKind.Sequential, Pack = 1 )]
class PROCESS_HEAP_ENTRY
{
    public IntPtr lpData;
    public uint cbData;
    public byte cbOverhead;
    public byte iRegionIndex;
    public ushort wFlags;
    public UNION u;
}
 
[DllImport( "Kernel32.dll", SetLastError = true )]
static extern int GetProcessHeaps (
    int NumberOfHeaps,
    IntPtr[] ProcessHeaps
    );
 
[DllImport( "Kernel32.dll", SetLastError = true )]
static extern bool HeapWalk (
    IntPtr hHeap,
    // т.к. PROCESS_HEAP_ENTRY ссылочный тип,
    // то нам не нужны модифифкаторы ref/out
    // У нас сразу получается указатель на
    // PROCESS_HEAP_ENTRY
    PROCESS_HEAP_ENTRY lpEntry
    );
 
static void Main ( string[] args ) {
    var phe      = new PROCESS_HEAP_ENTRY();
    var pBuffArr = new IntPtr[
        // Получаем кол-во куч (heap) у процесса
        // подробнее см. описание на MSDN
        GetProcessHeaps( 0, null )
    ];
 
    if ( pBuffArr.Length == 0 )
        return;
 
    GetProcessHeaps( pBuffArr.Length, pBuffArr );
 
    for ( int i = 0; i < pBuffArr.Length; ++i ) {
        if ( HeapWalk( pBuffArr[i], phe ) ) {
            // Остановимся здесь для просмотра значений
            Debugger.Break();
        }
    }
    Console.ReadLine();
}
4.4. Атрибуты In и Out

Данные атрибуты позволяют указать CLR когда и каким образом нужно выполнять преобразование над параметрами (marshaling). Применяются эти атрибуты исключительно для параметров методов и учитываются CLR только при COM взаимодействии или использовании неуправляемого кода. Используются преимущественно с ссылочными типами, т.к. большинство значимых типов CLR может без труда преобразовать самостоятельно, благодаря использованию ключевых слов ref/out при передаче значений по ссылке. С ссылочными типами дела обстоят немного иначе, CLR не может предсказать какие манипуляции с данными будут производиться - нужно ли копировать значения из управляемой памяти в неуправляемую перед вызовом неуправляемого метода (за это отвечает атрибут In) и нужно ли выполнять обратную операцию (копирование из неуправляемой памяти в управляемую) после того как неуправляемый метод вернул управление (за это отвечает атрибут Out).

Атрибут In - указывает CLR что преобразование над данными должно выполняться до вызова неуправляемого метода.
Внимание!Атрибут In нельзя применять совместно с ключевым словом out.

Атрибут Out - указывает CLR что при возвращении из неуправляемого метода нужно выполнить преобразование над данными, чтобы они приняли вид который был до передачи их в метод.

Данные атрибуты можно сочетать, тогда преобразование будет выполнятся до вызова метода и после.

Вот 2 примера использования данных атрибутов и их влияние на передаваемые/возвращаемые данные:
  1. Код библиотеки: (скачать проект - InOutAttributesExample.rar)
    C
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    
    #include <Windows.h>
    #include <stdio.h>
     
    #pragma pack(push, 1)
    typedef struct _INFO
    {
        BOOLEAN bTrueFalse;
        DWORD   dwCount;
    } INFO, *PINFO;
    #pragma pack(pop)
     
    #define EXPORT __declspec(dllexport)
     
    EXPORT
    void __stdcall
    GetInfo(
        PINFO info
        )
    {
        wprintf(
            L"\tUnmanaged, In:\tbTrueFalse == %d, dwCount == %d\r\n",
            (DWORD)info->bTrueFalse,
            info->dwCount
            );
     
        info->bTrueFalse = 1;
        info->dwCount    = 1024;
     
        wprintf(
            L"\tUnmanaged, Out:\tbTrueFalse == %d, dwCount == %d\r\n",
            (DWORD)info->bTrueFalse,
            info->dwCount
            );
    }
    Код консольного приложения:
    C#
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    
    using System;
    using System.Runtime.InteropServices;
     
    namespace WindowsFormsApplication10
    {
        static class Program
        {
            const string DLLNAME = "TestC.dll";
     
            [StructLayout( LayoutKind.Sequential, Pack = 1 )]
            class INFO
            {
                // Указываем что необходимо выполнить преобразование над полем:
                // поле bTrueFalse должно занимать 1 байт в памяти в неуправляемой среде
                [MarshalAs( UnmanagedType.I1 )]
                public bool bTrueFalse;
                public uint dwCount;
            }
     
            /* Объявляем 4 прототипа функции GetInfo, каждый из которых вызывает
             * экспортируемую из test.dll функцию GetInfo, с той лишь разницей, что
             * в каждом из прототипов используются различные сочетания атрибутов
             * In и Out, либо не используются вовсе.
             */
     
            [DllImport( DLLNAME )]
            static extern void GetInfo (
                INFO info
                );
     
            [DllImport( DLLNAME, EntryPoint = "GetInfo" )]
            static extern void GetInfo_In (
                [In] INFO info
                );
     
            [DllImport( DLLNAME, EntryPoint = "GetInfo" )]
            static extern void GetInfo_Out (
                [Out] INFO info
                );
     
            [DllImport( DLLNAME, EntryPoint = "GetInfo" )]
            static extern void GetInfo_InOut (
                [In, Out] INFO info
                );
     
            static void Main ( )
            {
                var info = new INFO();
                // -------------------------------------------------------------
                info.bTrueFalse = true;
                info.dwCount = 100;
                Console.WriteLine( "-= GetInfo: without attributes =-" );
                Console.WriteLine(
                    "\tManaged, In:\tbTrueFalse == {0}, dwCount == {1}",
                    Convert.ToInt32( info.bTrueFalse ), info.dwCount
                    );
                GetInfo( info );
                Console.WriteLine(
                    "\tManaged, Out:\tbTrueFalse == {0}, dwCount == {1}\r\n",
                    Convert.ToInt32( info.bTrueFalse ), info.dwCount
                    );
                // -------------------------------------------------------------
                info.bTrueFalse = true;
                info.dwCount = 100;
                Console.WriteLine( "-= GetInfo: [In] =-" );
                Console.WriteLine(
                    "\tManaged, In:\tbTrueFalse == {0}, dwCount == {1}",
                    Convert.ToInt32( info.bTrueFalse ), info.dwCount
                    );
                GetInfo_In( info );
                Console.WriteLine(
                    "\tManaged, Out:\tbTrueFalse == {0}, dwCount == {1}\r\n",
                    Convert.ToInt32( info.bTrueFalse ), info.dwCount
                    );
                // ------------------------------------------------------------
                info.bTrueFalse = true;
                info.dwCount = 100;
                Console.WriteLine( "-= GetInfo: [Out] =-" );
                Console.WriteLine(
                    "\tManaged, In:\tbTrueFalse == {0}, dwCount == {1}",
                    Convert.ToInt32( info.bTrueFalse ), info.dwCount
                    );
                GetInfo_Out( info );
                Console.WriteLine(
                    "\tManaged, Out:\tbTrueFalse == {0}, dwCount == {1}\r\n",
                    Convert.ToInt32( info.bTrueFalse ), info.dwCount
                    );
                // ------------------------------------------------------------
                info.bTrueFalse = true;
                info.dwCount = 100;
                Console.WriteLine( "-= GetInfo: [In, Out] =-" );
                Console.WriteLine(
                    "\tManaged, In:\tbTrueFalse == {0}, dwCount == {1}",
                    Convert.ToInt32( info.bTrueFalse ), info.dwCount
                    );
                GetInfo_InOut( info );
                Console.WriteLine(
                    "\tManaged, Out:\tbTrueFalse == {0}, dwCount == {1}\r\n",
                    Convert.ToInt32( info.bTrueFalse ), info.dwCount
                    );
     
                Console.ReadKey();
            }
        }
    }
    Вывод:
    Код
    -= GetInfo: without attributes =-
            Managed, In:    bTrueFalse == 1, dwCount == 100
            Unmanaged, In:  bTrueFalse == 1, dwCount == 100
            Unmanaged, Out: bTrueFalse == 1, dwCount == 1024
            Managed, Out:   bTrueFalse == 1, dwCount == 100
    
    -= GetInfo: [In] =-
            Managed, In:    bTrueFalse == 1, dwCount == 100
            Unmanaged, In:  bTrueFalse == 1, dwCount == 100
            Unmanaged, Out: bTrueFalse == 1, dwCount == 1024
            Managed, Out:   bTrueFalse == 1, dwCount == 100
    
    -= GetInfo: [Out] =-
            Managed, In:    bTrueFalse == 1, dwCount == 100
            Unmanaged, In:  bTrueFalse == 0, dwCount == 0
            Unmanaged, Out: bTrueFalse == 1, dwCount == 1024
            Managed, Out:   bTrueFalse == 1, dwCount == 1024
    
    -= GetInfo: [In, Out] =-
            Managed, In:    bTrueFalse == 1, dwCount == 100
            Unmanaged, In:  bTrueFalse == 1, dwCount == 100
            Unmanaged, Out: bTrueFalse == 1, dwCount == 1024
            Managed, Out:   bTrueFalse == 1, dwCount == 1024
  2. Код метода на C: (скачать проект - InOutAttributesExample.rar)
    C
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    
    EXPORT
    void __stdcall
    BooleanTest(
        BOOL* boolArray,
        int   length
        )
    {
        int i;
     
        for ( i = 0; i < length; ++i )
            boolArray[i] = !boolArray[i];
    }
    Код реализации на C#:
    C#
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    
            [DllImport( "TestC.dll" )]
            static extern void BooleanTest (
                [MarshalAs( UnmanagedType.LPArray, ArraySubType = UnmanagedType.Bool )]
                [In, Out] bool[] boolArray,
                [In]      int length
                );
     
            static void Main ( )
            {
                var arr = new bool[10];
     
                Console.WriteLine( "\r\nBefore:" );
     
                for ( int i = 0; i < arr.Length; ++i )
                    Console.WriteLine( arr[i] );
     
                BooleanTest( arr, arr.Length );
                Console.WriteLine( "\r\nAfter:" );
     
                for( int i = 0; i < arr.Length; ++i )
                    Console.WriteLine( arr[i] );
     
                Console.ReadKey();
            }


5. Использование небезопасного кода

Что такое небезопасный код в C#.NET? Это код в котором есть возможность использования указателей в явном виде (например как в C - void *, char *, ..., а не обёртку IntPtr). Unsafe код выполняется без контроля CLR, поэтому в некоторых моментах он может оказаться быстрее чем код с использованием IntPtr и методов из класса Marshal. Так же использование unsafe кода может облегчить использование PInvoke.
К сожалению в C# на использование указателей наложены некоторые ограничения:
  1. Использование указателей возможно только со стандартными значимыми типами (sbyte, byte, short, ushort, int, uint, bool, long, ulong, char, float, double, decimal и перечисления - enum), с любой структурой в которой поля только неуправляемых типов, а также использовать указатель на указатель (int **);
  2. Нельзя использовать указатели на ссылочные типы (исключение тип string [ - при использовании fixed] и массивы, элементами которого являются типы из п. 1), а также структуры в которых содержаться ссылочные типы, т.к. они могут быть подвержены сборке мусора.
Т.е. объявить, например, указатель на string не получится.
Класс Marshal, использование PInvoke, небезопасный код (unsafe)


Для использования unsafe кода в C#, достаточно разрешить его в свойствах проекта: "Properties -> Build -> Allow unsafe code" если используется Visual Studio. Если компилировать код вручную, вызывая csc.exe, то тогда нужно добавить параметр /unsafe.

5.1. Ключевое слово unsafe

Ключевое слово unsafe позволяет указать в коде на класс, метод, поле или фрагмент кода, где используется unsafe код.
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
    /* Если ключевое слово unsafe указано всему классу, то
     * успользовать указатели можно везде, без указания
     * ключевого слова usnsafe к каждому полю/методу
     */
    unsafe class Test0
    {
        int* buffer;
 
        public void CallSomeFunc ( ) {
            void* pMem = Marshal.AllocHGlobal( 1024 ).ToPointer();
 
            Marshal.FreeHGlobal( (IntPtr)pMem );
        }
    }
 
    /* Если ключевое слово unsafe не указано всему классу, то
     * успользовать указатели можно только там, где указано
     * ключевое слово usnsafe
     */
    class Test1
    {
        // Отдельное указание для поля
        unsafe int *buffer;
 
        // Указание unsafe для всего метода
        unsafe public void CallSomeFunc0 ( ) {
            void *pMem = Marshal.AllocHGlobal( 1024 ).ToPointer();
 
            *(decimal*)pMem = 10.5M;
 
            Marshal.FreeHGlobal( (IntPtr)pMem );
        }
 
        // Указание unsafe только для некоторого
        // региона в методе
        public void CallSomeFunc1 ( ) {
            unsafe {
                // Работа с указателями уже должна вестись только в
                // границах { }
                void *pMem = Marshal.AllocHGlobal( 1024 ).ToPointer();
                // Заносим значение 10 (4 байта) по адресу, на который
                // указывает pMem
                *(int*)pMem = 10;
 
                Marshal.FreeHGlobal( (IntPtr)pMem );
            }
 
            //// Раскомментируйте и будет ошибка
            //int *pointer = (int*)0;
        }
    }
Данное ключевое слово не добавляет каких либо конструкций в IL код при компиляции, оно просто позволяет разделять код на учатки: safe и unsafe.

5.2. Ключевое слово fixed

fixed используется для фиксирования управляемого объекта в памяти (чтобы GC его не удалил и не переместил) и получения адреса, по которому объект расположен в памяти. Также fixed служит для объявления буферов фиксированного размера (аналогично MarshalAs(UnmanagedType.ByValArray, SizeConst = ...)).

Фиксировать нужно только те объекты, которые могут быть перемещены или удалены сборщиком мусора (например поля классов, массивы, строки, параметры методов с модификаторами ref/out), адрес остальных объектов можно получить без fixed.
Синтаксис оператора (для получения адреса):
C#
1
2
3
fixed( type* name = expression ) {
    // TODO
}
где type может быть неуправляемым типом или void, expression переменная которая будет зафиксирована и адрес которой будет присвоен переменной name.

Пример:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// Заполнение строки определнным символом, без создания дополнительных строк
using System;
 
namespace FixedTest
{
    class Program
    {
        static void Main ( string[] args ) {
            string value = "Hello\0 world!";
            Console.WriteLine( value );
            value.Fill( '1' );
            Console.WriteLine( value );
            Console.ReadKey();
        }
    }
 
    static class Extension
    {
        /// <summary>
        /// Заполняет всю строку определнным символом key.
        /// </summary>
        unsafe public static void
        Fill ( this string val, char key ) {
            // Фиксируем строку в памяти и получаем
            // указатель на начало строки
            fixed ( char* buff = val ) {
                // Полученный указатель buff нельзя
                // менять, поэтому присвоем его значение
                // другой переменной
                char* temp = buff;
                // Указатель на конец строки
                char* end = temp + val.Length;
 
                while ( temp != end ) {
                    *temp = key;
                    temp++;
                }
            }
        }
    }
}
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
using System;
 
namespace FixedTest
{
    class Program
    {
        unsafe static void
        Main ( string[] args ) {
            int value = 10;
            var buffer = new int[] { 10, 34, 35, 1, 7532 };
            // fixed не надо
            int* pValue = &value;
            *pValue = 25;
 
            Console.WriteLine("Value: " +  value );
 
            // Получаем адрес 0-го элемента массива
            fixed ( int* pointer = buffer ) {
                // Меняем 0-й элемент массива
                *pointer = -10;
                // Меняем 1-й элемент массива
                *( pointer + 1 ) = -34;
                // или
                // pointer[0] = -10;
                // pointer[1] = -34;
            }
 
            // Получаем адрес 2го элемента массива
            fixed ( int* pointer = &buffer[2] ) {
                int size = buffer.Length - 2;
                // Меняем остальные элементы массива
                for ( int i = 0; i < size; ++i )
                    *( pointer + i ) *= ( -1 );
                    // или
                    // pointer[i] *= (-1);
            }
 
            Console.Write( "buffer array: " );
            for ( int i = 0; i < buffer.Length; ++i )
                Console.Write( buffer[i] + " " );
            Console.ReadKey();
        }
    }
}
Буферы фиксированного размера созданные с использованием ключевого слова fixed являются аналогами буферов созданных при помощи MarshalAs, но с важными ограничениями:
  1. fixed буферы могут быть созданы только в структурах;
  2. Элементами буфера могут быть только элементы типов: bool, byte, short, int, long, char, sbyte, ushort, uint, ulong, float или double.
Синтаксис оператора fixed в данном случае будет таким:
C#
1
2
3
4
<unsafe> struct Test
{
    <public> fixed type name[size];
}
где type - это один из заданных типов, name - имя поля, а size - размер буфера (константа). Ключевые слова обрамлённые в <> являются не обязательными (в зависимости от ситуации).

Пример использования буфера фиксированного размера и получения адреса управляемого объекта в памяти.
Получение информации обо всех MIB-2 интерфейсах в системе:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
// Получение информации обо всех MIB-2 интерфейсах
// в системе. Более подробная информация тут:
// [url]http://msdn.microsoft.com/en-us/library/windows/desktop/aa365943(v=vs.85).aspx[/url]
 
using System;
using System.Runtime.InteropServices;
using System.Text;
 
namespace GetIfTableApp
{
    unsafe class Program
    {
        const int MAX_INTERFACE_NAME_LEN = 256;
        const int MAXLEN_IFDESCR = 256;
        const int ERROR_INSUFFICIENT_BUFFER = 122;
        const int NO_ERROR = 0;
        const int MAXLEN_PHYSADDR = 8;
 
        enum IANAType : uint
        {
            /// <summary>
            /// Some other type of network interface.
            /// </summary>
            IF_TYPE_OTHER = 1U,
            /// <summary>
            /// An Ethernet network interface.
            /// </summary>
            IF_TYPE_ETHERNET_CSMACD = 6U,
            /// <summary>
            /// A token ring network interface.
            /// </summary>
            IF_TYPE_ISO88025_TOKENRING = 9U,
            /// <summary>
            /// A PPP network interface.
            /// </summary>
            IF_TYPE_PPP = 23U,
            /// <summary>
            /// A software loopback network interface.
            /// </summary>
            IF_TYPE_SOFTWARE_LOOPBACK = 24U,
            /// <summary>
            /// An ATM network interface.
            /// </summary>
            IF_TYPE_ATM = 37U,
            /// <summary>
            /// An IEEE 802.11 wireless network interface.
            /// </summary>
            IF_TYPE_IEEE80211 = 71U,
            /// <summary>
            /// A tunnel type encapsulation network interface.
            /// </summary>
            IF_TYPE_TUNNEL = 131U,
            /// <summary>
            /// An IEEE 1394 (Firewire) high performance serial bus network interface.
            /// </summary>
            IF_TYPE_IEEE1394 = 144U
        }
 
        enum INTERNAL_IF_OPER_STATUS : uint
        {
            IF_OPER_STATUS_NON_OPERATIONAL = 0,
            IF_OPER_STATUS_UNREACHABLE = 1,
            IF_OPER_STATUS_DISCONNECTED = 2,
            IF_OPER_STATUS_CONNECTING = 3,
            IF_OPER_STATUS_CONNECTED = 4,
            IF_OPER_STATUS_OPERATIONAL = 5,
        }
 
        struct MIB_IFROW
        {
            // Массив фиксированной длины типа char
            internal fixed char wszName[MAX_INTERFACE_NAME_LEN];
            public uint dwIndex;
            public IANAType dwType;
            public uint dwMtu;
            public uint dwSpeed;
            public uint dwPhysAddrLen;
            // Массив фиксированной длины типа byte
            internal fixed byte bPhysAddr[MAXLEN_PHYSADDR];
            public uint dwAdminStatus;
            public INTERNAL_IF_OPER_STATUS dwOperStatus;
            public uint dwLastChange;
            public uint dwInOctets;
            public uint dwInUcastPkts;
            public uint dwInNUcastPkts;
            public uint dwInDiscards;
            public uint dwInErrors;
            public uint dwInUnknownProtos;
            public uint dwOutOctets;
            public uint dwOutUcastPkts;
            public uint dwOutNUcastPkts;
            public uint dwOutDiscards;
            public uint dwOutErrors;
            public uint dwOutQLen;
            public uint dwDescrLen;
            // Массив фиксированной длины типа byte
            internal fixed byte bDescr[MAXLEN_IFDESCR];
 
            public string Name {
                get {
                    // Перевод массива символов в тип string
                    fixed ( char* pointer = wszName ) {
                        if ( pointer != null )
                            return new string( pointer );
                        else
                            return null;
                    }
                }
            }
 
            public string Description {
                get {
                    fixed ( byte* pName = bDescr ) {
                        if ( pName == null )
                            return null;
                        else
                            return new string( (sbyte*)pName );
                    }
                }
            }
 
            public string PhysAddr {
                get {
                    fixed ( byte* pName = bPhysAddr ) {
                        if ( pName == null )
                            return null;
                        else
                            return new string( (sbyte*)pName );
                    }
                }
            }
        }
 
        [StructLayout( LayoutKind.Sequential )]
        struct MIB_IFTABLE
        {
            internal int dwNumEntries;
            internal void* table;
        }
 
        [DllImport( "Iphlpapi.dll", SetLastError = true )]
        static extern int GetIfTable (
            MIB_IFTABLE* pIfTable,
            int* dwSize,
            bool bOrder
            );
 
        static int Main ( string[] args ) {
            Console.Title = "MIB-II interface table";
 
            MIB_IFROW[] mibs;
            ConsoleColor clr = Console.ForegroundColor;
            UnsafeGetIfTable( out mibs );
 
            if ( mibs != null ) {
                for ( int i = 0; i < mibs.Length; ++i ) {
                    Console.Clear();
                    Console.ForegroundColor = ConsoleColor.Red;
                    Console.WriteLine( "Row {0} from {1}:\r\n", i + 1, mibs.Length );
                    Console.ForegroundColor = clr;
                    Console.WriteLine( GetInfoFromIfRow( ref mibs[i] ) );
                    Console.ForegroundColor = ConsoleColor.Green;
                    Console.WriteLine( "\r\nPress any key to continue..." );
                    Console.ForegroundColor = clr;
                    Console.ReadKey();
                }
                Console.Clear();
                Console.WriteLine( "End write... Press 'Enter' to end..." );
            } else
                Console.WriteLine( "mibs == null... Press 'Enter' to end..." );
            Console.ReadLine();
            return 0;
        }
 
        static void UnsafeGetIfTable ( out MIB_IFROW[] mibs ) {
            // Размер структуры MIB_IFTABLE
            int dwSize = Marshal.SizeOf( typeof( MIB_IFTABLE ) );
            // Выделяем память под структуру
            MIB_IFTABLE* pMibIfTable = (MIB_IFTABLE*)Marshal.AllocHGlobal( dwSize );
            // Если GetIfTable вернет ERROR_INSUFFICIENT_BUFFER, то значит буфер мал
            if ( GetIfTable( pMibIfTable, &dwSize, false ) == ERROR_INSUFFICIENT_BUFFER )
                pMibIfTable = (MIB_IFTABLE*)Marshal.ReAllocHGlobal(
                    (IntPtr)pMibIfTable, (IntPtr)dwSize
                    );
 
            mibs = null;
 
            if ( GetIfTable( pMibIfTable, &dwSize, true ) == NO_ERROR ) {
                mibs = new MIB_IFROW[pMibIfTable->dwNumEntries];
                // Фиксируем массив в памяти
                fixed ( MIB_IFROW* pMibs = mibs ) {
                    // Размер структуры MIB_IFROW
                    uint rowSize = (uint)Marshal.SizeOf( typeof( MIB_IFROW ) );
                    MIB_IFROW* pBuff = pMibs;
                    // Адрес 0-го элемента массива
                    uint pTable = (uint)&pMibIfTable->table;
                    // Копируем все полученные структуруы в массив mibs
                    for ( int i = 0; i < pMibIfTable->dwNumEntries; i++ ) {
                        *( pBuff++ ) = *(MIB_IFROW*)( pTable );
                        pTable += rowSize;
                    }
                }
            }
 
            if ( pMibIfTable != null ) {
                Marshal.FreeHGlobal( (IntPtr)pMibIfTable );
                pMibIfTable = null;
            }
        }
 
        static string GetInfoFromIfRow ( ref MIB_IFROW ifRow ) {
            StringBuilder sb = new StringBuilder();
 
            sb.AppendLine( string.Format( "Индекс: {0}", ifRow.dwIndex ) );
            sb.AppendLine( string.Format( "Имя интерфейса: {0}", ifRow.Name ) );
            sb.AppendLine( string.Format( "Описание: {0}", ifRow.Description ) );
            sb.AppendLine( string.Format( "Тип: {0}", ifRow.dwType ) );
            sb.AppendLine( string.Format( "Maximum Transmission Unit: {0}", ifRow.dwMtu ) );
            sb.AppendLine( string.Format( "Скорость: {0} Мбит/сек", ifRow.dwSpeed / 1024 / 1024 ) );
            sb.AppendLine( string.Format( "Физический адрес адаптера для интерфейса: {0}", ifRow.PhysAddr ) );
            sb.AppendLine( string.Format( "Admin Status: {0}", Convert.ToBoolean( ifRow.dwAdminStatus ) ) );
            sb.AppendLine( string.Format( "Oper Status: {0}", ifRow.dwOperStatus ) );
            sb.AppendLine( "---------" );
            sb.AppendLine( string.Format( "dwInDiscards: {0}", ifRow.dwInDiscards ) );
            sb.AppendLine( string.Format( "dwInErrors: {0}", ifRow.dwInErrors ) );
            sb.AppendLine( string.Format( "dwInNUcastPkts: {0}", ifRow.dwInNUcastPkts ) );
            sb.AppendLine( string.Format( "dwInOctets: {0}", ifRow.dwInOctets ) );
            sb.AppendLine( string.Format( "dwInUcastPkts: {0}", ifRow.dwInUcastPkts ) );
            sb.AppendLine( string.Format( "dwInUnknownProtos: {0}", ifRow.dwInUnknownProtos ) );
            sb.AppendLine( string.Format( "dwOutDiscards: {0}", ifRow.dwOutDiscards ) );
            sb.AppendLine( string.Format( "dwOutErrors: {0}", ifRow.dwOutErrors ) );
            sb.AppendLine( string.Format( "dwOutNUcastPkts: {0}", ifRow.dwOutNUcastPkts ) );
            sb.AppendLine( string.Format( "dwOutOctets: {0}", ifRow.dwOutOctets ) );
            sb.AppendLine( string.Format( "dwOutQLen: {0}", ifRow.dwOutQLen ) );
            sb.AppendLine( string.Format( "dwOutUcastPkts: {0}", ifRow.dwOutUcastPkts ) );
            return sb.ToString();
        }
    }
}

5.3. Ключевое слово stackalloc

stackalloc используется для выделения памяти в стеке, а не в куче, как это происходит при вызове, например, Marshal.AllocHGlobal. Основные отличия между выделением памяти в стеке и в куче в том, что выделение памяти в стеке и доступ к ней осуществляется быстрее чем к куче, также есть шанс того, что данные в стеке попадут в кэш процессора, тогда доступ к ним (данным) будет максимально быстрым, в результате можно добиться некоторого ускорения алгоритма.
Не забываем что CLR довольно хорошо справляется с распределением памяти, в некоторых случаях использование stackalloc может не дать того прироста в скорости, который ожидался, т.к. CLR при возможности выделяет памяти немного больше чем требуется, в результате при создании какого либо объекта память не будет выделяться снова, а будет просто возвращен адрес в уже зарезервированном фрагменте.
Также к плюсам выделения памяти в стеке относится то, что память будет освобождена сразу после завершения метода.
Внимание!В связи с тем что память выделенная в стеке будет освобождена сразу после завершения метода, то нельзя выносить полученный указатель за пределы метода, т.к. при обращении к данным через этот указатель вы получите мусор.
Минус стековой памяти в том, что её объем относительно небольшой, всего 1 Мб (такое значение задается по умолчанию линкером), и при превышении установленного значения CLR бросит исключение StackOverflowException, после этого приложение будет экстренно завершено.
Важно!Однажды выделив в стеке память определенного размера, может случится так, что в следующий раз выделить этот же размер памяти не получится, или память будет выделена, но при выполнении какого-либо кода может быть брошено исключение StackOverflowException.

Каждый поток при создании получает своё стековое пространство. Размер этого пространства можно указывать при создании потока, начиная с .NET 4.0 на изменение размера стека наложено ограничение, подробнее о нём написано на MSDN. Изменить стандартный размер выделяемого пространства можно в PE заголовке файла (структура NT_OPTIONAL_HEADER поле SizeOfStackReserve).

Использовать stackalloc можно только во время инициализации объекта. Синтаксис использования stackalloc такой:
C#
1
type* name = stackalloc type[count];
где type - любой значимый тип (value type) данных на который можно представить ввиде указателя, count - размер выделяемой памяти, который расчитывается по формуле count * sizeof(type) (sizeof - размер объекта в неуправляемой памяти).

Пример:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
unsafe static void
Main ( string[] args )
{
    uint* stack = stackalloc uint[10];
 
    for ( int i = -10; i < 0; ++i )
    {
        //*(stack + i) = (uint)i;
        //Console.WriteLine( *(stack + i) );
 
        stack[i] = (uint)i;
        Console.WriteLine( stack[i] );
    }
    Console.ReadKey();
}
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
struct signed
{
    public int  i4;
    public long i8;
 
    public override string ToString ( )
    {
        return string.Format( "i4: {0} i8: {1}", i4, i8 );
    }
}
 
unsafe static void
Main ( string[] args )
{
    signed* stack = stackalloc signed[10];
 
    for ( int i = 0; i < 10; ++i )
    {
        stack[i].i4 = i;
        stack[i].i8 = -i;
        Console.WriteLine( stack[i] );
    }
    Console.ReadKey();
}
Пример того как не надо делать:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
unsafe void
SomeMethod ( )
{
    int size = 100;
    byte* result = StackAlloc( size );
    for ( int i = 0; i < size; ++i )
        Debug.WriteLine( result[i] );
}
 
unsafe byte*
StackAlloc ( int size )
{
    byte* ptr = stackalloc byte[size];
    for ( int i = 0; i < size; ++i )
        ptr[i] = (byte)i;
    return ptr;
}


На этом всё Спасибо за внимание.
111
Programming
Эксперт
94731 / 64177 / 26122
Регистрация: 12.04.2006
Сообщений: 116,782
15.08.2011, 14:04
Ответы с готовыми решениями:

небезопасный код (unsafe)
Столкнулся с проблемой: Visual Studio 2010 ругается на unsafe, а именно: &quot;ошибка CS0227:...

Что такое класс Marshal
подскажите, для каких целей используется класс Marshal

Небезопасный код
Всем привет, интересует такой вопрос. Мне нужно создать public partial class MyForm : Form {...

Небезопасный код в C#
Всем здравствуйте. Собственно есть задание: Задан стековый массив А(N, N). Поменять местами...

0
15.08.2011, 14:04
IT_Exp
Эксперт
87844 / 49110 / 22898
Регистрация: 17.06.2006
Сообщений: 92,604
15.08.2011, 14:04
Помогаю со студенческими работами здесь

SharpDevelop и небезопасный код
Привет всем. Подскажите пожалуйста как разрешить небезопасный код в SharpDevelop Поиск по форуму...

Безопасный и небезопасный код.
Есть некий класс unsafe class MyClass { //... public void SetKey(byte...

CLR и небезопасный код
Здравствуйте. Подскажите, пожалуйста. Есть программа на билдере, выводящая результаты на форму,...

Использование unsafe кода
Возможно ли переписать метод без использование unsafe кода? public static void...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
1
Закрытая тема Создать тему
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2024, CyberForum.ru