При разработке приложений на C++ для Windows разработчики неизбежно и довольно часто встречаются с такой неэкзотической сущностью как дескриптор. Их в Windows великое множество, при этом каждый обязательно после завершения работы с ним обязательно должен быть закрыт соответствующей функцией. Конечно, система после завершения процесса сама подчищает за ним, но с правильностью такого подхода лично я не согласен. Тем, кто пишет исключительно на C, позавидовать в этом случае непросто. Зато для разработчиков C++ есть масса облегчающих жизнь механизмов.
В данной статье я хочу рассмотреть универсальный класс, написанный мной, и рассчитаный на оборачивание практически любого Windows-дескриптора (возможно, я чего-то не знаю?).
Что представляют собой дескрипторы? С точки зрения Windows их можно разделить на дескрипторы объектов ядра и не-ядра, нас же интересует работа с ними на уровне C++, и, чтобы написать универсальный класс-обёртку, нам понадобится выявить их общие свойства. Далее я буду использовать словосочетание «освободить дескриптор», которое на самом деле должно звучать так: «освободить объект Windows через его дескриптор». Это чтобы избежать путаницы в терминологии — под объектом буду подразумевать объект языка C++.
Расммотрим жизненный цикл дескриптора:
1. Создание (инициализация). Все функции (или большинство), возвращающие дескриптор, в случае ошибки присваивают ему значение 0 (NULL) или -1 (0xFFFFFFFF, INVALID_HANDLE_VALUE). Значения больше ноля — успех.
2. Использование.
3. Освобождение, когда дескриптор более не нужен.
Последний пункт интересует больше всего — как освобождать дескриптор, когда сущесвует такое множество функций для этого? Могу перечислить несколько по памяти (которая в голове): CloseHandle, FindClose, closesocket, InternetCloseHandle и другие.
Ниже перечислены сигнатуры этих функций:
Каждая из них принимает тип дескриптора и возвращает некое целое. Осталось только разобраться с тем, как научить наш класс правильно освобождать дескриптор — для этого идеально подойдёт всеядный reinterpret_cast.
Собственно, сам класс.
В данной статье я хочу рассмотреть универсальный класс, написанный мной, и рассчитаный на оборачивание практически любого Windows-дескриптора (возможно, я чего-то не знаю?).
Что представляют собой дескрипторы? С точки зрения Windows их можно разделить на дескрипторы объектов ядра и не-ядра, нас же интересует работа с ними на уровне C++, и, чтобы написать универсальный класс-обёртку, нам понадобится выявить их общие свойства. Далее я буду использовать словосочетание «освободить дескриптор», которое на самом деле должно звучать так: «освободить объект Windows через его дескриптор». Это чтобы избежать путаницы в терминологии — под объектом буду подразумевать объект языка C++.
Расммотрим жизненный цикл дескриптора:
1. Создание (инициализация). Все функции (или большинство), возвращающие дескриптор, в случае ошибки присваивают ему значение 0 (NULL) или -1 (0xFFFFFFFF, INVALID_HANDLE_VALUE). Значения больше ноля — успех.
2. Использование.
3. Освобождение, когда дескриптор более не нужен.
Последний пункт интересует больше всего — как освобождать дескриптор, когда сущесвует такое множество функций для этого? Могу перечислить несколько по памяти (которая в голове): CloseHandle, FindClose, closesocket, InternetCloseHandle и другие.
Ниже перечислены сигнатуры этих функций:
BOOL __stdcall CloseHandle( HANDLE );
BOOL __stdcall FindClose( HANDLE );
int __stdcall closesocket( SOCKET );
BOOL __stdcall InternetCloseHandle( HINTERNET );
Каждая из них принимает тип дескриптора и возвращает некое целое. Осталось только разобраться с тем, как научить наш класс правильно освобождать дескриптор — для этого идеально подойдёт всеядный reinterpret_cast.
Собственно, сам класс.
template class AutoHandle
{
NON_COPYABLE( AutoHandle );
public:
typedef BOOL ( WINAPI * CloseProc )( THandle );
AutoHandle( THandle handle_, CloseProc close_proc_ )
: m_handle( handle_ ),
m_close_proc( close_proc_ )
{
}
~AutoHandle() throw()
{
close();
}
int close()
{
if ( !valid() )
return false;
int ret = 0;
if ( m_close_proc )
ret = static_cast( m_close_proc( m_handle ) );
m_handle = 0;
return ret;
}
THandle handle() const
{
return m_handle;
}
bool valid() const
{
return !!m_handle && ( reinterpret_cast( INVALID_HANDLE_VALUE ) != m_handle );
}
AutoHandle& operator = ( THandle rhs_ )
{
close();
m_handle = rhs_;
return *this;
}
operator bool () const
{
return valid();
}
DWORD wait( DWORD milliseconds_ = 0 )
{
return ::WaitForSingleObject( m_handle, milliseconds_ );
}
protected:
THandle m_handle;
private:
CloseProc m_close_proc;
};
Описание класса
Это шаблонный класс, принимающий в качестве параметра шаблона тип дескриптора. NON_COPYABLE - это макрос, запрещающий копирование объекта, так как он не предполагает подсчёта ссылок.
#define NON_COPYABLE( clsname ) \
private: \
clsname( const clsname& ); \
clsname& operator = ( const clsname& );
Далее идёт объявление прототипа функции, используемой для освобождения дескриптора. С его помощью можно в конструкторе указать адрес на функцию, сделав приведение в стиле reinterpret_cast. Класс снабжён функциями для передачи типа дескриптора Win32API-функциям, проверки на валидность, а также использования WaitForSingleObject. Шаблоны хороши ещё и тем, что те методы класса, сгенерированного из шаблона, которые не вызываются в клиентском коде, не компилируются. Поэтому при работе, скажем, с сокетом, если не будете делать вызова wait (а он и не рассчитан для сокетов), ошибок не будет.
Член m_handle класса объявлен защищённым, а не закрытым, неспроста - класс расширяемый. Но при этом нет смысла в виртуальном деструкторе, так как дескриптор - не та сущность, для которой актуален полиморфизм (сравните с ЦДельфин: public ЦРыба - да-да, дельфин - это рыба!). Зато мы сэкономим память за счёт того, что у нас не будет лишних указателей на виртуальные таблицы.
Небольшой пример
AutoHandle my_event( ::OpenEvent( READ_CONTROL | SYNCHRONIZE | EVENT_MODIFY_STATE,
FALSE, _T( "COOL_EVENT_SHA_LA_LA" ) ), ::CloseHandle );
if ( my_event.valid() )
return;
else
my_event = ::CreateEvent( 0, FALSE, FALSE, _T( "COOL_EVENT_SHA_LA_LA" ) );
//.......................
if ( WAIT_OBJECT_0 == my_event.wait( INFINITE ) )
{
// Дождались
}
my_event.close(); // Не обязательно
Если сигнатура функции не совпадает с CloseProc, используйте reinterpret_cast.
Вот и всё. Спасибо, что дочитали до конца :)