Сообщений 1    Оценка 20        Оценить  
Система Orphus

Автоматическое выделение памяти

Классы CAutoBufBase и CAutoBuf<>

Автор: Алексей Ширшов
Опубликовано: 07.10.2002
Исправлено: 13.03.2005
Версия текста: 1.5

Предисловие
Использование класса CAutoBufBase
Описание класса CAutoBufBase
Использование класса CAutoBuf<>
Описание класса CAutoBuf<>

Исходные тексты классов

Предисловие

Эти два класса родились благодаря многочисленным функциям, возвращающим код ошибки ERROR_INSUFFICIENT_BUFFER и книге «Программирование серверных приложений для Windows®2000» Дж. Рихтер, Дж. Кларк.

Класс CAutoBufBase предназначен для автоматического выделения памяти. Он представляет базовую функциональность для другого шаблонного класса CAutoBuf. Классы могут быть использованы в различных целях, однако основная их задача – упростить и повысить наглядность кода, в котором есть многочисленные вызовы функций, требующих буферы переменного размера. У таких функций, как правило, есть несколько параметров, куда передаются указатель на буфер, его размер и адрес переменной, куда будет записан размер скопированных данных. Если в первом параметре передать NULL, то функция вернет требуемый размер буфера. Такую операцию иногда приходиться делать несколько раз. Для упрощения работы с такими функциями и предназначены эти классы.

Использование класса CAutoBufBase

Начнем с простого примера. Наверное многие из Вас прочитали статью Игоря Вартанова «Как узнать, есть ли у пользователя права администратора?». Там есть такой кусок. Если покопаться на 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
        //Тут возникла ошибка

Ну и как это работает?

Описание класса CAutoBufBase

Методов и операторов-функций у класса довольно много. Все они очень простые, поэтому приведу здесь самые важные и часто используемые.

    //Получение адреса закрытой переменной, хранящей
  //размер буфера
    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.

Остальной код намного проще, чем приведенный выше. Думаю с ним вы разберетесь сами.

Использование класса CAutoBuf<>

Для примера, возьмем функцию 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<>

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        Оценить