Безопасные дескрипторы и критическое завершение
До появления среды .NET Framework версии 2.0 все дескрипторы операционной системы могли инкапсулироваться только в управляемый объект IntPtr оболочки. Хотя этот был способ удобен для взаимодействия с машинным кодом, возможна утечка дескрипторов из-за асинхронных исключений, например из-за неожиданного прерывания потока или переполнения стека. Эти асинхронные исключения, являющиеся препятствием для очистки ресурсов операционной системы, могут возникать почти в любом месте разрабатываемой программы. Они могут возникать в приложениях, использующих компьютер, выполняющий управляемый код, например Microsoft SQL Server.
В некоторых случаях объекты, подлежащие завершению, могли быть освобождены при сборке мусора во время выполнения метода в вызове неуправляемого кода. Если метод завершения освободил дескриптор, переданный вызову неуправляемого кода, это может привести к повреждению дескриптора. Дескриптор также мог быть освобожден, если метод блокирован во время вызова неуправляемого кода, например во время чтения файла.
Более важно, что, так как Windows агрессивно применяет дескрипторы повторно, дескриптор может быть повторно использован и указывать на другой ресурс, который может содержать важные данные. Этот явление известно как атака путем повторного использования, которая может повредить данные и является угрозой безопасности.
Начиная с платформы.NET Framework 2.0, класс SafeHandle упрощает несколько проблем времени существования этих объектов и интегрируется с вызовом неуправляемого кода во избежание утечки ресурсов операционной системы. Класс SafeHandle решает проблемы времени существования объекта, назначая и освобождая дескрипторы без прерываний. Он содержит критический метод завершения, гарантирующий для дескриптора закрытие и выполнение при выгрузке AppDomain, даже когда возможно нарушение нормального выполнения вызова неуправляемого кода.
Так как SafeHandle наследуется от CriticalFinalizerObject, все некритические методы завершения вызываются перед любым из критических методов завершения. Методы завершения вызываются для объектов, которые более не существуют в течение того же прохода сборщика мусора. Например, объект FileStream может выполнять обычный метод завершения, чтобы сбросить существующие буферизованные данные без риска утечки или повторного использования дескриптора. Это очень слабое упорядочение критических и некритических методов завершения не предназначено для обычного использования. Оно в основном существует, чтобы помочь выполнить миграцию существующих библиотек, позволяя этим библиотекам использовать SafeHandle без изменения своей семантики. Кроме того, критический метод завершения и все, что он вызывает, например метод SafeHandle.ReleaseHandle(), должно находиться в области с ограничением исполнения. Это накладывает ограничения на написание кода внутри графа вызовов метода завершения.
Начиная с .NET Framework версии 2.0, операции вызова неуправляемого кода автоматически увеличивают счетчик ссылок для дескрипторов, инкапсулированных в SafeHandle, и уменьшает эти счетчики после завершения вызова. Это гарантирует невозможность неожиданного повторного использования или закрытия дескриптора.
При создании объектов SafeHandle можно определить принадлежность базового дескриптора. Она определяет, будет ли объект SafeHandle освобождать дескриптор после удаления объекта. Это полезно для дескрипторов с особенными требованиям к времени существования или для использования дескриптора, временем существования которого управляет кто-то другой.
Классы безопасных дескрипторов
Класс SafeHandle в пространстве имен System.Runtime.InteropServices является абстрактным классом-оболочкой для дескрипторов операционной системы. Наследовать от этого класса сложно. Вместо этого используйте производные классы в пространстве имен Microsoft.Win32.SafeHandles, которые предоставляют безопасные дескрипторы для следующих элементов.
Файлы и каналы.
Представления памяти.
Конструкции шифрования.
Разделы реестра.
Дескрипторы ожидания.