Первые проблемы с 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 из пользовательского кода.
Применение TraceSrv
Работать с программой TraceSrv довольно легко. Код на сопровождающем компакт-диске вызывает TraceSrv из программ, написанных на трех различных языках. В листинге 11-4 приведен пример вызова TraceSrv из программы на VBScript, который показывает, насколько легко использовать TraceSrv. Пример на C++ более интересен в том отношении, что прежде чем обращаться к TraceSrv, нужно конвертировать строки трассы в строки типа BSTR. Программа DCOMTEST.CPP показана в листинге 11-5.
RFC — Remote Procedure Call, удаленный вызов процедуры (средство передачи сообщений, которое позволяет распределенному приложению вызывать сервис различных компьютеров в сети; обеспечивает процедурно-ориентированный подход в работе с сетью; применяется в распределенных объектных технологиях, таких как DCOM, CORBA, Java RMI. — Пер
Листинг 11-4. Вызов TraceSrv из VBScript
<SCRIPT LANGUAGE="VBScript"><!- -
Dim g_TraceObj
set g_TraceObj = CreateObject ( "TraceSrv.Trace.1" )
g_TraceObj.Trace( "This is a test of the...?" + vbCRLF )
Sub ButtonOne_OnClick
g_TraceObj.Trace ( "Hey! I'm Button One!" + vbCRLF )
End Sub
Sub ButtonTwo__OnClick
g_TraceObj.Trace ( "VBScript is dangerous!" + vbCRLF )
End Sub
- -></SCRIPT>
Листинг 11-5. Вызов TraceSrv из программы на C++ (DCOMtESf .СРР)
void main ( void )
{
HRESULT hr ;
ITrace * IpTrace ;
lUnknown * IpUnknown ;
// Инициализировать библиотеки СОМ+.
hr = CoInitializeEx ( NULL , COINIT_APARTMENTTHREADED ) ;
if ( FAILED ( hr ) )
{
printf ( "Unable to initialize COM+\n" ) ;
return ;
}
hr = CoCreatelnstance ( CLSIDJTrace ,
NULL , CLSCTX_SERVER , IID_IUnknown , (LPVOID*)SlpUnknown } ;
if ( FAILED ( hr ) )
{
LPVOID IpMsgBuf;
FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE__FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE__INSERTS,
NULL,
hr,
MAKELANGID ( LANG_NEUTRAL, SUBLANG__DEFAULT ),
(LPTSTR) SlpMsgBuf,
0,
NULL ) ;
printf ( " CoCreatelnstanceEx failed: Ox%08X\n" , hr ) ;
printf ( "FormatMessage returned: %s\n" , IpMsgBuf ) ;
return ;
}
hr = lpUnknown->Query!nterface ( IID_ITrace ,
(LPVOID*)&lpTrace ) ;
lpUnknown->Release ( ) ;
if ( FAILED ( hr } )
{
LPVOID IpMsgBuf;
FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS, NULL,
hr,
MAKELANGID ( LANG_NEUTRAL, SUBLANG_DEFAULT ),
(LPTSTR) SlpMsgBuf, 0,
NULL );
printf ( "Querylnterface failed: Ox%08X\n" , hr ) ;
printf { "FormatMessage returned: %s\n" , IpMsgBuf ) ;
return ;
}
OLECHAR * pszTemp ;
pszTemp = SysAllocString ( OLESTR ( "Hello from a C++ program!!!" ) ); ,
lpTrace->Trace ( pszTemp ) ;
SysFreeString ( pszTemp ) ;
lpTrace->Release ( ) ;
CoUninitialize ( ) ;
}
Программа TraceView и безопасность
Программа TraceSrv полезна сама по себе, но утилита просмотра, которая отображает на экране операторы трассировки, на самом деле улучшает ее. Я написал TraceView на языке Visual Basic, потому что это было довольно просто сделать. Если посмотреть на ее исходный код, то можно убедиться, что ничего особенного в нем нет.
Я попытался сделать TraceView немного более полезной, чем простой редактируемый элемент управления, добавив к ней панель инструментов, панель состояния, поддержку сохранения и восстановления позиции окна, сохранение файлов, поиск вперед и назад, и реализовал для окна возможность всегда оставаться наверху. Чтобы облегчить локализацию программы, все строки сохраняются в файле ресурса. Не буду углубляться в загрузку строк ресурса, но отмечу, что пришлось модифицировать сгенерированную функцию LoadResStrings (переименованную В LoadFormResStrings) так, чтобы она уведомляла пользователя о том, какие элементы ресурса не были загружены. Сначала TraceView работала прекрасно. Однако в ходе проверки различных способов присоединения TraceView к TraceSrv были выявлены некоторые проблемы. Если TraceView и TraceSrv располагались на одной машине, то Trace View могла соединяться с TraceSrv только тогда, когда она выполнялась как служба или как локальный сервер. TraceView могла также соединяться должным образом с TraceSrv, если TraceSrv выполнялась на другой машине как локальный сервер, использующий СОМ+-технологию. Однако когда я пробовал соединять TraceView с TraceSrv, выполняющейся на другой машине как СОМ+-служба, это всегда приводило к отказу, сопровождаемому VB-сообщением об ошибке "Run-time error -2147023071 (80070721) Automation Error" (Ошибка времени выполнения -2147023071 (80070721) Ошибка автоматизации). В файле WINERROR.H идентификатор (ID) этой ошибки выглядит так: RPC_S_SEC_PKG_ERROR, "A ' security package specific error occurred" (Произошла специфическая ошибка пакета защиты).
Этот идентификатор был мне незнаком, а попытавшись найти его в MSDN, я узнал только, что он определен в WINERROR.H и внесен в список приложений с системными ошибками.
Провозившись с этой проблемой несколько дней, я обнаружил, что мог бы добиться соединения VB-программы с удаленной службой TraceSrv лишь в том случае, если бы в объявлении Trace-объекта не использовал ключевое слово withEvents. Указывая ключевое слово withEvents, я всегда получал ошибку RPC_S_SEC_PKG_ERROR и пребывал в недоумении, пока один из друзей не указал мне, что неправильно установлена защита (security) службы TraceSrv.
Вернувшись "к своим баранам" и еще раз посмотрев, что же происходит, я начал кое-что понимать. Ключевое слово withEvents устанавливает интерфейс iconnectionPoint, который сервер использует для вызова клиента — это, по существу, обратный вызов. Чтобы выполнить обратный вызов клиента, сервер должен иметь корректные полномочия доступа. Когда TraceSrv выполняется на той же машине, что и TraceView, то, не зависимо от того, запущена ли она как локальный сервер или как служба, TraceSrv выполняется под тем же пользовательским идентификатором, что и TraceView. Выполнение TraceSrv на одной машине в качестве удаленного СОМ+-сервера, a TraceView — на другой, было успешным потому, что мне повезло. На обеих машинах, работавших под Windows NT Workstation без контроллера домена, я был зарегистрирован в как "John" с одним тем же паролем. Согласно статье Q158508 Knowledge Base ("Часто задаваемые вопросы по СОМ-безопасности") операционная система Windows NT Workstation "возвращается к режиму согласования имен и паролей учетных записей". Если на двух машинах, работающих под Windows NT Workstation, используются одни и те же ASCII-имена, и учетные записи имеют одни те же пароли, то средства защиты DCOM и других ресурсов NT (например, файловой системы) должны работать так, как если бы вы были действительно зарегистрированы ш этих двух машинах с одной и той же учетной записью".
Когда я, зарегистрировавшись на удаленной машине как "Bob", запускал на ней TraceSrv как удаленный сервер, и пытался соединять с ним TraceView на машине клиента, зарегистрировавшись на ней, как "John", я получал ошибку
RPC_S_SEC_PKG_ERROR.
В моем случае выполнение TraceSrv как удаленного сервера на отдельной машине не принимало во внимание каких-либо измеIнений в соединениях.
Запуск удаленного сервера с надлежащей защитой — довольно простая задача: нужно только при входе в систему зарегистрироваться в качестве пользтователя, имеющего сетевые права. Однако для служб Win32 решение этой задачи требует немного больших усилий. По умолчанию такие службы не имеют никаких полномочий безопасности, поэтому TraceSrv и вызывала соответствующую ошибку всякий раз, когда она пыталась что-то делать с интерфейсом iconnectionPoint, который она получала через параметр вызова. Необходимо было сделать так, чтобы клиент сообщил СОМ+-службам уровень безопасности, который они должны разрешать своим собственным интерфейсам. Уровень безопасности для клиентских интерфейсов определяется с помощью функции CoInitializeSecurity, которая должна вызываться немедленно после того, как ваше приложение вызывает Coinitiaiize. В программе TraceView, которая написана на языке Visual Basic, функция CoInitializeSecurity не будет работать. Если вывзов CoInitializeSecurity будет первым в функции sub Main, то вы получите код ошибки 0x80010119 (RPC_E_ TOO_LATE), что означает: "Security must be initialized before any interfaces are marshaled or unmarshaled. It cannot be changed once initialized." (Защита должна быть инициализирована перед маршализацией1или демаршализацией любого интерфейса. После инициализации она не может быть изменена.) Нетрудно видеть, что Visual Basic выполняет маршалинг намного раньше того, как пользовательский код получает вызов.
Маршалинг — передача данных через границы процесса. Здесь речь идет о передаче параметров и возврате результатов при передаче вызова в другое адресное пространство. — Пер.
Обойти это ограничение Visual Basic можно двумя способами. Первый — 1 запустить DCOMCNFG и установить для свойства Default Authentication Level на I вкладке Default Properties значение None. Такое решение подходит для небольшой замкнутой домашней сети, но для крупных реальных сетей разработки это не самое лучшее решение.
Второй подход более приемлем и безопасен: на машине, которая будет выполнять программу TraceSrv, зарегистрируйте ее как службу, запустите Панель управления, и щелкните на значке Services. Выберите элемент TraceSrv (в списке служб) и нажмите кнопку Start Service, а также кнопку Properties, чтобы отобразить диалоговое окно Properties программы TraceSrv. На вкладке Log On в переключателе Log On As выберите переключатель This Account и введите с клавиатуры имя пользователя и пароль для учетной записи, под которой предполагается выполнять TraceSrv. Теперь служба будет способна получить необходимую ей защиту от известной в сети учетной записи. Как указывается в статье из "COM Security Frequently Asked
Questions" (Часто задаваемые вопросы по СОМ-безопасности): "Учетная запись Localsystem является локально очень привилегированной... Однако ее владелец не имеет сетевых привилегий и не может покинуть машину через любые защищенные механизмы NT, включая файловую систему, именованные каналы, DCOM или безопасные RPC1-вызовы". Как только я получил службу, запускающуюся под надлежащей учетной записью, TraceView заработала прекрасно. Для работы с сервером домена можно предусмотреть создание специальной учетной записи, которую можно использовать для запуска инструментов, подобных TraceSrv. Например, можно организовать специальную учетную запись Build, которую ваши сборочные (build) машины будут использовать для отправки почты.
Вооружившись программой TraceSrv, можно начинать
Вооружившись программой TraceSrv, можно начинать отладку межъязыковых, межпроцессных, межмашинных приложений. Требования для TraceSrv и большинства ее реализаций довольно просты, но TraceSrv — одно из тех приложений, которые погружают нас в болото проблем безопасности Windows 2000. Поскольку все большая часть наших разработок основывается на СОМ-технологии, обеспечение безопасности становится значительной частью повседневной жизни программистов. В связи с тем, что из-за проблем безопасности появился новый слой программ, способных отказывать новыми, довольно странными и тонкими способами, необходимо посвятить некоторое время ознакомлению с вопросами обеспечения безопасности в операционных системах. Как указывалось в главе /, чем больше разработчик знает об операционной системе, тем быстрее он сможет решать все проблемы, связанные с ошибками безопасности и с другими, более традиционными типами ошибок.
Я призываю вас экспериментировать с TraceSrv в своих приложениях. Никогда не преуменьшайте мощи трассировки — она может быть одним из лучших инструментов отладки в вашем арсенале.
TraceSrv и DCOMCNFG
Для того чтобы разрешить доступ к TraceSrv через сеть, не следует запускать ее как службу, (т. к. после запуска TraceSrv работает как удаленный СОМ+-сервер). Для отладки такая гибкость очень удобна, потому что, запустив TraceSrv в отладчике, можно наблюдать за присоединением к ней клиентов, а при необходимости выполнять и отладку. Мне удобнее всего было, когда и клиентское приложение, и TraceSrv выполнялись под отладчиками на своих машинах. Когда какое-то из приложений достигает точки прерывания, следует приостановить и другое (чтобы избежать любых возможных проблем с тайм-аутами). Я всегда компилировал Visual Basic-клиент до уровня "родного" кода и запускал его под отладчиком Visual C++. Эта тактика гарантировала, что по достижении клиентом точки прерывания он "намертво" останавливается в отладчике. Причина, по которой нельзя остановить клиент, выполняющийся под VB-отладчиком, заключается в том, что с TraceSvr на самом деле соединяется не отлаживаемое приложение, а сам VB-отладчик.
Для того чтобы использовать TraceSrv через сеть, нужно выполнить программу DCOMCNFG.EXE, корректно прописывающую данные о ней в реестр. Первое, что нужно сделать — выполнить для своей машины установку СОМ+-СВОЙСТБ по умолчанию. Перед изменением СОМ+-свойств необходимо проверить с сетевым администратором умалчиваемые СОМ+-свойства в сетевом окружении всей фирмы. Если вы работаете в небольшой сети и обладаете привилегиями суперпользователя, то можно применить установки, перечисленные в табл. 11.1 и, наилучшим образом работавшие на всех машинах, на которых я тестировал TraceSrv.
Таблица 11.1. Умалчиваемые установки DCOMCNFG
На вкладке Default Properties в DCOMCNFG |
Enable Distributed COM Установлен (флажок) On This Computer |
Default Authentication Level Connect |
Default Impersonation Level Identify |
На вкладке Default Security в DCOMCNFG.EXE |
Default Access Permissions Everyone (Bee) Allow Access (Доступ разрешен) INTERACTIVE Allow Access NETWORK Allow Access SYSTEM Allow Access |
Default Launch Permissions Administrators Allow Launch (Администраторы) (Запуск разрешен) Everyone Allow Launch INTERACTIVE Allow Launch NETWORK Allow Launch SYSTEM Allow Launch |
Default Configuration Administrators Full Control Permissions (Полное управление) CREATOR OWNER Full Control Everyone Read (Чтение) INTERACTIVE Special Access (Специальный доступ. Включены все значеия за исключением Create Link, Write DAC и Write Owner) |
SYSTEM Full Control |
После регистрации TraceSrv ( либо как части построения, либо с ключом -RegServer командной строки) запустите DCOMCNFG, выберите TraceSrv (или Trace Class в Windows 98) и нажмите кнопку Properties. Я изменял установки только на вкладке Location. Для того чтобы TraceSrv выполнялась только на локальной машине, установите флажок Run Application On This Computer и сбросьте другие. Если требуется выполнять TraceSrv только на другой машине, то включите флажок опции Run Application On The Following Computer и укажите сервер. (Обратите внимание, что утилита DCOMCNFG позволит поместить на панель имя текущего компьютера, но тогда она не будет создавать сервер.) Чтобы избежать многих неприятностей, дважды проверьте, что все опции на вкладке Security установлены для использования по умолчанию.
Вообще-то не рекомендуется изменять установки в DCOMCNFG — параметры безопасности (security) и идентичности (identity), но если вы это сделали и TraceSrv больше не запускается, выполните утилиту из командной строки с ключом -unRegServer — реестр будет очищен, и можно регистрировать TraceSrv заново (см. выше). Автоматическая регистрация (registration) и "выписка" (unregistration) — удобные свойства ATL.
Теперь вы знаете, как проектировалась, строилась и устанавливалась TraceSrv, и, вероятно, думаете, что эта тема исчерпана. Сначала я тоже так думал, но затем, когда уже начал работать с TraceSrv, обнаружил некоторые действительно неприятные ошибки.
Требования к 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 как локальный сервер, его можно зарегистрировать и запустить как службу.