Monday, October 18, 2010

scoped_ptr

Недавно начал читать Александреску. Вот в процессе первых двух глав навалял свой scoped_ptr с custom deleter:


#ifndef __CMN_SCOPED_PTR_H__
#define __CMN_SCOPED_PTR_H__

#include "cmnNonCopyable.h"

namespace utils
{
    template < typename T, class Copyable = utils::noncopyable >
    class scoped_ptr : Copyable
    {
        struct deleter_base
        {
            virtual void release() = 0;
        } * Dt_;

        template < typename _Dx >
        class deleter
            : public deleter_base
            , Copyable
        {
            T*  p_;
            _Dx Dt_;

            template < int v > struct int2type { enum { value = v }; };

            template < typename _Type >
            class is_mem_func_ptr
            {
                template < typename T0 >
                struct is_mem_func_ptr_ { enum { res = 0 }; };

                template < typename R, typename S >
                struct is_mem_func_ptr_ < R (S::*)( ) > { enum { res = 1 }; };
            public:
                enum { res = is_mem_func_ptr_ < _Type >::res };
            };

            template < typename _DxiEx >
            void deleter_internal_ex(_DxiEx Dt, int2type < 1 >)
            {
                (*p_.*Dt)();
            }

            template < typename _DxiEx >
            void deleter_internal_ex(_DxiEx Dt, int2type < 0 >)
            {
                Dt(p_);
            }

            template < typename _Dxi >
            void deleter_internal(_Dxi Dt)
            {
                deleter_internal_ex(Dt, int2type < is_mem_func_ptr < _Dxi >::res >( ));
            }

            template < typename _Ret >
            void deleter_internal(_Ret (*Dt) ( ))
            {
                Dt();
            }

        public:
            deleter(_Dx Dt, T* p)
                : p_(p)
                , Dt_(Dt)
            { }

            virtual void release()
            {
                deleter_internal(Dt_);
            }
        };

    public:
        scoped_ptr()
            : Dt_(NULL)
            , array_(false)
            , p_(NULL)
        { }

        explicit scoped_ptr(T* p, bool array = false)
            : Dt_(NULL)
            , array_(array)
            , p_(p)
        { }

        template < typename _Dx >
        scoped_ptr(T* p, _Dx Dt)
            : Dt_(new deleter < _Dx > (Dt, p))
            , array_(false)
            , p_(NULL)
        { }

        ~scoped_ptr() { free(); }
        operator T* () { return p_; }
        T* operator->() { return p_; }

        T* release()
        {
            T* p = p_;
            p_ = NULL;

            return p;
        }

        void reset(T* p = NULL)
        {
            if (p != p_)
            {
                free();
            }

            p_ = p;
        }

        T* get() { return p_; }

    private:
        void free()
        {
            if (NULL == Dt_)
            {
                if (array_)
                {
                    delete [] p_;
                }
                else
                {
                    delete p_;
                }
            }
            else
            {
                Dt_->release();
            }
        }

    private:
        bool array_;
        T* p_;
    };
}

#ifdef SMALTI_NINJA
    #define ninja_ptr utils::scoped_ptr
#endif

#endif

//-----------------------------------------------------------------------------

Тест:

 
#include "stdafx.h"
#include

#define SMALTI_NINJA
#include "cmnScopedPtr.h"

struct ITest
{
    virtual void Release() volatile = 0;
};

class CTest : public ITest
{
    virtual void Release() volatile { delete this; }

public:
    static ITest* Create() { return new CTest; }
};

void Save(ITest* p) { }

class CSimple
{ };

int _tmain(int, _TCHAR* [])
{
    ninja_ptr < int > pInt(new int);
    ninja_ptr < CSimple > pSimple(new CSimple);
    ninja_ptr < CSimple > pSimpleArray(new CSimple[100], true);
    ninja_ptr < void >  pMalloc(::malloc(100), &::free);
    ninja_ptr < void >  pAbort((void*)0, &::abort);
    ninja_ptr < ITest > pRelease(CTest::Create(), &ITest::Release);
    ninja_ptr < ITest > pAutoSave(CTest::Create(), Save);

    return 0;
}

Sunday, July 25, 2010

Ошибка RPC_E_SERVERFAULT

Однажды я столкнулся с проблемой переноса проекта с 2003 на 2008 студию. Был некий тестовый проект. Это ATL exe-сервер, генеренный. Он умел хостить Adobe Flash ActiveX контрол. Так вот, при переносе его на 2008 студию флеш ролик не грузится. Проблема была в вызове метода:

spShockwaveFlash->put_Movie(bsURL);

он возвращал RPC_E_SERVERFAULT. В output пишет:

An internal error occurred in a remote procedure call (RPC).
0x80010105 The server threw an exception.


Хотя другие вызовы методов/свойств почему-то отработали, и запросы QI интерфейсов тоже отработали. Искать причину тяжело, т.к. это уже внутренность флеша и RPC. Путем экспирементов над тестовым проектом нашел решение:

#define _ATL_APARTMENT_THREADED

В итоге причина была в том, что по-умолчанию ATL использует _ATL_FREE_THREADED:

atldef.h
1.// Threading
2.
3.#ifndef _ATL_SINGLE_THREADED
4.#ifndef _ATL_APARTMENT_THREADED
5.#ifndef _ATL_FREE_THREADED
6.#define _ATL_FREE_THREADED
7.#endif
8.#endif
9.#endif


Макрос определяет синхронизацию для регистрации фабрик классов ATL ехе-сервера, синхронизацию в их счетчиках ссылок для CComObjectThreadModel и CComGlobalsThreadModel, и самое главное - определяет инициализацию СОМ для ехе-сервера. Инициализация происходит в конструкторе модуля:

atlbase.h
1.CAtlExeModuleT::CAtlExeModuleT() throw()
2.{
3....
4.HRESULT hr = T::InitializeCom();


далее:

atlbase.h
1....
2.static HRESULT InitializeCom() throw()
3.{
4.#if ((_WIN32_WINNT >= 0x0400 ) || defined(_WIN32_DCOM)) & defined(_ATL_FREE_THREADED)
5. return CoInitializeEx(NULL, COINIT_MULTITHREADED);
6.#else
7. return CoInitialize(NULL);
8.#endif
9.}
10....


Т.е., если вы не задефайнили _ATL_APARTMENT_THREADED для ехе-клиента ActiveX-а, где активикс создается в главном треде, у вас мало того что будут избыточные локи, а еще вы можете выхватить непонятную ошибку типа RPC_E_SERVERFAULT ввиду не правильной инициализации СОМ для главного потока. В msdn по этому поводу ничего не сказано, зато есть сорцы ATL.

Friday, May 28, 2010

Регулярные выражения и ATL

Если вы пользуетесь регулярными выражениями, то в ATL Server есть для этого средство:
atlrx.h - CAtlRegExp, CAtlREMatchContext. ATL Server сейчас уже не поставляют со студией.

Регулярные выражения уже есть и в STL TR1, но раньше их там не было, и поэтому приходилось использовать либо ATL либо сторонние либы.

Рендеринг и GDI

GDI сейчас под Windows самый древний API и тем не менее его используют даже для рендеринга сложной графики. Недавно я делал графический эффект волны на GDI, и мне нужен был быстрый способ изменять растр. Быстроту я измерял через AQTime, ну и визуально. Идеологически для этого берут сам растр и копируют в память, далее по этой памяти "шарятся" применяя некий алгоритм, и потом возвращают обратно эту память на контекст. Это обычно происходит по таймеру. Если брать простой случай - контекст - это экран, а источник - bmp, то есть несколько способов работы с памятью растра:
1. Самый неэффективный по скорости способ (если операций много): GetPixel/SetPixel; О причине их медленной работы можно прочитать в книге Фень Юань "Программирование графики для Windows" глава 7.
2. GetBitmapBits, SetBitmapBits; Во-первых эти функции уже устаревшие (только для совместимости остались), и работают только с DDB. Во-вторых, для рендеринга через эти функции вам понадобится уже как минимум 2 копирования растра - для взятия буфера на изменения и его установку назад.
3. Альтернатива предыдущим - GetDIBits, SetDIBits. Они копируют растр между форматами DIB и DDB. Для рендеринга на экран это неоправданно сложно, т.к. конвертация нам не нужна, плюс сложность работы с DIB. Опять же - как минимум 2 копирования растра + возможны расходы на конвертацию. Так же учтите, что для DIB нет поддержки GDI в рисовании. DIB растр хранится в памяти на уровне приложения (размер картинки ограничивается только виртуальным адресным пространством приложения) и вы имеете прямой доступ к представлению. В DDB вы не имеете прямого доступа к представлению зато можете рисовать через GDI, растры хранится в системной памяти и поэтому для них еще есть и ограничения по размеру (48MB - примерно 3500х3500 при 32bit цвете).
4. Ввиду выше описанных способов были придуманы DIB-секции. Это DIB-растр в который можно писать как через GDI так и напрямую в буфер. Отсюда выгода - при работе с буфером не нужно копирование вообще.

Также хочу дать еще несколько советов по рисованию/рендерингу в GDI.
1. Используйте концепцию двойной (или более) буферизации перед выводом на экран (CreateCompatibleDC, в WTL есть CMemDC, и т.п.).
2. Рисуйте только один раз - не рисуйте в одном и том же пикселе дважды.
3. Не используйте WM_ERASEBKGND.
4. Используйте GetUpdateRgn, GetUpdateRect или BeginPaint/EndPaint - если перерисовать нужно только малую часть.
5. Стили CS_VREDRAW и СS_HREDRAW означают что содержимое окна будет полностью перерисовываться при изменении его размеров по вертикали или по горизонтали соответственно.
6. Стиль WS_CLIPCHILDREN - чтобы не было при рисовании своего содержимого, затирания дочерних окон своим изображением, а затем поверх него рисования самих дочерних окон.
7. Используйте ExcludeClipRect/SelectClipRgn - для предотвращения повторного рисования.
8. В рендерере используйте таблицы синусов/косинусов, вместо функций - макросы, все локальные переменные выносите выше, используйте многопоточность для сложных алгоритмов.
9. Используйте списки сохранения промежуточных сжатий изображений при ресайзе - для ускорения следующих ресайзов относительно них. Также полезен пассивный предресайзинг.
10. Выделяйте на стеке обработчика WM_PAINT/WM_TIMER поменьше памяти, в идеале вообще не выделять или использовать повторно.

Пока всё. Надеюсь все написал что хотел.