Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Роббинс Д. - Отладка приложений для Microsoft .NET и Microsoft Windows - 2004

.pdf
Скачиваний:
322
Добавлен:
13.08.2013
Размер:
3.3 Mб
Скачать

ГЛАВА 3 Отладка при кодировании

91

 

 

Элемент add содержит два необходимых атрибута: name представляет строку, определяющую имя объекта TraceListener в том виде, в котором оно помещается в свойство TraceListener.Name, а type вызывает замешательство, и я объясню поче$ му. В документации показано только добавление типа, находящегося в глобаль$ ном кэше сборок (GAC), и сказано, что добавление собственного приемника го$ раздо сложнее, чем нужно. Один необязательный атрибут — initializeData — пред$ ставляет строку, передаваемую конструктору объекта TraceListener.

Чтобы добавить объект TraceListener из GAC, в элементе type надо только пол$ ностью указать класс объекта TraceListener. Согласно документации для добавле$ ния объекта TraceListener, не находящегося в GAC, вам придется иметь дело со всей атрибутикой вроде региональных параметров (culture) и маркеров открытых клю$ чей (public key tokens). К счастью, все, что нужно сделать, — это просто указать полностью класс, добавить запятую и имя сборки. Во избежание инициации ис$ ключения System.Configuration.ConfigurationException не добавляйте запятую и имя класса. Вот как правильно добавить глобальный класс TextWriterTraceListener:

<?xml version="1.0" encoding="UTF 8" ?> <configuration>

<system.diagnostics>

<trace autoflush="true" indentsize="0"> <listeners>

<add name="CorrectWay" type="System.Diagnostics.TextWriterTraceListener" initializeData="TextLog.log"/>

</listeners>

</trace>

</system.diagnostics>

</configuration>

Чтобы добавить объекты TraceListener, не находящиеся в GAC, надо разместить сборку, содержащую потомки класса TraceListener, в одном каталоге с двоичным файлом. Испробовав все комбинации путей и параметров конфигурации, я выяс$ нил, что включить сборку из другого каталога через конфигурационный файл нельзя. Добавляя потомок класса TraceListener, поставьте запятую и имя сборки. Вот как добавить BugslayerTextWriterTraceListener из BugslayerUtil.NET.DLL:

<?xml version="1.0" encoding="UTF 8" ?> <configuration>

<system.diagnostics>

<trace autoflush="true" indentsize="0"> <listeners>

<add name="AGoodListener" type=

"Wintellect.BugslayerTextWriterTraceListener,BugslayerUtil.NET"

initializeData="BSUTWTL.log"/>

</listeners>

</trace>

</system.diagnostics>

</configuration>

92 ЧАСТЬ I Сущность отладки

Утверждения в приложениях ASP.NET и Web-сервисах XML

Я действительно рад видеть платформу для разработки, в которую изначально заложены идеи по обработке утверждений. Пространство имен System.Diagnostics содержит все эти полезные классы, квинтэссенция которых — Debug. Как и боль$ шинство из вас, я начал изучать .NET с создания консольных приложений и при$ ложений Windows Forms, поскольку в то время они проще всего уживались в моей голове. Когда я перешел к ASP.NET, я уже использовал Debug.Assert и подумал, что Microsoft правильно поступила, избавившись от информационных окон. Безуслов$ но, они поняли, что при работе в ASP.NET мне потребуется возможность при сра$ батывании утверждения перейти в отладчик. Представьте мое удивление, когда я инициировал утверждение и ничего не прекратилось! Я увидел обычный вывод утверждения в окне Output отладчика, но не увидел вызовов OutputDebugString с информацией об утверждении. Поскольку Web$сервисы XML в .NET по существу являются приложениями ASP.NET без пользовательского интерфейса, я проделал то же самое с Web$сервисом и получил те же результаты. (Далее в этом разделе в термине ASP.NET я буду совмещать ASP.NET и Web$сервисы XML .) Поразительно! Это означало, что в ASP.NET нет настоящих утверждений! А без них можно и не программировать! Единственная хорошая новость в том, что в приложениях ASP.NET DefaultTraceListener не отображает обычное информационное окно.

Без утверждений я чувствовал себя голым и знал, что с этим надо что$то де$ лать. Подумав, не создать ли новый объект для утверждений, я решил, что правиль$ нее всего будет держаться Debug.Assert как единственного способа обработки утвер$ ждений. Это позволяло мне решить сразу несколько ключевых проблем. Первая заключалась в наличии единого способа работы с утверждениями для всей плат$ формы .NET — я совсем не хотел беспокоиться о том, будет ли код запущен в Windows Forms или ASP.NET, и применять неверные утверждения. Вторая пробле$ ма касалась библиотек сторонних производителей, в которых имеется Debug.Assert: как их использавать, чтобы их утверждения появлялись в том же месте, где и все другие.

Третья проблема состояла в том, чтобы сделать обращение к библиотеке утвер$ ждений максимально безболезненным. Написав массу утилит, я понял важность легкой интеграции библиотеки утверждений в приложение. Последняя пробле$ ма, которую я хотел решить, заключалась в наличии серверного элемента управ$ ления, позволяющего легко видеть утверждения на странице. Весь код находится в BugslayerUtil.NET.DLL, так что вы можете открыть этот проект с тестовой про$ граммой BSUNAssertTest, расположенной в подкаталоге Test каталога Bugslayer$ Util.NET. Прежде чем открыть проект, не забудьте создать виртуальный каталог в Microsoft Internet Information Services (IIS), ссылающийся на каталог BSUNAssertTest.

Проблемы, которые я хотел решить, указывали на создание специального класса, наследуемого от TraceListener. Через секунду я расскажу об этом коде, но незави$ симо от того, насколько классным получился бы TraceListener, мне нужен был способ подключить свой объект TraceListener и удалить DefaultTraceListener. Как бы там ни было, это требовало изменений в коде с вашей стороны, потому что мне нуж$ но выполнить некоторый код. Чтобы упростить применение утверждений и обес$ печить максимально ранний вызов библиотеки утверждений, я использовал класс, наследуемый от System.Web.HttpApplication, так как его конструктор и метод Init

ГЛАВА 3 Отладка при кодировании

93

 

 

вызываются в приложении ASP.NET в первую очередь. Первым шагом на пути к нирване утверждений является наследование от вашего класса Global из Glo$ bal.ASAX.cs (или Global.ASAX.vb) с использованием моего класса AssertHttpApplication. Это позволит правильно подключить мой ASPTraceListener и поместить в ссылку на него в отделе состояния приложения в разделе «ASPTraceListener», так что вы сможете в ходе работы изменять параметры вывода. Если все, что вам нужно в при$ ложении, — это возможность остановить его при срабатывании утверждения, то больше от вас ничего не потребуется.

Для вывода утверждений на страницу я написал очень простой элемент управ$ ления, который вполне логично называется AssertControl. Чтобы добавить его на панель инструментов, щелкните правой кнопкой вкладку Web Forms и выберите из контекстного меню команду Add/Remove Items. В диалоговом окне Customize Toolbox перейдите на вкладку .NET, щелкните кнопку Browse и в окне File Open перейдите к BugslayerUtil.NET.DLL. Теперь вы можете просто перетаскивать Assert$ Control на любую страницу, в которой вам потребуются утверждения. Вам не при$ дется прописывать элемент управления в вашем коде, потому что класс ASPTrace Listener обнаружит его на странице и создаст соответствующий вывод. AssertControl будет найден, даже если он вложен в другой элемент управления. Если при обра$ ботке страницы на сервере ни одно утверждение не инициировалось, AssertControl не выводит ничего. Иначе он отображает те же сообщения утверждений и инфор$ мацию о стеке, что выводятся в Windows$ или консольных приложениях. Поскольку на странице могут инициироваться несколько утверждений, AssertControl отобра$ жает их все. На рис. 3$2 показана страница BSUNAssertTest после инициации ут$ верждения. Текст в нижней части страницы — это вывод AssertControl.

Вся работа выполняется в классе ASPTraceListener, большая часть которого пред$ ставлена в листинге 3$4. Чтобы объединить в себе все необходимое, ASPTraceListener включает несколько свойств, позволяющих перенаправлять и изменять вывод в процессе работы (табл. 3$1).

Табл. 3-1. Свойства вывода и управления ASPTraceListener

Свойство

Значение по умолчанию

Описание

ShowDebugLog

true

Показывает вывод в подключенном

 

 

отладчике.

ShowOutputDebugString

false

Показывает вывод через

 

 

OutputDebugString.

EventSource

null/Nothing

Имя источника события для записи

 

 

вывода в журнал событий. Внутри

 

 

BugslayerUtil.NET.DLL не получаются

 

 

разрешения и не выполняются про$

 

 

верки безопасности для доступа

 

 

к журналу событий. Перед установ$

 

 

кой EventSource вам придется запро$

 

 

сить разрешения.

Writer

null/Nothing

Объект TextWriter для записи вывода

 

 

в файл.

LaunchDebuggerOnAssert

true

Если подключен отладчик, он сразу

 

 

останавливает выполнение при

 

 

инициации утверждения.

 

 

 

94 ЧАСТЬ I Сущность отладки

Вывод AssertControl

Рис. 3 2. Приложение ASP.NET, отображающее утверждение через AssertControl

Всю работу по выводу информации утверждения, которая включает поиск эле$ ментов управления утверждений на странице, выполняет метод ASPTraceListener.Hand leOutput, показанный в листинге 3$4. Моя первая попытка создания метода Handle Output была гораздо запутаннее. Я мог получить текущий IHttpHandler для текуще$ го HTTP$запроса из статического свойства HttpContext.Current.Handler, но не на$ шел способа определить, являлся ли обработчик реальной System.Web.UI.Page. Если бы я смог выяснить, что это страница, я мог бы легко идти дальше и найти эле$ менты управления утверждений на странице. Моя первая попытка заключалась в написании кода с использованием интерфейсов отражения, чтобы я смог сам просматривать цепи наследования. Когда я заканчивал примерно пятисотую строку кода, Джефф Просиз (Jeff Prosise) невинно поинтересовался, не слышал ли я про оператор is, который определяет совместимость типа объекта, существующего в период выполнения, с заданным типом. Создание функциональности моего соб$ ственного оператора is стало интересным упражнением, но мне надо было со$ всем другое.

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

ГЛАВА 3 Отладка при кодировании

95

 

 

ВASPTraceListener.FindAssertControl я решил задействовать преимущество ключе$ вого слова out, которое позволяет передавать параметр метода ссылкой, но не тре$ бует его инициализации. Логичнее рассматривать ненайденный элемент управ$ ления как null, и ключевое слово out позволяло это сделать.

Последнее, что я делаю с утверждением в методе ASPTraceListener.HandleOutput, — определяю, переходить ли при инициации утверждения в отладчик. Прекрасный объект System.Diagnostics.Debugger позволяет общаться с отладчиком из вашего кода. Если в последнем идет отладка кода, свойство Debugger.IsAttached будет иметь зна$ чение true, и, просто вызвав Debugger.Break, вы можете имитировать точку преры$ вания в отладчике. Конечно, такое решение предполагает, что вы отлаживаете этот конкретный Web$сайт. Мне еще нужно предусмотреть случай вызова отладчика, когда вы работаете не из него.

Вклассе Debugger содержится замечательный метод Launch, позволяющий запу$ стить отладчик и подключить его к вашему процессу. Однако, если учетная запись пользователя, под которой выполняется процесс, не находится в группе Debugger Users, Debugger.Launch не сработает. Если нужно подключать отладчик из кода ут$ верждения, когда отладчик не запущен, придется получить учетную запись для работы ASP.NET, находящуюся в группе Debugger Users. Прежде чем продолжить, должен сказать, что, разрешая ASP.NET вызывать отладчик, вы потенциально со$ здаете угрозу безопасности, поэтому делайте это только на отладочных машинах, не подключенных к Интернету.

ASP.NET в Windows 2000 и XP работает под учетной записью ASPNET, так что именно ее надо добавить в группу Debugger Users. Добавив учетную запись, пере$ запустите IIS, чтобы Debugger.Launch отобразил диалог Just$In$Time (JIT) Debugging.

ВWindows Server 2003 ASP.NET работает под учетной записью NETWORK SERVICE. Добавив NETWORK SERVICE в группу Debugger Users, перезагрузите машину.

Обеспечив работу Debugger.Launch настройкой параметров безопасности, я должен был убедиться, что Debugger.Launch будет вызываться только при подходящих усло$ виях. Вызов Debugger.Launch, когда в систему сервера никто не вошел, привел бы к большим проблемам, потому что отладчик по требованию мог бы ждать нажатия клавиши в окне, до которого никто не смог бы добраться! В классе ASPTraceListener мне следовало убедиться, что HTTP$запрос производится с локальной машины, потому что это указывает на то, что кто$то вошел в систему и отлаживает утвер$ ждение. Метод ASPTraceListener.IsRequestFromLocalMachine проверяет, не является ли 127.0.0.1 адресом хоста или не равна ли серверная переменная LOCAL_ADDR адресу хоста пользователя.

Последнее замечание по поводу вызова отладчика касается Terminal Services. Если у вас открыто окно Remote Desktop Connection с подключением к серверу, Web$адрес для любых запросов к серверу, как и следует ожидать, будет представ$ ляться в виде IP$адреса сервера. По умолчанию мой код утверждения при совпа$ дении адреса запроса с адресом сервера вызывает Debugger.Launch. Тестируя при$ ложение ASP.NET и запустив с помощью Remote Desktop браузер на сервере, я получил сильный шок при срабатывании утверждения. (Помните, что я не отла$ живал процесс ни на одной машине.)

Я ожидал увидеть информационное окно с предупреждением о нарушении правил безопасности или диалоговое окно JIT Debugger, но увидел лишь завис$

96 ЧАСТЬ I Сущность отладки

ший браузер. Я был здорово растерян, пока не подошел к серверу и не подвигал мышь. Там на фоне экрана регистрации находилось мое информационное окно! Мне стало ясно, что, хотя это выглядело как ошибка, все было объяснимо. Поскольку информационное окно или диалог JIT Debugger вызываются из$под учетной за$ писи ASPNET/NETWORK SERVICE, ASP.NET не знает, что подключение осуществ$ лялось через сеанс Terminal Services. Эти учетные записи не могут отслеживать, из какого сеанса был вызван Debugger.Launch. Соответственно вывод направлялся только на реальный экран компьютера.

Хорошая новость в том, что если вы подключили отладчик, то независимо от того, сделали вы это в окне Remote Desktop Connection или на другой машине, вызов Debugger.Launch работает точно так, как должен, и прерывает выполнение, переходя в отладчик. Кроме того, если вы направили вызов серверу из браузера на другой машине, то вызов Debugger.Launch не остановит выполнение. Мораль: если для подключения к серверу вы собираетесь использовать Remote Desktop Connection и запустить браузер внутри этого окна (скажем, на сервере), вам следует подклю$ чить отладчик к процессу ASP.NET на этом сервере.

То, что Microsoft не предусмотрела утверждения в ASP.NET, непростительно, но, вооружившись хотя бы AssertControl, вы можете начать программировать. Если вы ищете элемент управления, чтобы научиться писать к ним расширения, AssertControl может послужить экспериментальным скелетом. Интересным расширением Assert$ Control могло бы стать использование в коде JavaScript для создания улучшенно$ го UI вроде диалогового окна Web, чтобы сообщать пользователям о возникших проблемах.

Листинг 3-4. Важные методы ASPTraceListener

public class ASPTraceListener : TraceListener

{

/* КОД УДАЛЕН ДЛЯ КРАТКОСТИ * /

// Метод, вызываемый при нарушении утверждения. public override void Fail ( String Message ,

String DetailMessage )

{

//По независящим от меня причинам практически невозможно

//всегда знать число элементов в стеке для Debug.Assert.

//Иногда их 4, иногда — 5. Увы, единственный способ, которым

//я могу решить эту проблему, — выяснить вручную. Лентяй. StackTrace StkSheez = new StackTrace ( ) ;

int i = 0 ;

for ( ; i < StkSheez.FrameCount ; i++ )

{

MethodBase Meth = StkSheez.GetFrame(i).GetMethod ( ) ;

// Если ничего не получили, выходим отсюда. if ( null != Meth )

{

if ( "Debug" == Meth.ReflectedType.Name )

ГЛАВА 3 Отладка при кодировании

97

 

 

{

i++ ;

break ;

}

}

}

BugslayerStackTrace Stk = new BugslayerStackTrace ( i ) ;

HandleOutput ( Message , DetailMessage , Stk ) ;

}

/* КОД УДАЛЕН ДЛЯ КРАТКОСТИ * /

///<summary>

///Закрытый заголовок сообщения об утверждении.

///</summary>

private const String AssertionMsg = "ASSERTION FAILURE!\r\n" ;

///<summary>

///Закрытая строка с переводом каретки и возвратом строки.

///</summary>

private const String CrLf = "\r\n" ;

///<summary>

///Закрытая строка с разделителем.

///</summary>

private const String Border = "————————————————————\r\n" ;

///<summary>

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

///</summary>

///<remarks>

///Обрабатывает весь вывод утверждения или трассировки.

///</remarks>

///<param name="Message">

///Отображаемое сообщение.

///</param>

///<param name="DetailMessage">

///Отображаемый подробный комментарий.

///</param>

///<param name="Stk">

///Значение, содержащее информацию о стеке для утверждения.

///Если не равно null, эта функция вызвана из утверждения.

///Вывод трассировки устанавливает этот параметр в null.

///</param>

protected void HandleOutput ( String

Message

,

String

DetailMessage ,

BugslayerStackTrace

Stk

)

{

 

 

//Создаем StringBuilder для помощи в создании

//текстовой строки для вывода.

см. след. стр.

98 ЧАСТЬ I Сущность отладки

StringBuilder StrOut = new StringBuilder ( ) ;

// Если StackArray не null, это утверждение. if ( null != Stk )

{

StrOut.Append ( Border ) ;

StrOut.Append ( AssertionMsg ) ;

StrOut.Append ( Border ) ;

}

//Присоединяем сообщение. StrOut.Append ( Message ) ; StrOut.Append ( CrLf ) ;

//Присоединяем подробное сообщение, если оно есть. if ( null != DetailMessage )

{

StrOut.Append ( DetailMessage ) ; StrOut.Append ( CrLf ) ;

}

//Если это утверждение, показываем стек под разделителем. if ( null != Stk )

{

StrOut.Append ( Border ) ;

}

//Просматриваем и присоединяем

//всю имеющуюся информацию о стеке.

if ( null != Stk )

 

 

 

{

 

 

 

Stk.SourceIndentString = "

 

" ;

Stk.FunctionIndent = "

"

;

 

StrOut.Append ( Stk.ToString ( ) ) ;

}

//Поскольку в нескольких местах

//мне понадобится строка, создаем ее. String FinalString = StrOut.ToString ( ) ;

if ( ( true == m_ShowDebugLog

)

&&

 

( true == Debugger.IsLogging ( ) )

 

)

{

 

 

 

Debugger.Log ( 0 , null , FinalString ) ;

}

if ( true == m_ShowOutputDebugString )

{

OutputDebugStringA ( FinalString ) ;

}

if ( null != m_EvtLog )

ГЛАВА 3 Отладка при кодировании

99

 

 

{

m_EvtLog.WriteEntry ( FinalString ,

System.Diagnostics.EventLogEntryType.Error ) ;

}

if ( null != m_Writer )

{

m_Writer.WriteLine

( FinalString ) ;

// ДОбавляем CRLF,

просто

на всякий случай.

m_Writer.WriteLine

( "" )

;

m_Writer.Flush ( )

;

 

}

// Всегда выполняйте вывод на страницу! if ( null != Stk )

{

//Выполняем вывод предупреждения в текущий TraceContext. HttpContext.Current.Trace.Warn ( FinalString ) ;

//Ищем на странице AssertionControl.

//Сначала убедимся, что описатель представляет страницу! if ( HttpContext.Current.Handler is System.Web.UI.Page )

{

System.Web.UI.Page CurrPage = (System.Web.UI.Page)HttpContext.Current.Handler ;

//Обходим сложности, если на странице нет

//элементов управления (в чем я сомневаюсь!)

if ( true == CurrPage.HasControls( ) )

 

{

 

// Ищем элемент управления.

 

AssertControl AssertCtl = null ;

 

FindAssertControl ( CurrPage.Controls ,

 

out AssertCtl

) ;

// Если он есть, добавляем утверждение.

 

if ( null != AssertCtl )

 

{

 

AssertCtl.AddAssertion ( Message

,

DetailMessage ,

Stk

) ;

}

 

}

 

}

// Наконец, если нужно, запускаем отладчик. if ( true == m_LaunchDebuggerOnAssert )

{

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

//Debugger.Break. Не важно, где именно запущен отладчик,

см. след. стр.

100ЧАСТЬ I Сущность отладки

//если он работает в этом процессе. if ( true == Debugger.IsAttached )

{

Debugger.Break ( ) ;

}

else

{

//С изменениями в модели безопасности версии

//.NET RTM, учетная запись ASPNET, которую использует

//ASPNET_WP.EXE, перенесена из System в User.

//Для работы Debugger.Launch надо добавить

//ASPNET в группу Debugger Users. Хотя в отладочных

//системах это безопасно, в рабочих системах

//следует соблюдать осторожность.

bool bRet = IsRequestFromLocalMachine ( ) ; if ( true == bRet )

{

Debugger.Launch ( ) ;

}

}

}

}

else

{

// TraceContext доступен прямо из HttpContext. HttpContext.Current.Trace.Write ( FinalString ) ;

}

}

///<summary>

///Определяет, пришел ли запрос от локальной машины.

///</summary>

///<remarks>

///Проверяет, равен ли IP адрес адресу 127.0.0.1

///или серверной переменной LOCAL_ADDR.

///</remarks>

///<returns>

///Возвращает true, если запрос пришел от локальной машины,

///в противном случае — false.

///</returns>

private bool IsRequestFromLocalMachine ( )

{

// Получаем объект для запроса.

HttpRequest Req = HttpContext.Current.Request ;

// Замкнут ли клиент на себя?

bool bRet = Req.UserHostAddress.Equals ( "127.0.0.1" ) ; if ( false == bRet )

{

// Получаем локальный IP адрес из серверных переменных.

Соседние файлы в предмете Программирование на C++