Сообщений 6 Оценка 180 Оценить |
Многие библиотеки, предназначенные для облегчения жизни программистов, предоставляют множество классов-оберток для упрощения работы с такими объектами, как HKEY, HWND, HMENU, объектами GDI. Тем не менее, такие популярные объекты как хендлы (handles) почему-то бывают забыты разработчиками библиотеки. В данной статье приведен простой пример такого класса.
Представлены единственной
HANDLE m_h; |
объявленной как public. Скорее всего, использовать ее непосредственно не понадобится. В классе реализованы операторы HANDLE и LPHANDLE, предоставляющие доступ к этой переменной.
Самое узкое место этого (и ему подобных) классов. Дело в том, что будучи нормальным C++ классом, наш CHandle должен иметь конструктор копирования, позволяющий использовать одни HANDLE-объект несколькими CHandle-объектами. Но в таком случае, только деструктор последнего CHandle-объекта должен закрыть единственный HANDLE-объект. Т.е. необходимо реализовать механизм подсчета ссылок. В данном классе эта тяжкая работа поручена операционной системе; используется API ::DuplicateHandle(). Это не самое легковесное решение, и его использование сведено к минимуму. Конструктор копирования использует эту функцию, а обычный конструктор и переопределенный оператор присваивания нет.
RSDN::CHandle hRead, hWrite; // Duplicate handle не используется BOOL b = ::CreatePipe(&hRead, &hWrite, NULL, 0); // Duplicate handle не используется RSDN::CHandle hEvent = ::CreateEvent(NULL, false, false, NULL); // Вызывается Duplicate handle RSDN::CHandle hDup = hRead; // ОШИБКА: НЕ ВЫЗЫВАЕТСЯ Duplicate handle, // после уничтожения обного из объектов // у второго остается недействительный HANDLE RSDN::CHandle hDup = hRead.m_h; |
Обратите внимание, что operator=(const CHandle&) и конструктор копирования CHandle(const CHandle&) неявно вызывают метод Duplicate(). Избегайте использования этих методов, заменяя их на явный вызов Duplicate() или Attach(other.Detach()):
RSDN::CHandle hPrev; for(/*...*/) { RSDN::CHandle hNext = ::CreateFile(/*...*/); // ... hPrev = hNext; } |
В этом примере все время происходит вызов функций Duplicate/Close, которых можно избежать, изменив код следующим образом:
RSDN::CHandle hPrev; for(/*...*/) { RSDN::CHandle hNext = ::CreateFile(/*...*/); // ... hPrev.Attach(hNext.Detach()); } |
Эти методы были добавлены для поддержки вот такого (довольно экзотического) кода:
__STD::vector<CAdapt<RSDN::CHandle> > m_vector; for(int i = 0; i < 10; i++) m_vector.push_back(::CreateThread(/*...*/)); |
Который, тем не менее, можно (и нужно) переделать в что-то вроде:
__STD::vector<CAdapt<RSDN::CHandle> > m_vector; m_vector.resize(10); for(int i = 0; i < 10; i++) m_vector[i].m_T = ::CreateThread(/*...*/); |
Кроме проверки на NULL и INVALID_HANDLE_VALUE он также пытается проверить сам хендл, используя для этого API ::GetHandleInformation(). Но только если имеется директива _DEBUG. В Release версии хендл проверяется только на NULL и INVALID_HANDLE_VALUE. К сожалению, ::GetHandleInformation() не реализован в Win9x, и при отладке в этой операционной системе дополнительная проверка не производится.
HANDLE m_h | Переменная, хранящая оригинальный хендл |
CHandle() | Конструктор, инициализирует m_h нулем |
CHandle(HANDLE h) | Конструктор, Duplicate не вызывается |
CHandle(const CHandle& other) | Конструктор, Duplicate вызывается |
~CHandle() | Деструктор, вызывает Close |
bool operator!() const | Обратное к IsValid |
operator HANDLE() const | Позволяет использовать этот объект вместо HANDLE |
LPHANDLE operator&() | Возвращает адрес m_h, позволяет использовать этот объект вместо HANDLE* |
CHandle& operator=(HANDLE h) | m_h = h, Duplicate не вызывается |
CHandle& operator=(const CHandle& other) | m_h = other.m_h, Duplicate вызывается |
void Attach(HANDLE h) | Вызывает ::ClosеHandle если нужно, затем m_h = h, Duplicate не вызывается |
HANDLE Detach() | Возвращает m_h и выставляет ее в нуль |
bool IsValid() const | Возвращает false, если m_h равен нулю или INVALID_HANDLE_VALUE |
void Close() | Проверяет m_h и вызывает ::CloseHandle если нужно |
bool Duplicate(HANDLE hSource, bool bInherit = false) | m_h = ::DuplicateHandle(hSource) |
DWORD Wait(DWORD dwTimeout = INFINITE) const | Ожидает события m_h |
DWORD WaitWithMessageLoop(DWORD dwTimeout = INFINITE) const | Ожидает события m_h с обработкой сообщений |
Класс CHandle не зависит от других библиотек и может быть использован в любом ATL/MFC/API приложении.
Сообщений 6 Оценка 180 Оценить |