Pull to refresh

Учимся красиво работать с дескрипторами Windows

При разработке приложений на C++ для Windows разработчики неизбежно и довольно часто встречаются с такой неэкзотической сущностью как дескриптор. Их в Windows великое множество, при этом каждый обязательно после завершения работы с ним обязательно должен быть закрыт соответствующей функцией. Конечно, система после завершения процесса сама подчищает за ним, но с правильностью такого подхода лично я не согласен. Тем, кто пишет исключительно на C, позавидовать в этом случае непросто. Зато для разработчиков C++ есть масса облегчающих жизнь механизмов.

В данной статье я хочу рассмотреть универсальный класс, написанный мной, и рассчитаный на оборачивание практически любого 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.

Вот и всё. Спасибо, что дочитали до конца :)
Tags:
Hubs:
You can’t comment this publication because its author is not yet a full member of the community. You will be able to contact the author only after he or she has been invited by someone in the community. Until then, author’s username will be hidden by an alias.