Сообщений 1 Оценка 20 Оценить |
Эти два класса родились благодаря многочисленным функциям, возвращающим код ошибки ERROR_INSUFFICIENT_BUFFER и книге «Программирование серверных приложений для Windows®2000» Дж. Рихтер, Дж. Кларк.
Класс CAutoBufBase предназначен для автоматического выделения памяти. Он представляет базовую функциональность для другого шаблонного класса CAutoBuf. Классы могут быть использованы в различных целях, однако основная их задача – упростить и повысить наглядность кода, в котором есть многочисленные вызовы функций, требующих буферы переменного размера. У таких функций, как правило, есть несколько параметров, куда передаются указатель на буфер, его размер и адрес переменной, куда будет записан размер скопированных данных. Если в первом параметре передать NULL, то функция вернет требуемый размер буфера. Такую операцию иногда приходиться делать несколько раз. Для упрощения работы с такими функциями и предназначены эти классы.
Начнем с простого примера. Наверное многие из Вас прочитали статью Игоря Вартанова «Как узнать, есть ли у пользователя права администратора?». Там есть такой кусок. Если покопаться на RSDN, можно найти очень много подобного кода. Этот я взял наугад.
do // выделение буфера для запрошенной из токена информации { if (pInfoBuffer ) delete pInfoBuffer; pInfoBuffer = new BYTE[dwInfoBufferSize]; if (!pInfoBuffer ) __leave; SetLastError( 0 ); if (!GetTokenInformation(hAccessToken, TokenGroups, pInfoBuffer, dwInfoBufferSize, &dwInfoBufferSize ) && (ERROR_INSUFFICIENT_BUFFER != GetLastError())) __leave; else ptgGroups = (PTOKEN_GROUPS)pInfoBuffer; } while (GetLastError()); // если была ошибка, значит начального размера недостаточно |
Давайте разберемся, что он делает. В цикле происходит вызов функции GetTokenInformation() для получения информации о группах маркера. Заранее невозможно определить размер возвращаемой информации – функция сама указывает требуемый размер буфера при вызове ее с передачей как адрес буфера NULL или, если размер выделенного буфера недостаточен. Функция возвращает требуемый размер, устанавливая при этом код ошибки в ERROR_INSUFFICIENT_BUFFER. Если произошла другая ошибка – делается переход на финальный обработчик блока исключений. Далее цикл повторяется снова, однако переменная dwInfoBufferSize уже содержит нужный размер буфера. Происходит удаление старого буфера, выделяется новый и вызывается функция. Если она завершиться удачно – цикл прервется.
Вот так будет выглядеть аналогичный код, использующий наш класс.
BOOL fOk; CAutoBufBase abuf; do{ fOk = GetTokenInformation(hAccessToken,TokenGroups,abuf,abuf, abuf.GetSizeAddr()); if (!fOk){ if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) break; abuf.Alloc(); } } while(!fOk); if (fOk){ //Тут все нормально } else //Тут возникла ошибка |
Ну и как это работает?
Методов и операторов-функций у класса довольно много. Все они очень простые, поэтому приведу здесь самые важные и часто используемые.
//Получение адреса закрытой переменной, хранящей //размер буфера PDWORD GetSizeAddr() { return (PDWORD)&m_BufSize; }; operator PVOID() const { return m_pBuf; }; operator DWORD() const { return (DWORD)m_BufSize; }; DWORD Size() const { return m_BufSize; }; ... //Protected attributes protected: size_t m_BufSize; PVOID m_pBuf; |
Теперь Вам должно быть ясно, почему корректен такой синтаксис вызова функции
GetTokenInformation(hAccessToken,TokenGroups,abuf,abuf,abuf.GetSizeAddr()); |
Разберемся с функцией Alloc().
PVOID CAutoBufBase::Alloc(size_t dwBufSize) { DWORD BufSize = m_BufSize; if (dwBufSize != -1) //Если параметр опущен - берем размер BufSize = dwBufSize; //из внутренней переменной if (m_pBuf){ if (m_BufSize < BufSize) //Перераспределяем память, только если m_pBuf = realloc(m_pBuf,BufSize);//запрошено больше чем есть } else m_pBuf = malloc(BufSize);//Выделяем память m_BufSize = BufSize; return m_pBuf; } |
Здесь примечательно несколько вещей. Во-первых, если в параметре передать число –1 – функция возьмет значение из внутренней переменной. Адрес этой переменной мы передаем функции GetTokenInformation(), которая и заполняет ее нужным значением. Во-вторых, память выделяется с использованием стандартных средств библиотеки C, что дает возможность отслеживать ее с помощью отладочной библиотеки.
ПРИМЕЧАНИЕ Именно потому, что у Рихтера память выделялась при помощи WinAPI функций, я решил написать свой класс, использующий стандартные функции С. Негоже игнорировать всю мощь отладочной библиотеки crtdbg. |
Остальной код намного проще, чем приведенный выше. Думаю с ним вы разберетесь сами.
Для примера, возьмем функцию QueryServiceConfig(). Описание ее можно найти в MSDN или статье Александра Федотова «Управление системными службами Windows NT». Вызывать мы ее будем следующим образом.
CAutoBuf<QUERY_SERVICE_CONFIG> pQSC; BOOL fOk; do{ fOk = QueryServiceConfig(hServ,pQSC,pQSC,pQSC); if (!fOk){ if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) break; pQSC.Alloc(); } } while(!fOk); if (fOk){ if (pQSC->dwServiceType == SERVICE_FILE_SYSTEM_DRIVER) MessageBox(NULL,_T("System Driver"),_T("Type"),0); } |
Как видите, здесь все аналогично предыдущему примеру. Только все заметно проще. :) Объяснять код нет смысла, поэтому переходим к следующему пункту.
CAutoBuf – это шаблонный класс, унаследованный от CAutoBufBase. В нем переопределены несколько функций и операторов. В частности, функция GetSizeAddr() завернута в удобный оператор. Почему я это не сделал в базовом классе? Дело в том, что operator PDWORD() для компилятора (MSVC++ 6.0) представляется точно также как operator PVOID(). При компиляции, ошибок и предупреждений не возникает, однако в вместо вызова operator PDWORD() происходит вызов operator PVOID(). Но так как в CAutoBuf operator PVOID() отсутствует, я решил его снова ввести.
Класс CAutoBuf очень маленький, поэтому я приведу его полное описание.
//Удобный шаблон template<class T> class CAutoBuf : public CAutoBufBase { public: //Конструктор CAutoBuf(PVOID pBuf = 0):CAutoBufBase(pBuf){}; //Деструктор ~CAutoBuf(){}; //Выделение памяти T* Alloc(DWORD dwBufSize=-1) { return (T*)CAutoBufBase::Alloc(dwBufSize); }; //Отсоединение T* Detach() { return (T*)CAutoBufBase::Detach(); }; T* GetBuffer() { return (T*)m_pBuf; }; operator T*() const { return (T*)m_pBuf; }; operator DWORD() const { return m_BufSize; }; operator PDWORD() { return GetSizeAddr(); }; T* operator->() const { return (T*)m_pBuf; }; //Создание и инициализация (заполнение) буфера void Copy(T* pT,size_t dwpTSize) { Alloc(dwpTSize); memcpy(m_pBuf,pT,dwpTSize); }; }; |
Как видите – все очень просто. В классе имеется оператор доступа operator->, который позволяет без лишних приведений типа работать с членами структуры.
ПРЕДУПРЕЖДЕНИЕ В принципе, ничего не мешает Вам использовать класс таким образом: CAutoBuf<TCHAR> pBuf; Однако, Вы должны быть готовы в этом случае к warning C4284. |
Вот собственно и все. Используйте на здоровье.
Сообщений 1 Оценка 20 Оценить |