Программирование на Visual C++. Архив рассылки
Шрифт:
Окно Watch
Окно Watch позволяет просматривать значения переменных и выражений. Переменные и выражения можно размещать на любой из четырёх вкладок. Добавить переменную или выражение в окно Watch можно одним из следующих способов:
– Ввести с клавиатуры
– Перетащить из окна редактора или из окна Variables
– Добавить из окна Quick watch
Чтобы изменить значение переменной, достаточно два раза кликнуть на старом значении и ввести новое. Значение выражений изменять нельзя.
Чтобы узнать тип переменной или выражения, нужно щёлкнуть по ним правой кнопкой и выбрать Properties из всплывающего меню.
В окне Watch можно наблюдать любые регистры процессора и изменять их значения, хотя это удобнее делать в окне Registers. Можно также использовать регистры в выражениях.
Можно указать отладчику, в каком формате выводить значение переменной/выражения, используя флаги форматирования. Эти флаги добавляются к имени переменной или выражению через запятую. Большинство из них совпадает с символами
Флаг wm превращает код сообщения в его название, например:
Флаг wc позволяет стиль окна, например:
Флаг hr переводит коды ошибок Win32 и значения HRESULT, возвращаемые функциями COM, в удобочитаемый вид, например:
Наконец, в Visual C++ есть числовой флаг, который позволяет просмотреть заданное количество элементов массива, адресуемого указателем (по умолчанию показывается всего один элемент). Допустим, мы выделили динамический массив из 10 целых чисел:
Чтобы просмотреть его содержимое в окне Watch, нужно ввести:
Псевдорегистр ERR
Как известно, получить расширенный код ошибки после вызова функций Win32 API можно с помощью GetLastError. Однако расставлять по всей программе вызовы GetLastError крайне неудобно. Поэтому в отладчике Visual C++ предусмотрен специальный псевдорегистр ERR, который всегда содержит расширенный код ошибки. Особенно удобно наблюдать значение этого регистра, использую уже знакомый нам флаг hr. Добавьте ERR,hr в окно Watch, и информация об ошибках в вызовах функций API всегда будет у вас перед глазами.
Другие окна отладчика
Окно Registers. Позволяет просматривать и изменять значения регистров процессора.
Окно Memory. Позволяет просматривать и изменять содержимое ячеек памяти.
Окно Call Stack. Показывает последовательность вызванных функций. Используя контекстное меню, можно отобразить также типы и значения параметров этих функций. К тексту любой функции можно переместиться, сделав двойной щелчок на её имени. Обратите внимание, что точки останова можно ставить прямо в этом окне.
Окно Disassembly. Показывает текст отлаживаемой программы на языке ассемблера. Иногда без помощи этого окна ошибку в программе найти не удаётся.
Диалоги
Диалоги отладчика предоставляют вам ряд дополнительных возможностей. Они вызываются из меню Debug.
Quick Watch. Имеет возможности, аналогичные возможностям окна Watch, с той разницей, что в нём можно просматривать только одну переменную за раз. Используется, когда вам не хочется добавлять переменную в окно Watch.
Exceptions. Позволяет настроить реакцию отладчика на возникновение системных и пользовательских исключений.
Threads. Показывает список активных потоков. Позволяет приостановить (suspend) или продолжить (resume) поток, а также установить на него фокус.
Modules. Показывает список загруженных модулей. Для каждого модуля выводится диапазон адресов и имя файла.
Edit and Continue
В заключение хотелось бы упомянуть о новой мощной возможности, которая появилась в Visual C++ 6.0 – Edit and Continue. С её помощью вы можете вносить изменения в код программы и перестраивать её, не прерывая сеанса отладки.
Для этого достаточно вызвать команду Apply code changes из меню Debug (или нажать Alt+F10), после того как вы подправили исходные тексты. Более того, Visual C++ может вызывать для вас эту команду автоматически. Это будет происходить, если в окне Tools->Options на вкладке Debug установить флаг Debug commands invoke Edit and Continue.
Q. У меня dialog-base приложение, живет в systray. Необходимо, чтобы приложение при повторном запуске находило уже запущеный экземпляр программы и активизировало его. Я пытался сделать это через FindWindow, в которую передается имя зарегистрированного класса окна, и заголовок окна, которое разыскивается. По заголовку я искать не могу, так как он все время у меня меняется. Следовательно, нужно искать по зарегистрированному имени класса окна. Вот тут то и начинается проблема. Я его не знаю. MFC сама их раздает dialog-based приложениям. А переопределить это имя можно было бы в PreCreateWindow, но этот метод CDialog не наследует из CWnd. Во всех остальных методах, имя класса уже зарегистрированно, т.е. менять его поздно. Как быть?
На этот вопрос пришло просто огромное количество ответов, и разных. Отдельное спасибо хочу сказать Андрею Твердохлебову, который прислал ссылку на действительно замечательную статью на эту тему, которая позволила многое прояснить. Именно в ней приводится по-настоящему правильный и надежный способ.
К
сожалению, действительно хороших ответов, несмотря на большое общее количество, получилось довольно мало. Многие удовольствовались в ответе решением задачи ограничения числа запущенных экземпляров до одного, совершенно забывая о том, что задача еще и активизировать уже запущенный экземпляр.A1 Относительно вопроса, заданного в №25 данной рассылки хотелось бы сразу высказать сильное сомнение по поводу возможности его решения при помощи использования имен оконных классов. Мне кажется, что каждый экземпляр приложения в операционной системе Windows имеет, как это не пародоксально звучит, свой набор зарегистрированных оконных классов. Общность стандартных (системных) оконных классов поддерживается автоматической загрузкой в адресное пространство системных библиотек, которые, в момент своей инициализации, регистрируют свои оконные классы. Список DLL (только они позволяют делать общедоступными определенные виды окон), подгружаемых автоматом каждому приложению, хранится где-то в реестре (не помню точно где). Еще одним доводом в пользу предположения об уникальности списка зарегистрированных оконных классов каждого приложения (экземпляра приложения) служит сама процедура RegisterClass(Ex). В качестве аргумента данной процедуры выступает указатель на структуру, одним из элементов которой является указатель (адрес) на оконную процедуру. Нет 100% гарантии того, что разные DLL проекцируются в одно и то же адресное пространство всех приложений без исключений. Следовательно, адрес оконной процедуры перестает нести смысловую нагрузку. Из изложенного выше, позвольте сделать вывод: если в результате регистрации приложением оконного класса произошла ошибка типа "Оконный класс с указанным именем уже существует" это означает лишь то, что именно ЭТО приложение уже зарегистрировало подобный класс. И наоборот, если регистрация прошла успешно, то это не значит, что нет такого приложения (экземпляра приложения) которое не зарегистрировало бы оконный класс с подобным именем. Следовательно подобный подход при решении подобной проблемы невозможен.
Решить указанную проблему можно лишь при помощи объектов ядра операционной системы, объектов файловой системы (так, по моему, решаются подобные задачи в OS типа Linux/Unix) и некоторых других объектов (типа mailslot, TCP ports и наверное можно придумать что либо еще). Важно выполнение следующих условий:
1. Объекты должны быть доступны из различных приложений
2. Объекты должны быть одинаковым образом идентефицированы для всех приложений (идет отказ от дескрипторов объектов, которые актуальны только в рамках одного процесса).
3. Желательно (а может обязательно?), что бы OS поддерживала синхронизацию доступа к данным объектам.
Самый простой способ идентефикации объектов заключается в присвоении им строковых имен. Такой способ применяестся к объектам ядра, файловой системы, mailslot. TCP способ использует разименовку по номеру порта. И имя и номер имеют одно и то же значение для всех приложений. Для функции RegisterClass(Ex), похоже, не выполняется первое условие. Способ определения повторного запуска экземпляра приложения давно известен и использует объект ядра системы типа "mutex". В задаче, кроме того, требуется подать сигнал активизации первому экземпляру приложения. Пришлось модифицировать известный способ, попутно решив эту проблему для себя, и использовать объект ядра типа "event". В общем, принцип работы схемы выглядит так:
1. Попытка получить доступ к объекту ядра по имени
1.1. Доступ получить не удалось из-за отстутствия объекта с указанным именем – данный экземпляр приложения первый. Переходим к п. 2
1.2. Доступ получен – большая вероятность того что данное приложение запущено во второй раз. Почему не 100% уверенность? Делаем скидку на то, что кто то другой мог выбрать для своих нужд именно этот тип объектов и именно с этим именем :). Переходим на п. 3
2. Инициализация и активация главного окна
2.1. Создаем объект ядра с именем, использованным в п. 1
2.2. Инициализируем и запускаем приложение
2.3. Время от времени проверяем поступление сигнала о запуске второй копии приложения
3. Передача сигнала первому приложению о запуске второй копии и выход из программы.
Очень подробный и интересный ответ, но к сожалению не совсем корректный. Насчет классов – действительно, они доступны только внутри зарегистрировавшего их процесса (в MSDN есть хорошая статья "Window Classes in Win32" by Kyle Marsh). Однако я не совсем согласен с логическими построениями автора (или понял их неправильно). Т.к. имя класса по идее уникально, то естественно, что ИМЕННО ЭТО приложение зарегистрировало класс. А как иначе?..
Вопросы вызывают еще два момента. Во первых, при создании объектов ядра НИ В КОЕМ СЛУЧАЕ не нужно сначала проверять, существует ли такой объект (п.1). Иначе можно нарваться на т.н. race conditions, описанные в вышеупомянутой статье – ситуация, когда два экземпляра стартуют почти одновременно. Получается, что одна копия проверяет, что объект не существует, и создает его. Но прежде чем она его создаст, вторая копия тоже убеждается в том, что объекта еще нет, и тоже собирается со спокойной совестью его создать. В результате первая копия успевает создать объект, а второй копии создать объект так и не удается, но это уже не важно, т.к. вторая копия все равно запускается.