Взаимодействие с компонентами COM и Win32

Исполняющая среда .NET предоставляет большое число атрибутов, позволяющих программам C# взаимодействовать с компонентами, написанными с использованием модели COM и библиотек DLL Win32. Например, атрибут DllImport можно использовать для метода static extern, чтобы указать, что реализацию метода следует искать в библиотеке DLL Win32. Эти атрибуты находятся в пространстве имен System.Runtime.InteropServices; подробную документацию по этим атрибутам можно найти в документации по среде выполнения .NET.

Взаимодействие с другими языками .NET

Атрибут IndexerName

Индексаторы реализованы в .NET с помощью индексированных свойств и имеют имя в метаданных .NET. Если атрибут IndexerName отсутствует для индексатора, по умолчанию используется имя Item. Атрибут IndexerName позволяет разработчику переопределить это значение по умолчанию и задать другое имя.

namespace System.Runtime.CompilerServices.CSharp
{
[AttributeUsage(AttributeTargets.Property)]
public class IndexerNameAttribute: Attribute
{
public IndexerNameAttribute(string indexerName) {...}

public string Value { get {...} }
}
}

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

Базовый язык C#, как определено в предыдущих главах, заметно отличается от C и C++ тем, что в нем не используются указатели в качестве типа данных. Вместо этого C# предоставляет ссылки и возможность создавать объекты, управляемые сборщиком мусора. Эта разработка в сочетании с другими особенностями делает язык C# гораздо более безопасным, чем C и C++. В базовом языке C# просто невозможно иметь неинициализированную переменную, «висящий» указатель или выражение, индексирующее массив за пределами его границ. Таким образом устраняются целые категории ошибок, регулярно досаждающих в программах на C и C++.

Хотя практически у каждой конструкции с типом указателя в C или C++ имеется аналог ссылочного типа в C#, тем не менее в некоторых ситуациях доступ к типам указателей становится необходимостью. Например, согласование с операционной системой, доступ к устройству, сопоставленному памяти, или реализация критического по времени алгоритма могут оказаться неосуществимыми без доступа к указателям. Для удовлетворения этой потребности C# предоставляет возможность писать небезопасный код.

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

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

Небезопасные контексты

Небезопасные возможности C# доступны только в небезопасных контекстах. Небезопасный контекст вводится включением модификатора unsafe в объявление типа или члена, или использованием небезопасного_оператора:

· объявление класса, структуры, интерфейса или делегата может включать модификатор unsafe, в этом случае все текстовое пространство этого объявления (включая тело класса, структуры или интерфейса) считается небезопасным контекстом;

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

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

Соответствующие грамматические расширения показаны ниже. Для краткости все порождения, описанные в предыдущих главах, заменяются многоточием (...).

class-modifier:
...
unsafe

struct-modifier:
...
unsafe

interface-modifier:
...
unsafe

delegate-modifier:
...
unsafe

field-modifier:
...
unsafe

method-modifier:
...
unsafe

property-modifier:
...
unsafe

event-modifier:
...
unsafe

indexer-modifier:
...
unsafe

operator-modifier:
...
unsafe

constructor-modifier:
...
unsafe

destructor-declaration:
attributesopt externopt unsafeopt ~ identifier ( ) destructor-body
attributesopt unsafeopt externopt ~ identifier ( ) destructor-body

static-constructor-modifiers:
externopt unsafeopt static
unsafeopt externopt static
externopt static unsafeopt
unsafeopt static externopt
static externopt unsafeopt
static unsafeopt externopt

embedded-statement:
...
unsafe-statement

unsafe-statement:
unsafe block

В этом примере

public unsafe struct Node
{
public int Value;
public Node* Left;
public Node* Right;
}

модификатор unsafe, указанный в объявлении структуры, делает все текстовое пространство объявления структуры небезопасным контекстом. Таким образом можно объявить поля Left и Right с типом указателя. Этот пример можно было бы записать так:

public struct Node
{
public int Value;
public unsafe Node* Left;
public unsafe Node* Right;
}

Здесь модификаторы unsafe в объявлениях полей делают эти объявления небезопасными контекстами.

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

public class A
{
public unsafe virtual void F() {
char* p;
...
}
}

public class B: A
{
public override void F() {
base.F();
...
}
}

Модификатор unsafe метода F в классе A просто делает текстовое пространство F небезопасным контекстом, в котором можно использовать небезопасные возможности языка. В переопределении F в классе B нет необходимости вновь задавать модификатор unsafe, если, конечно, в самом методе F в классе B не требуются небезопасные возможности.

Ситуация несколько другая, когда тип указателя является частью подписи метода

public unsafe class A
{
public virtual void F(char* p) {...}
}

public class B: A
{
public unsafe override void F(char* p) {...}
}

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

Типы указателей

В небезопасном контексте тип (§4) может быть типом_указателя, а также типом_значений или ссылочным_типом. Однако тип_указателя можно также использовать в выражении typeof (§7.6.10.6) вне небезопасного контекста, так как такое использование не является небезопасным.

type:
...
pointer-type

Тип_указателя записывается как неуправляемый_тип или как зарезервированное слово void с последующей лексемой *:

pointer-type:
unmanaged-type *
void *

unmanaged-type:
type

Тип, указанный перед * в типе указателя, называется типом_референта типа указателя. Он представляет тип переменной, на которую указывает значение типа указателя.

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

Неуправляемый_тип — это любой тип, не являющийся ссылочным_типом или сформированным типом и не содержащий поля ссылочного_типа или сформированного типа на любом уровне вложенности. Иначе говоря, неуправляемым_типом является один из следующих типов:

· sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal или bool.

· любой перечислимый_тип;

· любой тип_указателя;

· любой определенный пользователем тип_структуры, который не является сформированным типом и содержащий поля только неуправляемых_типов.

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

Примеры типов указателей приведены в следующей таблице:

Пример Описание
byte* Указатель на byte
char* Указатель на char
int** Указатель на указатель на int
int*[] Одномерный массив указателей на int
void* указатель на неизвестный тип

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

В отличие от C и C++, если несколько указателей объявлены в одном объявлении, в C# звездочка * записывается только вместе с базовым типом, а не как префиксный знак пунктуации с каждым именем указателя. Пример

int* pi, pj; // NOT as int *pi, *pj;

Значение указателя, имеющего тип T* представляет адрес переменной типа T. Оператор косвенного обращения к указателю * (§18.5.1) можно использовать для доступа к этой переменной. Например, если дана

переменная P типа int*, выражение *P означает переменную типа int, находящуюся по адресу, который содержится в P.

Как и ссылка на объект, указатель может иметь значение null. Результат применения оператора косвенного обращения к указателю со значением null зависит от реализации. Указатель со значением null представляется нулями во всех разрядах.

Тип void* представляет указатель на неизвестный тип. Поскольку тип референта неизвестен, оператор косвенного обращения нельзя применить к указателю типа void*, и с таким указателем нельзя выполнять никакие вычисления. Однако указатель типа void* можно привести к любому другому типу указателя (и наоборот).

Типы указателя являются отдельной категорией типов. В отличие от ссылочных типов и типов значения, типы указателя не наследуют от object и нет никаких преобразований между типами указателя и object. В частности, упаковка и распаковка (§4.3) не поддерживаются для указателей. Однако допускаются преобразования между различными типами указателя и между типами указателя и целыми типами. Это описано в §18.4.

Тип_указателя не может использоваться в качестве аргумента типа (§4.4), а вывод типа (§7.5.2) не работает при вызове универсальных методов, в процессе которого в качестве аргумента типа будет выведен тип указателя.

Тип_указателя можно использовать в качестве типа поля с модификатором volatile (§10.5.3).

Хотя указатели можно передавать в качестве параметров ref или out, это может привести к неопределенному поведению, поскольку указатель может быть установлен для указания на локальную переменную, которая больше не существует при возврате из вызванного метода, или если фиксированный объект, на который указывал указатель, больше не является фиксированным. Пример:

using System;

class Test
{
static int value = 20;

unsafe static void F(out int* pi1, ref int* pi2) {
int i = 10;
pi1 = &i;

fixed (int* pj = &value) {
// ...
pi2 = pj;
}
}

static void Main() {
int i = 10;
unsafe {
int* px1;
int* px2 = &i;

F(out px1, ref px2);

Console.WriteLine("*px1 = {0}, *px2 = {1}",
*px1, *px2); // undefined behavior
}
}
}

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

unsafe static int* Find(int* pi, int size, int value) {
for (int i = 0; i < size; ++i) {
if (*pi == value)
return pi;
++pi;
}
return null;
}

В небезопасном контексте имеется несколько конструкций для операций с указателями:

· оператор * можно использовать для косвенного обращения к указателю (§18.5.1);

· оператор -> можно использовать для доступа к члену структуры посредством указателя (§18.5.2);

· оператор [] можно использовать для индексирования указателя (§18.5.3);

· оператор & можно использовать для получения адреса переменной (§18.5.4);

· операторы +++ и -- можно использовать для увеличения и уменьшения указателей (§18.5.5);

· операторы + и - можно использовать для выполнения арифметических операций с указателем (§18.5.6);

· операторы ==, !=, <, >, <=, and => можно использовать для сравнения указателей (§18.5.7).

· оператор stackalloc можно использовать для выделения памяти из стека вызовов (§18.7);

· оператор fixed можно использовать для временной фиксации переменной, чтобы можно было получить ее адрес (§18.6).

Наши рекомендации