ПРОГРАММИРОВАНИЕ    НА    V I S U A L   C + +
РАССЫЛКА САЙТА       
RSDN.RU  

    Выпуск No. 53 от 4 ноября 2001 г.

РАССЫЛКА ЯВЛЯЕТСЯ ЧАСТЬЮ ПРОЕКТА RSDN , НА САЙТЕ КОТОРОГО ВСЕГДА МОЖНО НАЙТИ ВСЮ НЕОБХОДИМУЮ РАЗРАБОТЧИКУ ИНФОРМАЦИЮ, СТАТЬИ, ФОРУМЫ, РЕСУРСЫ, ПОЛНЫЙ АРХИВ ПРЕДЫДУЩИХ ВЫПУСКОВ РАССЫЛКИ И МНОГОЕ ДРУГОЕ.

Приветствую вас, дорогие подписчики!

Прежде всего хочу извиниться - в предыдущем выпуске в статье про Direct3D по моему недосмотру были указаны некорректные ссылки на примеры программ. Ну, все мы люди ;-) Вот верные ссылки:

Демонстрационное приложение (только .exe) (72 kb)
Демонстрационное приложение (исходный код) (44 kb)

Также ссылки исправлены в той версии выпуска, которая лежит в архиве на сайте RSDN.

И еще один вопрос: мне продолжают приходить письма с различными вопросами по программированию. К сожалению, у меня сейчас нет времени даже на то, чтобы просто отвечать на все эти письма, не говоря уже о содержащихся в них вопросах. Поэтому хочу всем напомнить, что в ФОРУМЕ на сайте RSDN вы можете получить ответ (и даже не один!) на любой свой вопрос по программированию.

Поэтому большая просьба вопросы задавать там, вам наверняка ответят. А если вы считаете какой-нибудь из вопросов очень интересным и достойным внимания подписчиков этой рассылки, то я буду благодарен, если вы мне пришлете ссылку на соответствующее сообщение форума. Дело в том, что у рассылки начиная с этого выпуска появляется новая рубрика - "ФОРУМ RSDN - ИЗБРАННОЕ", и там будут публиковаться самые интересные дискуссии. Надеюсь, это сделает рассылку для вас еще интереснее.


 CТАТЬЯ

Подключение к событиям объектной модели DHTML при использовании WebBrowser-control

Демонстрационное приложение - event.zip (181 Kb)

Введение

В последнее время путешествуя по форумам и группам новостей все чаще можно встретить вопросы, касающиеся ActiveX-элемента WebBrowser. К сожалению зачастую эти вопросы теряются в общем ворохе сообщений, а материалов касающихся использования функциональности Internet Exploler катастрофически мало. В то же время у этих технологий находиться все больше поклонников. Уж слишком заманчиво выглядит возможность написать собственный броузер, приложив минимум усилий.

Например, текущая версия продукта, в разработке которой я принимаю участие, использует эти технологии для отображения информации из документно-ориентированной базы данных. При этом, как показывает практика, интерфейс web-представлений в данном случае, гораздо дружественнее пользователю, чем использование множества диалоговых форм, а чтобы его сменить достаточно подправить пару шаблонов в ресурсах.

Предлагаемая вашему вниманию статья посвящена одному из аспектов написания подобных программ. В ней будет рассмотрено как можно подключиться с событиям объектной модели броузера, а значит позволить вашему приложению использовать те преимущества, которые дает DHTML. Я попытаюсь рассказать вам как непосредственно обрабатывать события, возбуждаемые объектной моделью броузера в процессе работы пользователя со страницей.

Немного теории

Как и большинство ActiveX элементов WebBrowser является источником событий подключаемых через стандартный механизм Connection Point. К числу таких событий относятся OnBeforeNavigate, OnDocumentComplete и т.п. Несомненно они важны для управления приложением в целом, однако их возможностей явно недостаточно, если мы захотим более тесно познакомиться с DOM DHTML, например, узнать о перемещении мыши над элементами страницы или о нажатии клавиши на клавиатуре или вообще быть в курсе всех событий, которые можно использовать в сценариях DHTML.

При использовании объектной модели DHTML из сценариев возможно создавать собственные обработчики событий простым присваиванием наблюдаемого элемента соответствующим свойствам, например:


<SCRIPT LANGUAGE="jscript">
function mousedownhandler()
{
    // функция обработчик
}

function afterPageLoads()
{
    someElement.onmousedown = mousedownhandler;
}
</SCRIPT>

Возникает вопрос. А можно ли получить нечто подобное из клиента на С++? Конечно, причем похожим образом. Достаточно создать свою функцию обработки и зарегистрировать ее.

Теперь поподробнее. Для этого нужно проделать не так уж и много. Необходимо реализовать простой Com-объект для каждой функции обработчика. Этот объект должен реализовывать всего два стандартных интерфейса IUnknown и IDispatch. Далее ссылка на этот объект присваивается соответствующему свойству элемента, событие которого мы хотим наблюдать. При возникновении события броузер просто вызовет IDispatch::Invoke нашего объекта со значением DISPID = DISPID_VALUE (=0).

Замечу, что это не единственный способ заставить приложения реагировать на события. Например, можно заставить работать механизм window.external. Тогда соответствующий скрипт будет выглядеть например, так:


function mousedownhandler()
{
    // функция обработки
    window.external.onmousedown;
}

Думаю идея понятна. Однако этот способ удобен если мы сами формируем страницу. Сегодня мы пойдем первым путем.

Пишем шаблоный класс

Итак, настало время применить все вышеизложенное на практике. Конечно, можно руками написать COM-объекты для каждой функции, однако представьте, что число обработчиков переваливает за десяток, а каждый раз нужно реализовывать по сути одно и тоже. Думаю, понятно, к чему я клоню :) Самое время вспомнить о шаблонах С++. Итак, напишем простенький шаблонный класс. Чтобы не зависеть от конкретной библиотеки реализуем пару IUnknown, IDispatch вручную. Реализация IUnknown вполне стандартна, а из IDispatch необходимо реализовать только функцию Invoke.

template <class T> class CHtmlEventObject : public IDispatch  
{

typedef void (T::*EVENTFUNCTIONCALLBACK)(DISPID id, VARIANT* pVarResult);

public:
   CHtmlEventObject() { m_cRef = 0; }    
   ~CHtmlEventObject() {}

   HRESULT __stdcall QueryInterface(REFIID riid, void** ppvObject)
   {
      *ppvObject = NULL;

      if (IsEqualGUID(riid, IID_IUnknown))
         *ppvObject = reinterpret_cast<void**>(this);

      if (IsEqualGUID(riid, IID_IDispatch))
         *ppvObject = reinterpret_cast<void**>(this); 
         
      if (*ppvObject)
      {
          ((IUnknown*)*ppvObject)->AddRef();
          return S_OK;
      }
      else return E_NOINTERFACE;
   }

   DWORD __stdcall AddRef()
   {
      return InterlockedIncrement(&m_cRef);
   }

   DWORD __stdcall Release()
   {
      if (InterlockedDecrement(&m_cRef) == 0)
      {
         delete this;
         return 0;
      }
      return m_cRef;
   }

   STDMETHOD(GetTypeInfoCount)(unsigned int FAR* pctinfo)
   { return E_NOTIMPL; }

   STDMETHOD(GetTypeInfo)(unsigned int iTInfo, LCID  lcid, ITypeInfo FAR* FAR*  ppTInfo)
   { return E_NOTIMPL; }

   STDMETHOD(GetIDsOfNames)(REFIID riid, OLECHAR FAR* FAR* rgszNames, unsigned int cNames, 
                            LCID lcid, DISPID FAR* rgDispId)
   { return S_OK; }

   STDMETHOD(Invoke)(DISPID dispIdMember, REFIID riid, LCID lcid,
      WORD wFlags, DISPPARAMS* pDispParams, VARIANT* pVarResult,
      EXCEPINFO * pExcepInfo, UINT * puArgErr)
   {
      if (DISPID_VALUE == dispIdMember)
         (m_pT->*m_pFunc)(m_id, pVarResult);
      else
         TRACE(_T("Invoke dispid = %d\n"), dispIdMember);
    
      return S_OK;
   }

public:
   static LPDISPATCH CreateHandler(T* pT,
                        EVENTFUNCTIONCALLBACK pFunc, DISPID id)
   {
      CHtmlEventObject<T>* pFO = new CHtmlEventObject<T>;
      pFO->m_pT = pT;
      pFO->m_pFunc = pFunc;
      pFO->m_id = id;
      return reinterpret_cast<LPDISPATCH>(pFO);
   }

protected:
   T* m_pT;
   EVENTFUNCTIONCALLBACK m_pFunc;
   DISPID m_id;
   long m_cRef;
};

Как применять этот класс? Проще простого.

Шаг 1. Создаем свою функцию обработчик по прототипу OnEvent(DISPID id, VARIANT* pVarResult). В принципе ее можно разместить где угодно. Я предпочитаю создавать ее в классе представления, наследнике CHtmlView. При этом все обработчики сосредоточены в одном месте и не нужно беспокоится о взаимодействии с классом документа.

Шаг 2. Регистрируем ее в качестве обработчика интересующего нас события. Для этого через вызов CHtmlEventObject::CreateObject создаем экземпляр нашего COM-объекта. Передаем в него адрес функции обработчика и собственный идентификатор события. После этого передаем ссылку на него интересующему нас элементу.

// Создаем объект-обработчик
LPDISPATCH dispFO = CHtmlEventObject<CEventView>::CreateHandler(this, OnKeyDown, 1);
VARIANT vIn;                            
V_VT(&vIn) = VT_DISPATCH; 
V_DISPATCH(&vIn) = dispFO; 

// устанавливаем обработчик document.onkeydown
hr = pHtmlDoc->put_onkeydown( vIn );  

Здесь есть одна тонкость. Зарегистрировать обработчик можно только тогда, когда документ уже загружен, иначе GetHtmlDocument() вернет NULL. Для этого можно отслеживать событие OnDocumentComplete. Ну вот собственно и все.

Получаем информацию о событии

Рассмотрим еще раз прототип функции обработчика

OnEvent(DISPID id, VARIANT* pVarResult);

В качестве id передается значение которое мы указали при регистрации обработчика. Это может пригодиться если мы захотим реализовать обработку нескольких событий в одной функции. Для этого нужно зарегистрировать одну и ту же функцию с разными DISPID, тогда по приходу событий мы сможет их различать.

pVarResult нужно, если не требуется обработки по умолчанию.При этом достаточно в pVarResult вернуть VARIANT_FALSE.

Итак, когда вызывается наш обработчик никакой дополнительной информации о событии в функцию не передается. А как же тогда поподробнее узнать, что произошло? Для этого необходимо воспользоваться интерфейсом IHTMLEventObj, доступным через объект window текущего документа. Посредством этого интерфейса можно получить подробную информацию о произошедшем событии, например, элемент, послуживший источником событий, состояние клавиш, местоположение курсора мыши и состояние ее кнопок.

Вот его краткое описание из MSDN:

Методы IHTMLEventObj

get_altKey Состояние клавиши Alt
get_button Возвращает информацию о нажатых кнопках мыши
get_cancelBubble Возвращает будет ли продолжена обработка события вверх по иерархии обработчиков
get_clientX Возвращает горизонтальную позицию курсора мыши относительно клиентской области окна
get_clientY Возвращает вертикальную позицию курсора мыши относительно клиентской области окна
get_ctrlKey Состояние клавиши Ctrl
get_fromElement Возвращает указатель на интерфейс IHTMLElement позволяющий получить доступ к элементу с которого "ушел" курсор мыши при событиях onmouseover или onmouseout.
get_keyCode Возвращает код нажатой клавиши
get_offsetX Возвращает горизонтальную позицию курсора относительно контейнера элемента
get_offsetY Возвращает позицию курсора относительно контейнера элемента
get_qualifier Возвращает идентификатор события
get_reason Возвращает состояние передачи данных для объекта источника данных
get_returnValue Возвращаемое значение события или диалога
get_screenX Горизонтальная координата относительно координат экрана
get_screenY Вертикальная координата относительно координат экрана
get_shiftKey Состояние клавиши Shift
get_srcElement Возвращает указатель на интерфейс IHTMLElement послуживший источником событий
get_srcFilter Возвращает объект фильтр возбудивший событие onfilterchange
get_toElement Возвращает указатель на интерфейс IHTMLElement позволяющий получить доступ к элементу с на который "пришел" курсор мыши при событиях onmouseover или onmouseout
get_type Возвращает строковое название события
get_x Возвращает горизонтальную позицию мыши относительно родительского объекта в иерархии, позиционированного с помощью атрибутов CSS
get_y Возвращает вертикальную позицию мыши относительно родительского объекта в иерархии, позиционированного с помощью атрибутов CSS
put_cancelBubble Задать будет ли продолжена обработка события вверх по иерархии обработчиков
put_keyCode Задать код нажатой клавиши
put_returnValue Задать возвращаемое событием значение

Стоит заметить, что интерфейс IHTMLEventObj доступен только на время обработки конкретного события. При этом не все свойства в контексте определенного события имеют смысл. Например, значения возвращаемые функциями get_fromElement и get_toElement доступны только при обработке событий мыши onmouseover и onmouseout.

В следующем примере в обработчике обределяется нажатая клавиша и выводится соответствующее диалоговое окно. Если была нажата клавиша Enter, то дальнейшая обработка отменяется.

void CMyHtmlView::OnKeyDown( DISPID id, VARIANT* pVarResult)
{
    HRESULT hr;    
    LPDISPATCH pDispatch = GetHtmlDocument();    
    if( pDispatch != NULL )
    {
        IHTMLDocument2* pHtmlDoc;
        hr = pDispatch->;
        QueryInterface( __uuidof( IHTMLDocument2 ), (void**)&pHtmlDoc );
        
        IHTMLWindow2*  pWindow;
        IHTMLEventObj* pEvent;
        
        hr = pHtmlDoc->get_parentWindow(&pWindow);
        ASSERT( SUCCEEDED( hr ) );    
        hr = pWindow->get_event(&pEvent);
        ASSERT( SUCCEEDED( hr ) );
        
        // Определяем нажатую клавишу        
        long nKey;
        hr = pEvent->get_keyCode( &nKey );
        ASSERT( SUCCEEDED( hr ) );

        // Если Enter не хотим обрабатывать дальше
        if ( nKey == VK_RETURN)
        {            
            V_VT(pVarResult) = VT_BOOL; 
            V_BOOL(pVarResult) = FALSE;             
        }
        
        pDispatch->Release();
        pWindow->Release();
        pEvent->Release();
        pHtmlDoc->Release();
        
        CString sMes;
        sMes.Format("CEventView::OnKeyDown(DISPID = %d)\nKeyCode: %d", id, nKey);
        AfxMessageBox(sMes);
    }           
}
В заключение

Чтобы собрать воедино все фрагменты приведу небольшой пример (event.zip). Запустите его и выберите команду меню Event\OnKeydown. Теперь понажимайте клавиши внутри страницы. И посмотрите, что из этого получится. В этом примере регистрируется только один обработчик, но я думаю дочитав эту статью вы без труда сможете реализовать любой другой.

В заключение хочется заметить, что в этой статье я затронул только один небольшой аспект использования элемента WebBrowser. Если Вас заинтересует данная тема, пишите, продолжим.


 ВОПРОС - ОТВЕТ

Как получить список запущенных приложений?

Тестовое приложение Process Viewer - pview.zip (130 Kb)

Операционная система Windows знаменита своими окнами. Поэтому, когда заходит речь о приложениях, пользователи ассоциируют их с окнами, хотя с технической точки зрения правильнее было бы оперировать понятием процесса. В частности, под списком приложений пользователи часто подразумевают не список процессов, а список главных окон приложений, видимых на экране. В таком случае, задача перечисления приложений сводится к перечислению их главных окон.

Для перечисления всех окон верхнего уровня служит функция EnumWindows, однако, если мы просто вызовем эту функцию, то обнаружим, что она возвращает много больше окон, чем видно приложений на экране. Очевидно, мы должны игнорировать невидимые окна и окна, имеющие владельца (такие как диалоговые панели). Но даже и после этой фильтрации в списке будет несколько больше окон, чем отображает Task Manager.

Проведя несколько экспериментов с Windows NT Task Manager, удалось установить, что он игнорирует окна с пустым заголовком, и, как ни странно, окна с заголовком "Program Manager". Да-да, если вы создатите свое окно с таким заголовком, то оно не появится в списке приложений Task Manager. Обычно в системе есть только одно окно "Program Manager" - это то окно, на котором находится рабочий стол. Понятно, что пользователи не ассоциируют это окно с каким-либо приложением, для них оно является неотъемлемой частью компьютера, и поэтому Task Manager должен игнорировать это окно. Непонятно только, почему разработчики Task Manager решили определять это окно по его заголовку, а не по имени класса окна, которое есть "progman".

После того, как мы вставим все эти проверки, наш список уже ничем не будет отличаться от выводимого Task Manager. Ниже приведен код функции EnumApplications, которая реализует перечисление приложений. Интерфейс функции построен в стиле функций-перечислителей в Win32 API: она принимает указатель на пользовательскую функцию, которую вызывает для каждого перечисляемого приложения.


typedef BOOL (CALLBACK * PFNENUMAPP)(
    IN HWND hWnd,             // идентификатор главного окна приложения
    IN LPCTSTR pszName,       // название приложения
    IN HICON hIcon,           // иконка приложения
    IN LPARAM lParam          // пользовательский параметр
    );

typedef struct _ENUMAPPDATA {
    LPARAM        lParam;
    PFNENUMAPP    pfnEnumApp;
} ENUMAPPDATA, * PENUMAPPDATA;

static
BOOL CALLBACK EnumWindowsCallback(
    IN HWND hWnd,
    IN LPARAM lParam
    )
{
    PENUMAPPDATA pEnumData = (PENUMAPPDATA)lParam;
    _ASSERTE(_CrtIsValidPointer(pEnumData, sizeof(ENUMAPPDATA), 1));

    if (!IsWindowVisible(hWnd) || GetWindow(hWnd, GW_OWNER) != NULL)
        return TRUE;

    TCHAR szClassName[80];
    GetClassName(hWnd, szClassName, 80);

    if (lstrcmpi(szClassName, _T("Progman")) == 0)
        return TRUE;

    // получаем заголовок окна 
    TCHAR szText[256];
    DWORD cchText = GetWindowText(hWnd, szText, 256);

    if (cchText == 0)
        return TRUE;

    HICON hIcon = NULL;

    // получаем иконку окна 
    if (SendMessageTimeout(hWnd, WM_GETICON, ICON_SMALL, 0, 
                            SMTO_ABORTIFHUNG|SMTO_BLOCK, 1000, 
                            (DWORD_PTR *)&hIcon))
    {
        if (hIcon == NULL)
        {
            if (!SendMessageTimeout(hWnd, WM_GETICON, ICON_BIG, 0, 
                                SMTO_ABORTIFHUNG|SMTO_BLOCK, 1000, 
                                (DWORD_PTR *)&hIcon))
                hIcon = NULL;
        }
    }
    else
        hIcon = NULL;

    if (hIcon == NULL)
        hIcon = (HICON)GetClassLong(hWnd, GCL_HICONSM);
    if (hIcon == NULL)
        hIcon = (HICON)GetClassLong(hWnd, GCL_HICON);
    if (hIcon == NULL)
        hIcon = LoadIcon(NULL, IDI_APPLICATION);

    // вызываем пользовательскую функцию
    return pEnumData->pfnEnumApp(hWnd, szText, hIcon, pEnumData->lParam);
}

BOOL EnumApplications(
    IN PFNENUMAPP pfnEnumApp,
    IN LPARAM lParam
    )
{
    _ASSERTE(pfnEnumApp != NULL);

    ENUMAPPDATA EnumData;
    EnumData.pfnEnumApp = pfnEnumApp;
    EnumData.lParam = lParam;

    return EnumWindows(EnumWindowsCallback, (LPARAM)&EnumData);
}

Как видно, функция EnumApplications чрезвычайно проста - она просто вызывает EnumWindows и вся основная работа по фильтрации ненужных окон ложится на вспомогательную функцию EnumWindowsCallback.

В функции EnumWindowsCallback мы сначала отсеиваем невидимые окна, и окна, имеющие владельца. Затем мы проверяем, не является ли данное окно окном рабочего стола. Здесь мы не уподобляемся разработчикам Windows NT Task Manager и используем имя класса окна для проверки. Наконец, мы отбрасываем окна с пустым заголовком.

После того, как мы определи, что данное окно представляет некоторое приложение, мы собираем информацию об окне, чтобы передать ее пользовательской функции. Сначала мы получаем заголовок окна с помощью хорошо известной функции GetWindowText. Затем мы пытаемся получить иконку окна. Обратите внимание, мы используем функцию SendMessageTimeout с флагом SMTO_ABORTIFHUNG для посылки сообщения WM_GETICON. Это гарантирует, что наше приложение не зависнет, даже если приложение, которому принадлежит окно, перестало обрабатывать сообщения.

Когда все параметры определены, мы вызываем пользовательскую функцию. Пользовательская функция, в свою очередь, может распоряжаться этими данными по своему усмотрению. Например, в тестовом приложении Process Viewer, которое сопровождает эту статью, она добавляет очередной элемент в список приложений.

Cсылки
  1. Q175030 HOWTO: Enumerate Applications in Win32, Microsoft Knowledge Base.


 ФОРУМ RSDN - ИЗБРАННОЕ

Тема: ООП и наследование

Utandr:

Вопрос: 
  Есть базовый класс, из ЕГО конструктора вызывается метод ЭТОГО же (базового) 
  класса. 

Cbase::Cbase() 
{ 
someFunction(); 
} 

void Cbase::someFunction() 
{ 
  <SOME ACTION>
} 

Так вот. Этот класс наследуют другие классы. НО! В их конструкторах нет вызова 
someFunction(), - КОТОРАЯ ВИРТУАЛЬНАЯ, и по логике должна _переназначаться_ 
классами, которые наследуют Cbase 

Но при объявлении Csomefrombase : public Cbase, при объявлении объекта 
вызывается someFunction() класса Cbase! Но нужно, чтобы вызывалась 
ТОЛЬКО Csomefrombase::someFunction()! 

Кто-нибудь подскажет, как решить данную проблему?

IT:

Предположим, всё работает по твоей логике. Угадай, что произойдёт вот в таком случае? 

class A {
public:
    A() { f(); }
    virtual void f() {}
};

class B: public A {
public:
    int *n;
    B() { n = new int[10]; }
};

class C: public B {
public:
    virtual void f() 
    { 
        for (int i=0; i<10; i++)
            n = 0;
    }
};

Андрей Тарасевич:

Из конструктора даже виртуальные методы вызываются в соостветствии со 
статическим (а не динамическим) типом класса. Т.е. при вызове метода 'foo()' из 
конструктора класса 'A' всегда вызывается метод 'A::foo()', независимо от того, 
переопределялся ли это метод в наследнике или нет. В этом есть смысл: в момент 
выполнения конструктора базового класса класс-наследник еще не сконструировался 
и попытки вызывать его методы ни к чему хорошему не приведут (см. пример от IT)

IT:

Всё правильно, но я бы хотел уточнить некоторые детали реализации. На самом деле 
функции вызываются как обычно, т.е. по всем правилам вызова виртуальных функций 
через VMT. Если бы даже конструктор и вызывал виртуальные функции по другому, то 
его можно было бы легко обмануть, вызвав из него любую функцию из которой уже 
вызвать виртуальную. 
Всё гораздо проще. указатель на VMT инициализируется, как известно, в самом начале работы конструктор, но ПОСЛЕ вызова конструктора базового класса. Поэтому, каждый конструктор устанавливает VMT на своём уровне и здесь его уже никак не обмануть. С деструкторами ситуация такая же, только в обратном порядке.

Андрей Тарасевич:

Все это действительно детали реализации. В данном конкретном случае естественный 
порядок инициализации указателя на VMT как нельзя лучше способствует соблюдению 
спецификации языка. Создатели компиляторов знают об этой спецификации и, 
например, в MSVC++ виртуальные методы при прямом вызове их из конструктора 
вызываются статически, а не виртуально (т.е. в общем случае ты не прав, говоря,
что "на самом деле функции вызываются как обычно"). А вот если попытаться сделать 
непрямой вызов виртуального метода (через метод-последник), то тут уже действительно 
срабатывает "правильное" значение указателя на VMT. 

Геннадий Васильев:

U>> Кто-нибудь подскажет, как решить данную проблему? 
Разделить создание объекта и конструирование, например, ввести метод Init(), вызываемый после конструктора.

class A
{
    int field;
  public:
    A(void) : field(0){}
    virtual ~A(void){}
    virtual bool Init(int val)
    {
      // Здесь - дополнительная инициализация
      field = val;
      return true;     
    }
};

// Ну и, собственно, наследник
class B : public A
{
  public:
    virtual bool Init(int val);
};

// Фрагмент программы:
A *pa = new B; // Это - не опечатка! ;)
if (pa)
{
  // к моменту вызова Init объект B уже создан,
  // его VMT нициализирована, бояться нечего :))
  if (!pa -> Init(123))
  {
    delete pa;
    pa = NULL;
  }
}


В принципе, можно поступить поизящней - уложить подобные действия в шаблонную функцию, как например в ATL реализована CComObject::CreateInstance.
АТ>Из конструктора даже виртуальные методы вызываются в соостветствии со статическим (а не динамическим) типом класса. Т.е. при вызове метода 'foo()' из конструктора класса 'A' всегда вызывается метод 'A::foo()', независимо от того, переопределялся ли это метод в наследнике или нет. В этом есть смысл: в момент выполнения конструктора базового класса класс-наследник еще не сконструировался и попытки вызывать его методы ни к чему хорошему не приведут (см. пример от IT)
А кроме того - можно попасться на вызове чисто виртуального метода, когда указатель на него еще не установлен. Например - так:

class C
{
  public:
    C(void){ Init(); }
    void Init(void)
    {
      InitExtra(); // Вызываем виртуальную функцию
    }
    virtual void InitExtra(void) = 0; // sic! pure virtual!
};

class D : public C
{
  public:
    D(void){}
    // Определим функцию InitExtra
    virtual void InitExtra(void)
    { /* что-то содержательное */ }
};


// Фрагмент программы:
C *pc = new C; // Здесь компилятор выругается по поводу
               // недопустимости создания абстракного класса.

D *pd = new D; // А здесь компилятор все пропустит.
               // Но! см. ниже...


При создании объекта класса D первым, как полагается, будет создан объект класса C. В его конструкторе будет вызван статический метод Init, который, в свою очередь, вызовет виртуальный InitExtra, и вылетит с ошибкой Pure virtual call или что-то вроде этого, поскольку в VMT класса C на месте InitExtra находится 0 (вернее - вызов обработчика аварийной ситуации), а VMT наследника еще не создан.


Это все на сегодня. До встречи!

Алекс Jenter   jenter@rsdn.ru
Duisburg, 2001.    Публикуемые в рассылке материалы принадлежат сайту RSDN.

| Предыдущие выпуски     | Статистика рассылки