Отладка приложений

       

Первые проблемы с TraceSrv


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

Эта проблема поставила меня в тупик, потому что я не думал, что будет так трудно создать интерфейс с единственным экземпляром программы. Промучившись целый день, я был готов переопределить метод iciassFactory:: Createinstance и заставить его всегда возвращать один и тот же интерфейс itrace. Это изменило бы ожидаемое поведение Createinstance, но, по крайней мере, позволило бы иметь только один экземпляр интерфейса. К счастью, разбираясь в коде ATL, я наткнулся на класс ccomciassFactorySingieton, который, как говорит документация, предназначен для создания единственного экземпляра метода — как раз то, что мне было нужно. Этот класс обрабатывается макросом DECLARE_CLASSFACTORY_SINGLETON (CTrace), определенным в TRACE.H. Итак, данная ошибка была вызвана моим незнанием ATL.

Однажды, в самом начале использования TraceSrv, я заметил, что класс ccomBSTR выполнял все распределения и освобождения памяти почти при каждом вызове метода. Разработав класс CFastBSTR, я полагал, что тестирование будет делом нетрудным. Однако при проверке различных сценариев я получил утверждение (в конце CTrace: :ProcessTrace), которое можно найти в заключительной части листинга 11-2. В TraceSrv применяется макрос ASSERT, рассмотренный в главе 3, а из-за того что TraceSrv спроектирована как служба, я вызывал функцию setoiagAssertoptions и выключал отображение панели сообщений.

Я получал это сообщение, когда выполнял TraceSrv без присоединенной программы просмотра трассы. Просматривая код функции Fire_TraceEvent, который был сгенерирован командой Implement Connection Point в IDE, я заметил кое-что очень интересное.
Оригинальный код функции Fire_TraceEvent приведен в листинге 11-3. Будьте внимательны при просмотре кода и попробуйте найти ошибку.

Листинг 11-3. Функция Fire_ TraceEvent с ошибкой

HRESULT Fire_TraceEvent( BSTR bstrText )

{




CComVariant varResult;

Т* рТ = static_cast<T*>( this );

int nConnectionlndex;

CComVariant* pvars = new CComVariant[1];

int nConnections = m_vec.GetSize( );

for ( nConnectionlndex = 0;

nConnectionlndex < nConnections; nConnection!ndex++ ) 

{

pT->Lock();

CComPtr<IUnknown> sp = m_vec.GetAt( nConnectionlndex );

pT->Unlock( );

IDispatch* pDispatch = reinterpret_cast<IDispatch*>( sp.p );

if (pDispatch != NULL)

{

VariantClear( SvarResult );

pvars[0] = bstrText;

DISPPARAMS disp = { pvars, NULL, 1, 0 };

pDispatch->Invoke( 0xl,

IID_NULL,

LOCALE_USER_DEFAULT, 

DISPATCH_METHOD,

 &disp, SvarResult, 

NULL, NULL ); 



}

delete[] pvars; 

return varResult.scode; 

}

Имейте в виду, что утверждение срабатывало только тогда, когда программа просмотра к TraceSrv не присоединялась. Внимательно посмотрев на Fire_rraceEvent, вы увидите, что цикл for никогда не выполняется, если программа просмотра не присоединена. Однако сгенерированный код возвращает varResuit.scode, который инициализируется только внутри цикла for. Следовательно, когда программа просмотра не присоединена, функция возвращает неинициализированное значение. В отладочных построениях функция Fire_rraceEvent возвращала значение Охсссссссс (символ-заполнитель, который при компиляции с ключом /GZ помещается в локальные переменные).

Решение проблемы неинициализированной переменной было довольно простым. Я переименовал файл, который генерировала команда Implement Connection Point, (TRACESRVCP.H) в CORRECTEDTRACESRVCP.H и после объявления переменной varResult установил varResuit.scode равным s ок. Хотя использование неинициализированных переменных в практике программирования не рекомендуется, но теперь, по крайней мере, разработчики Visual C++ возвращают результаты вызовов IDispatch:: invoke.В предыдущих версиях Visual C++ это было невозможно. Как только я решил эту маленькую проблему, TraceSrv стал выполняться довольно хорошо.

Прежде чем завершить эту главу, рассмотрим программу TraceView, обеспечивающую безопасность в Win32, и вызовы TraceSrv из пользовательского кода.



Содержание раздела