Критичные финализаторы и SafeHandle

Критичные финализаторы подобны CER в том отношении, что код внутри них защищен от асинхронных исключений и прочих подобных опасностей, исходящих от виртуальной исполняющей системы и находящихся вне вашего влияния. Чтобы указать, что объект имеет критичный финализатор, просто унаследуйте его от CriticalFinalizerObject.

Поступив так, вы гарантируете объекту наличие критичного финализатора, выполняющегося в контексте CER и потому подчиняющегося всем правилам, налагаемым CER. Вдобавок CLR будет выполнять критичные финализаторы после завершения работы с остальными некритичными финализируемыми объектами.

В действительности необходимость в создании критично финализируемого объекта возникает редко. Объекты с критичными финализаторами чаще всего используются в функционально совместимом коде.

Требуемое поведение можно получить за счет наследования от SafeHandle, так как часто в конечном итоге производится привязка к обработчикам в этом коде. Класс SafeHandle — это важнейший инструмент при создании “родного” функционально совместимого кода через P/Invoke или СОМ, поскольку позволяет гарантировать, что не произойдет никаких утечек ресурсов внутри CLR.

До появления .NET 2.0 это было невозможно. Во времена .NET 1.1 обычно нужно было представлять непрозрачный тип “родного” дескриптора посредством управляемого типа IntPtr. Это работало неплохо, если не учитывать то, что не гарантировалась очистка лежащего в основе ресурса в случае возникновения асинхронного исключения, такого как ThreadAbortException. Как обычно, добавление в .NET 2.0 дополнительного уровня посредничества6 в форме SafeHandle позволило смягчить остроту этой проблемы.

Прежде чем принять решение о создании наследника SafeHandle, поищите среди наследников SafeHandle, определенных в .NET Framework — нет ли там подходящего. Например, если разрабатывается код для прямого обращения к драйверу устройства вызовом Win32-функции DeviceIoControl через P/lnvoke, то типа SafeHandle будет вполне достаточно для хранения дескриптора, с помощью которого открывается драйвер напрямую.

При создании собственного класса-наследника SafeHandle должна быть выполнена короткая последовательность шагов. В качестве примера давайте создадим производный от SafeHandle класс — SafeBluetoothRadioFindHandle, чтобы можно было перечислить подключенные к системе устройства Bluetooth, если таковые есть.

Шаблон перечисления устройств Bluetooth в “родном” коде довольно прост и распространен в Win32 API. Для этого вызывается Win32-функция BluetoothFindFirstRadio, которая в случае успеха возвращает дескриптор первого устройства через выходной параметр и дескриптор перечисления — через возвращаемое значение.

Затем можно получить любое дополнительное устройство вызовом Win32-функции BluetoothFindNextRadio, передав ей дескриптор перечисления из функции BluetoothFindFirstRadio. По завершении работы потребуется вызвать Win32-функцию BluetoothFindRadioClose на дескрипторе перечисления.

Рассмотрим приведенный ниже код:

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.Runtime.InteropServices;
using System.Runtime.ConstrainedExecution;
using System.Security;
using System.Security.Permissions;
using System.Text;
using Microsoft.Win32.SafeHandles; //
// Соответствует BLUETOOTH_FIND_RADIO_PARAMS из Win32 //
[StructLayout( LayoutKind.Sequential )]
class BluetoothFindRadioParams
{
public BluetoothFindRadioParams()  {
dwSize = 4;
}
public UInt32 dwSize;
}
//
// Соответствует BLUETOOTH_RADIO_INFO из Win32 //
[StructLayout( LayoutKind.Sequential,
CharSet = CharSet.Unicode )] struct BluetoothRadioInfо {
public const int BLUETOOTH_MAX_NAME_SIZE = 248;
public UInt32 dwSize;
public UInt64 address;

Авторитетный специалист по С++ Эндрю Кениг (Andrew Koenig) любит называть это фундаментальной теоремой разработки программного обеспечения, т.е. любая проблема может быть решена добавлением уровня посредничества.

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
[MarshalAs( UnmanagedType.ByValTStr,
SizeConst = BLUETOOTH_MAX_NAME_SIZE ) ] public string szName;
public UInt32 ulClassOfDevices-public UIntl6 ImpSubversion;
public UIntl6 manufacturer;
}
//
// Безопасный обработчик перечисления устройств Bluetooth //
[SecurityPermission( SecurityAction.Demand, UnmanagedCode = true ) ]
sealed public class SafeBluetoothRadioFindHandle : SafeHandleZeroOrMinusOnelsInvalid
{
private SafeBluetoothRadioFindHandle() : base ( true )  {
} override protected bool ReleaseHandle()  {
return BluetoothFindRadioClose( handle );
}
[DllImport( "Irprops.cpl" )]
[ReliabilityContract ( Consistency.WillNotCorruptState,
Cer.Success )] [SuppressUnmanagedCodeSecurity]
private static extern bool BluetoothFindRadioClose(IntPtr hFind );
}
public class EntryPoint {
private const int ERROR_SUCCESS = 0;
static void Main()  {
SafeFileHandle radioHandle;
using( SafeBluetoothRadioFindHandle radioFindHandle
= BluetoothFindFirstRadio(new BluetoothFindRadioParams(),
out radioHandle) )  {
if( !radioFindHandle.Islnvalid )  {
BluetoothRadioInfо radiolnfo = new BluetoothRadioInfо();
radiolnfо.dwSize = 520;
UInt32 result = BluetoothGetRadioInfо ( radioHandle,
ref radiolnfo ) ;
if ( result == ERROR_SUCCESS )  {
// Вывести информацию на консоль.
Console.WriteLine( "address = {0:X}",
radiolnfо.address );
Console.WriteLine ( "szName = {0}",
radiolnfо.szName );
Console.WriteLine ( "ulClassOfDevice = {0}",
radiolnfo.ulClassOfDevice );
Console.WriteLine ( "ImpSubversion = {0}",
radiolnfо.ImpSubversion );
Console.WriteLine ( "manufacturer = {0}",
radiolnfо.manufacturer );
}
radioHandle.Dispose();
}
}
}
[DllImport( "Irprops.cpl" )]
private static extern SafeBluetoothRadioFindHandle
BluetoothFindFirstRadio( [MarshalAs(UnmanagedType.LPStruct)]
BluetoothFindRadioParams pbtfrp, out SafeFileHandle phRadio ) ;
[DllImport( "Irprops.cpl" )] private static extern UInt32
BluetoothGetRadioInfo ( SafeFileHandle hRadio,
ref BluetoothRadioInfо pRadioInfo ) ;
}

Суть этого примера заключена в SafeBluetoothRadioFindHandle. Его можете унаследовать непосредственно от SafeHandle, но исполняющая система предлагает два вспомогательных класса, SafeHandleZeroOrMinusOnelsInvalid и SafeHandleMinusOne Is Invalid, от которых выполнять наследование удобней.

Будьте осторожны, имея дело с функциями Win32 через P/Invoke, и всегда тщательно читайте документацию, чтобы знать, чему равно неверное значение дескриптора. Интерфейс Win32 API известен путаницей в этом вопросе. Например, Win32-фнкция CreateFile для обозначения неудачи возвращает -1, а функция CreateEvent в случае ошибки возвращает дескриптор null. В обоих случаях типом возвращаемого значения является handle.

При разработке собственного наследника SafeHandle необходимо помнить о нескольких вещах.

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

• Предусматривайте конструктор по умолчанию, который инициализирует наследника Saf eHandle. Обратите внимание, что Saf eBluetoothRadioFindHandle объявляет приватный конструктор по умолчанию. Поскольку уровень P/Invoke обладает особыми полномочиями, он может создавать экземпляры объекта даже в случае приватного конструктора. Приватный конструктор удерживает клиентов от создания экземпляров без вызова функций Win32, создающих лежащий в основе ресурс.

• Переопределяйте виртуальное свойство Is Invalid. В данном случае в этом не было необходимости, поскольку базовый класс Saf eHandle ZeroOrMinusOne Is In valid делает это самостоятельно.

• Переопределяйте виртуальный метод ReleaseHandle, используемый для очистки ресурса. Обычно в нем осуществляется вызов Р/Invoke для освобождения неуправляемого ресурса. В данном примере вызывается BluetoothFindRadioClose. Обратите внимание, что при объявлении метода для вызова Р/Invoke применяется контракт надежности, поскольку метод ReleaseHandle вызывается в контексте CER. Вдобавок разумно применить к методу атрибут SuppressUnmanagedCod eSecurityAttribute, чтобы предотвратить проверку стека средством CAS среды CLR при каждом вызове. Но помните, что это подразумевает защищенность самого кода.

После определения наследника SafeHandle вы готовы использовать его через объявления P/Invoke. В предыдущем примере объявлялся метод BluetoothFindFirstRadio для вызова через P/Invoke.

Если посмотреть на описание этой функции в Microsoft Developer Network (MSDN), то станет ясно, что она возвращает тип BLUETOOTHRADIOFIND, который представляет собой дескриптор внутреннего объекта перечисления устройств Bluetooth. Во времена .NET 1.1 тип возврата метода потребовалось бы объявить как IntPtr.

Начиная с .NET 2.0, в качестве типа его возврата указывается Saf eBluetoothRadioFindHandle, а уровень маршализации P/Invoke обрабатывает все остальное. Теперь дескриптор перечисления защищен от утечки исполняющей системой на случай каких-либо асинхронных исключений, исходящих от виртуальной системы выполнения.

При маршализации между методом СОМ или функцией Win32, которая возвращает дескриптор, содержащийся в структуре (в противоположность возврату просто дескриптора), уровень функциональной совместимости не предоставляет поддержки работы с наследниками SafeHandle.

В таких редких случаях нужно вызывать метод SetHandle наследника SafeHandle после получения структуры от функции или метода СОМ, чтобы экземпляр SafeHandle смог управлять временем жизни неуправляемого ресурса.

Однако если приходится поступать подобным образом, необходимо убедиться, что операции, создающие дескриптор, и последующий вызов SetHandle происходят внутри CER, чтобы ничто не могло прервать процесс выделения ресурса и присваивания дескриптора объекту SafeHandle. В противном случае есть вероятность возникновения утечки ресурсов.

Вы можете следить за любыми ответами на эту запись через 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