Оценка 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. |
Вот собственно и все. Используйте на здоровье.
Оценка 20 Оценить ![]() ![]() ![]() ![]() ![]() ![]()
|