Not a kernel guy

… in the Windows kernel team

Tuesday, October 31, 2006

C++ обёртка для HANDLE.

Пару лет назад я пользовался вот такой C++ обёрткой для HANDLE (часть методов и обработка ошибок опущены):

/// @brief  A generic policy-based wrapper for a handle value
template <typename T, typename PolicyT>
class
{
public:
    /// @brief  Construct a wrapper from a raw value
    HandleT(const T& _handle = PolicyT::null()):
        m_handle(_handle)
    {}

    /// @brief  Duplicate a handle
    HandleT(const HandleT& _right):
        m_handle(PolicyT::copy(_right.get()))
    {}

    ~HandleT()
    {
        release();
    }

    /// @brief  Duplicate a handle
    HandleT& operator=(const HandleT& _right)
    {
        release();
        m_handle = PolicyT::copy(_right.get());

        return *this;
    }

    /// @brief  Close the enclosed handle
    void
    release()
    {
        PolicyT::release(m_handle);
        m_handle = PolicyT::null();
    }

private:
    T m_handle;
};

Идея была – написать класс, который:

  1. Автоматически освобождает ресурсы при выходе из области видимости;
  2. Обеспечивает легкость копирования описателей;
  3. Умеет работать с различными типами описателей.

Чтобы удовлетворить третий пункт базовые операции были вынесены в отдельные классы “политик” (policy classes). Политика стандартного Win32 HANDLE выглядела так (обработка ошибок опущена):

/// @brief Handle pocily for Win32 HANDLE
struct
{
    static HANDLE null()
    {
        return NULL;
    }

    /// @brief  Release a handle
    static void release(const HANDLE& _handle)
    {
        if (_handle)
        {
            ::CloseHandle(_handle);
        }
    }

    /// @brief  Make a copy of the given handle
    static HANDLE copy(const HANDLE& _handle)
    {
        HANDLE retval = NULL;

        if (_handle != NULL)
        {
            DuplicateHandle(
                GetCurrentProcess(),
                _handle,
                GetCurrentProcess(),
                &retval,
                0,
                FALSE,
                DUPLICATE_SAME_ACCESS);
        }

        return retval;
    }
};

И, наконец, тип класса обертки объявлялся так:

/// @brief  A wrapper for Win32 HANDLE
typedef HandleT<HANDLE, HandlePolicy> Handle;

Таким образом, класс Handle соответствовал двум оставшимся требованиям и был, надо сказать, весьма удобен в использовании. Тем не менее, эта реализация обладала одним существенным недостатком. Каким? Для копирования описателя использовался функция DuplicateHandle. Что не так с DuplicateHandle? DuplicateHandle - слишком мощная и тяжеловесная операция для простого копирования описателя. С++ класс, по определению, используется в рамках одного приложения. DuplicateHandle – умеет копировать описатели между процессами и в силу этого её реализация относительно сложна. Описатели процесса организованы в таблицу и добавление описателя в таблицу довольно дорогая операция. Не говоря уже о том, что DuplicateHandle делает вызов в ядро, что тоже не добавляет скорости. В результате, приложение, интенсивно копирующее описатели, генерирует многочисленные вызовы DuplicateHandle, которые, в общем-то, не нужны.

Каким образом это можно исправить? Довольно легко. Достаточно вместо HANDLE использовать “умный” указатель на HANDLE со счётчиком ссылок. Тогда копирование описателя будет эквивалентно копированию указателя - т.е. копированию 4 или 8 байт и инкременту счетчика.

Posted at 11:21 pm •

RSS feed | Trackback URI

4 Comments »

Comment by zz|sergant — November 28, 2006 @ 3:09 pm

Неплохо было бы кстати проверять в операторе присваивания сам на себя, иначе хэндл освободится в безобидной на вид операции.

Т.е. if (this != &_right) { release(); copy(); } return *this;

 
Comment by Not a kernel guy — November 28, 2006 @ 5:29 pm

Да, про это я забыл.

 
Comment by Vladimir Scherbina — December 1, 2006 @ 9:22 am

На kernel ньюсгруппе как раз разгорелся спор относительно того, стоит ли вообще копировать HANDLE обьекты: http://groups.google.com/group/microsoft.public.win32.programmer.kernel/browse_thread/thread/33681c187575bbf5/10899c1d908f7da5#10899c1d908f7da5

Правда, там идет речь о сущностях типа “процесс” …

 
Comment by Not a kernel guy — December 1, 2006 @ 7:07 pm

> Правда, там идет речь о сущностях типа “процесс” …

По-моему они там в трёх соснах заплутали… :-)

Тем не менее там правильно сказали, что описатели ключей реестра выпадают из общей картины - DuplicateHandle копируют HKEY, но не работают с описателями возвращёнными из RegConnectRegistry, а вместо CloseHandle нужно использовать RegCloseKey. Причина этого в том, что часть функциональности реестра сидит в user mode (advapi32.dll) и не все HKEY скрывают реальный объект ядра. Это тянет на отдельный пост, кстати.

Ну и наконец, есть еще одна проблема с DuplicateHandle. По неподтвержденным данным, DuplicateHandle не копирует описатели LPC портов между разными процессами. Но это всё равно не видно на уровне Win32 API.

 

Your Comment (smaller | larger)

You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Powered by WordPress