Применение неуправляемого кода

По умолчанию приложения на C# относятся к управляемому коду. Но при необходимости управляемый код может взаимодействовать с неуправляемым кодом. К неуправляемому коду, вызываемому из управляемых C# приложений, можно отнести функции DLL-библиотек и сервисы COM-компонентов. Приложение управляемого кода также может включать фрагменты небезопасного кода. Небезопасный код тоже относится к неуправляемому коду, так как выделение и освобождение памяти в нем не контролируется исполняющей средой .NET.

Небезопасный код

Фрагмент небезопасного кода в C# следует помечать ключевым словом unsafe. Например: int i1;unsafe {int *i2=&i1;}

Ключевым словом unsafe требуется обязательно помечать любой фрагмент кода, который содержит указатель.

Модификатор unsafe может быть указан для методов, свойств и конструкторов (за исключением статических конструкторов), а также для блоков кода.

Например:

using System;

class Class1

{   unsafe static void M1 (int* p)

   // Небезопасный код: указатель на int

   {   *p *= *p;   }  // *p – доступ к значению

   unsafe public static void Main()

   // Небезопасный код: применен оператор  получения адреса (&)

   {  int i = 4;        M1 (&i);        Console.WriteLine (i);

   }

}

Чтобы использовать небезопасный код, следует установить опцию компилятора /unsafe. Для этого в среда прогаммирования Visual Studio .NET достаточно выбрать имя проекта в окне Solution Explorer и через контекстное меню вызвать диалог Property Pages а затем на странице Configuration Properties | Build установить значение опции Allow unsafe code blocks равным True.

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

Выполняющая среда .NET для эффективного управления памятью при удалении одних объектов "перемещает" другие объекты, чтобы исключить фрагментацию памяти свободными блоками памяти. Таким образом, выполняющая среда .NET по умолчанию не гарантирует, что объект, на который получен указатель, всегда будет иметь один и тот же адрес. Для предотвращения перемещения объекта следует использовать оператор fixed, который имеет следующее формальное описание:

fixed ( тип* имя_указателя = выражение )

                      выполняемый_оператор_или_блок

В качестве типа можно указывать любой неуправляемый тип или void. Выражение является указателем на заданный тип. Фиксация объекта применяется только для указанного выполняемого оператора или блока. Доступ к фиксированной переменной не ограничен областью видимости небезопасного кода. Поэтому фиксированная переменная может указывать на значение, располагаемое в более широкой области видимости, чем данная область видимости небезопасного кода. При этом компилятор C# не выдает предупреждений о такой ситуации.

Однако, компилятор C# не позволит установить указатель на управляемую переменную вне оператора fixed.

Например:

class Point

   { public int x, y; }

class Class1

 {[STAThread]

  static void Main(string[] args)

   { unsafe

            { Point pt = new Point();  // pt — это управляемая  переменная

            fixed ( int* p = &pt.x ) {  *p = 1 }  // pt.x – указывает на значение

                                                                       // размерного типа

            }

 }  }

Одним оператором fixed можно фиксировать несколько указателей, но только в том случае, если они одного типа.

Например:

fixed (byte* pa1 = array1, pa2 = array2) {…}

В том случае, если требуется зафиксировать указатели различных типов, можно использовать вложенные операторы fixed.

 Например:

fixed (int* p1 = &p.x)

   fixed (double* p2 = &array1[5]) {    }

Указатели, которые инициализированы оператором fixed, не могут быть изменены.

Если объект, на который устанавливается указатель, размещается в стеке (например, переменная типа int), то его местоположение фиксировать не требуется. Разместить блок памяти в стеке можно и явным образом, используя оператор stackalloc, который имеет следующее формальное описание:

тип * имя_указателя = stackalloc тип [ выражение ];

В качестве типа может быть указан любой неуправляемый тип.

Например:

using System;

class Class1

{public static unsafe void Main()

 {   int* a1 = stackalloc int[100];

   int* p = a1;        // Указатель на первый элемент массива

   *p++ = *p++ = 1;

   for (int i=2; i<100; ++i, ++p)

       *p = p[-1] + p[-2];

   for (int i=0; i<10; ++i) Console.WriteLine (a1[i]);

 }

}

Время жизни указателя, инициализированного с применением stackalloc, ограничено временем выполнения метода, в котором этот указатель объявлен. Инициализировать указатели посредством   stackalloc можно только для локальных переменных.

DLL-библиотеки

Для того, чтобы вызвать метод из DLL-библиотеки, его следует объявить с модификатором extern и атрибутом DllImport.

Например:

[DllImport("Имя_библиотеки.dll")]

static extern int M1(int i1, string s1);

Класс атрибута DllImportAttribute имеет следующее определение:

namespace System.Runtime.InteropServices

{

   [AttributeUsage(AttributeTargets.Method)]

   public class DllImportAttribute: System.Attribute

   {

      public DllImportAttribute(string dllName) {…}

      public CallingConvention CallingConvention;

      public CharSet CharSet;                          // Набор символов

      public string EntryPoint;              // Имя метода

      public bool ExactSpelling;           // Точное соответствие

                                                           // написания имени метода

      public bool PreserveSig; // Определяет, будет ли предотвращено

          // изменение сигнатуры метода  (по умолчанию установлено

          // значение true). При изменении сигнатуры возвращаемое

          // значение будет иметь тип HRESULT и будет

          // добавлен out-параметр retval

      public bool SetLastError;

      public string Value { get {…} }

   }

}

Атрибут DllImport имеет один позиционный параметр, определяющий имя DLL-библиотеки, и пять именованных параметров. Параметр EntryPoint позволяет указать имя метод из DLL-библиотеки.  При этом имя метода, помечаемого данным атрибутом, может отличаться от имени метода в DLL-библиотеке.

Например:

[DllImport("myDll.dll", EntryPoint="M1")]

static extern int New_name_of_M1(int i1, string s1);

Именованный параметр CharSet определяет используемый в DLL-библиотеке набор символов (ANSI или Unicode). По умолчанию используется значение CharSet.Auto.

Например:

[DllImport("myDll.dll", CharSet CharSet.Ansi)]

static extern int M1(int i1, string s1);

Для каждого типа параметра при вызове метода из DLL-библиотеки выполняющая среда .NET производит подразумеваемое по умолчанию преобразование типов (например, тип string в тип LPSTR (Win32). Для того, чтобы явным образом указать тип, используемый в методе DLL-библиотеки, следует применить к параметру атрибут MarshalAsAttribute.

Например:

[DllImport("myDll.dll", CharSet CharSet.Unicode)]

static extern int M1(int i1,

         [MarshalAs(UnmanagedType.LPWStr)]

          string s1);

Атрибут MarshalAsAttribute может быть прикреплен к полю, методу или параметру.  Прикрепление данного атрибута к методу позволяет указать явное преобразование типа для возвращаемого значения.

 

 

Вы можете следить за любыми ответами на эту запись через RSS 2.0 ленту. Вы можете оставить ответ, или trackback с вашего собственного сайта.

Оставьте отзыв

XHTML: Вы можете использовать следующие теги: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

 
Rambler's Top100