понедельник, 10 декабря 2007 г.

Полезные мелочи

Как-то на днях надо было повозиться с XPath и Xslt и для быстрой проверки XPath запросов искал небольшую утилитку. Попробовал несколько и остановился на VisualXPath, ее и хочу порекомендовать. И несмотря на удобную фичу по отладке xslt, которая появилась в VS 2005 стал пользоваться VisualXPath регулярно. Среди плюсов ходчу отметить, всесьма простой интерфейс, коррекно работает с namespase, xml исходного документа и результата запроса отображается в древодвидной структуре с раскраской синтаксиса (как в IE, врезультате немноготормозит при открытии больщих файлов, но весьма удобно при анализе). Может помочь построить несложный XPath запрос. Проект VisualXPath для VS 2003 любезно выложен автором с иходным кодом.

четверг, 15 ноября 2007 г.

Шах и ... пат

На днях пытался устранить предупреждение компилятора по использованию устаревших методов Thread.Suspend и Thread.Resume. Привожу его:

Thread.Suspend has been deprecated. Please use other classes in System.Threading, such as Monitor, Mutex, Event, and Semaphore, to synchronize Threads or protect resources. ...”

Фрагмент кода, где используются устаревшие методы, выглядит приблизительно так:

//...
thread.Suspend();
try
{
StackTrace stackTrace = new StackTrace(thread, false);
//...
}
finally
{
thread.Resume();
}
//...

В MSDN-документации к конструктору StackTrace(Thread targetThread, bool needFileInfo) сказано, что передаваемый поток должен быть в состоянии Suspended, иначе возникнет ThreadStateException. Небольшой эксперимент подтвердил, что MSDN находится в актуальном состоянии :-). Интересно, что указанный конструктор не помечен как obsolete, и других способов получить трассировку стека (stack trace) для потока в .net 2.0 я не нашел.
Использование предлагаемых “Monitor, Mutex, Event, and Semaphore” приводит к тому, что поток переходит в состояние ThreadState.WaitSleepJoin, что никак не устроило StackTrace. Замечательная статья Managed Thread States из всеми любимого MSDN подтвердила мою догадку, что перевести поток в состояние Suspended можно только путем вызова метода Thread.Suspend. Такой вот трабл :-(.

пятница, 19 октября 2007 г.

Unable to attach to the process


Вот такое сообщение я получил от студии, когда попытался подключиться к IE, чтобы отладить панельку, написанную на C#. Кстати, хорошая и почти единственная статья о том, как сделать свой тулбар для эксплорера, написана вот здесь: Extending Explorer with Band Objects using .NET and Windows Forms. А теперь - детально. Панель для эксплорера представляет собой так называемый COM Callable Wrapper (CCW) и была написана на .NET 1.1 в Visual Studio 2003 - аттачился я, соответственно, в ней же. Как вы уже поняли, такой способ не сработал. Затем я попытался отладить компонент при помощи другого хост-контейнера (ActiveX Control Test Container) – та же ерунда. Думаю, ладно не студия, так WinDbg. Пусть не так удобно, но на безрыбье и рак рыба. Приатачился! Собирался подгрузить SOS.dll и тут обратил внимание, что в процесс IE загрузился не .NET 1.1 a .NET 2.0! Снес с машины второй фреймворк (к счастью, у меня была такая возможность). И опа! - все хоккей, студия замечательно подключилась. По правде говоря, с СОМом из .NET активно не работал и по этому был немало удивлен. Навел справки. Оказывается, при активации CCW по умолчанию запускается старшая по версии CLR из установленных на машине. (см. статью MSDN Configuring a COM Application for Side-By-Side Execution), однако это поведение можно изменить при помощи config-файла для приложения, которое активирует CCW. В статье есть примечание, что такой подход не работает для Internet Explorer и Microsoft Office :). Решил проверить. Поставил обратно NET 2.0 и убедился, что в IE грузится именно он. Затем создал файл IEXPLORE.EXE.config:
<?xml version ="1.0"?>
<configuration>
<startup>
<supportedRuntime version="v1.1.4322" safemode="false"/>
</startup>
</configuration>

К удивлению обнаружил что для IE 6.0 все замечательно работает и подгружается именно версия 1.1.4322. Конечно, для продакшена такой подход не годится, но для отладки при разработке – вполне.

вторник, 9 октября 2007 г.

События и многопоточный код

Недавно, просматривая главу книги Framework Design Guidelines, посвященную событиям (event) наткнулся на интересное замечание. Это же замечание про события процитировано и на блоге Brad Abrams Events, Delegate and Multithreading . Суть его в следующем. Предположим, что у нашего класса есть некое событие Clicked, объявленное следующим образом:
    public event EventHandler Clicked;
Если метод генерации события написан в виде:
    protected void OnUnsafeRaiseClick()
{
if (Clicked != null) Clicked(this, EventArgs.Empty);
}
- это приводит к небезопасному поведению в случае многопоточного исполнения, когда один поток пытается сгенерировать событие, а второй в этот момент отписывает последний делегат. При определенном стечении обстоятельств проверка
    Clicked != null
может пройти успешно (второй поток еще не отписал последний делегат), а попытка выполнить
    Clicked(this, EventArgs.Empty)
может привести к исключению System.NullReferenceException (если второй поток уже отписал последнее событие). Рекомендуемым способом генерации события будет следующий:
    protected void OnSafeRaiseClick()
{
EventHandler handler = Clicked;
if (handler != null)
handler(this, EventArgs.Empty);
}
По правде говоря, мне было совсем не очевидно, почему копирование ссылки делает исполнение кода безопасным в многопоточной среде, а поиск в Google конкретно по этому вопросу ничего не дал, поэтому провел свое маленькое исследование.
Немного теории. Если вы не очень хорошо знаете внутреннюю кухню событий и делегатов, то предварительно рекомендую просмотреть статью Делегаты и события. Как известно, подписка и отписка от события при помощи C#-операторов += и -= приводит к вызову статических методов Delegate.Combine и Delegate.Remove соответственно. Работа этих методов на наследниках от MulticastDelegate приводит к тому, что будет создан новый делегат, с новым списком вызовов (invocation list), а старый будет собран сборщиком мусора (если на него нет ссылок).
Вернемся теперь к интересной ситуации, когда один поток пытается исполнить метод OnSafeRaiseClick, а второй поток отписывает делегаты. После исполнения первым потоком кода
    EventHandler handler = Clicked
модификация вторым потоком Clicked приведет к созданию нового делегата с новым списком вызовов и событие Clicked будет хранить ссылку на новый делегат или null (если отписали последнее событие), а handler будет хранить ссылку на первоначальный делегат. Далее исполнение первым потоком
    if (handler != null)
handler(this, EventArgs.Empty);
будет вестись над исходным делегатом и проблемы не возникнет.
Интересен тот факт, что дополнительных накладных расходов в методе OnSafeRaiseClick по сравнению с OnUnsafeRaiseClick при однопоточном исполнении очень немного.

четверг, 4 октября 2007 г.

Текстовый файл в ресурсе сборки

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

  • Добавляем файл как Embedded Resource.
  • По имени файла в ресурсе получаем поток, позволяющий считать файл.
  • Работаем с потоком, чтобы получить нужный программный объект. Например, строку с содержимым текстового файла или объект изображения/иконки.

А теперь более подробно. Для внедрения файла в сборку, надо подключить его к проекту и выставить значение свойства Build Action в Embedded Resource. Теперь после компиляции файл будет внедрен в ресурсы сборки. Переходим к извлечению. Имя файла в ресурсе формируется из префикса, в качестве которого выступает Default namespace, из свойств проекта, и относительного пути к файлу (включая само имя файла).

Для надежности можно посмотреть имя внедренного файла через ildasm.exe:

Остается последний шаг — получить из ресурсов сборки поток (объект типа Stream) и извлечь из него данные. Код по извлечению потока из ресурсов сборки и данных из потока удобно поместить во вспомогательный класс. Пример такого класса приведен во фрагменте кода ниже.

static class ResourceHelper
{
private static Stream GetStream(string resourceName)
{
return typeof(ResourceHelper).Assembly.GetManifestResourceStream(resourceName);
}

internal static string GetText(string resourceName)
{
using (StreamReader reader = new StreamReader(GetStream(resourceName)))
{
return reader.ReadToEnd();
}
}

internal static Image GetImage(string resourceName)
{
using (Stream imageStream = GetStream(resourceName))
{
return Image.FromStream(imageStream);
}
}
}

И, наконец, пример использования вспомогательного класса:


private void Form1_Load(object sender, EventArgs e)

{

pictureBox1.Image = ResourceHelper.GetImage("WindowsApplication2.Images.Logo.PNG");

textBox1.Text = ResourceHelper.GetText("WindowsApplication2.Scripts.SampleSQL.txt");

}










вторник, 2 октября 2007 г.

Брейкпоинт в другом домене

На днях коллега попросил помочь ему разобраться в ситуации. Ситуация достаточно интересная, и поэтому я решил написать о ней в блоге. Мой коллега отлаживал приложение на тестовой машине. Он подключился отладчиком к работающему приложению, подгрузил отладочные символы (pdb -файлы) и поставил брейкпоинт в нужном ему методе. Код, который его интересовал, выглядел приблизительно так:


Однако отладчик упорно игнорировал точку прерывания, хотя показывал, что отладочные символы для метода загружены. И я, откровенно говоря, был в замешательстве. Немного поразмыслив, на ум пришел тот факт, что исполнение кода в новом AppDomain приводит к тому, что в него все сборки загружаются отдельно. Отсюда я пришел к гипотезе, что в новый домен не загрузились отладочные символы. Для проверки моего предположения мы скопировали pdb-файл в тот же каталог, откуда загружалась и отлаживаемая сборка. Кстати, узнать путь реальной загрузки сборки можно либо из окна Output (View-> Output), либо окна Modules (Debug->Windows->Modules). И действительно, при очередной попытке исполнить проблемный код, отладчик наконец-то остановился на долгожданном брейкпоинте.

четверг, 13 сентября 2007 г.

День Программиста

Друзья и коллеги! Сегодня 256-й день года, который традиционно считается Днем Программиста. С чем всех и поздравляю!

воскресенье, 9 сентября 2007 г.

Каждый охотник желает знать, где сидит фазан

Эта знакомая с детства фраза – не что иное, как пример мнемонической фразы. Напомню, что мнемоника — это совокупность приемов, имеющих целью облегчить запоминание возможно большего числа сведений, фактов и т.п. путем образования искусственных ассоциаций. Во фразе, которая приведена в заголовке статьи, первая буква каждого слова помогает вспомнить цвет. А сегодня мы поговорим об использовании мнемоники в Visual Studio. В этой среде разработки, как в типичном Windows-приложении, есть два основных способа вызова команд с клавиатуры: горячие клавиши и мнемоника.

С горячими клавишами все понятно. В главном меню они высвечиваются справа от текста пункта меню, а для команд панели инструментов высвечиваются во всплывающей подсказке, если установлена соответствующая опция (Tools -> Customize -> Show ScreenTips on toolbars). Число горячих клавиш ограничено – они назначены только для самых популярных команд.
Другой, менее популярный, но более мощный способ быстрого вызова команд, – использование мнемоники. Мнемоника позволяет при помощи клавиши Alt и выделенных символов вызвать практически любую команду главного меню приложения. Их проще запоминать, поскольку, как правило, они назначаются по первым буквам пунктов меню и при нажатой клавише Alt подчеркиваются. Вот некоторые часто используемые мной мнемонические команды студии:

  • Alt + W + L — закрыть все открытые окна-закладки (Window -> Close All Documents)
  • Alt + F + T — закрыть солюшен (File -> Close Solution)
  • Alt + B + U — скомпилировать текущий проект из солюшена (Build -> Build …)
  • Alt + D + X — вызвать окно Exceptions (Debug -> Exceptions)
  • Alt + P + R — добавить ссылку на сборку(Project -> Add Reference)
  • Alt + P + C — добавить новый класс в проект (Project -> Add Class)
  • Alt + T + O — открыть окно настроек студии «Options»(Tools -> Options)

Мнемоникой удобно пользоваться и в других Windows-приложениях (сочетания приведены для английской версии Windows):

  • Alt + D — в проводнике и Internet Explorer перемещает курсор ввода в поле адреса.
  • Alt + V + L — в проводнике переключает в режим просмотра в виде списка(View -> List)
  • Alt + V + D — в проводнике переключает в режим просмотра в виде таблицы(View -> Details)
  • Alt + T + O — в Internet Explorer открывает окно «Internet Options» (Tools -> Internet Options)

Между прочим, мнемонические команды работают и в 7-й версии Internet Explorer, несмотря на то, что у него меню по умолчанию скрыто.Так что не забывайте назначать мнемонику на пунктах меню, хотя я, к сожалению, сам постоянно забываю это делать :-).

пятница, 7 сентября 2007 г.

Техника быстрой локализации ошибок при отладке

Процесс отладки упрощенно состоит из нескольких этапов. Сперва необходимо добиться повторяемости ошибки. Как правило, это делает тот, кто находит и/или регистрирует баг, но в наиболее сложных ситуациях (когда ошибка возникает нерегулярно или ее зависимость от внешних факторов не очевидна) это приходится делать самим.

Далее необходимо локализовать баг в программном коде. Чтобы не читать метры кода, лучше максимально сократить тот диапазон, который в дальнейшем придется изучать, чтобы понять, какой фрагмент кода приводит к ошибке. Затем надо понять причину проблемы и, наконец, исправить.

По моему опыту, локализация ошибки – наиболее трудоемкий этап в 95% случаев. Если ошибка стала проявляться сравнительно недавно (то есть, можно указать дату и/или номер билда, когда ошибки точно не было и дату и/или номер билда, когда ошибка точно проявляется), могу предложить простую, но эффективную технику, которой сам успешно пользуюсь. Способ локализации ошибки основан на алгоритме двоичного поиска и состоит из двух последовательных действий:

  1. Локализация билда системы, когда баг стал проявляться.
  2. Локализация фрагмента кода, где проявляется баг.

А теперь – подробней.

Локализуем билд системы, когда баг стал проявляться

Фиксируем граничные билды: проверяем тот билд, где в мы уверены, что бага нет, затем проверяем билд, где мы уверены, что баг проявляется. Может показаться, что эти проверки излишни (мы ведь и так уверены), однако это не так. Неверное определение граничных билдов приведет к значительно большей потери времени и, как показывает практика, случаются не так уж и редко, так что я рекомендую не полениться и выполнить их! Границы зафиксированы и теперь ищем билд, который находится посередине, и проверяем его. Тут возможны 2 варианта. Если серединный билд содержит баг, значит он появился раньше чем мы предполагали. Если серединный билд чист перед законом, то баг пробрался в систему позже. Фиксируем новые граничные билды. Этот нехитрый процесс повторяем до тех пор, пока не найдем два последовательных билда: первый чистый, следующий за ним — с багом.

Локализуем фрагмент кода, где проявляется баг

По системе контроля версий сливаем исходный код билда без бага и фиксируем ВСЕ изменения между билдами. Тут желательно еще раз проверить, что баг действительно проявляется. Далее вносим в чистый билд половину всех изменений. Определять половину можно на глаз (аптекарская точность здесь не важна), главное, чтобы код компилировался, и мы могли проверить повторяемость бага. Снова смотрим поведение программы. Если баг стал проявляться, то уменьшаем порцию изменений вдвое. Если нет, то от оставшейся части изменений также находим половину и добавляем ее к системе. Этот процесс повторяется до тех пор, пока не обнаружатся те строки кода, добавление которых приводит к появлению бага.

Надеюсь, описал понятно. Если есть вопросы и/или требуются где-то пояснения, не стесняйтесь, спрашивайте! Техника проста, по сути – механическая работа, не требующая большого интеллекта, который следует поберечь для других случаев :).


понедельник, 3 сентября 2007 г.

Ловкость рук и никакого мошенничества

Заметил, так же, что многие даже и не подозревают, что если Tab позволяет перемещаться по контролам в прямом порядке: слева на права и сверху в низ, то комбинация Shift + Tab перемещает фокус в обратном направлении.

Еще комбинация из разряда особо не известных J. Ctrl + C на стандартных окнах сообщений (MessageBox) позволяет скопировать в буфер в текстовом виде содержимое этого окна: заголовок окна, сообщение, надписи на кнопках.

воскресенье, 2 сентября 2007 г.

Нетрадиционное использование using и IDisposable

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

private void button1_Click(object sender, EventArgs e)
{
Cursor oldCursor = Cursor.Current;
try
{
Cursor.Current = Cursors.WaitCursor;
// do something time consuming
DoSomethingTimeConsuming();
}
finally
{
Cursor.Current = oldCursor;
}
}

Хочу обратить внимание, что конструкция try finally необходима, что бы в случае необработанного исключения в DoSomethingTimeConsuming все равно восстановить курсор. Однако у данного обработчика есть несколько недостатков:

  • конструкция достаточно объемна
  • необходимость блока try finally не очевидна и поэтому блок можно забыть.
  • данный код приходится дублировать для каждого подобного обработчика.

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

private void button2_Click(object sender, EventArgs e)
{
using (WaitCursor wch = new WaitCursor())
{
// do something time consuming
DoSomethingTimeConsuming();
}
}

Теперь код обработчика кнопки, по управлению курсором мыши стал намного меньше, а работа по управлению курсором мыши перешла в класс WaitCursor и не будет дублироваться при создании новых обработчиков.

Код для класса WaitCursor:
internal class WaitCursor: IDisposable
{
private Cursor m_OldCursor;

internal WaitCursor()
{
m_OldCursor = Cursor.Current;
Cursor.Current = Cursors.WaitCursor;
}

public void Dispose()
{
Cursor.Current = m_OldCursor;
}
}

Использование using приводит к тому что блок try finally будет сгенерирован автоматически. Так же автоматически конструкцией using будет вызван метод WaitCursor. Dispose, который и восстановит курсор.
Хочу отметить, что подобный механизм можно использовать не только для управления курсором.

пятница, 17 августа 2007 г.

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

Хочу поделиться простой техникой установки точек прерывания (breakpoints) на методах классов, к которым у вас нет исходных кодов.
Такой вариант использования брейкпоинтов может понадобиться, если у вас по каким-либо причинам нет исходных текстов компоненты или вас совершенно не интересует исходный код метода, а, скажем, только стек вызова этого метода. Таким образом, данная техника позволяет устанавливать точки прерывания на методах классов NET Framework или третьих фирм. Установка точек прерывания на ключевых методах кода позволяет быстро получить ответ на вопросы типа: почему был вызван некий метод, в какой момент происходит установка свойства, вызывался ли интересующий метод вообще. Такой техникой я обычно пользуюсь, когда нахожусь на стадии локализации проблемы и/или при проверке различных гипотез при отладке. Грамотная установка точек прерывания позволяет быстро узнать стек вызов для интересующего метода, чтобы в итоге понять, откуда у проблемы «ноги растут». Эта техника установки точек прерывания работает, как для Visual Studio 2003, так и для Visual Studio 2005. На других версиях среды разработки я не проверял, но, скорее всего, работает и там. Хочу еще раз подчеркнуть, что эта техника работает только для управляемого кода. Если есть необходимость работать с брейкпоинтами для неуправляемого кода, то лучше воспользоваться более мощным отладчиком, например WinDBG (http://www.microsoft.com/whdc/devtools/debugging/default.mspx), но это уже совсем другая история.
Далее рассмотрим пошагово, как устанавливать точки прерываний в Visual Studio 2003. Предположим, нас интересует, в какой момент у формы вызывается метод OnPaint для класса Form из пространства имен System.Windows.Forms. Особо не задумывайтесь над смыслом примера — он служит лишь для того, чтобы показать, как устанавливать точки прерывания.
Итак, создайте новый проект WindowsApplication (File -> NewProject -> Windows Application). Затем в главном меню Visual Studio выберите пункт Debug, в нем подпункт-меню Windows и, наконец, пункт Breakpoints. Откроется плавающее окно со списком точек прерывания (рис. 1).
Рис.1 – Главное окно Visual Studio 2003 с активированной панелью Breakpoints
На панели инструментов этого окна выберите команду New. Появится диалоговое окно для задания координат новой точки прерывания. Переключитесь на закладку Function и в поле ввода Function напишите полное имя функции, на которой будет устанавливаться точка прерывания, а в выпадающем списке Language выберите Unknown (рис. 2).


Рис. 2 – Диалоговое окно для установки новой точки прерывания

Все. Завершите установку новой точки прерывания, нажав кнопку OK.
В Visual Studio 2005 установка точки прерывания осуществляется точно так же. Дополнительно для Visual Studio 2005 надо убрать опцию Enable Just My Code. Откройте окно настроек студии Tools -> Options. В иерархическом списке настроек найдите и выделите узел Debugging и затем в списке General правой части окна сбросьте флаг Enable Just My Code (рис. 3).

Рис. 3 – Окно настроек Visual Studio 2005

Теперь можно запускать приложение на выполнение под отладкой (Debug -> Start Debugging).

После старта приложения отладчик остановит приложение на нашей точке прерывания. Студия выдаст предупреждение, что для этой точки прерывания она не смогла найти исходные коды, что вообще-то и ожидалось. Нажмите OK, либо Show Disassembly. Чтобы посмотреть стек вызовов, откройте окно Call Stack (Debug -> Windows -> Call Stack) (рис. 4).



Рис . 4 – Отладчик Visual Studio 2003 с активированной панелью Call Stack

Если стек показывается не полностью, а вместо некоторых имен функций выводится [Non user code], выделите строку с текстом [Non-user Code], вызовите контекстное меню и включите опцию Show Non-user Code.

И напоследок несколько полезных советов.

Чтобы задать точку прерывания, к примеру, на изменение свойства, надо поставить точку прерывания на его set –методе. Например, чтобы остановиться в отладчике в момент изменения свойства формы Text, в поле Function надо указать следующий текст:

System.Windows.Forms.Control.set_Text

Обратите внимание, что свойство Text определено на уровне класса Control из пространства имен System.Windows.Forms и именно его мы используем при создании новой точки для класса Form.
Чтобы установить брейкпоинт на вызове конструктора, например класса BitVector32, в качестве функции надо указать следующий текст:

System.Collections.Specialized.BitVector32.BitVector32
Небольшое замечание по поводу установки брейкпоинтов. Точка прерывания должна быть установлена до первого вызова метода, то есть до того, как будет выполнена его JIT-компиляция. Идеальный вариант – вообще до старта приложения. Для отладки простого приложения, как в примере, это тривиальная задача. Если же точку прерывания надо поставить при отладке компоненты, которая подключается к существующему приложению (хост-приложение), то задача немного усложняется. В этом случае можно поступить несколькими способами.

Во-первых, можно воспользоваться отладчиком DbgCLR.exe, который идет с .NET Framework SDK (у меня он находится здесь: C:\Program Files\Microsoft Visual Studio .NET 2003\SDK\v1.1\GuiDebug для .NET 1.1 и здесь С:\Program Files\Microsoft Visual Studio 8\SDK\v2.0\GuiDebug для .NET 2.0). Установка точек прерывания и отладка в нем аналогична Visual Studio, но у него есть возможность указать приложение для отладки (Debug -> Program To Debug).

Во-вторых, при отладке приложений в Visual Studio можно для отлаживаемой компоненты указать, что для ее отладки будет запущено определенное приложение. Для этого открываете Solution Explorer и в контекстном меню проекта компоненты выбираете пункт Properties. В 2005 студии откроется закладка с именем проекта. Переключитесь на пункт Debug. Далее в группе Start Action выберите вариант Start external program и в поле справа укажите путь и имя хост-приложения (рис. 5).

Рис. 5 – Установка хост-приложения для отладки компоненты в Visual Studio 2005


Для 2003 студии откроется диалоговое окно настроек проекта. В иерархическом списке в левой части окна раскройте узел Configuration Properties и выберите пункт Debugging. В списке настроек (в правой части окна), в группе Start Action в пункте Debug Mode, установите значение Program. Нажмите кнопку Apply и после этого в пункте Start Application укажите путь и имя хост-приложения (рис. 6).


Рис. 6 – Установка хост-приложения для отладки компоненты в Visual Studio 2003

Вот и сказочке конец, кто прочел – тот молодец. Надеюсь, данная техника установки точек прерывания будет вам полезна в работе.