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

Требования к TraceSrv


Сначала сформулируем цели проектирования TraceSrv, потому что это вообще лучший способ понять, что именно должна выполнять любая программа. Итак:

1. TraceSrv должна быть совместима с общими языками программирования, включая, как минимум, C++, Visual Basic, Borland Delphi, Visual Basic for Applications, Java, Jscript и VBScript.

2. TraceSrv должна быть очень проста для использования внутри языка программирования.

3. TraceSrv должна всегда выполняться так, чтобы любое приложение могло в любой момент соединяться с ней.

4. Операторы трассировки программы, которая выполняется на нескольких машинах, должны направлять свои результаты в один каталог (файл).

5. Приложения просмотра трасс (trace viewer applications) должны видеть строки трасс от нескольких машин одновременно.



6. Должны быть доступны следующие операции обработки необязательных параметров (опций) трассировочных строк:

• добавление (в строку трассировки) префикса со временем получения строки;

• добавление префикса с номером строки;

• добавление префикса с идентификатором (ID) процесса, который послал строку трассировки;

• добавление в конец записи трассы символов возврата каретки и перевода строки, если необходимо;

• посылать предложения трассировки через основной отладчик, где выполняется процесс TraceSrv.

7. Если хотя бы один из параметров TraceSrv, перечисленных в п. 6 изменяется, все активные программы просмотра трасс должны быть уведомлены, для того чтобы все эти программы (даже на других машинах) были скоординированы с текущими опциями.

На первый взгляд, требования к TraceSrv могут показаться чрезмерно завышенными из-за необходимости многоязычного программирования и работы в сети. Я предполагал, что можно переадресовать многоязычную поддержку простой динамически компонуемой библиотеке (DLL), которую мог бы загружать кто угодно. Поскольку я — прежде всего системный программист, а не Web-разработчик, то сказалось незнание языков VBScript и Java. В частности, при ближайшем знакомстве с VBScript я понял, что никакие хакер-ские трюки не заставят VBScript напрямую вызывать DLL.
Свет, наконец,

забрезжил в конце тоннеля, когда выяснилось, что VBScript поддерживает функцию createObject; мне как раз был необходим СОМ-объект, a VBScript способен прекрасно его использовать. Поскольку СОМ-объект работает почти на всех языках, было решено сделать TraceSrv простым СОМ-объектом.

СОМ-технология легко решает проблему сетевого программирования. Имеется свободно распространяемый расширенный вариант СОМ-технологии — СОМ+, который поддерживает "непрерывное выполнение", потому что СОМ+-серверы могут выполняться как службы Microsoft Win32. Если применяется служба автоматического запуска, то СОМ-объект всегда готов к использованию.

Моя первая схватка с СОМ+-службами (известными в свое время как службы DCOM2) произошла в далекие времена альфа-версии Microsoft Windows NT 4 и была довольно неприятной. Мало того что нужно было написать службы (не самая легкая работа), но пришлось также изрядно повозиться с СОМ-объектами (перед их подключением). К счастью, всю черную работу по написанию СОМ+-служб выполняет библиотека активных шаблонов (ATL3), которая поставляется с Microsoft Visual C++ 6 и даже включает мастер, помогающий сгенерировать код.

Как только было обозначено главное направление разработки, возникла необходимость в определении интерфейса для TraceSrv. Главный интерфейс TraceSrv (itrace) определен в IDL-файле4TRACESRV.IDL, показанном в листинге 11-1. Для передачи в TraceSrv операторов трассировки я применяю метод Trace интерфейса iTrace, а чтобы приспособиться к разнообразию языков, установил специальный строчный тип BSTR (см. параметр bstrText в описании операторов трассировки).

COM+ технология сочетает использование модели компонентных объектов (СОМ) и сервера транзакций корпорации Microsoft (MTS — Microsoft Transaction Server). — Пер.

DCOM (Distributed Component Object Model) — распределенная модель компонентных объектов. — Пер.

3ATL — ActiveX Template Library. — Пер.

IDL-файл — специальный файл, автоматически генерируемый мастерами ATL при создании СОМ-компонента.


В нем определяется (в специальном формате) собственно СОМ-объект, его интерфейс и библиотека типов. Синтаксис IDL-файла очень похож на C++. Перед объявлением каждого объекта в квадратных скобках указываются его атрибуты. — Пер



Листинг 11-1.TRACERV.IDL

 /*- - - - - - - - - - - - - - - - - - - - - - - - - - 

"Debugging Applications" (Microsoft Press)

Copyright (c) 1997-2000 John Robbins — All rights reserved.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - */

import "oaidl.idl";

 import "ocidl.idl"; 

[

object ,

uuid ( 4D42AOOC-7774-11D3-9F57-OOC04FA34F2C ) ,

 dual ,

helpstring ( "ITrace Interface" ) , 

pointer_default ( unique ) 

]

interface ITrace : IDispatch 

{

[ id ( 1 ) ,

helpstring ( "method Trace" ) ]

HRESULT Trace ( [ in ] BSTR bstrText ) ;

 [ id ( 2 ) ,

helpstring ( "method FullTrace" ) ]

HRESULT FullTrace ( [ in ] BSTR bstrText , [ in ] long dwPID ) ;

 [ propget, id ( 3 ) ,

helpstring ( "property ShowTimeStamps" ) ]

HRESULT ShowTimeStamps ( [ out, retval ] VARIANT_BOOL *pVal ) ;

 [ propput, id ( 3 ) ,

helpstring ( "property ShowTimeStamps" ) ]

HRESULT ShowTimeStamps ( [ in ] VARIANT_BOOL newVal ) ; 

[ propget,

id ( 4 ) ,

helpstring ( "property ShowTraceAsODS" ) ]

HRESULT ShowTraceAsODS ( [ out, retval ] VARIANT_BOOL *pVal ) ; 

[ propput,

id ( 4 ) ,

helpstring ( "property ShowTraceAsODS" ) ]

HRESULT ShowTraceAsODS ( [ in ] VARIANT_BOOL newVal ) ;

 [ propget,

id ( 5 ) ,

helpstring ( "property ShowItemNumber" ) ]

HRESULT ShowItemNumber ( [ out, retval ] VARIANT_BOOL *pVal ) ; 

[ propput,

id ( 5 ) ,

helpstring ( "property ShowItemNumber" ) ]

HRESULT ShowItemNumber ( [ in ] VARIANT_BOOL newVal ) ;

 [ propget,

id ( 6 ) ,

helpstring ( "property ShowPID" ). ]

HRESULT ShowPID ( [ out, retval ] VARIANT_BOOL *pVal ) ; 



[ propput,

id ( 6 ) ,

helpstring ( "property ShowPID" ) ]

 HRESULT ShowPID ( [ in ] VARIANTJ30OL newVal ) ;

 [ propget,

 id ( 7 ) ,

helpstring ( "property AddCRLF" ) ]

HRESULT AddCRLF ( [ out, retval ] VARIANT_BOOL *pVal ) ;

 [ propput, 

id ( 7 ) ,

helpstring ( "property AddCRLF" ) ]

 HRESULT AddCRLF ( [ in ] VARIANT_BOOL newVal ) ;

 } ;

 [

uuid ( 4D42AOOO-7774-11D3-9F57-OOC04FA34F2C ) ,

 version ( 1.0 ) ,

helpstring ( "TraceSrv 1.0 Type Library" ) ]

library TRACESRVLib 

{

importlib ( "stdole32.tlb" ) ;

 importlib ( "stdole2.tlb" ) ; 

[

uuid ( 4D42AOOE-7774-11D3-9F57-OOC04FA34F2C ) ,

 helpstring ( "_ITraceEvents Interface" ) 

]

dispinterface _ITraceEvents 

{

properties: methods: 

[ id ( 1 ) ,

helpstring ( "method TraceEvent" ) ] HRESULT TraceEvent ( BSTR bstrText ) ; 

[ id ( 2 ) ,

helpstring ( "method ChangeShowTimeStamps" ) ]

 HRESULT ChangeShowTimeStamps ( VARIANT_BOOL bNewVal ) ;

 [ id ( 3 ) ,

helpstring ( "method ChangeShowTraceAsODS" ) ]

 HRESULT ChangeShowTraceAsODS ( VARIANT_BOOL bNewVal ) ;

 [ id ( 4 ) ,

helpstring ( "method ChangeShowItemNumber" ) ] 

HRESULT ChangeShowItemNumber ( VARIANT_BOOL bNewVal ) ; 

[ id ( 5 ) ,

helpstring ( "method ChangeShowPID" ) ] 

HRESULT ChangeShowPID ( VARIANT_BOOL bNewVal ) ; 

 [ id ( 6 ) ,

helpstring ( "method ChangeAddCRLF" ) }

HRESULT ChangeAddCRLF ( VARIANT__BOOL bNewVal ) ;

 } ; 

[

uuid ( 4D42AOOD-7774-11D3-9F57-OOC04FA34F2C ) , 

helpstring ( "Trace Class" ) 

]

coclass Trace 

{

[ default ] interface ITrace ;

[ default, source ] dispinterface _ITraceEvents ; 

} ; 

} ;

Для того чтобы написать программу просмотра операторов трассировки, нужно просто обрабатывать события интерфейса iTraceEvents.


В интерфейсе ITrace определены свойства1TraceSrv, которые реализуют перечисленные выше (в п. 6 списка требований) параметры операторов трассировки (на тот случай, если приложение, использующее TraceSrv, захочет изменить их). Когда свойство программы TraceSrv изменяется, она генерирует событие, которое должна обработать специальная программа просмотра трассы — TraceView. Эта программа (она рассмотрена чуть ниже) показывает, как следует обрабатывать каждое событие, которое генерирует TraceSrv.

Мастер AppWizard (создающий СОМ-приложения средствами ATL) строит почти 90% кода СОМ+-службы. Мне пришлось написать только интерфейс TraceSrv и обработчики. Большая часть этих кодов находится в файлах TraceSrvTRACE.H и TRACE.CPP на сопровождающем компакт-диске. Они, в основном, выполняют установку и получение свойств и запуск событий. Единственная неординарная функция CTrасе: :ProcessTrace (обрабатывающая строки трассы) показана в листинге 11-2.

Здесь речь идет о том, что атрибуты propput и propget IDL-файла информируют некоторые языки (типа Visual Basic), что с указанным в них методом нужно обращаться, как со свойством. — Пер.

Листинг 11-2. Функция  CTrасе: :ProcessTrace

HRESULT CTrace :: ProcessTrace ( BSTR bstrText , long dwPID) 

{

// Все перепроверяйте и ничему не верьте!

ASSERT ( this ) ;

ASSERT ( NULL != bstrText ) ; 

// Длина входной строки. Длина вычисляется после того, как

// проверен указатель.

int ilnputLen = 0 ; 

if ( NULL == bstrText ) 

{

return ( Error ( IDS_NULLSTRINGPASSED , 

 GUID_NULL ,

 E_INVALIDARG ) ) ;

 }

// bstrText содержит некоторый указатель.

// Удостовериться, что указатель содержит правильное значение. 

ASSERT ( FALSE = IsBadReadPtr ( bstrText , sizeof ( BSTR ) ) ) ; 

ASSERT ( L';\0'; != *bstrText ); 

if ( ( TRUE == IsBadReadPtr ( bstrText , sizeof ( BSTR ) ) ) ||

( L';\0'; == *bstrText ) ) 

{

return ( Error ( IDS_INVALIDSTRING , GUID_NULL

E_INVALIDARG ) ) ; 



}

// Теперь, когда указатель проверен, получить длину 

// входной строки (в символах).

 iInputLen = IstrlenW ( bstrText ) ;

// Вычислить максимальное число байт, необходимых для

 // входной строки. 

UINT uiSize = ( ilnputLen * sizeof ( OLECHAR ) ) +

k_SIZE_FULLFORMATBYTES ;

// Захватить объект lock, чтобы защитить класс m_cOutput.

 // Grab the lock to protect the m_cOutput class.

 ObjectLock lock ( this ) ;

 // Если это первое обращение к ProcessTrace (m_lBuffSize - 0), 

//то этот if-блок служит исходной точкой распределения памяти,

 if ( uiSize >= m_cOutput.BufferSize ( ) ) 

{

// Удалить существующий буфер и распределить больший.

 m_cOutput.Free ( ) ;

// Распределить буфер, вдвое превышающий размер входной строки

. // Это делается для того, чтобы выполнять распределение памяти

 //не так часто. Это компромисс между неиспользуемой

 // дополнительной памятью и временем на непрерывные распределения.

 // Умножение размера буфера на 2 гарантирует также, что

 //сохраняется четный размер памяти. Программа работает

 // с символами Unicode, поэтому следует избегать нечетных // распределений памяти. 

UINT uiAllocSize = uiSize * 2 ;

// Убедитесь, что получен минимальный размер буфера. 

// Минимальный размер буфера 2 Кбайт, так что в большинстве 

// случаев код в этом if-блоке выполняется только однажды.

if ( k_MIN_TRACE_BUFF_SIZE > uiAllocSize ) 

{

uiAllocSize = k_MIN_TRACE_BUFF_SIZE ;

 }

OLECHAR * pTemp = m_cOutput.Allocate ( uiAllocSize ) ;

 ASSERT ( NULL != pTemp ) ;

 if ( NULL == pTemp ) 

{

return ( Error ( IDSJXJTOFMEMORY ,

 GUID_NULL , EJDUTOFMEMORY ) ) ;

 }

}

// Все проверено, теперь можно начать реальную работу.

 // Увеличить на 1 итоговый счетчик.

 m_dwCurrCount++ ; 

if ( 100000 == m_dwCurrCount )

 {

m_dwCurrCount = 0 ;

 }

// Установить указатель маркера в начало буфера вьвода 



OLECHAR * pCurr = m__cOutput.GetDataBuffer ( ) ;

 if ( -1 = m_vbShowItemNumber ) 

{

pCurr += wsprintfW ( pCurr , L"%05d " , m_dwCurrCount ) ;

 }

if ( -1 == m_vbShowTimeStamps ) 

{

// Показать метку времени в формате местного пользователя.

 // (здесь сервер, а не в клиент!). Я устанавливаю метку

 // в 24-часовом формате.

int iLen = GetTimeFormatW ( LOCALE_USER_DEFAULT ,

LOCALE_NOUSEROVERRIDE |

TIME_FORCE24HOURFORMAT |

TIME_NOTIMEMARKER , 

NULL

NULL , 

pCurr ,

 k_SIZE_TIME ) ; ASSERT ( 0 != iLen ) ;

// Переместить указатель, но не забыть о наличии

 // NULL-символа в конце строки. 

pCurr 4= ( iLen - I ) ;

11 GetTimeFormat не добавляет дополнительного пробела,

 // поэтому добавляем его сейчас.

*pCurr = L' ' ;

pCurr++ ;

 }

if ( -1 == m_vbShowPID )

 {

pCurr += wsprintfW ( pCurr , L"[%04X] " , dwPID ) ; 

}

// Теперь поместите в буфер фактическое сообщение и копируйте

 // NULL-терминатор в конец строки.

 IstrcpynW ( pCurr , bstrText , IlnputLeri + 1 ) ;

 // Переместить pCurr, чтобы указать на NULL-терминатор.

 pCurr += ilnputLen ;

// Проверить, не нужны ли символы CRLF в конце строки, 

if ( -1 == m_vbAddCRLF ) 

{

if ( ( L';\xOD'; != *( pCurr _ 2 ) ) || 

( L';\xOA'; != *( pCurr _ 1 ) ) )

{

*( pCurr ) = L';\xOD;;

*( pCurr + 1 } = L';\xOA'; ;

 pCurr += 2 ;

*pCurr = YL';\0'; ;



}

// Предполагается ли получить снимок для отладчика режима ядра?

 if ( -1 == m_vbShowTraceAsODS )

 {

OutputDebugStringW ( (OLECHAR*) m_cOutput ) ; 

}

// Подсчитать длину строки. 

m_cOutput.GetStringByteLength ( ) ;

 // Вывод сообщения о результате трассировки.

#ifdef _DEBUG

HRESULT hr =

#endif

Fire_TraceEvent ( m_cOutput ) ;

 #ifdef _DEBUG

if ( ! SUCCEEDED ('hr ) ) 

{

ASSERT ( SUCCEEDED ( hr ) ) ;

TRACE ( Т ( "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n" ) ) ; 



TRACE ( _T ( "TraceSrv FireJTraceEvent failed!!\n" ) ) ;

 TRACE ( Т ( "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n" ) ) ; 

}

#endif

return ( S_OK ) ; 

}

В целом, реализация TraceSvr довольно проста. Команда Implement Connection Point меню ClassView делает обработку кода интерфейса IconnectionPoint очень приятной. По сравнению с ATL Proxy Generator из Microsoft Visual C++ 5, эта команда значительно усовершенствована.

Обработке строк типа BSTR было уделено много внимания. Поскольку имелись в виду сценарии, в которых количество операторов трассировки должно увеличиваться весьма интенсивно, то хотелось удостовериться, что строки Обрабатывались максимально быстро. Функция  СТгасе: : ProcessTrace в TRACE.CPP выполняет много манипуляций со строками, особенно если учитывать различные элементы, которые могут быть размещены в начале и конце заключительного строчного вывода программы TraceSrv. Первоначально для строчных манипуляций был предназначен класс CComBSTR. Но в результате пошаговой трассировки выяснилось, что почти для каждого метода и оператора в классе он каждый раз выделял или освобождал память с помощью функций Sysxxxstring. Хотя в некоторых приложениях использование ccomBSTR совершенно законно, в программах (таких как TraceSrv), манипулирующих строками, это может привести к снижению реальной производительности.

Для того чтобы ускорить обработку строк, я написал простой класс с именем CFastBSTR, который обрабатывает тип BSTR напрямую. Класс находится в файле FASTBSTR.H. Единственной его работой является выделение памяти для одиночного буфера данных и варьирование ведущего размера (DWORD) функции GetstringByteLength. Может показаться, что я должен был неминуемо увязнуть в семантике автоматизации типа BSTR, но в этом случае увеличение производительности было важнее, чем консервативное программирование. Если такой подход кажется вам неудобным, то код в CFastBSTR нетрудно изменить, чтобы использовать обычные функции Sysxxxstring.



Нужно указать еще на одну деталь: рабочее пространство проекта имеет четыре различные конфигурации для сборки приложений — отладочную (debug) и выпускную (release) для многобайтовых символов, и две аналогичных — для символов Unicode. Многобайтовые конфигурации позволяют регистрировать TraceSrv на машинах с Windows 98. Как указано в главе 5, если вы нацеливаетесь исключительно на Windows 2000, то следует компилировать программы, ориентируясь на полноценную работу с Unicode. Поскольку я проектировал TraceSrv как службу Windows 2000, которая определенно не будет выполняться под Windows 98, то версию, устанавливаемую на серверной машине, нужно компилировать в одной из Unicode-конфигураций.

Теперь, познакомившись с кодом TraceSrv, рассмотрим особенности работы с готовой утилитой TraceSrv. Проект, созданный в Visual C++ 6, который находится на сопровождающем компакт-диске, сгенерирован в основном с помощью мастера COM AppWizard библиотеки активных шаблонов (ATL), так что последний шаг построения должен регистрировать TraceSrv. Все регистрируемые компоненты TraceSrv являются частью свободно распространяемого ATL-кода, но сама программа TraceSrv регистрируется только как локальный ЕХЕ-сервер. TraceSrv не будет выполняться как служба Win32, если не указать в командной строке ключ -service. Можно было сделать регистрацию службы частью процедуры построения, но я выбрал не это, потому что отладка служб Win32 без отладчика режима ядра (типа SoftICE) не проста. Кроме того, если вы находитесь в середине цикла "исправление-компиляция-отладка", то выход в режим командной строки (т. е. на командный процессор) и выполнение команды net stop tracesrv только для того, чтобы заставить конструкцию работать — настоящая пытка. После того как вы осуществили достаточное количество сеансов отладки и тестирования, запуская TraceSrv как локальный сервер, его можно зарегистрировать и запустить как службу.



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